aboutsummaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
authorwzedlare <vedatak01@gmail.com>2017-06-18 16:38:26 +0000
committerwzedlare <vedatak01@gmail.com>2017-06-19 16:57:11 +0000
commitc7d4e3fd588e3ba3d3fa4d5cfa224aa54bc288bf (patch)
treeb8b64cb9deb6832c1e41f58f0f143514beafc709 /drivers/power
parent28c99c87b881bb664c44bb26e80a681f87d54e60 (diff)
p2a42: Import fully working kernel sourceHEADn7.1
Change-Id: Ia4c94f09e29843b1af34d466243378a357e97b70
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Kconfig1
-rw-r--r--drivers/power/Makefile3
-rw-r--r--drivers/power/lenovo/Kconfig37
-rw-r--r--drivers/power/lenovo/Makefile3
-rw-r--r--drivers/power/lenovo/bq2589x_charger.c2332
-rw-r--r--drivers/power/lenovo/bq2589x_charger.h342
-rw-r--r--drivers/power/lenovo/bq2589x_charger_dual.c1820
-rw-r--r--drivers/power/lenovo/smb1351_charger_lenovo.c6382
-rw-r--r--drivers/power/qcom/debug_core.c63
-rw-r--r--drivers/power/qpnp-fg.c169
-rw-r--r--drivers/power/qpnp-smbcharger.c180
-rw-r--r--drivers/power/qpnp-typec.c42
-rw-r--r--drivers/power/reset/msm-poweroff.c63
-rw-r--r--drivers/power/smb135x-charger.c10
14 files changed, 11383 insertions, 64 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 7d27cc93..92642fce 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -622,3 +622,4 @@ endif # POWER_SUPPLY
source "drivers/power/avs/Kconfig"
source "drivers/power/qcom/Kconfig"
+source "drivers/power/lenovo/Kconfig"
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index babd404f..e4c66da9 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -60,7 +60,7 @@ obj-$(CONFIG_POWER_AVS) += avs/
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_SMB349_USB_CHARGER) += smb349-charger.o
obj-$(CONFIG_SMB349_DUAL_CHARGER) += smb349-dual-charger.o
-obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o
+#obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o
obj-$(CONFIG_SMB350_CHARGER) += smb350_charger.o
obj-$(CONFIG_SMB135X_CHARGER) += smb135x-charger.o
obj-$(CONFIG_SMB1360_CHARGER_FG) += smb1360-charger-fg.o
@@ -81,3 +81,4 @@ obj-$(CONFIG_MSM_BCL_CTL) += msm_bcl.o
obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o
obj-$(CONFIG_POWER_RESET) += reset/
obj-$(CONFIG_ARCH_MSM) += qcom/
+obj-y += lenovo/
diff --git a/drivers/power/lenovo/Kconfig b/drivers/power/lenovo/Kconfig
new file mode 100644
index 00000000..e19d2b4d
--- /dev/null
+++ b/drivers/power/lenovo/Kconfig
@@ -0,0 +1,37 @@
+config CHARGER_BQ2589X
+ tristate "BQ2589x charger driver"
+ depends on I2C
+ help
+ Say Y here to enable support for batteries with BQ27x00 (I2C/HDQ) chips.
+
+config CHARGER_BQ2589X_DUAL
+ tristate "BQ2589x charger driver"
+ depends on I2C
+ help
+ Say Y here to enable support for batteries with BQ27x00 (I2C/HDQ) chips.
+
+config SMB1351_USB_CHARGER
+ tristate "smb1351 usb charger driver"
+ depends on I2C
+ help
+ Say Y here to enable support for batteries with BQ27x00 (I2C/HDQ) chips.
+
+config LENOVO_FAST_CHARGER
+ tristate "lenovo fast charger driver"
+ help
+ Say Y here to enable support for lenovo fast charger.
+
+config CHARGER_DUAL
+ tristate "lenovo dual charger driver"
+ help
+ Say Y here to enable support for lenovo fast charger.
+
+config OTG_SUPPORT
+ tristate "lenovo otg support"
+ help
+ Say Y here to enable support for lenovo fast charger.
+
+config CHARGING_APP_CONTROL
+ tristate "lenovo charging app control support"
+ help
+ Say Y here to enable support for lenovo fast charger.
diff --git a/drivers/power/lenovo/Makefile b/drivers/power/lenovo/Makefile
new file mode 100644
index 00000000..8724241d
--- /dev/null
+++ b/drivers/power/lenovo/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_CHARGER_BQ2589X) += bq2589x_charger.o
+obj-$(CONFIG_CHARGER_BQ2589X_DUAL) += bq2589x_charger_dual.o
+obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351_charger_lenovo.o
diff --git a/drivers/power/lenovo/bq2589x_charger.c b/drivers/power/lenovo/bq2589x_charger.c
new file mode 100644
index 00000000..999f34dd
--- /dev/null
+++ b/drivers/power/lenovo/bq2589x_charger.c
@@ -0,0 +1,2332 @@
+/*
+ * bq2589x_charger.c - Charger driver for TI BQ25892/25890
+ *
+ */
+
+//#define DEBUG
+
+#ifndef __BQ2589X_CHARGER_C_
+#define __BQ2589X_CHARGER_C_
+#endif
+
+#define EXT_CHARGER_POWER_SUPPLY
+
+#ifdef CONFIG_DEBUG_FS
+#undef CONFIG_DEBUG_FS
+#endif
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/power_supply.h>
+#include <linux/sfi.h>
+#include <linux/pm_runtime.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/wakelock.h>
+#include <linux/version.h>
+#include <linux/usb/otg.h>
+#include <linux/rpmsg.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#ifdef CONFIG_OF
+#include <linux/of.h>
+#include <linux/of_device.h>
+#endif
+#include <linux/regulator/consumer.h>
+#include <linux/reboot.h>
+#include "bq2589x_charger.h"
+
+#define DEV_NAME "bq2589x"
+
+#define CHARGER_TASK_JIFFIES (HZ * 15)/* 150sec */
+#define CHARGER_HOST_JIFFIES (HZ * 30) /* 60sec */
+#define CHARGER_CHECK_JIFFIES (HZ * 2)
+
+/* Max no. of tries to i2c operation */
+#define MAX_TRY 3
+
+/* Max no. of tries to reset the bq2589xi WDT */
+#define MAX_RESET_WDT_RETRY 8
+
+#ifdef CONFIG_DEBUG_FS
+#define bq2589x_MAX_MEM 12
+#endif
+
+#define TYPEC_PSY_NAME "typec"
+
+struct bq2589x_otg_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+};
+
+struct bq2589x_chip {
+ struct i2c_client *client;
+ struct bq2589x_platform_data *pdata;
+ enum bq2589x_chip_type chip_type;
+#ifdef EXT_CHARGER_POWER_SUPPLY
+ struct power_supply charger_psy;
+ int temp_debug_flag;
+#endif
+ struct delayed_work chrg_task_wrkr;
+ struct delayed_work fault_work;
+ struct delayed_work charger_check_work;
+ struct work_struct otg_evt_work;
+ struct mutex event_lock;
+ struct bq2589x_otg_regulator otg_vreg;
+ /* Wake lock to prevent platform from going to S3 when charging */
+ struct wake_lock wakelock;
+ /* timeout Wake lock when charger status changed*/
+ struct wake_lock irq_wk;
+ struct regulator *vdd;
+ int chgr_stat;
+ int cc;
+ int cv;
+ int iusb_max;
+ int ibat_normal;
+ int vbat_normal;
+ int vbat_cool;
+ int vbat_warm;
+ int ibat_warm;
+ int ibat_cool;
+ int warm_temp;
+ int cool_temp;
+ int iterm;
+ int batt_temp;
+ int board_temp;
+ int batt_voltage;
+ int batt_status;
+ int irq_gpio;
+ int irq;
+ int chg_en_gpio;
+ int otg_en_gpio;
+#ifdef LENOVO_OTG_USB_SHORT
+ int otg_usb_short_gpio;
+ bool otg_usb_short_state;
+#endif
+ char ic_name[10];
+ bool bat_is_warm;
+ bool bat_is_cool;
+ bool is_charging_enabled;
+ bool online;
+ bool sfttmr_expired;
+ bool sfi_tabl_present;
+ bool is_first_start;
+#ifdef CONFIG_OTG_SUPPORT
+ bool boost_mode;
+#endif
+ bool is_factory_mode;
+ bool is_factory_cable;
+ bool typec_dfp;
+};
+
+static struct power_supply *fg_psy = NULL;
+static struct power_supply *usb_psy = NULL;
+static struct power_supply *typec_psy = NULL;
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *bq2589x_dbgfs_root;
+static char bq2589x_dbg_regs[bq2589x_MAX_MEM][4];
+#endif
+
+static int factory_kill_disable;
+module_param(factory_kill_disable, int, 0644);
+
+//extern int tlmm_set_config_pullup(unsigned gpio, unsigned enable);
+
+static int bq2589x_get_chip_version(struct bq2589x_chip *chip);
+
+static int retry_sleep_ms[] = {
+ 10, 20, 30, 40, 50
+};
+
+/*
+ * Genenric register read/write interfaces to access registers in charger ic
+ */
+static int bq2589x_write_reg(struct i2c_client *client, u8 reg, u8 val)
+{
+ s32 ret;
+ int retry_count = 0;
+
+retry:
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+ if (ret < 0 && retry_count < MAX_TRY) {
+ /* sleep for few ms before retrying */
+ msleep(retry_sleep_ms[retry_count++]);
+ goto retry;
+ }
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "i2c write fail: can't write %02x to %02x: %d\n",
+ val, reg, ret);
+ return ret;
+ }
+ pr_debug("Writing 0x%02x=0x%02x\n", reg, val);
+ return 0;
+}
+
+static int bq2589x_read_reg(struct i2c_client *client, u8 reg)
+{
+ s32 ret;
+ int retry_count = 0;
+ u8 val;
+
+retry:
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0 && retry_count < MAX_TRY) {
+ /* sleep for few ms before retrying */
+ msleep(retry_sleep_ms[retry_count++]);
+ goto retry;
+ }
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "i2c read fail: can't read from %02x: %d\n", reg, ret);
+ return ret;
+ } else {
+ val = (u8) ret;
+ }
+
+ return val;
+}
+
+static int bq2589x_reg_read_mask(struct i2c_client *client, u8 reg, u8 mask, u8 shift)
+{
+ int ret;
+
+ if (shift > 8)
+ return -EINVAL;
+
+ ret = bq2589x_read_reg(client, reg);
+ if (ret < 0)
+ return ret;
+
+ return (ret & (mask<<shift)) >> shift;
+}
+
+static int bq2589x_reg_write_mask(struct i2c_client *client, u8 reg, u8 val, u8 shift, u8 mask)
+{
+ int ret;
+
+ if (shift > 8)
+ {
+ pr_err("%s shift out range %d\n", __func__, shift);
+ return -EINVAL;
+ }
+
+ ret = bq2589x_read_reg(client, reg);
+ if (ret < 0)
+ {
+ pr_err("%s read err %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret &= ~(mask<<shift);
+ ret |= val << shift;
+
+ if((reg==BQ2589X_REG_14)&&(shift!=BQ2589X_REG_14_REG_RST))
+ ret &= 0x7f;
+
+ return bq2589x_write_reg(client, reg, ret);
+}
+
+//#define DEBUG
+/*
+ * This function dumps the bq2589x registers
+ */
+static void bq2589x_dump_registers(struct bq2589x_chip *chip)
+{
+ int ret;
+
+// dev_info(&chip->client->dev, "%s\n", __func__);
+#ifndef DEBUG
+ pr_warn("%s :", __func__);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_00);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Input Src Ctrl reg read fail\n");
+ pr_warn( "00 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_01);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Pwr On Cfg reg read fail\n");
+ pr_warn( "01 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_03);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Chrg Curr Ctrl reg read fail\n");
+ pr_warn( "03 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_04);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Pre-Chrg Term reg read fail\n");
+ pr_warn( "04 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_05);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Chrg Volt Ctrl reg read fail\n");
+ pr_warn( "05 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_06);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Thermal Regulation reg read fail\n");
+ pr_warn( "06 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_09);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "09 reg read fail\n");
+ pr_warn( "09 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0A);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0A %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0B);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0B %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0C);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0C %x; ", ret);
+
+ //bq2589x_reg_write_mask(chip->client, BQ2589X_REG_02, 0x3, 6, 0x3);
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0D);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0D %x \n", ret);
+/*
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0E);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0E %x \n", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_11);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "11 reg read fail\n");
+ pr_warn( "11 %x \n", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_12);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "12 reg read fail\n");
+ pr_warn( "12 %x \n", ret);
+*/
+#else
+ int i;
+
+ bq2589x_reg_write_mask(chip->client, BQ2589X_REG_02, 0x3, 6, 0x3);
+
+ pr_warn("bq25892 REG dump");
+ for(i=0;i<0x14;i++)
+ {
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_00+i);
+ if (ret < 0)
+ pr_warn("%s reg %x read fail\n", __func__, i);
+ pr_warn("[%x]=%x ", i, ret);
+ }
+
+ //ret = gpio_get_value(chip->chg_en_gpio);
+ //dev_info(&chip->client->dev, "en io %d\n", ret);
+#endif
+}
+
+/*
+ * This function verifies if the bq2589xi charger chip is in Hi-Z
+ * If yes, then clear the Hi-Z to resume the charger operations
+ */
+static int bq2589x_clear_hiz(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ pr_debug("%s\n", __func__);
+
+ /*
+ * Read the bq2589xi REG00 register for charger Hi-Z mode.
+ * If it is in Hi-Z, then clear the Hi-Z to resume the charging
+ * operations.
+ */
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_00);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev,
+ "Input src cntl read failed\n");
+ goto i2c_error;
+ }
+
+ if (ret & (1<<BQ2589X_REG_00_EN_HIZ)) {
+ dev_warn(&chip->client->dev, "Charger IC in Hi-Z mode\n");
+ ret &= ~(1<<BQ2589X_REG_00_EN_HIZ);
+ ret = bq2589x_write_reg(chip->client, BQ2589X_REG_00, ret);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "Input src cntl write failed\n");
+ goto i2c_error;
+ }
+ msleep(150);
+ }
+
+ return ret;
+i2c_error:
+ dev_err(&chip->client->dev, "%s\n", __func__);
+ return ret;
+}
+
+static int bq2589x_reset_regs(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ pr_info("%s\n", __func__);
+
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_14, 1, BQ2589X_REG_14_REG_RST, BQ2589X_REG_14_REG_RST_MASK);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "Input src cntl read failed\n");
+ goto i2c_error;
+ }
+
+ //bq2589x_dump_registers(chip);
+ return ret;
+i2c_error:
+ dev_err(&chip->client->dev, "%s\n", __func__);
+ return ret;
+}
+
+static int fg_chip_get_property(enum power_supply_property psp)
+{
+ union power_supply_propval val;
+ int ret = -ENODEV;
+
+ if (!fg_psy)
+ fg_psy = power_supply_get_by_name("battery");
+ if (fg_psy) {
+ ret = fg_psy->get_property(fg_psy, psp, &val);
+ if (!ret)
+ return val.intval;
+ }
+ return ret;
+}
+
+
+static int usb_chip_get_property(enum power_supply_property psp)
+{
+ union power_supply_propval val;
+ int ret = -ENODEV;
+
+ if (!usb_psy)
+ usb_psy = power_supply_get_by_name("usb");
+
+ if (usb_psy) {
+ ret = usb_psy->get_property(usb_psy, psp, &val);
+ if (!ret)
+ return val.intval;
+ }
+ return ret;
+}
+
+static int usb_chip_set_property(enum power_supply_property psp, int value)
+{
+ union power_supply_propval val;
+ int ret = -ENODEV;
+
+ if (!usb_psy)
+ usb_psy = power_supply_get_by_name("usb");
+
+ if (usb_psy) {
+ val.intval = value;
+ ret = usb_psy->set_property(usb_psy, psp, &val);
+ if (!ret)
+ return val.intval;
+ }else
+ pr_err("%s usb psy not available\n", __func__);
+
+ return ret;
+}
+
+static int typec_chip_get_property(enum power_supply_property psp)
+{
+ union power_supply_propval val;
+ int ret = -ENODEV;
+
+ if (!typec_psy)
+ typec_psy = power_supply_get_by_name(TYPEC_PSY_NAME);
+
+ if (typec_psy) {
+ ret = typec_psy->get_property(typec_psy, psp, &val);
+ if (!ret)
+ return val.intval;
+ }
+ return ret;
+}
+
+static void typec_update_otg_status(struct bq2589x_chip *chip, int mode, bool force)
+{
+ pr_err("typec mode = %d\n", mode);
+
+ if (mode == POWER_SUPPLY_TYPE_DFP) {
+ chip->typec_dfp = true;
+ power_supply_set_usb_otg(usb_psy, chip->typec_dfp);
+ /* update FG */
+ //set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, get_prop_batt_status(chip));
+ } else if (force || chip->typec_dfp) {
+ chip->typec_dfp = false;
+ power_supply_set_usb_otg(usb_psy, chip->typec_dfp);
+ /* update FG */
+ //set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, get_prop_batt_status(chip));
+ }
+}
+
+/*
+static void typec_update_status(struct bq2589x_chip *chip)
+{
+ union power_supply_propval type = {0, };
+ union power_supply_propval capability = {0, };
+ int rc;
+
+ get_property_from_typec(chip, POWER_SUPPLY_PROP_TYPE, &type);
+ if (type.intval != POWER_SUPPLY_TYPE_UNKNOWN) {
+ get_property_from_typec(chip,
+ POWER_SUPPLY_PROP_CURRENT_CAPABILITY,
+ &capability);
+ chip->typec_current_ma = capability.intval;
+
+ if (!chip->skip_usb_notification) {
+ rc = chip->usb_psy->set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
+ &capability);
+ if (rc)
+ pr_err("typec failed to set current max rc=%d\n",
+ rc);
+ pr_smb(PR_TYPEC, "SMB Type-C mode = %d, current=%d\n",
+ type.intval, capability.intval);
+ }
+ } else {
+ pr_smb(PR_TYPEC,
+ "typec detection not completed continuing with USB update\n");
+ }
+}*/
+
+#ifdef CONFIG_CHARGER_DUAL
+static int charger_dual_chip_set_property(enum power_supply_property psp, int value)
+{
+ union power_supply_propval val;
+ int ret = -ENODEV;
+ static struct power_supply *charger_dual_psy = NULL;
+
+ if (!charger_dual_psy)
+ charger_dual_psy = power_supply_get_by_name("ext-charger-dual");;
+
+ if (charger_dual_psy) {
+ val.intval = value;
+ ret = charger_dual_psy->set_property(charger_dual_psy, psp, &val);
+ if (!ret)
+ return val.intval;
+ }else
+ pr_err("%s usb psy not available\n", __func__);
+
+ return ret;
+}
+#endif
+
+static int chrg_ilim_to_reg(int ilim)
+{
+ int reg;
+
+ if(ilim<=100)
+ reg = 0;
+ else if(ilim>=3250)
+ reg = BQ2589X_REG_00_ILIM_MASK;
+ else
+ reg = (ilim - 100)/50;
+
+ return reg;
+}
+
+static u8 chrg_iterm_to_reg(int iterm)
+{
+ u8 reg;
+
+ if (iterm <= 64)
+ reg = 0;
+ else if(iterm>=1024)
+ reg = BQ2589X_REG_05_ITERM_MASK;
+ else
+ reg = (iterm - 64) /64;
+
+ return reg;
+}
+
+static u8 chrg_cur_to_reg(int cur)
+{
+ u8 reg;
+
+ if (cur <= 0)
+ reg = 0;
+ else if(cur>=5056)
+ reg = BQ2589X_REG_04_ICHG_MASK;
+ else
+ reg = cur /64;
+
+ return reg;
+}
+
+static u8 chrg_volt_to_reg(int volt)
+{
+ u8 reg;
+
+ if (volt <= 3840)
+ reg = 0;
+ else if(volt>=4608)
+ reg = BQ2589X_REG_06_VREG_MASK;
+ else
+ reg = (volt - 3840) /16 + 1;
+
+ return reg;
+}
+
+static u8 vindpm_to_reg(int vindpm)
+{
+ u8 reg;
+
+ if (vindpm <= 3900)
+ reg = 0;
+ else if(vindpm>=7000)
+ reg = BQ2589X_REG_01_VINDPM_MASK;
+ else
+ reg = (vindpm - 3900) /100;
+
+ return reg;
+}
+
+static int program_timers(struct bq2589x_chip *chip, int wdt_duration,
+ bool sfttmr_enable)
+{
+ int ret;
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_07);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "TIMER CTRL reg read failed\n");
+ return ret;
+ }
+
+ ret |= wdt_duration;
+ if (sfttmr_enable)
+ ret |= CHRG_TIMER_EXP_CNTL_EN_TIMER;
+ else
+ ret &= ~CHRG_TIMER_EXP_CNTL_EN_TIMER;
+
+ ret = bq2589x_write_reg(chip->client, BQ2589X_REG_07, ret);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "TIMER CTRL I2C write failed\n");
+
+ return ret;
+}
+
+/* This function should be called with the mutex held */
+static int reset_wdt_timer(struct bq2589x_chip *chip)
+{
+ int ret = 0, i;
+
+ for (i = 0; i < MAX_RESET_WDT_RETRY; i++) {
+ ret = bq2589x_reg_write_mask(chip->client,
+ BQ2589X_REG_03, 1,
+ BQ2589X_REG_03_WD_RST, BQ2589X_REG_03_WD_RST_MASK);
+ if (ret < 0)
+ pr_warn("I2C write failed:%s\n", __func__);
+ else
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ *This function will modify the VINDPM as per the battery voltage
+ */
+static int bq2589x_modify_vindpm(struct bq2589x_chip *chip, u8 vindpm)
+{
+ int ret;
+ static u8 vindpm_prev = 0xff;
+
+ if(vindpm_prev==vindpm) {
+ dev_warn(&chip->client->dev, "same vindpm val, ignored\n");
+ return 0;
+ }
+
+ /* Get the input src ctrl values programmed */
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_01);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "INPUT CTRL reg read failed\n");
+ return ret;
+ }
+
+ /* Assign the return value of REG00 to vindpm_prev */
+ vindpm_prev = (ret & (BQ2589X_REG_01_VINDPM_MASK<<BQ2589X_REG_01_VINDPM));
+ ret &= ~(BQ2589X_REG_01_VINDPM_MASK<<BQ2589X_REG_01_VINDPM);
+
+ /*
+ * If both the previous and current values are same do not program
+ * the register.
+ */
+ vindpm |= ret;
+ ret = bq2589x_write_reg(chip->client, BQ2589X_REG_01, vindpm);
+ if (ret < 0) {
+ dev_info(&chip->client->dev, "VINDPM failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_DEBUG_FS
+#define DBGFS_REG_BUF_LEN 3
+
+static int bq2589x_show(struct seq_file *seq, void *unused)
+{
+ u16 val;
+ long addr;
+
+ if (kstrtol((char *)seq->private, 16, &addr))
+ return -EINVAL;
+
+ val = bq2589x_read_reg(bq2589x_client, addr);
+ seq_printf(seq, "%x\n", val);
+
+ return 0;
+}
+
+static int bq2589x_dbgfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, bq2589x_show, inode->i_private);
+}
+
+static ssize_t bq2589x_dbgfs_reg_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ char buf[DBGFS_REG_BUF_LEN];
+ long addr;
+ unsigned long value;
+ int ret;
+ struct seq_file *seq = file->private_data;
+
+ if (!seq || kstrtol((char *)seq->private, 16, &addr))
+ return -EINVAL;
+
+// if (copy_from_user(buf, user_buf, DBGFS_REG_BUF_LEN-1))
+// return -EFAULT;
+
+ buf[DBGFS_REG_BUF_LEN-1] = '\0';
+ if (kstrtoul(buf, 16, &value))
+ return -EINVAL;
+
+ dev_info(&bq2589x_client->dev, "[dbgfs write] Addr:0x%x Val:0x%x\n", (u32)addr, (u32)value);
+
+ ret = bq2589x_write_reg(bq2589x_client, addr, value);
+ if (ret < 0)
+ dev_warn(&bq2589x_client->dev, "I2C write failed\n");
+
+ return count;
+}
+
+static const struct file_operations bq2589x_dbgfs_fops = {
+ .owner = THIS_MODULE,
+ .open = bq2589x_dbgfs_open,
+ .read = seq_read,
+ .write = bq2589x_dbgfs_reg_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int bq2589x_create_debugfs(struct bq2589x_chip *chip)
+{
+ int i;
+ struct dentry *entry;
+
+ bq2589x_dbgfs_root = debugfs_create_dir(DEV_NAME, NULL);
+ if (IS_ERR(bq2589x_dbgfs_root)) {
+ dev_warn(&chip->client->dev, "DEBUGFS DIR create failed\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < bq2589x_MAX_MEM; i++) {
+ sprintf((char *)&bq2589x_dbg_regs[i], "%x", i);
+ entry = debugfs_create_file(
+ (const char *)&bq2589x_dbg_regs[i],
+ S_IRUGO,
+ bq2589x_dbgfs_root,
+ &bq2589x_dbg_regs[i],
+ &bq2589x_dbgfs_fops);
+ if (IS_ERR(entry)) {
+ debugfs_remove_recursive(bq2589x_dbgfs_root);
+ bq2589x_dbgfs_root = NULL;
+ dev_warn(&chip->client->dev, "DEBUGFS entry Create failed\n");
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+static inline void bq2589x_remove_debugfs(struct bq2589x_chip *chip)
+{
+ if (bq2589x_dbgfs_root)
+ debugfs_remove_recursive(bq2589x_dbgfs_root);
+}
+#else
+static inline int bq2589x_create_debugfs(struct bq2589x_chip *chip)
+{
+ return 0;
+}
+static inline void bq2589x_remove_debugfs(struct bq2589x_chip *chip)
+{
+}
+#endif
+
+static inline int bq2589x_set_cv(struct bq2589x_chip *chip, int cv)
+{
+ u8 regval;
+
+ dev_info(&chip->client->dev, "%s: %d\n", __func__, cv);
+ chip->cv = cv;
+ regval = chrg_volt_to_reg(cv);
+
+ return bq2589x_reg_write_mask(chip->client, BQ2589X_REG_06, regval, BQ2589X_REG_06_VREG, BQ2589X_REG_06_VREG_MASK);
+}
+
+static int bq2589x_set_appropriate_cv(struct bq2589x_chip *chip)
+{
+ int cv = 0;
+ if(chip->bat_is_cool)
+ cv = chip->vbat_cool;
+ else if(chip->bat_is_warm)
+ cv = chip->vbat_warm;
+ else
+ cv = chip->vbat_normal;
+
+ printk("charger : set cv %d\n", cv);
+
+ return bq2589x_set_cv(chip,cv);
+}
+
+static inline int bq2589x_get_appropriate_cv(struct bq2589x_chip *chip)
+{
+ if(chip->bat_is_cool)
+ return chip->vbat_cool;
+ else if(chip->bat_is_warm)
+ return chip->vbat_warm;
+ else
+ return chip->vbat_normal;
+}
+
+static void bq2589x_cv_check_and_set(struct bq2589x_chip *chip)
+{
+ int ret,cv,cv_reg;
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_06);
+ if (ret < 0)
+ {
+ dev_warn(&chip->client->dev, "Chrg Volt Ctrl reg read fail\n");
+ return;
+ }
+
+ ret = (ret>>2);
+ cv = bq2589x_get_appropriate_cv(chip);
+ cv_reg = chrg_volt_to_reg(cv);
+ if(ret != cv_reg)
+ {
+ printk("Charger:cv value 0x%x get from charger chip is not correct :0x%x(%d)\n",ret,cv_reg, cv);
+ bq2589x_set_appropriate_cv(chip);
+ }
+}
+
+static inline int bq2589x_set_cc(struct bq2589x_chip *chip, int cc)
+{
+ u8 regval;
+
+ //dev_info(&chip->client->dev, "%s: %d\n", __func__, cc);
+ regval = chrg_cur_to_reg(cc);
+ printk("%s: %d 0x%x\n", __func__, cc, regval);
+
+ return bq2589x_reg_write_mask(chip->client, BQ2589X_REG_04,regval, BQ2589X_REG_04_ICHG, BQ2589X_REG_04_ICHG_MASK);
+}
+
+static inline int bq2589x_set_iusb(struct bq2589x_chip *chip, int iusb_max)
+{
+ int regval;
+
+ chip->iusb_max = iusb_max;
+ regval = chrg_ilim_to_reg(iusb_max);
+
+ pr_info("fast_charger: %s:%d %x\n", __func__, iusb_max,regval);
+ if (regval < 0)
+ return regval;
+
+ return bq2589x_reg_write_mask(chip->client, BQ2589X_REG_00, regval, BQ2589X_REG_00_ILIM, BQ2589X_REG_00_ILIM_MASK);
+}
+
+static void bq2589x_set_ibat_max(struct bq2589x_chip *chip, int ibat_max)
+{
+ static int pre_val = -1;
+
+ pr_info("charger: %s, %d\n", __func__, ibat_max);
+ if(pre_val!=ibat_max)
+ {
+ pre_val = ibat_max;
+ chip->cc = ibat_max;
+ if(chip->cc==0)
+ chip->is_charging_enabled = false;
+ else
+ chip->is_charging_enabled = true;
+
+ bq2589x_set_cc(chip, ibat_max);
+ }else
+ pr_info("%s ignore same val(%d)\n", __func__, pre_val);
+}
+
+static void bq2589x_set_iusb_max(struct bq2589x_chip *chip, int iusb_max)
+{
+ static int pre_val = -1;
+
+ if(pre_val==iusb_max)
+ {
+ pr_info("%s ignore same val(%d)\n", __func__, iusb_max);
+ return;
+ }
+
+ pr_info("charger: %s, %d\n", __func__, iusb_max);
+ chip->iusb_max = iusb_max;
+ pre_val = iusb_max;
+
+ bq2589x_set_iusb(chip, chip->iusb_max);
+
+// bq2589x_dump_registers(chip);
+}
+
+static int bq2589x_set_appropriate_cc(struct bq2589x_chip *chip)
+{
+ int cc = 0;
+
+ if(chip->bat_is_cool)
+ cc = chip->ibat_cool;
+ else if(chip->bat_is_warm)
+ cc = chip->ibat_warm;
+ else
+ cc = chip->ibat_normal;
+
+ printk("charger : set cc %d\n", cc);
+ bq2589x_set_ibat_max(chip, cc);
+
+ return 0;
+}
+
+static int bq2589x_set_iterm(struct bq2589x_chip *chip, int iterm)
+{
+ u8 reg_val;
+
+ if (iterm > bq2589x_CHRG_ITERM_OFFSET)
+ dev_info(&chip->client->dev, "%s ITERM set for%d >128mA", __func__, iterm);
+
+ reg_val = chrg_iterm_to_reg(iterm);
+ msleep(500);
+
+ return bq2589x_reg_write_mask(chip->client, BQ2589X_REG_05, reg_val, BQ2589X_REG_05_ITERM, BQ2589X_REG_05_ITERM_MASK);
+}
+
+#if 0
+bool is_charging_enabled(void)
+{
+ struct bq2589x_chip *chip = NULL;
+
+ if(bq2589x_client != NULL)
+ chip = i2c_get_clientdata(bq2589x_client);
+ else
+ return 1;
+ return chip->is_charging_enabled;
+}
+
+bool is_in_otg_mode(void)
+{
+ struct bq2589x_chip *chip = NULL;
+
+ if(bq2589x_client != NULL)
+ chip = i2c_get_clientdata(bq2589x_client);
+ else
+ return 0;
+ return chip->boost_mode;
+}
+#endif
+
+static int bq2589x_enable_charging(struct bq2589x_chip *chip, bool val)
+{
+ int ret;
+
+ dev_info(&chip->client->dev, "%s: %d\n", __func__, val);
+ ret = program_timers(chip, CHRG_TIMER_EXP_CNTL_WDT160SEC, true);
+ if (ret < 0) {
+ dev_err(&chip->client->dev,
+ "program_timers failed: %d\n", ret);
+ return ret;
+ }
+
+#ifndef CONFIG_CHARGING_APP_CONTROL
+ {
+ int regval;
+ /*
+ * Program the iusb_max here in case we are asked to resume the charging
+ * framework would send only set CC/CV commands and not the iusb_max. This
+ * would make sure that we program the last set iusb_max into the register
+ * in case for some reasons WDT expires
+ */
+ regval = chrg_ilim_to_reg(chip->iusb_max);
+
+ if (regval < 0) {
+ dev_err(&chip->client->dev, "read ilim failed: %d\n", regval);
+ return regval;
+ }
+
+ printk("%s ichg max is %d,reg is %x\n", __func__, chip->iusb_max,regval);
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_00, regval, BQ2589X_REG_00_ILIM, BQ2589X_REG_00_ILIM_MASK);
+ if (ret < 0) {
+ dev_err(&chip->client->dev, "iusb_max programming failed: %d\n", ret);
+ return ret;
+ }
+ }
+#endif
+
+ /*
+ * check if we have the battery emulator connected. We do not start
+ * charging if the emulator is connected. Disable the charging
+ * explicitly.
+ */
+ if (chip->sfi_tabl_present) {
+ bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 0, BQ2589X_REG_03_CHG_CONFIG, BQ2589X_REG_03_CHG_CONFIG_MASK);
+ pr_err("%s sfi_tabl_present is not true, disable charging\n", __func__);
+ return ret;
+ }
+
+ if (chip->sfttmr_expired)
+ {
+ pr_info("%s Safety timer expired\n", __func__);
+ return ret;
+ }
+
+ if(val)
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 1, BQ2589X_REG_03_CHG_CONFIG, BQ2589X_REG_03_CHG_CONFIG_MASK);
+ else
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 0, BQ2589X_REG_03_CHG_CONFIG, BQ2589X_REG_03_CHG_CONFIG_MASK);
+
+ if (ret < 0)
+ pr_warn("%s charger enable/disable failed\n", __func__);
+ else {
+ if (val)
+ chip->online = true;
+ else
+ chip->online = false;
+ }
+
+ return ret;
+}
+
+static int bq2589x_enable_charger(struct bq2589x_chip *chip, int val)
+{
+ int ret = 0;
+
+ /*stop charger, by putting it in HiZ mode*/
+ if (val == 0) {
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_00, 1, BQ2589X_REG_00_EN_HIZ, BQ2589X_REG_00_EN_HIZ_MASK);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Input src cntl write failed\n");
+ else
+ chip->is_charging_enabled = false;
+ }else
+ {
+ bq2589x_clear_hiz(chip);
+ bq2589x_enable_charging(chip, true);
+ chip->is_charging_enabled = true;
+ }
+
+ pr_warn("%s: %d, %d\n", __func__, val, chip->is_charging_enabled);
+ return ret;
+}
+
+static int bq2589x_get_charging_status(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ if(chip->is_first_start) {
+ pr_err("%s i2c did not init, return defalut val\n", __func__);
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0B);
+ ret &= SYSTEM_STAT_CHRG_MASK;
+ switch (ret) {
+ case SYSTEM_STAT_NOT_CHRG:
+ chip->chgr_stat = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case SYSTEM_STAT_CHRG_DONE:
+ chip->chgr_stat = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case SYSTEM_STAT_PRE_CHRG:
+ case SYSTEM_STAT_FAST_CHRG:
+ chip->chgr_stat = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ default:
+ chip->chgr_stat = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ return chip->chgr_stat;
+}
+
+static bool bq2589x_is_charging_done(struct bq2589x_chip *chip)
+{
+ int batt_voltage = 0;
+
+ batt_voltage = fg_chip_get_property(POWER_SUPPLY_PROP_VOLTAGE_NOW)/1000;
+ if (batt_voltage < 0) {
+ dev_err(&chip->client->dev, "Can't read voltage from FG\n");
+ return false;
+ }
+ chip->batt_voltage = batt_voltage;
+
+ if((POWER_SUPPLY_STATUS_FULL== bq2589x_get_charging_status(chip)) && (chip->batt_voltage > 4250))
+ return true;
+
+ if(chip->bat_is_warm && chip->batt_voltage > 4050)
+ return true;
+
+ return false;
+}
+
+/* IRQ handler for charger Interrupts configured to GPIO pin */
+static irqreturn_t bq2589x_irq_isr(int irq, void *devid)
+{
+// struct bq2589x_chip *chip = (struct bq2589x_chip *)devid;
+
+ /**TODO: This hanlder will be used for charger Interrupts */
+// dev_dbg(&chip->client->dev,
+// "IRQ Handled for charger interrupt: %d\n", irq);
+ return IRQ_WAKE_THREAD;
+}
+
+/* IRQ handler for charger Interrupts configured to GPIO pin */
+static irqreturn_t bq2589x_irq_thread(int irq, void *devid)
+{
+ struct bq2589x_chip *chip = (struct bq2589x_chip *)devid;
+ int reg_status;
+ static int usb_state = -1;
+
+ msleep(100);
+
+ wake_lock_timeout(&chip->irq_wk,HZ);
+ /*
+ * check the bq2589x status/fault registers to see what is the
+ * source of the interrupt
+ */
+ reg_status = bq2589x_read_reg(chip->client, BQ2589X_REG_0B);
+ if (reg_status < 0)
+ dev_err(&chip->client->dev, "STATUS register read failed:\n");
+
+ pr_err("ww_debug %s reg0x%x\n", __func__, reg_status);
+
+ if(((reg_status & SYSTEM_STAT_VBUS_HOST) == SYSTEM_STAT_VBUS_HOST) ||
+ ((reg_status & SYSTEM_STAT_VBUS_CDP) == SYSTEM_STAT_VBUS_CDP))
+ {
+ if (!wake_lock_active(&chip->wakelock))
+ wake_lock(&chip->wakelock);
+
+ if (1)/*(usb_state!=1)*/ {
+ pr_info("%s, set usb present!\n", __func__);
+ usb_state = 1;
+ usb_chip_set_property(POWER_SUPPLY_PROP_DP_DM, POWER_SUPPLY_DP_DM_DPF_DMF);
+ usb_chip_set_property(POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_HEALTH_GOOD);
+ usb_chip_set_property(POWER_SUPPLY_PROP_TYPE, POWER_SUPPLY_TYPE_USB);
+ usb_chip_set_property(POWER_SUPPLY_PROP_PRESENT, 1);
+ if(chip->is_factory_mode) {
+ pr_info("CHG is factory mode, schedule charger det work\n");
+ schedule_delayed_work(&chip->charger_check_work, CHARGER_CHECK_JIFFIES);
+ }
+ }
+ }else{
+ msleep(150);
+ /* Release the wake lock */
+ if (wake_lock_active(&chip->wakelock))
+ wake_unlock(&chip->wakelock);
+
+ if (usb_state!=0) {
+ pr_info("%s, set usb not present!\n", __func__);
+ usb_state = 0;
+ usb_chip_set_property(POWER_SUPPLY_PROP_TYPE, POWER_SUPPLY_TYPE_UNKNOWN);
+ usb_chip_set_property(POWER_SUPPLY_PROP_PRESENT, 0);
+ //usb_chip_set_property(POWER_SUPPLY_PROP_DP_DM, POWER_SUPPLY_DP_DM_DPR_DMR);
+#ifndef CONFIG_CHARGING_APP_CONTROL
+ bq2589x_set_iusb_max(chip, 500);
+#endif
+
+ if((chip->is_factory_cable)&&(chip->is_factory_mode)) {
+ schedule_delayed_work(&chip->charger_check_work, CHARGER_CHECK_JIFFIES);
+ }
+ }
+ }
+
+ reg_status &= SYSTEM_STAT_CHRG_DONE;
+ if (reg_status == SYSTEM_STAT_CHRG_DONE) {
+/* mutex_lock(&chip->event_lock);
+ bq2589x_enable_hw_term(chip, false);
+ mutex_unlock(&chip->event_lock);
+ bq2589x_hw_init(chip);
+ */
+ /* schedule the thread to let the framework know about FULL */
+ }
+
+// if(fg_psy != NULL)
+// power_supply_changed(fg_psy);
+
+ schedule_delayed_work(&chip->fault_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static void bq2589x_hw_init(struct bq2589x_chip *chip)
+{
+ int ret = 0;
+
+ ret = program_timers(chip, CHRG_TIMER_EXP_CNTL_WDT160SEC,true);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "TIMER enable failed\n");
+
+ bq2589x_set_appropriate_cv(chip);
+
+ if(chip->iterm)
+ bq2589x_set_iterm(chip,chip->iterm);
+
+ bq2589x_reg_write_mask(chip->client, BQ2589X_REG_02, 0, BQ2589X_REG_02_ICO_EN, BQ2589X_REG_02_ICO_EN_MASK);
+ bq2589x_reg_write_mask(chip->client, BQ2589X_REG_00, 0, BQ2589X_REG_00_EN_ILIM, BQ2589X_REG_00_EN_ILIM_MASK);
+ bq2589x_write_reg(chip->client,BQ2589X_REG_0D,0x93);
+
+ //bq2589x_set_iusb_max(chip, 500);
+ if(chip->is_factory_mode) {
+ bq2589x_set_iusb_max(chip, 3000);
+ bq2589x_set_ibat_max(chip, 0);
+ } else {
+ bq2589x_set_iusb_max(chip, 500);
+ bq2589x_set_cc(chip, 500);
+ }
+
+ bq2589x_enable_charging(chip, chip->is_charging_enabled);
+}
+
+static void bq2589x_fault_worker(struct work_struct *work)
+{
+ struct bq2589x_chip *chip = container_of(work, struct bq2589x_chip, fault_work.work);
+ int reg_fault;
+
+ /* Check if battery fault condition occured. Reading the register
+ value two times to get reliable reg value, recommended by vendor*/
+ reg_fault = bq2589x_read_reg(chip->client, BQ2589X_REG_0C);
+ if (reg_fault < 0)
+ dev_err(&chip->client->dev, "FAULT register read failed:\n");
+
+ if(reg_fault!=0x00)
+ dev_info(&chip->client->dev, "FAULT reg %x\n", reg_fault);
+
+ if (reg_fault & FAULT_STAT_WDT_TMR_EXP) {
+ dev_warn(&chip->client->dev, "WDT expiration fault\n");
+ if (chip->is_charging_enabled) {
+ /*when WDT expiration ,bq2589x register will be reset,so we need reconfig it*/
+ bq2589x_hw_init(chip);
+ } else
+ dev_info(&chip->client->dev, "No charger connected\n");
+ }
+
+ if ((reg_fault & FAULT_STAT_CHRG_TMR_FLT) == FAULT_STAT_CHRG_TMR_FLT) {
+ chip->sfttmr_expired = true;
+ pr_info("%s Safety timer expired\n", __func__);
+ }
+}
+
+static void bq2589x_temp_monitor(struct bq2589x_chip *chip)
+{
+ bool bat_cool = false;
+ bool bat_warm = false;
+
+ if(chip->batt_temp < chip->cool_temp){
+ bat_cool = true;
+ bat_warm = false;
+ }else if(chip->batt_temp > chip->cool_temp && chip->batt_temp < chip->warm_temp){
+ bat_cool = false;
+ bat_warm = false;
+ }else if(chip->batt_temp > chip->warm_temp){
+ bat_warm = true;
+ bat_cool = false;
+ }
+
+ if (chip->bat_is_cool ^ bat_cool || chip->bat_is_warm ^ bat_warm) {
+ chip->bat_is_cool = bat_cool;
+ chip->bat_is_warm = bat_warm;
+#ifndef CONFIG_CHARGING_APP_CONTROL
+ bq2589x_set_appropriate_cc(chip);
+#endif
+ bq2589x_set_appropriate_cv(chip);
+ }
+}
+
+static void bq2589x_task_worker(struct work_struct *work)
+{
+ struct bq2589x_chip *chip =
+ container_of(work, struct bq2589x_chip, chrg_task_wrkr.work);
+ int ret, jiffy = CHARGER_TASK_JIFFIES;
+ int soc = fg_chip_get_property(POWER_SUPPLY_PROP_CAPACITY);
+ int cur;
+ int vbus;
+
+ if(fg_psy != NULL)
+ power_supply_changed(fg_psy);
+
+ mutex_lock(&chip->event_lock);
+ ret = reset_wdt_timer(chip);
+ mutex_unlock(&chip->event_lock);
+ if (ret < 0)
+ pr_err("%s WDT reset failed:\n", __func__);
+
+ if (chip->is_first_start) {
+ int type;
+
+ //check vbus status after prob
+ chip->is_first_start = false;
+ if (typec_psy) {
+ pr_err("ww_debug typec operation\n");
+ type = typec_chip_get_property(POWER_SUPPLY_PROP_TYPE);
+ typec_update_otg_status(chip, type, true);
+ }
+ bq2589x_irq_thread(0, chip);
+ }
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0C);
+ if (ret < 0)
+ pr_err("%s FAULT register read failed:\n", __func__);
+ else
+ {
+ if(ret!=0x00)
+ {
+ pr_info("%s FAULT reg %x\n", __func__, ret);
+
+ if (ret & FAULT_STAT_WDT_TMR_EXP) {
+ pr_warn("%s WDT expiration fault\n", __func__);
+ if (chip->is_charging_enabled) {
+ /*when WDT expiration ,bq2589x register will be reset,so we need reconfig it*/
+ bq2589x_hw_init(chip);
+ } else
+ pr_info("%s No charger connected\n", __func__);
+ }
+ }
+ }
+
+ /*
+ * If we have an OTG device connected, no need to modify the VINDPM
+ * check for Hi-Z
+ */
+ if ((chip->boost_mode) ||(chip->is_factory_mode)) {
+ jiffy = CHARGER_HOST_JIFFIES;
+ goto sched_task_work;
+ }
+
+ /*sometimes WDT interrupt won't assert, so charger parameters will be reseted to
+ * default value. we do check here, if the CV value is not correct, we reinit the
+ * charger parameters*/
+ bq2589x_cv_check_and_set(chip);
+ /* Modify the VINDPM */
+
+sched_task_work:
+ vbus = fg_chip_get_property(POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION);
+ cur = fg_chip_get_property(POWER_SUPPLY_PROP_CURRENT_NOW);
+ chip->batt_status = bq2589x_get_charging_status(chip);//fg_chip_get_property(POWER_SUPPLY_PROP_STATUS);
+ chip->batt_temp = fg_chip_get_property(POWER_SUPPLY_PROP_TEMP);
+ chip->batt_voltage = fg_chip_get_property(POWER_SUPPLY_PROP_VOLTAGE_NOW) / 1000;
+ if (chip->batt_voltage < 0) {
+ dev_err(&chip->client->dev, "Can't read voltage from FG\n");
+ }
+
+ /* convert voltage into millivolts */
+ if (POWER_SUPPLY_STATUS_FULL== chip->batt_status) {
+ if(soc != 100)
+ {
+ dev_warn(&chip->client->dev, "%s battery full,but soc is not 100", __func__);
+ }
+ }
+
+ bq2589x_write_reg(chip->client,BQ2589X_REG_0D, 0x93);//vindpm 4.5V
+
+ bq2589x_temp_monitor(chip);
+
+ ret = usb_chip_get_property(POWER_SUPPLY_PROP_TYPE);
+ pr_warn("bq2589x battery voltage is %d, capacity is %d, temp is %d, status is %d, cur %d, vbus %d type %d\n",
+ chip->batt_voltage, soc,chip->batt_temp,chip->batt_status, cur, vbus, ret);
+
+ bq2589x_dump_registers(chip);
+
+ if(soc < 5 || chip->batt_voltage < 3400)
+ jiffy = jiffy /3;
+
+ schedule_delayed_work(&chip->chrg_task_wrkr, jiffy);
+}
+
+static void bq2589x_charger_check_worker(struct work_struct *work)
+{
+ struct bq2589x_chip *chip = container_of(work, struct bq2589x_chip, charger_check_work.work);
+ enum power_supply_type charger_type;
+
+ if (!chip->is_factory_cable) {
+ charger_type = usb_chip_get_property(POWER_SUPPLY_PROP_TYPE);
+ if((charger_type==POWER_SUPPLY_TYPE_USB)&&(chip->is_factory_mode))
+ chip->is_factory_cable = true;
+ else
+ chip->is_factory_cable = false;
+
+ pr_info("CHG %s, charger type %d is_factorymode %d is_cable %d\n", __func__,
+ charger_type, chip->is_factory_mode, chip->is_factory_cable);
+ } else {
+ //if (!factory_kill_disable && !reboot_in_progress()) {
+ if (!factory_kill_disable ) {
+ pr_err("CHG - Factory Cable removed, power-off\n");
+ kernel_power_off();
+ } else
+ pr_err("CHG - Factory cable removed - kill disabled\n");
+
+ chip->is_factory_cable = false;
+ }
+}
+
+static int bq2589x_get_chip_version(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ /* check chip model number */
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_14);
+ if (ret < 0) {
+ dev_err(&chip->client->dev, "i2c read err:%d\n", ret);
+ return -EIO;
+ }
+ dev_info(&chip->client->dev, "version reg: 0x%x\n", ret);
+
+ ret = (ret&0x38) >> 3;
+ switch (ret) {
+ case BQ25852_IC_VERSION:
+ chip->chip_type = BQ25852;
+ memcpy(chip->ic_name, "BQ25852", sizeof("BQ25852"));
+ break;
+ case BQ25890_IC_VERSION:
+ chip->chip_type = BQ25890;
+ memcpy(chip->ic_name, "BQ25890", sizeof("BQ25890"));
+ break;
+ case bq25895_IC_VERSION:
+ chip->chip_type = BQ25895;
+ memcpy(chip->ic_name, "BQ25895", sizeof("BQ25895"));
+ break;
+ default:
+ dev_err(&chip->client->dev, "device version mismatch: 0x%x set default\n", ret);
+ chip->chip_type = BQ25852;
+ memcpy(chip->ic_name, "BQ25852", sizeof("BQ25852"));
+ break;
+ }
+
+ dev_info(&chip->client->dev, "chip type:%x\n", chip->chip_type);
+ return 0;
+}
+
+static ssize_t charger_ver_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret = 0;
+ struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+ struct bq2589x_chip *chip = i2c_get_clientdata(client);
+
+ ret = sprintf(buf, "%s\n",chip->ic_name);
+
+ return ret;
+}
+
+static DEVICE_ATTR(chrg_version, S_IRUGO|S_IWUSR, charger_ver_get,NULL);
+
+#ifdef LENOVO_OTG_USB_SHORT
+int bq2589x_turn_otg_vbus(bool votg_on);
+
+int bq2589x_otg_short_config(struct bq2589x_chip *chip, int en)
+{
+ int value;
+
+ if(en==1)
+ {
+ if(gpio_is_valid(chip->otg_usb_short_gpio)) {
+ //ret = gpio_request(chip->otg_usb_short_gpio, "otg_usb_short_gpio");
+ //bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 0, BQ2589X_REG_03_OTG_CONFIG, BQ2589X_REG_03_OTG_CONFIG_MASK);
+ bq2589x_turn_otg_vbus(0);
+ usleep(1000*1000);
+ gpio_set_value(chip->otg_usb_short_gpio,1);
+ usleep(1000*1000);
+ //bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 1, BQ2589X_REG_03_OTG_CONFIG, BQ2589X_REG_03_OTG_CONFIG_MASK);
+ bq2589x_turn_otg_vbus(1);
+
+ chip->otg_usb_short_state = true;
+ }else
+ chip->otg_usb_short_state = false;
+ }else
+ {
+ if(gpio_is_valid(chip->otg_usb_short_gpio)) {
+ //ret = gpio_request(chip->otg_usb_short_gpio, "otg_usb_short_gpio");
+ bq2589x_turn_otg_vbus(0);
+ gpio_set_value(chip->otg_usb_short_gpio,0);
+ usleep(2000*1000);
+ bq2589x_turn_otg_vbus(1);
+
+ chip->otg_usb_short_state = false;
+ }else
+ chip->otg_usb_short_state = false;
+ }
+
+ value = gpio_get_value(chip->otg_usb_short_gpio);
+
+ return value;
+}
+
+static ssize_t otg_usb_short_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ ssize_t ret = strnlen(buf, PAGE_SIZE);
+ int cmd;
+ int value = -1;
+ struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+ struct bq2589x_chip *chip = i2c_get_clientdata(client);
+
+ sscanf(buf, "%x", &cmd);
+
+ if(chip->otg_usb_short_state==cmd)
+ {
+ pr_err("%s new cmd is as same as the old value(%d)\n", __func__, chip->otg_usb_short_state);
+ return ret;
+ }
+
+ value = bq2589x_otg_short_config(chip, cmd);
+
+ printk("otg usb short state is %d, cmd %d, ret %d\n",chip->otg_usb_short_state, cmd, value);
+
+ return ret;
+}
+
+
+ssize_t otg_usb_short_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+ struct bq2589x_chip *chip = i2c_get_clientdata(client);
+
+ printk("otg usb short state is %d\n",chip->otg_usb_short_state);
+ ret = sprintf(buf, "%d\n", chip->otg_usb_short_state);
+
+ return ret;
+}
+
+static DEVICE_ATTR(otg_usb_short, S_IRUGO|S_IWUSR,otg_usb_short_get,otg_usb_short_set);
+#endif
+
+static struct attribute *fs_attrs[] = {
+ &dev_attr_chrg_version.attr,
+#ifdef LENOVO_OTG_USB_SHORT
+ &dev_attr_otg_usb_short.attr,
+#endif
+ NULL,
+};
+
+static struct attribute_group fs_attr_group = {
+ .attrs = fs_attrs,
+};
+
+static int bq2589x_parse_dt(struct device *dev,struct bq2589x_chip *chip)
+{
+ struct device_node *np = dev->of_node;
+
+ chip->irq_gpio = of_get_named_gpio_flags(np, "charger,irq-gpio", 0, 0);
+ chip->chg_en_gpio = of_get_named_gpio_flags(np, "charger,en-gpio", 0, 0);
+ chip->otg_en_gpio = of_get_named_gpio_flags(np, "charger,otg-en-gpio", 0, 0);
+#ifdef LENOVO_OTG_USB_SHORT
+ chip->otg_usb_short_gpio = of_get_named_gpio_flags(np,
+ "charger,otg-short-gpio", 0, 0);
+#endif
+
+ of_property_read_u32(np,"charger,ichg-max" ,&chip->iusb_max);
+ of_property_read_u32(np,"charger,iterm-ma" ,&chip->iterm);
+ of_property_read_u32(np,"charger,ibat-max" ,&chip->ibat_normal);
+ of_property_read_u32(np,"charger,vbat-mv" ,&chip->vbat_normal);
+ of_property_read_u32(np,"charger,warm-temp" ,&chip->warm_temp);
+ of_property_read_u32(np,"charger,ibat-warm" ,&chip->ibat_warm);
+ of_property_read_u32(np,"charger,ibat-cool" ,&chip->ibat_cool);
+ of_property_read_u32(np,"charger,cool-temp" ,&chip->cool_temp);
+ of_property_read_u32(np,"charger,vbat-warm" ,&chip->vbat_warm);
+ of_property_read_u32(np,"charger,vbat-cool" ,&chip->vbat_cool);
+#ifdef CONFIG_FAKE_BATTERY
+ chip->sfi_tabl_present = 1;
+#else
+ chip->sfi_tabl_present = 0;
+#endif
+ chip->is_charging_enabled = true;
+ chip->bat_is_warm = false;
+ chip->bat_is_cool = false;
+ chip->is_first_start = true;
+
+ return 0;
+}
+
+#ifdef CONFIG_OTG_SUPPORT
+static int bq2589x_turn_otg_vbus(struct bq2589x_chip* chip, bool votg_on)
+{
+ int ret = 0;
+
+ pr_info("turn on otg vbus %d\n", votg_on);
+
+ if (votg_on) {
+ /* Program the timers */
+ ret = program_timers(chip,
+ CHRG_TIMER_EXP_CNTL_WDT80SEC,
+ false);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "TIMER enable failed %s\n", __func__);
+ goto i2c_write_fail;
+ }
+
+#ifdef CONFIG_CHARGER_DUAL
+ charger_dual_chip_set_property(POWER_SUPPLY_PROP_CHARGE_ENABLED, 0);
+#endif
+
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_0A, 0x6, BQ2589X_REG_0A_BOOST_LIM, BQ2589X_REG_0A_BOOST_LIM_MASK);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "otg boost lim write 1.3A failed\n");
+ goto i2c_write_fail;
+ }
+
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_0A, 0xc, BQ2589X_REG_0A_BOOSTV, BQ2589X_REG_0A_BOOSTV_MASK);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "otg boost lim write 1.3A failed\n");
+ goto i2c_write_fail;
+ }
+
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 2, BQ2589X_REG_03_CHG_CONFIG, 0x3);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "otg set config failed\n");
+ goto i2c_write_fail;
+ }
+
+ chip->boost_mode = true;
+ } else {
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 0, BQ2589X_REG_03_OTG_CONFIG, BQ2589X_REG_03_OTG_CONFIG_MASK);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "otg set config failed\n");
+ goto i2c_write_fail;
+ }
+
+ chip->boost_mode = false;
+
+#ifdef CONFIG_CHARGER_DUAL
+ charger_dual_chip_set_property(POWER_SUPPLY_PROP_CHARGE_ENABLED, 1);
+#endif
+
+ //restart charger when come back from otg mode
+ if( chip->is_charging_enabled )
+ bq2589x_enable_charging(chip, true);
+ }
+
+ return ret;
+i2c_write_fail:
+ dev_err(&chip->client->dev, "%s: Failed\n", __func__);
+ return ret;
+}
+
+static int bq2589x_otg_regulator_enable(struct regulator_dev *rdev)
+{
+ struct bq2589x_chip *chip = rdev_get_drvdata(rdev);
+
+ return bq2589x_turn_otg_vbus(chip, true);
+}
+
+static int bq2589x_otg_regulator_disable(struct regulator_dev *rdev)
+{
+ struct bq2589x_chip *chip = rdev_get_drvdata(rdev);
+
+#ifdef LENOVO_OTG_USB_SHORT
+ {
+ int val;
+
+ val = bq2589x_otg_short_config(chip, 0);
+ pr_info("bq2589x_otg_short_config = %d", val);
+ }
+#endif
+
+ return bq2589x_turn_otg_vbus(chip, false);
+}
+
+static int bq2589x_otg_regulator_is_enable(struct regulator_dev *rdev)
+{
+ struct bq2589x_chip *chip = rdev_get_drvdata(rdev);
+ return chip->boost_mode;
+}
+
+struct regulator_ops bq2589x_otg_reg_ops = {
+ .enable = bq2589x_otg_regulator_enable,
+ .disable = bq2589x_otg_regulator_disable,
+ .is_enabled = bq2589x_otg_regulator_is_enable,
+};
+
+static int bq2589x_regulator_init(struct device *dev,struct bq2589x_chip *chip)
+{
+ int rc = 0;
+ struct regulator_init_data *init_data;
+ struct regulator_config cfg = {};
+
+ init_data = of_get_regulator_init_data(dev, dev->of_node);
+ if (!init_data) {
+ dev_err(dev, "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ if (init_data->constraints.name) {
+ chip->otg_vreg.rdesc.owner = THIS_MODULE;
+ chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
+ chip->otg_vreg.rdesc.ops = &bq2589x_otg_reg_ops;
+ chip->otg_vreg.rdesc.name = init_data->constraints.name;
+
+ cfg.dev = dev;
+ cfg.init_data = init_data;
+ cfg.driver_data = chip;
+ cfg.of_node = dev->of_node;
+
+ init_data->constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_STATUS;
+
+ chip->otg_vreg.rdev = regulator_register(
+ &chip->otg_vreg.rdesc, &cfg);
+ if (IS_ERR(chip->otg_vreg.rdev)) {
+ rc = PTR_ERR(chip->otg_vreg.rdev);
+ chip->otg_vreg.rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ dev_err(dev, "OTG reg failed, rc=%d\n", rc);
+ }
+ }
+
+ return rc;
+}
+#endif
+
+//dummy function for unused static functions
+void dummy_function(void)
+{
+ bq2589x_reg_read_mask(NULL, 0, 0, 0);
+ bq2589x_modify_vindpm(NULL, 0);
+ vindpm_to_reg(0);
+ bq2589x_is_charging_done(NULL);
+#ifdef CONFIG_CHARGING_APP_CONTROL
+ bq2589x_set_appropriate_cc(NULL);
+#endif
+}
+
+#ifdef EXT_CHARGER_POWER_SUPPLY
+//static char *bq2589x_chip_name = "ext-charger-dual";
+static char *bq2589x_chip_name = "ext-charger";
+
+static enum power_supply_property bq2589x_power_supply_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TEMP,
+// POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_CHARGE_ENABLED,
+ POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_USB_OTG,
+};
+
+static int bq2589x_power_supply_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq2589x_chip *chip = container_of(psy, struct bq2589x_chip, charger_psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = bq2589x_get_charging_status(chip);
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = bq2589x_get_charging_status(chip);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = chip->batt_temp;
+ break;
+/* case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = chip->model;
+ break;*/
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ val->intval = (chip->is_charging_enabled)? 1:0;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = chip->iusb_max;
+ break;
+ default:
+ pr_err("%s unsupported psy %d\n", __func__, psp);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bq2589x_power_supply_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bq2589x_chip *chip = container_of(psy, struct bq2589x_chip, charger_psy);
+ int value;
+
+ switch (psp) {
+ //just for charging temp protect function debug
+ case POWER_SUPPLY_PROP_TEMP:
+ pr_err("%s debug charging temp, intval = %d\n", __func__, val->intval);
+ if((val->intval<-20)&&(val->intval>60))
+ chip->temp_debug_flag = false;
+ else
+ {
+ chip->temp_debug_flag = true;
+ chip->batt_temp = val->intval;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ bq2589x_enable_charger(chip, val->intval);
+#ifdef CONFIG_CHARGER_DUAL
+ charger_dual_chip_set_property(POWER_SUPPLY_PROP_CHARGE_ENABLED, val->intval);
+#endif
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ bq2589x_enable_charging(chip, val->intval);
+#ifdef CONFIG_CHARGER_DUAL
+ charger_dual_chip_set_property(POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED, val->intval);
+#endif
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ value = val->intval;
+ bq2589x_set_iusb_max(chip, value);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ value = val->intval;
+ bq2589x_set_ibat_max(chip, value);
+ break;
+ case POWER_SUPPLY_PROP_FLASH_ACTIVE:
+ pr_warn("%s not support POWER_SUPPLY_PROP_FLASH_ACTIVE \n", __func__);
+ break;
+ case POWER_SUPPLY_PROP_USB_OTG:
+ pr_warn("%s property set otg %d \n", __func__, val->intval);
+ bq2589x_turn_otg_vbus(chip, (val->intval?true:false));
+ break;
+ default:
+ pr_err("%s not support power_supply property cmd\n", __func__);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int bq2589x_power_supply_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_FLASH_ACTIVE:
+ case POWER_SUPPLY_PROP_USB_OTG:
+ return 1;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int bq2589x_power_supply_init(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ chip->charger_psy.name = bq2589x_chip_name;//chip->name;
+ chip->charger_psy.type = POWER_SUPPLY_TYPE_USB;
+ chip->charger_psy.properties = bq2589x_power_supply_props;
+ chip->charger_psy.num_properties = ARRAY_SIZE(bq2589x_power_supply_props);
+ chip->charger_psy.get_property = bq2589x_power_supply_get_property;
+ chip->charger_psy.set_property = bq2589x_power_supply_set_property;
+ chip->charger_psy.property_is_writeable = bq2589x_power_supply_property_is_writeable;
+
+ ret = power_supply_register(&chip->client->dev, &chip->charger_psy);
+ if (ret)
+ {
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static void bq2589x_power_supply_exit(struct bq2589x_chip *chip)
+{
+ //cancel_delayed_work_sync(&chip->work);
+ power_supply_unregister(&chip->charger_psy);
+
+}
+#endif
+
+static int bq2589x_regulator_get(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ chip->vdd = regulator_get(&chip->client->dev, "vdd");
+ if (IS_ERR_OR_NULL(chip->vdd)) {
+ pr_err("%s: fail to get 1.8v LDO\n", __func__);
+ return -3;
+ }
+
+ if (regulator_count_voltages(chip->vdd) > 0) {
+ ret = regulator_set_voltage(chip->vdd, 1800000, 1800000);
+ if (ret) {
+ pr_err("%s: regulator set_vtg vdd_reg failed rc=%d\n", __func__, ret);
+ regulator_put(chip->vdd);
+ return -4;
+ }
+ }
+
+ ret = regulator_enable(chip->vdd);
+ if (ret) {
+ pr_err("%s: Regulator vdd_reg enable failed rc=%d\n", __func__, ret);
+ }
+
+ return 0;
+}
+
+static irqreturn_t bq2589x_usbid_change_handler(int irq, void *_chip)
+{
+ //struct bq2589x_chip *chip = _chip;
+ bool otg_present = 0;
+
+ //need to be judged by io level
+ //otg_present = chip->boost_mode;
+ usb_chip_set_property(POWER_SUPPLY_PROP_USB_OTG, otg_present ? 1 : 0);
+ if (otg_present)
+ pr_info( "OTG detected\n");
+
+ return IRQ_HANDLED;
+}
+
+static bool bq2589x_charger_is_factory_mode(void)
+{
+ struct device_node *np = of_find_node_by_path("/chosen");
+ bool factory = false;
+
+ if (np)
+ factory = of_property_read_bool(np, "mmi,factory-cable");
+
+ of_node_put(np);
+
+ return factory;
+}
+
+static int bq2589x_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct bq2589x_chip *chip;
+ int ret;
+
+ printk("%s \n", __func__);
+
+ fg_psy = power_supply_get_by_name("battery");
+ if (!fg_psy) {
+ pr_err("bq25892 fg supply not found deferring probe\n");
+ return -EPROBE_DEFER;
+ }
+ chip = kzalloc(sizeof(struct bq2589x_chip), GFP_KERNEL);
+ if (!chip) {
+ dev_err(&client->dev, "bq25892 mem alloc failed\n");
+ return -ENOMEM;
+ }
+
+#ifdef CONFIG_OF
+ if (client->dev.of_node) {
+ ret = bq2589x_parse_dt(&client->dev, chip);
+ if (ret)
+ return ret;
+
+ if (of_property_read_bool(client->dev.of_node, "charger,external-typec")) {
+ typec_psy = power_supply_get_by_name(TYPEC_PSY_NAME);
+ if (!typec_psy) {
+ pr_err("Type-C supply not found, deferring probe\n");
+ return -EPROBE_DEFER;
+ }
+ }
+ }
+#endif
+ chip->client = client;
+
+ bq2589x_regulator_get(chip);
+
+ /*assigning default value for min and max temp*/
+ i2c_set_clientdata(client, chip);
+ chip->chgr_stat = POWER_SUPPLY_STATUS_UNKNOWN;
+ chip->is_factory_cable = false;
+ chip->is_factory_mode = bq2589x_charger_is_factory_mode();
+ if (chip->is_factory_mode) {
+ dev_warn(&client->dev,
+ "Entering Factory Mode\n");
+ }
+
+ /* check chip model number */
+ ret = bq2589x_get_chip_version(chip);
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c read err:%d\n", ret);
+ i2c_set_clientdata(client, NULL);
+ kfree(chip);
+ regulator_disable(chip->vdd);
+ return -EIO;
+ }
+
+ printk("%s chip version %s \n", __func__, chip->ic_name);
+
+ INIT_DELAYED_WORK(&chip->chrg_task_wrkr, bq2589x_task_worker);
+ INIT_DELAYED_WORK(&chip->fault_work, bq2589x_fault_worker);
+ if(chip->is_factory_mode)
+ INIT_DELAYED_WORK(&chip->charger_check_work, bq2589x_charger_check_worker);
+ mutex_init(&chip->event_lock);
+
+ /* Initialize the wakelock */
+ wake_lock_init(&chip->wakelock, WAKE_LOCK_SUSPEND, "ctp_charger_wakelock");
+ wake_lock_init(&chip->irq_wk, WAKE_LOCK_SUSPEND, "charger_irq_wakelock");
+
+ /* Init Runtime PM State */
+ pm_runtime_put_noidle(&chip->client->dev);
+ pm_schedule_suspend(&chip->client->dev, MSEC_PER_SEC);
+
+ /* create debugfs for maxim registers */
+ ret = bq2589x_create_debugfs(chip);
+ if (ret < 0) {
+ dev_err(&client->dev, "debugfs create failed\n");
+ }
+
+#ifdef EXT_CHARGER_POWER_SUPPLY
+ ret = bq2589x_power_supply_init(chip);
+ if (ret) {
+ pr_err("failed to register power supply: %d\n", ret);
+ }
+ ret = sysfs_create_group(&chip->charger_psy.dev->kobj,&fs_attr_group);
+#else
+ ret = sysfs_create_group(&client->dev.kobj,&fs_attr_group);
+#endif
+
+ if (ret) {
+ dev_err(&client->dev, "failed to setup sysfs ret = %d\n", ret);
+ }
+
+ bq2589x_usbid_change_handler(0, chip);
+ usb_chip_set_property(POWER_SUPPLY_PROP_DP_DM, POWER_SUPPLY_DP_DM_DPR_DMR); //enable usb dp dm ldo
+
+ /*
+ * Request for charger chip gpio.This will be used to
+ * register for an interrupt handler for servicing charger
+ * interrupts
+ */
+ if (gpio_is_valid(chip->irq_gpio)) {
+ ret = gpio_request(chip->irq_gpio, "chg_irq_gpio");
+ ret = gpio_direction_input(chip->irq_gpio);
+ chip->irq = gpio_to_irq(chip->irq_gpio);
+ } else {
+ pr_err("invalid irq gpio\n");
+ chip->irq = -1;
+ }
+
+ if (chip->irq < 0) {
+ pr_err("%s chgr_int_n GPIO is not available\n", __func__);
+ } else {
+ //tlmm_set_config_pullup(chip->irq_gpio, 1);
+ ret = request_threaded_irq(chip->irq,
+ bq2589x_irq_isr, bq2589x_irq_thread,
+ IRQF_TRIGGER_FALLING, "bq2589x", chip);
+// IRQF_TRIGGER_HIGH, "bq2589x", chip);
+ if (ret) {
+ pr_warn("%s failed to register irq for pin %d as %d\n", __func__, chip->irq_gpio, IRQF_TRIGGER_FALLING);
+ } else {
+ pr_warn("%s registered charger irq for pin %d\n", __func__, chip->irq_gpio);
+ }
+ }
+
+#if 1
+ if (gpio_is_valid(chip->chg_en_gpio)) {
+ ret = gpio_request(chip->chg_en_gpio, "chg_en_gpio");
+ gpio_direction_output(chip->chg_en_gpio,0);
+ gpio_set_value(chip->chg_en_gpio,0);
+ }
+ else
+ pr_err("%s chg_en_gpio is invalid\n", __func__);
+#endif
+#ifdef LENOVO_OTG_USB_SHORT
+ if(gpio_is_valid(chip->otg_usb_short_gpio)) {
+ ret = gpio_request(chip->otg_usb_short_gpio, "otg_usb_short_gpio");
+ gpio_direction_output(chip->otg_usb_short_gpio,0);
+ gpio_set_value(chip->otg_usb_short_gpio,0);
+ }
+#endif
+
+ bq2589x_hw_init(chip);
+
+#ifdef CONFIG_OTG_SUPPORT
+ bq2589x_regulator_init(&client->dev, chip);
+#endif
+ bq2589x_enable_charger(chip, 1);
+
+ bq2589x_dump_registers(chip);
+
+ schedule_delayed_work(&chip->chrg_task_wrkr, msecs_to_jiffies(1000));
+
+ pr_info("%s prob success\n", __func__);
+
+ return 0;
+}
+
+static int bq2589x_remove(struct i2c_client *client)
+{
+ struct bq2589x_chip *chip = i2c_get_clientdata(client);
+
+ bq2589x_remove_debugfs(chip);
+
+#ifdef EXT_CHARGER_POWER_SUPPLY
+ bq2589x_power_supply_exit(chip);
+#endif
+
+ if (chip->irq > 0)
+ free_irq(chip->irq, chip);
+
+ i2c_set_clientdata(client, NULL);
+ wake_lock_destroy(&chip->wakelock);
+
+ kfree(chip);
+ return 0;
+}
+
+static void bq2589x_shutdown(struct i2c_client *client)
+{
+ struct bq2589x_chip *chip = i2c_get_clientdata(client);
+
+ printk("bq2589x_shutdown \r\n");
+
+ wake_lock_destroy(&chip->wakelock);
+
+ if (chip->irq > 0) {
+ free_irq(chip->irq, chip);
+ }
+ cancel_delayed_work_sync(&chip->chrg_task_wrkr);
+ cancel_delayed_work_sync(&chip->fault_work);
+
+ if(1)
+ {
+ if(gpio_is_valid(chip->chg_en_gpio)) {
+ printk("disable charging for shutting down\r\n");
+ gpio_set_value(chip->chg_en_gpio,1);
+ }
+
+ bq2589x_set_iusb_max(chip, 0);
+
+ msleep(250);
+ }
+
+ bq2589x_reset_regs(chip);
+
+ printk("bq2589x_shutdown complete\r\n");
+}
+
+#ifdef CONFIG_PM
+static int bq2589x_suspend(struct device *dev)
+{
+ struct bq2589x_chip *chip = dev_get_drvdata(dev);
+
+ if (chip->irq > 0) {
+ /*
+ * Once the WDT is expired all bq2589x registers gets
+ * set to default which means WDT is programmed to 40s
+ * and if there is no charger connected, no point
+ * feeding the WDT. Since reg07[1] is set to default,
+ * charger will interrupt SOC every 40s which is not
+ * good for S3. In this case we need to free chgr_int_n
+ * interrupt so that no interrupt from charger wakes
+ * up the platform in case of S3. Interrupt will be
+ * re-enabled on charger connect.
+ */
+ free_irq(chip->irq, chip);
+ //enable_irq_wake(chip->irq);
+ }
+ cancel_delayed_work_sync(&chip->chrg_task_wrkr);
+ cancel_delayed_work_sync(&chip->fault_work);
+
+ //regulator operation
+#if 1
+ {
+ int ret= regulator_disable(chip->vdd);
+ if (ret) {
+ pr_err("%s: Regulator vdd_reg disable failed rc=%d\n", __func__, ret);
+ }
+ }
+#endif
+
+ dev_dbg(&chip->client->dev, "bq2589x suspend,cancel charger worker\n");
+ return 0;
+}
+
+static int bq2589x_resume(struct device *dev)
+{
+ struct bq2589x_chip *chip = dev_get_drvdata(dev);
+ int ret;
+
+ //regulator operation
+#if 1
+ ret = regulator_enable(chip->vdd);
+ if (ret) {
+ pr_err("%s: Regulator vdd_reg enable failed rc=%d\n", __func__, ret);
+ }
+#endif
+
+ if (chip->irq > 0) {
+ //tlmm_set_config_pullup(chip->irq_gpio, 1);
+ ret = request_threaded_irq(chip->irq,
+ bq2589x_irq_isr, bq2589x_irq_thread,
+ IRQF_TRIGGER_FALLING, "bq2589x", chip);
+ if (ret) {
+ dev_warn(dev, "failed to register irq for pin %d\n",
+ chip->irq_gpio);
+ } else {
+ dev_warn(dev, "registered charger irq for pin %d\n",
+ chip->irq_gpio);
+ }
+ //disable_irq_wake(chip->irq);
+ }
+ schedule_delayed_work(&chip->fault_work, 0);
+ schedule_delayed_work(&chip->chrg_task_wrkr, msecs_to_jiffies(20));
+
+ dev_dbg(&chip->client->dev, "bq2589x resume,shecdule charger work and fault work\n");
+ return 0;
+}
+#else
+#define bq2589x_suspend NULL
+#define bq2589x_resume NULL
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int bq2589x_runtime_suspend(struct device *dev)
+{
+
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int bq2589x_runtime_resume(struct device *dev)
+{
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int bq2589x_runtime_idle(struct device *dev)
+{
+
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+#else
+#define bq2589x_runtime_suspend NULL
+#define bq2589x_runtime_resume NULL
+#define bq2589x_runtime_idle NULL
+#endif
+
+MODULE_DEVICE_TABLE(i2c, bq2589x_id);
+
+static const struct dev_pm_ops bq2589x_pm_ops = {
+ .suspend = bq2589x_suspend,
+ .resume = bq2589x_resume,
+ .runtime_suspend = bq2589x_runtime_suspend,
+ .runtime_resume = bq2589x_runtime_resume,
+ .runtime_idle = bq2589x_runtime_idle,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id bq2589x_match[] = {
+ { .compatible = "ti,bq2589x" },
+ { },
+};
+#endif
+
+static const struct i2c_device_id bq2589x_id[] = {
+ { DEV_NAME, 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, bq2589x_id);
+
+static struct i2c_driver bq2589x_battery_driver = {
+ .driver = {
+ .name = DEV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &bq2589x_pm_ops,
+#ifdef CONFIG_OF
+ .of_match_table = of_match_ptr(bq2589x_match),
+#endif
+ },
+ .probe = bq2589x_charger_probe,
+ .remove = bq2589x_remove,
+ .shutdown = bq2589x_shutdown,
+ .id_table = bq2589x_id,
+};
+
+static inline int bq2589x_battery_i2c_init(void)
+{
+ int ret = i2c_add_driver(&bq2589x_battery_driver);
+ printk("%s:bq2589x register_i2c driver\n",__func__);
+ if (ret)
+ printk(KERN_ERR "Unable to register bq2589x i2c driver\n");
+
+ return ret;
+}
+
+static inline void bq2589x_battery_i2c_exit(void)
+{
+ i2c_del_driver(&bq2589x_battery_driver);
+}
+
+static int __init bq2589x_battery_init(void)
+{
+ int ret;
+ ret = bq2589x_battery_i2c_init();
+
+ pr_notice("bq2589x %s %d\n", __func__, ret);
+
+ return ret;
+}
+module_init(bq2589x_battery_init);
+
+static void __exit bq2589x_battery_exit(void)
+{
+ bq2589x_battery_i2c_exit();
+}
+module_exit(bq2589x_battery_exit);
+
diff --git a/drivers/power/lenovo/bq2589x_charger.h b/drivers/power/lenovo/bq2589x_charger.h
new file mode 100644
index 00000000..ff9c5c0c
--- /dev/null
+++ b/drivers/power/lenovo/bq2589x_charger.h
@@ -0,0 +1,342 @@
+/*
+ * bq2589x_charger.h - Charger driver for TI BQ25892/25890
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/power_supply.h>
+
+#ifndef __BQ2589X_CHARGER_H_
+#define __BQ2589X_CHARGER_H_
+
+#if defined(__BQ2589X_CHARGER_C_)||defined(__BQ2589X_CHARGER_DUAL_C_)
+
+#define CONFIG_OTG_SUPPORT
+#define CONFIG_CHARGING_APP_CONTROL
+#define CONFIG_CHARGER_DUAL
+
+#define BQ2589X_REG_00 0x00
+#define BQ2589X_REG_01 0x01
+#define BQ2589X_REG_02 0x02
+#define BQ2589X_REG_03 0x03
+#define BQ2589X_REG_04 0x04
+#define BQ2589X_REG_05 0x05
+#define BQ2589X_REG_06 0x06
+#define BQ2589X_REG_07 0x07
+#define BQ2589X_REG_08 0x08
+#define BQ2589X_REG_09 0x09
+#define BQ2589X_REG_0A 0x0a
+#define BQ2589X_REG_0B 0x0b
+#define BQ2589X_REG_0C 0x0c
+#define BQ2589X_REG_0D 0x0d
+#define BQ2589X_REG_0E 0x0e
+#define BQ2589X_REG_0F 0x0f
+#define BQ2589X_REG_10 0x10
+#define BQ2589X_REG_11 0x11
+#define BQ2589X_REG_12 0x12
+#define BQ2589X_REG_13 0x13
+#define BQ2589X_REG_14 0x14
+
+//---------------------------reg00--------------------------
+#define BQ2589X_REG_00_EN_HIZ (7)
+#define BQ2589X_REG_00_EN_ILIM (6)
+#define BQ2589X_REG_00_ILIM (0)
+
+#define BQ2589X_REG_00_EN_HIZ_MASK 0x1
+#define BQ2589X_REG_00_EN_ILIM_MASK 0x1
+#define BQ2589X_REG_00_ILIM_MASK 0x3f
+
+//---------------------------reg01--------------------------
+#define BQ2589X_REG_01_BHOT (6)
+#define BQ2589X_REG_01_BCOLD ( 5)
+#define BQ2589X_REG_01_VINDPM (0)
+
+#define BQ2589X_REG_01_BHOT_MASK 0x7
+#define BQ2589X_REG_01_BCOLD_MASK 0x1
+#define BQ2589X_REG_01_VINDPM_MASK 0x1f
+
+//---------------------------reg02--------------------------
+#define BQ2589X_REG_02_CONV_START (7)
+#define BQ2589X_REG_02_CONV_RATE (6)
+#define BQ2589X_REG_02_BOOST_FREQ (5)
+#define BQ2589X_REG_02_ICO_EN (4)
+#define BQ2589X_REG_02_HVDCP_EN (3)
+#define BQ2589X_REG_02_MAXC_EN (2)
+#define BQ2589X_REG_02_FORCE_DPDM (1)
+#define BQ2589X_REG_02_AUTO_DPDM_EN (0)
+
+#define BQ2589X_REG_02_CONV_START_MASK 0x1
+#define BQ2589X_REG_02_CONV_RATE_MASK 0x1
+#define BQ2589X_REG_02_BOOST_FREQ_MASK 0x1
+#define BQ2589X_REG_02_ICO_EN_MASK 0x1
+#define BQ2589X_REG_02_HVDCP_EN_MASK 0x1
+#define BQ2589X_REG_02_MAXC_EN_MASK 0x1
+#define BQ2589X_REG_02_FORCE_DPDM_MASK 0x1
+#define BQ2589X_REG_02_AUTO_DPDM_EN_MASK 0x1
+//---------------------------reg03--------------------------
+#define BQ2589X_REG_03_BAT_LOADEN (7)
+#define BQ2589X_REG_03_WD_RST (6)
+#define BQ2589X_REG_03_OTG_CONFIG (5)
+#define BQ2589X_REG_03_CHG_CONFIG (4)
+#define BQ2589X_REG_03_SYS_MIN (1)
+
+#define BQ2589X_REG_03_BAT_LOADEN_MASK 0x1
+#define BQ2589X_REG_03_WD_RST_MASK 0x1
+#define BQ2589X_REG_03_OTG_CONFIG_MASK 0x1
+#define BQ2589X_REG_03_CHG_CONFIG_MASK 0x1
+#define BQ2589X_REG_03_SYS_MIN_MASK 0x7
+//---------------------------reg04--------------------------
+#define BQ2589X_REG_04_ICHG (0)
+
+#define BQ2589X_REG_04_ICHG_MASK 0x7f
+//---------------------------reg05--------------------------
+#define BQ2589X_REG_05_IPRECHG (4)
+#define BQ2589X_REG_05_ITERM (0)
+
+#define BQ2589X_REG_05_IPRECHG_MASK 0xf
+#define BQ2589X_REG_05_ITERM_MASK 0xf
+//---------------------------reg06--------------------------
+#define BQ2589X_REG_06_VREG (2)
+#define BQ2589X_REG_06_BATLOWV (1)
+#define BQ2589X_REG_06_VRECHG (0)
+
+#define BQ2589X_REG_06_VREG_MASK 0x3f
+#define BQ2589X_REG_06_BATLOWV_MASK 0x1
+#define BQ2589X_REG_06_VRECHG_MASK 0x1
+//---------------------------reg07--------------------------
+#define BQ2589X_REG_07_EN_TERM (7)
+#define BQ2589X_REG_07_WATCHDOG (4)
+#define BQ2589X_REG_07_EN_TIMER (3)
+#define BQ2589X_REG_07_CHG_TIMER (1)
+#define BQ2589X_REG_07_JEITA_ISET (0)
+
+#define BQ2589X_REG_07_EN_TERM_MAS_M_MASKASK 0x1
+#define BQ2589X_REG_07_WATCHDOG_MAS_MASK 0x3
+#define BQ2589X_REG_07_EN_TIMER_MAS_MASK 0x1
+#define BQ2589X_REG_07_CHG_TIMER_MAS_MASK 0x1
+#define BQ2589X_REG_07_JEITA_ISET_MAS_MASK 0x1
+//---------------------------reg08--------------------------
+#define BQ2589X_REG_08_BAT_COMP (5)
+#define BQ2589X_REG_08_VCLAMP (2)
+#define BQ2589X_REG_08_TREG (0)
+
+#define BQ2589X_REG_08_BAT_COMP_MASK 0x7
+#define BQ2589X_REG_08_VCLAMP_MASK 0x7
+#define BQ2589X_REG_08_TREG_MASK 0x3
+//---------------------------reg09--------------------------
+#define BQ2589X_REG_09_FORCE_ICO (7)
+#define BQ2589X_REG_09_TMR2X_EN (6)
+#define BQ2589X_REG_09_BATFET_DIS (5)
+#define BQ2589X_REG_09_JEITA_VSET (4)
+#define BQ2589X_REG_09_BATFET_RST_EN (2)
+
+#define BQ2589X_REG_09_FORCE_ICO_MASK 0x1
+#define BQ2589X_REG_09_TMR2X_EN_MASK 0x1
+#define BQ2589X_REG_09_BATFET_DIS_MASK 0x1
+#define BQ2589X_REG_09_JEITA_VSET_MASK 0x1
+#define BQ2589X_REG_09_BATFET_RST_EN_MASK 0x1
+//---------------------------reg0a--------------------------
+#define BQ2589X_REG_0A_BOOSTV (4)
+#define BQ2589X_REG_0A_BOOST_LIM (0)
+
+#define BQ2589X_REG_0A_BOOSTV_MASK 0xf
+#define BQ2589X_REG_0A_BOOST_LIM_MASK 0x7
+//---------------------------reg0b--------------------------
+#define BQ2589X_REG_0B_VBUS_STAT (5)
+#define BQ2589X_REG_0B_CHG_STAT (3)
+#define BQ2589X_REG_0B_PG_STAT (2)
+#define BQ2589X_REG_0B_SDP_STAT (1)
+#define BQ2589X_REG_0B_VSYS_STAT (0)
+
+#define BQ2589X_REG_0B_VBUS_STAT_MASK 0x7
+#define BQ2589X_REG_0B_CHG_STAT_MASK 0x3
+#define BQ2589X_REG_0B_PG_STAT_MASK 0x1
+#define BQ2589X_REG_0B_SDP_STAT_MASK 0x1
+#define BQ2589X_REG_0B_VSYS_STAT_MASK 0x1
+//---------------------------reg0c--------------------------
+#define BQ2589X_REG_0C_WATCHDOG_FULAT (7)
+#define BQ2589X_REG_0C_BOOST_FULAT (6)
+#define BQ2589X_REG_0C_CHR_FULAT (4)
+#define BQ2589X_REG_0C_BAT_FULAT (3)
+#define BQ2589X_REG_0C_NTC_FULAT (0)
+
+#define BQ2589X_REG_0C_WATCHDOG_FULAT_MASK 0x1
+#define BQ2589X_REG_0C_BOOST_FULAT_MASK 0x1
+#define BQ2589X_REG_0C_CHR_FULAT_MASK 0x3
+#define BQ2589X_REG_0C_BAT_FULAT_MASK 0x1
+#define BQ2589X_REG_0C_NTC_FULAT_MASK 0x7
+//---------------------------reg0d--------------------------
+#define BQ2589X_REG_0D_FORCE_VINDPM (7)
+#define BQ2589X_REG_0D_VINDPM (0)
+
+#define BQ2589X_REG_0D_FORCE_VINDPM_MASK 0x1
+#define BQ2589X_REG_0D_VINDPM_MASK 0x7f
+//---------------------------reg0e--------------------------
+#define BQ2589X_REG_0E_THERM_STAT (7)
+#define BQ2589X_REG_0E_BATV (0)
+
+#define BQ2589X_REG_0E_THERM_STAT_MASK 0x1
+#define BQ2589X_REG_0E_BATV_MASK 0x7f
+//---------------------------reg0f--------------------------
+#define BQ2589X_REG_0F_SYSV (0)
+
+#define BQ2589X_REG_0F_SYSV_MASK 0x7f
+//---------------------------reg10--------------------------
+#define BQ2589X_REG_10_TSPCT (0)
+
+#define BQ2589X_REG_10_TSPCT_MASK 0x7f
+//---------------------------reg11--------------------------
+#define BQ2589X_REG_11_VBUS_GD (7)
+#define BQ2589X_REG_11_VBUSV (0)
+
+#define BQ2589X_REG_11_VBUS_GD_MASK 0x1
+#define BQ2589X_REG_11_VBUSV_MASK 0x7f
+//---------------------------reg12--------------------------
+#define BQ2589X_REG_12_ICHGR (0)
+
+#define BQ2589X_REG_12_ICHGR_MASK 0x7f
+//---------------------------reg13--------------------------
+#define BQ2589X_REG_13_VDPM_STAT (7)
+#define BQ2589X_REG_13_IDPM_STAT (6)
+#define BQ2589X_REG_13_IDPM_LIM (0)
+
+#define BQ2589X_REG_13_VDPM_STAT_MASK 0x1
+#define BQ2589X_REG_13_IDPM_STAT_MASK 0x1
+#define BQ2589X_REG_13_IDPM_LIM_MASK 0x3f
+//---------------------------reg14---------------------------
+#define BQ2589X_REG_14_REG_RST (7)
+#define BQ2589X_REG_14_ICO_OPTIMIZED (6)
+#define BQ2589X_REG_14_PN (3)
+#define BQ2589X_REG_14_TS_PROFILE (2)
+#define BQ2589X_REG_14_DEV_REV (0)
+
+#define BQ2589X_REG_14_REG_RST_MASK 0x1
+#define BQ2589X_REG_14_ICO_OPTIMIZED_MASK 0x1
+#define BQ2589X_REG_14_PN_MASK 0x7
+#define BQ2589X_REG_14_TS_PROFILE_MASK 0x1
+#define BQ2589X_REG_14_DEV_REV_MASK 0x3
+
+#define TEMP_NR_RNG 4
+#define BATTID_STR_LEN 8
+#define RANGE 25
+/* User limits for sysfs charge enable/disable */
+#define USER_SET_CHRG_DISABLE 0
+#define USER_SET_CHRG_LMT1 1
+#define USER_SET_CHRG_LMT2 2
+#define USER_SET_CHRG_LMT3 3
+#define USER_SET_CHRG_NOLMT 4
+
+#define INPUT_CHRG_CURR_0 0
+#define INPUT_CHRG_CURR_100 100
+#define INPUT_CHRG_CURR_500 500
+#define INPUT_CHRG_CURR_950 950
+#define INPUT_CHRG_CURR_1500 1500
+/* Charger Master Temperature Control Register */
+#define MSIC_CHRTMPCTRL 0x18E
+/* Higher Temprature Values*/
+#define CHRTMPCTRL_TMPH_60 (3 << 6)
+#define CHRTMPCTRL_TMPH_55 (2 << 6)
+#define CHRTMPCTRL_TMPH_50 (1 << 6)
+#define CHRTMPCTRL_TMPH_45 (0 << 6)
+
+/* Lower Temprature Values*/
+#define CHRTMPCTRL_TMPL_15 (3 << 4)
+#define CHRTMPCTRL_TMPL_10 (2 << 4)
+#define CHRTMPCTRL_TMPL_05 (1 << 4)
+#define CHRTMPCTRL_TMPL_00 (0 << 4)
+
+
+#define bq2589x_CHRG_ITERM_OFFSET 128
+
+#define CHRG_TIMER_EXP_CNTL_EN_TIMER (1 << BQ2589X_REG_07_EN_TIMER)
+
+#define CHRG_TIMER_EXP_CNTL_WDTDISABLE (0 << BQ2589X_REG_07_WATCHDOG)
+#define CHRG_TIMER_EXP_CNTL_WDT40SEC (1 << BQ2589X_REG_07_WATCHDOG)
+#define CHRG_TIMER_EXP_CNTL_WDT80SEC (2 << BQ2589X_REG_07_WATCHDOG)
+#define CHRG_TIMER_EXP_CNTL_WDT160SEC (3 << BQ2589X_REG_07_WATCHDOG)
+
+#define SYSTEM_STAT_VBUS_HOST (1 << BQ2589X_REG_0B_VBUS_STAT)
+#define SYSTEM_STAT_VBUS_CDP (2 << BQ2589X_REG_0B_VBUS_STAT)
+
+#define SYSTEM_STAT_CHRG_MASK (BQ2589X_REG_0B_CHG_STAT_MASK<< BQ2589X_REG_0B_CHG_STAT)
+#define SYSTEM_STAT_NOT_CHRG (0 << BQ2589X_REG_0B_CHG_STAT)
+#define SYSTEM_STAT_PRE_CHRG (1 << BQ2589X_REG_0B_CHG_STAT)
+#define SYSTEM_STAT_FAST_CHRG (2 << BQ2589X_REG_0B_CHG_STAT)
+#define SYSTEM_STAT_CHRG_DONE (3 << BQ2589X_REG_0B_CHG_STAT)
+
+#define FAULT_STAT_WDT_TMR_EXP (1 << BQ2589X_REG_0C_WATCHDOG_FULAT)
+#define FAULT_STAT_OTG_FLT (1 << BQ2589X_REG_0C_BOOST_FULAT)
+#define FAULT_STAT_CHRG_TMR_FLT (3 << BQ2589X_REG_0C_CHR_FULAT)
+
+#define bq2589x_VENDER_REV_REG 0xA
+/* D3, D4, D5 indicates the chip model number */
+#define BQ25852_IC_VERSION 0x0
+#define BQ25890_IC_VERSION 0x3
+#define bq25895_IC_VERSION 0x7
+
+#define NR_RETRY_CNT 3
+
+/*********************************************************************
+ * SFI table entries Structures
+ ********************************************************************/
+enum bq2589x_chip_type {
+ BQ25852,
+ BQ25890,
+ BQ25895,
+};
+
+/*********************************************************************
+ * Platform Data Section
+ *********************************************************************/
+struct bq2589x_platform_data {
+ /* safety charegr setting */
+ int max_cv;
+ int ichg_max;
+ int ibat_max;
+ int min_temp;
+
+ /*gpio config*/
+ int chg_irq_gpio;
+
+ /* Function pointers for platform specific initialization */
+ int (*init_platform_data)(void);
+ int (*get_irq_number)(void);
+ int (*query_otg)(void *, void *);
+ int (*drive_vbus)(bool);
+ int (*get_battery_pack_temp)(int *);
+ void (*free_platform_data)(void);
+};
+#else
+
+#if 0
+#ifdef CONFIG_CHARGER_BQ2589X
+extern int bq2589x_slave_mode_enable_charging(int volt, int cur, int ilim);
+extern int bq2589x_slave_mode_disable_charging(void);
+extern int bq2589x_query_battery_status(void);
+extern int bq2589x_get_battery_pack_temp(int *temp);
+extern int bq2589x_get_battery_health(void);
+extern bool bq2589x_is_volt_shutdown_enabled(void);
+#else
+static int bq2589x_get_battery_health(void)
+{
+ return 0;
+}
+#endif
+
+#ifdef CONFIG_CHARGER_BQ2589X_DUAL
+extern int bq2589x_dual_slave_mode_enable_charging(int volt, int cur, int ilim);
+extern int bq2589x_dual_slave_mode_disable_charging(void);
+extern int bq2589x_dual_query_battery_status(void);
+extern int bq2589x_dual_get_battery_pack_temp(int *temp);
+extern int bq2589x_dual_get_battery_health(void);
+extern bool bq2589x_dual_is_volt_shutdown_enabled(void);
+#else
+static int bq2589x_get_battery_health(void)
+{
+ return 0;
+}
+#endif
+#endif
+
+#endif /* __BQ2589X_CHARGER_C_ */
+#endif /* __BQ2589X_CHARGER_H_ */
diff --git a/drivers/power/lenovo/bq2589x_charger_dual.c b/drivers/power/lenovo/bq2589x_charger_dual.c
new file mode 100644
index 00000000..bb2f00d2
--- /dev/null
+++ b/drivers/power/lenovo/bq2589x_charger_dual.c
@@ -0,0 +1,1820 @@
+/*
+ * bq2589x_charger_dual.c - Charger driver for TI BQ25892/25890. The parallel charger.
+ *
+ */
+
+//#define DEBUG
+
+#ifndef __BQ2589X_CHARGER_DUAL_C_
+#define __BQ2589X_CHARGER_DUAL_C_
+#endif
+
+#define EXT_CHARGER_POWER_SUPPLY
+
+#ifdef CONFIG_DEBUG_FS
+#undef CONFIG_DEBUG_FS
+#endif
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/power_supply.h>
+#include <linux/sfi.h>
+#include <linux/pm_runtime.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/wakelock.h>
+#include <linux/version.h>
+#include <linux/usb/otg.h>
+#include <linux/rpmsg.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#ifdef CONFIG_OF
+#include <linux/of.h>
+#include <linux/of_device.h>
+#endif
+#include <linux/regulator/consumer.h>
+#include "bq2589x_charger.h"
+
+#define DEV_NAME "bq2589x-dual"
+
+#define CHARGER_TASK_JIFFIES (HZ * 15)/* 150sec */
+#define CHARGER_HOST_JIFFIES (HZ * 30) /* 60sec */
+
+/* Max no. of tries to i2c operation */
+#define MAX_TRY 3
+
+/* Max no. of tries to reset the bq2589xi WDT */
+#define MAX_RESET_WDT_RETRY 8
+
+#ifdef CONFIG_DEBUG_FS
+#define bq2589x_MAX_MEM 12
+#endif
+
+struct bq2589x_otg_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+};
+
+struct bq2589x_chip {
+ struct i2c_client *client;
+ struct bq2589x_platform_data *pdata;
+ enum bq2589x_chip_type chip_type;
+#ifdef EXT_CHARGER_POWER_SUPPLY
+ struct power_supply charger_psy;
+ int temp_debug_flag;
+#endif
+ struct delayed_work chrg_task_wrkr;
+ struct delayed_work fault_work;
+ struct work_struct otg_evt_work;
+ struct mutex event_lock;
+ struct bq2589x_otg_regulator otg_vreg;
+ /* Wake lock to prevent platform from going to S3 when charging */
+ struct wake_lock wakelock;
+ /* timeout Wake lock when charger status changed*/
+ struct wake_lock irq_wk;
+ struct regulator *vdd;
+ int chgr_stat;
+ int cc;
+ int cv;
+ int iusb_max;
+ int ibat_normal;
+ int vbat_normal;
+ int vbat_cool;
+ int vbat_warm;
+ int ibat_warm;
+ int ibat_cool;
+ int warm_temp;
+ int cool_temp;
+ int iterm;
+ int batt_temp;
+ int board_temp;
+ int batt_voltage;
+ int batt_status;
+ int irq_gpio;
+ int irq;
+ int chg_en_gpio;
+ int otg_en_gpio;
+ char ic_name[10];
+ bool bat_is_warm;
+ bool bat_is_cool;
+ bool is_charging_enabled;
+ bool online;
+ bool sfttmr_expired;
+ bool sfi_tabl_present;
+#ifdef CONFIG_OTG_SUPPORT
+ bool boost_mode;
+#endif
+};
+
+static struct power_supply *fg_psy = NULL;
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *bq2589x_dbgfs_root;
+static char bq2589x_dbg_regs[bq2589x_MAX_MEM][4];
+#endif
+
+static int bq2589x_get_chip_version(struct bq2589x_chip *chip);
+
+static int retry_sleep_ms[] = {
+ 10, 20, 30, 40, 50
+};
+
+/*
+ * Genenric register read/write interfaces to access registers in charger ic
+ */
+static int bq2589x_write_reg(struct i2c_client *client, u8 reg, u8 val)
+{
+ s32 ret;
+ int retry_count = 0;
+
+retry:
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+ if (ret < 0 && retry_count < MAX_TRY) {
+ /* sleep for few ms before retrying */
+ msleep(retry_sleep_ms[retry_count++]);
+ goto retry;
+ }
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "i2c write fail: can't write %02x to %02x: %d\n",
+ val, reg, ret);
+ return ret;
+ }
+ pr_debug("Writing 0x%02x=0x%02x\n", reg, val);
+ return 0;
+}
+
+static int bq2589x_read_reg(struct i2c_client *client, u8 reg)
+{
+ s32 ret;
+ int retry_count = 0;
+ u8 val;
+
+retry:
+ ret = i2c_smbus_read_byte_data(client, reg);
+ if (ret < 0 && retry_count < MAX_TRY) {
+ /* sleep for few ms before retrying */
+ msleep(retry_sleep_ms[retry_count++]);
+ goto retry;
+ }
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "i2c read fail: can't read from %02x: %d\n", reg, ret);
+ return ret;
+ } else {
+ val = (u8) ret;
+ }
+
+ return val;
+}
+
+static int bq2589x_reg_read_mask(struct i2c_client *client, u8 reg, u8 mask, u8 shift)
+{
+ int ret;
+
+ if (shift > 8)
+ return -EINVAL;
+
+ ret = bq2589x_read_reg(client, reg);
+ if (ret < 0)
+ return ret;
+
+ return (ret & (mask<<shift)) >> shift;
+}
+
+static int bq2589x_reg_write_mask(struct i2c_client *client, u8 reg, u8 val, u8 shift, u8 mask)
+{
+ int ret;
+
+ if (shift > 8)
+ {
+ pr_err("%s shift out range %d\n", __func__, shift);
+ return -EINVAL;
+ }
+
+ ret = bq2589x_read_reg(client, reg);
+ if (ret < 0)
+ {
+ pr_err("%s read err %d\n", __func__, ret);
+ return ret;
+ }
+
+ ret &= ~(mask<<shift);
+ ret |= val << shift;
+
+ if((reg==BQ2589X_REG_14)&&(shift!=BQ2589X_REG_14_REG_RST))
+ ret &= 0x7f;
+
+ return bq2589x_write_reg(client, reg, ret);
+}
+
+
+/*
+ * This function dumps the bq2589x registers
+ */
+static void bq2589x_dump_registers(struct bq2589x_chip *chip)
+{
+ int ret;
+
+// dev_info(&chip->client->dev, "%s\n", __func__);
+#ifndef DEBUG
+ pr_warn("%s dual:", __func__);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_00);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Input Src Ctrl reg read fail\n");
+ pr_warn( "00 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_01);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Pwr On Cfg reg read fail\n");
+ pr_warn( "01 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_03);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Chrg Curr Ctrl reg read fail\n");
+ pr_warn( "03 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_04);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Pre-Chrg Term reg read fail\n");
+ pr_warn( "04 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_05);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Chrg Volt Ctrl reg read fail\n");
+ pr_warn( "05 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_06);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Thermal Regulation reg read fail\n");
+ pr_warn( "06 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_09);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "09 reg read fail\n");
+ pr_warn( "09 %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0A);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0A %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0B);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0B %x; ", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0C);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0C %x; ", ret);
+
+ //bq2589x_reg_write_mask(chip->client, BQ2589X_REG_02, 0x3, 6, 0x3);
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0D);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0D %x \n", ret);
+/*
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0E);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Vendor Rev reg read fail\n");
+ pr_warn( "0E %x \n", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_11);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "11 reg read fail\n");
+ pr_warn( "11 %x \n", ret);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_12);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "12 reg read fail\n");
+ pr_warn( "12 %x \n", ret);
+*/
+#else
+ int i;
+
+ bq2589x_reg_write_mask(chip->client, BQ2589X_REG_02, 0x3, 6, 0x3);
+
+ pr_warn("bq25892 dual REG dump");
+ for(i=0;i<0x14;i++)
+ {
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_00+i);
+ if (ret < 0)
+ pr_warn("%s reg %x read fail\n", __func__, i);
+ pr_warn("[%x]=%x ", i, ret);
+ }
+
+ //ret = gpio_get_value(chip->chg_en_gpio);
+ //dev_info(&chip->client->dev, "en io %d\n", ret);
+#endif
+
+
+}
+
+/*
+ * This function verifies if the bq2589xi charger chip is in Hi-Z
+ * If yes, then clear the Hi-Z to resume the charger operations
+ */
+static int bq2589x_clear_hiz(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ pr_debug("%s\n", __func__);
+
+
+ /*
+ * Read the bq2589xi REG00 register for charger Hi-Z mode.
+ * If it is in Hi-Z, then clear the Hi-Z to resume the charging
+ * operations.
+ */
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_00);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev,
+ "Input src cntl read failed\n");
+ goto i2c_error;
+ }
+
+ if (ret & (1<<BQ2589X_REG_00_EN_HIZ)) {
+ dev_warn(&chip->client->dev, "Charger IC in Hi-Z mode\n");
+ ret &= ~(1<<BQ2589X_REG_00_EN_HIZ);
+ ret = bq2589x_write_reg(chip->client, BQ2589X_REG_00, ret);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "Input src cntl write failed\n");
+ goto i2c_error;
+ }
+ msleep(150);
+ }
+
+ return ret;
+i2c_error:
+ dev_err(&chip->client->dev, "%s\n", __func__);
+ return ret;
+}
+
+static int bq2589x_reset_regs(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ pr_info("%s\n", __func__);
+
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_14, 1, BQ2589X_REG_14_REG_RST, BQ2589X_REG_14_REG_RST_MASK);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "Input src cntl read failed\n");
+ goto i2c_error;
+ }
+
+ //bq2589x_dump_registers(chip);
+ return ret;
+i2c_error:
+ dev_err(&chip->client->dev, "%s\n", __func__);
+ return ret;
+}
+
+static int fg_chip_get_property(enum power_supply_property psp)
+{
+ union power_supply_propval val;
+ int ret = -ENODEV;
+
+ if (!fg_psy)
+ fg_psy = power_supply_get_by_name("battery");
+ if (fg_psy) {
+ ret = fg_psy->get_property(fg_psy, psp, &val);
+ if (!ret)
+ return val.intval;
+ }
+ return ret;
+}
+
+static int chrg_ilim_to_reg(int ilim)
+{
+ int reg;
+
+ if(ilim<=100)
+ reg = 0;
+ else if(ilim>=3250)
+ reg = BQ2589X_REG_00_ILIM_MASK;
+ else
+ reg = (ilim - 100)/50;
+
+ return reg;
+}
+
+static u8 chrg_iterm_to_reg(int iterm)
+{
+ u8 reg;
+
+ if (iterm <= 64)
+ reg = 0;
+ else if(iterm>=1024)
+ reg = BQ2589X_REG_05_ITERM_MASK;
+ else
+ reg = (iterm - 64) /64;
+
+ return reg;
+}
+
+static u8 chrg_cur_to_reg(int cur)
+{
+ u8 reg;
+
+ if (cur <= 0)
+ reg = 0;
+ else if(cur>=5056)
+ reg = BQ2589X_REG_04_ICHG_MASK;
+ else
+ reg = cur /64;
+
+ return reg;
+}
+
+static u8 chrg_volt_to_reg(int volt)
+{
+ u8 reg;
+
+ if (volt <= 3840)
+ reg = 0;
+ else if(volt>=4608)
+ reg = BQ2589X_REG_06_VREG_MASK;
+ else
+ reg = (volt - 3840) /16 + 1;
+
+ return reg;
+}
+
+static u8 vindpm_to_reg(int vindpm)
+{
+ u8 reg;
+
+ if (vindpm <= 3900)
+ reg = 0;
+ else if(vindpm>=7000)
+ reg = BQ2589X_REG_01_VINDPM_MASK;
+ else
+ reg = (vindpm - 3900) /100;
+
+ return reg;
+}
+
+static int program_timers(struct bq2589x_chip *chip, int wdt_duration,
+ bool sfttmr_enable)
+{
+ int ret;
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_07);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "TIMER CTRL reg read failed\n");
+ return ret;
+ }
+
+ ret |= wdt_duration;
+
+ if (sfttmr_enable)
+ ret |= CHRG_TIMER_EXP_CNTL_EN_TIMER;
+ else
+ ret &= ~CHRG_TIMER_EXP_CNTL_EN_TIMER;
+
+ ret = bq2589x_write_reg(chip->client, BQ2589X_REG_07, ret);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "TIMER CTRL I2C write failed\n");
+
+ return ret;
+}
+
+/* This function should be called with the mutex held */
+static int reset_wdt_timer(struct bq2589x_chip *chip)
+{
+ int ret = 0, i;
+
+ for (i = 0; i < MAX_RESET_WDT_RETRY; i++) {
+ ret = bq2589x_reg_write_mask(chip->client,
+ BQ2589X_REG_03, 1,
+ BQ2589X_REG_03_WD_RST, BQ2589X_REG_03_WD_RST_MASK);
+ if (ret < 0)
+ pr_warn("I2C write failed:%s\n", __func__);
+ else
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ *This function will modify the VINDPM as per the battery voltage
+ */
+static int bq2589x_modify_vindpm(struct bq2589x_chip *chip, u8 vindpm)
+{
+ int ret;
+ static u8 vindpm_prev = 0xff;
+
+ if(vindpm_prev==vindpm) {
+ dev_warn(&chip->client->dev, "same vindpm val, ignored\n");
+ return 0;
+ }
+
+ /* Get the input src ctrl values programmed */
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_01);
+ if (ret < 0) {
+ dev_warn(&chip->client->dev, "INPUT CTRL reg read failed\n");
+ return ret;
+ }
+
+ /* Assign the return value of REG00 to vindpm_prev */
+ vindpm_prev = (ret & (BQ2589X_REG_01_VINDPM_MASK<<BQ2589X_REG_01_VINDPM));
+ ret &= ~(BQ2589X_REG_01_VINDPM_MASK<<BQ2589X_REG_01_VINDPM);
+
+ /*
+ * If both the previous and current values are same do not program
+ * the register.
+ */
+ vindpm |= ret;
+ ret = bq2589x_write_reg(chip->client, BQ2589X_REG_01, vindpm);
+ if (ret < 0) {
+ dev_info(&chip->client->dev, "VINDPM failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+#ifdef CONFIG_DEBUG_FS
+#define DBGFS_REG_BUF_LEN 3
+
+static int bq2589x_show(struct seq_file *seq, void *unused)
+{
+ u16 val;
+ long addr;
+
+ if (kstrtol((char *)seq->private, 16, &addr))
+ return -EINVAL;
+
+ val = bq2589x_read_reg(bq2589x_client, addr);
+ seq_printf(seq, "%x\n", val);
+
+ return 0;
+}
+
+static int bq2589x_dbgfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, bq2589x_show, inode->i_private);
+}
+
+static ssize_t bq2589x_dbgfs_reg_write(struct file *file,
+ const char __user *user_buf, size_t count, loff_t *ppos)
+{
+ char buf[DBGFS_REG_BUF_LEN];
+ long addr;
+ unsigned long value;
+ int ret;
+ struct seq_file *seq = file->private_data;
+
+ if (!seq || kstrtol((char *)seq->private, 16, &addr))
+ return -EINVAL;
+
+// if (copy_from_user(buf, user_buf, DBGFS_REG_BUF_LEN-1))
+// return -EFAULT;
+
+ buf[DBGFS_REG_BUF_LEN-1] = '\0';
+ if (kstrtoul(buf, 16, &value))
+ return -EINVAL;
+
+ dev_info(&bq2589x_client->dev, "[dbgfs write] Addr:0x%x Val:0x%x\n", (u32)addr, (u32)value);
+
+ ret = bq2589x_write_reg(bq2589x_client, addr, value);
+ if (ret < 0)
+ dev_warn(&bq2589x_client->dev, "I2C write failed\n");
+
+ return count;
+}
+
+static const struct file_operations bq2589x_dbgfs_fops = {
+ .owner = THIS_MODULE,
+ .open = bq2589x_dbgfs_open,
+ .read = seq_read,
+ .write = bq2589x_dbgfs_reg_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int bq2589x_create_debugfs(struct bq2589x_chip *chip)
+{
+ int i;
+ struct dentry *entry;
+
+ bq2589x_dbgfs_root = debugfs_create_dir(DEV_NAME, NULL);
+ if (IS_ERR(bq2589x_dbgfs_root)) {
+ dev_warn(&chip->client->dev, "DEBUGFS DIR create failed\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < bq2589x_MAX_MEM; i++) {
+ sprintf((char *)&bq2589x_dbg_regs[i], "%x", i);
+ entry = debugfs_create_file(
+ (const char *)&bq2589x_dbg_regs[i],
+ S_IRUGO,
+ bq2589x_dbgfs_root,
+ &bq2589x_dbg_regs[i],
+ &bq2589x_dbgfs_fops);
+ if (IS_ERR(entry)) {
+ debugfs_remove_recursive(bq2589x_dbgfs_root);
+ bq2589x_dbgfs_root = NULL;
+ dev_warn(&chip->client->dev, "DEBUGFS entry Create failed\n");
+ return -ENOMEM;
+ }
+ }
+
+ return 0;
+}
+static inline void bq2589x_remove_debugfs(struct bq2589x_chip *chip)
+{
+ if (bq2589x_dbgfs_root)
+ debugfs_remove_recursive(bq2589x_dbgfs_root);
+}
+#else
+static inline int bq2589x_create_debugfs(struct bq2589x_chip *chip)
+{
+ return 0;
+}
+static inline void bq2589x_remove_debugfs(struct bq2589x_chip *chip)
+{
+}
+#endif
+
+static inline int bq2589x_set_cv(struct bq2589x_chip *chip, int cv)
+{
+ u8 regval;
+
+ dev_info(&chip->client->dev, "%s: %d\n", __func__, cv);
+ chip->cv = cv;
+ regval = chrg_volt_to_reg(cv);
+
+ return bq2589x_reg_write_mask(chip->client, BQ2589X_REG_06, regval, BQ2589X_REG_06_VREG, BQ2589X_REG_06_VREG_MASK);
+}
+
+static int bq2589x_set_appropriate_cv(struct bq2589x_chip *chip)
+{
+ int cv = 0;
+ if(chip->bat_is_cool)
+ cv = chip->vbat_cool;
+ else if(chip->bat_is_warm)
+ cv = chip->vbat_warm;
+ else
+ cv = chip->vbat_normal;
+
+ printk("charger-dual : set cv %d\n", cv);
+
+ return bq2589x_set_cv(chip,cv);
+}
+
+static inline int bq2589x_get_appropriate_cv(struct bq2589x_chip *chip)
+{
+ if(chip->bat_is_cool)
+ return chip->vbat_cool;
+ else if(chip->bat_is_warm)
+ return chip->vbat_warm;
+ else
+ return chip->vbat_normal;
+}
+
+static void bq2589x_cv_check_and_set(struct bq2589x_chip *chip)
+{
+ int ret,cv,cv_reg;
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_06);
+ if (ret < 0)
+ {
+ dev_warn(&chip->client->dev, "Chrg Volt Ctrl reg read fail\n");
+ return;
+ }
+
+ ret = (ret>>2);
+
+ cv = bq2589x_get_appropriate_cv(chip);
+ cv_reg = chrg_volt_to_reg(cv);
+ if(ret != cv_reg)
+ {
+ printk("Charger:cv value 0x%x get from charger chip is not correct :0x%x(%d)\n",ret,cv_reg, cv);
+ bq2589x_set_appropriate_cv(chip);
+ }
+}
+
+static inline int bq2589x_set_cc(struct bq2589x_chip *chip, int cc)
+{
+ u8 regval;
+
+ //dev_info(&chip->client->dev, "%s: %d\n", __func__, cc);
+ regval = chrg_cur_to_reg(cc);
+ printk("%s-dual: %d 0x%x\n", __func__, cc, regval);
+
+ return bq2589x_reg_write_mask(chip->client, BQ2589X_REG_04,regval, BQ2589X_REG_04_ICHG, BQ2589X_REG_04_ICHG_MASK);
+}
+
+static inline int bq2589x_set_iusb(struct bq2589x_chip *chip, int iusb_max)
+{
+ int regval;
+
+ chip->iusb_max = iusb_max;
+ regval = chrg_ilim_to_reg(iusb_max);
+
+ pr_info("fast_charger-dual: %s:%d %x\n", __func__, iusb_max,regval);
+ if (regval < 0)
+ return regval;
+
+ return bq2589x_reg_write_mask(chip->client, BQ2589X_REG_00, regval, BQ2589X_REG_00_ILIM, BQ2589X_REG_00_ILIM_MASK);
+}
+
+static void bq2589x_set_ibat_max(struct bq2589x_chip *chip, int ibat_max)
+{
+ static int pre_val = -1;
+
+ pr_info("charger-dual: %s, %d\n", __func__, ibat_max);
+ if(pre_val!=ibat_max)
+ {
+ pre_val = ibat_max;
+ chip->cc = ibat_max;
+ if(chip->cc==0)
+ chip->is_charging_enabled = false;
+ else
+ chip->is_charging_enabled = true;
+
+ bq2589x_set_cc(chip, ibat_max);
+ }else
+ pr_info("%s ignore same val(%d)\n", __func__, pre_val);
+}
+
+static void bq2589x_set_iusb_max(struct bq2589x_chip *chip, int iusb_max)
+{
+ static int pre_val = -1;
+
+ if(pre_val==iusb_max)
+ {
+ pr_info("%s-dual ignore same val(%d)\n", __func__, iusb_max);
+ return;
+ }
+
+ pr_info("charger-dual: %s, %d\n", __func__, iusb_max);
+ chip->iusb_max = iusb_max;
+ pre_val = iusb_max;
+
+ bq2589x_set_iusb(chip, chip->iusb_max);
+}
+
+static int bq2589x_set_appropriate_cc(struct bq2589x_chip *chip)
+{
+ int cc = 0;
+
+ if(chip->bat_is_cool)
+ cc = chip->ibat_cool;
+ else if(chip->bat_is_warm)
+ cc = chip->ibat_warm;
+ else
+ cc = chip->ibat_normal;
+
+ printk("charger-dual : set cc %d\n", cc);
+
+ bq2589x_set_ibat_max(chip, cc);
+
+ return 0;
+}
+
+static int bq2589x_set_iterm(struct bq2589x_chip *chip, int iterm)
+{
+ u8 reg_val;
+
+ if (iterm > bq2589x_CHRG_ITERM_OFFSET)
+ dev_info(&chip->client->dev, "%s ITERM set for%d >128mA", __func__, iterm);
+
+ reg_val = chrg_iterm_to_reg(iterm);
+ msleep(500);
+
+ return bq2589x_reg_write_mask(chip->client, BQ2589X_REG_05, reg_val, BQ2589X_REG_05_ITERM, BQ2589X_REG_05_ITERM_MASK);
+}
+
+#if 0
+bool is_charging_enabled(void)
+{
+ struct bq2589x_chip *chip = NULL;
+
+ if(bq2589x_client != NULL)
+ chip = i2c_get_clientdata(bq2589x_client);
+ else
+ return 1;
+ return chip->is_charging_enabled;
+}
+
+bool is_in_otg_mode(void)
+{
+ struct bq2589x_chip *chip = NULL;
+
+ if(bq2589x_client != NULL)
+ chip = i2c_get_clientdata(bq2589x_client);
+ else
+ return 0;
+ return chip->boost_mode;
+}
+#endif
+
+static int bq2589x_enable_charging(struct bq2589x_chip *chip, bool val)
+{
+ int ret;
+
+ dev_info(&chip->client->dev, "%s: %d\n", __func__, val);
+
+ ret = program_timers(chip, CHRG_TIMER_EXP_CNTL_WDT160SEC, true);
+ if (ret < 0) {
+ dev_err(&chip->client->dev,
+ "program_timers failed: %d\n", ret);
+ return ret;
+ }
+
+#ifndef CONFIG_CHARGING_APP_CONTROL
+ {
+ int regval;
+ /*
+ * Program the iusb_max here in case we are asked to resume the charging
+ * framework would send only set CC/CV commands and not the iusb_max. This
+ * would make sure that we program the last set iusb_max into the register
+ * in case for some reasons WDT expires
+ */
+ regval = chrg_ilim_to_reg(chip->iusb_max);
+
+ if (regval < 0) {
+ dev_err(&chip->client->dev, "read ilim failed: %d\n", regval);
+ return regval;
+ }
+
+ printk("%s ichg max is %d,reg is %x\n", __func__, chip->iusb_max,regval);
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_00, regval, BQ2589X_REG_00_ILIM, BQ2589X_REG_00_ILIM_MASK);
+ if (ret < 0) {
+ dev_err(&chip->client->dev, "iusb_max programming failed: %d\n", ret);
+ return ret;
+ }
+ }
+#endif
+
+ /*
+ * check if we have the battery emulator connected. We do not start
+ * charging if the emulator is connected. Disable the charging
+ * explicitly.
+ */
+ if (chip->sfi_tabl_present) {
+ bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 0, BQ2589X_REG_03_CHG_CONFIG, BQ2589X_REG_03_CHG_CONFIG_MASK);
+ pr_err("%s sfi_tabl_present is not true, disable charging\n", __func__);
+ return ret;
+ }
+
+ if (chip->sfttmr_expired)
+ {
+ pr_info("%s Safety timer expired\n", __func__);
+ return ret;
+ }
+
+ if(val)
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 1, BQ2589X_REG_03_CHG_CONFIG, BQ2589X_REG_03_CHG_CONFIG_MASK);
+ else
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_03, 0, BQ2589X_REG_03_CHG_CONFIG, BQ2589X_REG_03_CHG_CONFIG_MASK);
+
+ if (ret < 0)
+ pr_warn("%s charger enable/disable failed\n", __func__);
+ else {
+ if (val)
+ chip->online = true;
+ else
+ chip->online = false;
+ }
+
+ return ret;
+}
+
+static int bq2589x_enable_charger(struct bq2589x_chip *chip, int val)
+{
+ int ret = 0;
+
+ /*stop charger, by putting it in HiZ mode*/
+ if (val == 0) {
+ ret = bq2589x_reg_write_mask(chip->client, BQ2589X_REG_00, 1, BQ2589X_REG_00_EN_HIZ, BQ2589X_REG_00_EN_HIZ_MASK);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "Input src cntl write failed\n");
+ else
+ chip->is_charging_enabled = false;
+
+ }else
+ {
+ bq2589x_clear_hiz(chip);
+ bq2589x_enable_charging(chip, true);
+ chip->is_charging_enabled = true;
+ }
+
+ pr_warn("%s-dual: %d, %d\n", __func__, val, chip->is_charging_enabled);
+
+// bq2589x_dump_registers(chip);
+
+ return ret;
+}
+
+static int bq2589x_get_charging_status(struct bq2589x_chip *chip)
+{
+ int ret;
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0B);
+ pr_warn("%s-dual: ret 0x%x\n", __func__, ret);
+
+ ret &= SYSTEM_STAT_CHRG_MASK;
+
+ switch (ret) {
+ case SYSTEM_STAT_NOT_CHRG:
+ chip->chgr_stat = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case SYSTEM_STAT_CHRG_DONE:
+ chip->chgr_stat = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case SYSTEM_STAT_PRE_CHRG:
+ case SYSTEM_STAT_FAST_CHRG:
+ chip->chgr_stat = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ default:
+ chip->chgr_stat = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ return chip->chgr_stat;
+}
+
+static bool bq2589x_is_charging_done(struct bq2589x_chip *chip)
+{
+ int batt_voltage = 0;
+
+ batt_voltage = fg_chip_get_property(POWER_SUPPLY_PROP_VOLTAGE_NOW)/1000;
+ if (batt_voltage < 0) {
+ dev_err(&chip->client->dev, "Can't read voltage from FG\n");
+ return false;
+ }
+ chip->batt_voltage = batt_voltage;
+
+ if((POWER_SUPPLY_STATUS_FULL == bq2589x_get_charging_status(chip)) && (chip->batt_voltage > 4250))
+ return true;
+
+ if(chip->bat_is_warm && chip->batt_voltage > 4050)
+ return true;
+
+ return false;
+}
+
+/* IRQ handler for charger Interrupts configured to GPIO pin */
+static irqreturn_t bq2589x_irq_isr(int irq, void *devid)
+{
+// struct bq2589x_chip *chip = (struct bq2589x_chip *)devid;
+
+ /**TODO: This hanlder will be used for charger Interrupts */
+// dev_dbg(&chip->client->dev,
+// "IRQ Handled for charger interrupt: %d\n", irq);
+
+ return IRQ_WAKE_THREAD;
+}
+
+/* IRQ handler for charger Interrupts configured to GPIO pin */
+static irqreturn_t bq2589x_irq_thread(int irq, void *devid)
+{
+ struct bq2589x_chip *chip = (struct bq2589x_chip *)devid;
+ int reg_status;
+
+ msleep(100);
+
+ wake_lock_timeout(&chip->irq_wk,HZ);
+ /*
+ * check the bq2589x status/fault registers to see what is the
+ * source of the interrupt
+ */
+ reg_status = bq2589x_read_reg(chip->client, BQ2589X_REG_0B);
+ if (reg_status < 0)
+ dev_err(&chip->client->dev, "STATUS register read failed:\n");
+
+ if(((reg_status & SYSTEM_STAT_VBUS_HOST) == SYSTEM_STAT_VBUS_HOST) ||
+ ((reg_status & SYSTEM_STAT_VBUS_CDP) == SYSTEM_STAT_VBUS_CDP))
+
+ {
+ if (!wake_lock_active(&chip->wakelock))
+ wake_lock(&chip->wakelock);
+ }else{
+ msleep(150);
+ /* Release the wake lock */
+ if (wake_lock_active(&chip->wakelock))
+ wake_unlock(&chip->wakelock);
+
+#ifndef CONFIG_CHARGING_APP_CONTROL
+ bq2589x_set_iusb_max(chip, 0);
+#endif
+ }
+
+ reg_status &= SYSTEM_STAT_CHRG_DONE;
+
+ if (reg_status == SYSTEM_STAT_CHRG_DONE) {
+/* mutex_lock(&chip->event_lock);
+ bq2589x_enable_hw_term(chip, false);
+ mutex_unlock(&chip->event_lock);
+ bq2589x_hw_init(chip);
+ */
+ /* schedule the thread to let the framework know about FULL */
+ }
+
+// if(fg_psy != NULL)
+// power_supply_changed(fg_psy);
+
+ schedule_delayed_work(&chip->fault_work, 0);
+ return IRQ_HANDLED;
+}
+
+static void bq2589x_hw_init(struct bq2589x_chip *chip)
+{
+ int ret = 0;
+
+ ret = program_timers(chip, CHRG_TIMER_EXP_CNTL_WDT160SEC,true);
+ if (ret < 0)
+ dev_warn(&chip->client->dev, "TIMER enable failed\n");
+
+ bq2589x_set_appropriate_cv(chip);
+
+ if(chip->iterm)
+ bq2589x_set_iterm(chip,chip->iterm);
+
+ bq2589x_reg_write_mask(chip->client, BQ2589X_REG_02, 0, BQ2589X_REG_02_ICO_EN, BQ2589X_REG_02_ICO_EN_MASK);
+ bq2589x_reg_write_mask(chip->client, BQ2589X_REG_00, 0, BQ2589X_REG_00_EN_ILIM, BQ2589X_REG_00_EN_ILIM_MASK);
+ bq2589x_write_reg(chip->client,BQ2589X_REG_0D,0x93);
+
+ bq2589x_set_iusb_max(chip, 0);
+ bq2589x_set_cc(chip, 0);
+
+ if(chip->is_charging_enabled)
+ bq2589x_enable_charging(chip, true);
+}
+
+static void bq2589x_fault_worker(struct work_struct *work)
+{
+ struct bq2589x_chip *chip = container_of(work, struct bq2589x_chip, fault_work.work);
+ int reg_fault;
+
+ /* Check if battery fault condition occured. Reading the register
+ value two times to get reliable reg value, recommended by vendor*/
+ reg_fault = bq2589x_read_reg(chip->client, BQ2589X_REG_0C);
+ if (reg_fault < 0)
+ dev_err(&chip->client->dev, "FAULT register read failed:\n");
+
+ if(reg_fault!=0x00)
+ dev_info(&chip->client->dev, "FAULT reg %x\n", reg_fault);
+
+ if (reg_fault & FAULT_STAT_WDT_TMR_EXP) {
+ dev_warn(&chip->client->dev, "WDT expiration fault\n");
+ if (chip->is_charging_enabled) {
+ /*when WDT expiration ,bq2589x register will be reset,so we need reconfig it*/
+ bq2589x_hw_init(chip);
+ } else
+ dev_info(&chip->client->dev, "No charger connected\n");
+ }
+
+ if ((reg_fault & FAULT_STAT_CHRG_TMR_FLT) == FAULT_STAT_CHRG_TMR_FLT) {
+ chip->sfttmr_expired = true;
+ pr_info("%s Safety timer expired\n", __func__);
+ }
+}
+
+static void bq2589x_temp_monitor(struct bq2589x_chip *chip)
+{
+ bool bat_cool = false;
+ bool bat_warm = false;
+
+ if(chip->batt_temp < chip->cool_temp){
+ bat_cool = true;
+ bat_warm = false;
+ }else if(chip->batt_temp > chip->cool_temp && chip->batt_temp < chip->warm_temp){
+ bat_cool = false;
+ bat_warm = false;
+ }else if(chip->batt_temp > chip->warm_temp){
+ bat_warm = true;
+ bat_cool = false;
+ }
+
+ if (chip->bat_is_cool ^ bat_cool || chip->bat_is_warm ^ bat_warm) {
+ chip->bat_is_cool = bat_cool;
+ chip->bat_is_warm = bat_warm;
+#ifndef CONFIG_CHARGING_APP_CONTROL
+ bq2589x_set_appropriate_cc(chip);
+#endif
+ bq2589x_set_appropriate_cv(chip);
+ }
+}
+
+static void bq2589x_task_worker(struct work_struct *work)
+{
+ struct bq2589x_chip *chip =
+ container_of(work, struct bq2589x_chip, chrg_task_wrkr.work);
+ int ret, jiffy = CHARGER_TASK_JIFFIES;
+ int soc = fg_chip_get_property(POWER_SUPPLY_PROP_CAPACITY);
+
+ mutex_lock(&chip->event_lock);
+ ret = reset_wdt_timer(chip);
+ mutex_unlock(&chip->event_lock);
+ if (ret < 0)
+ pr_err("%s WDT reset failed:\n", __func__);
+
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_0C);
+ if (ret < 0)
+ pr_err("%s FAULT register read failed:\n", __func__);
+ else
+ {
+ if(ret!=0x00)
+ {
+ pr_info("%s FAULT reg %x\n", __func__, ret);
+
+ if (ret & FAULT_STAT_WDT_TMR_EXP) {
+ pr_warn("%s WDT expiration fault\n", __func__);
+ if (chip->is_charging_enabled) {
+ /*when WDT expiration ,bq2589x register will be reset,so we need reconfig it*/
+ bq2589x_hw_init(chip);
+ } else
+ pr_info("%s No charger connected\n", __func__);
+ }
+ }
+ }
+
+ /*
+ * If we have an OTG device connected, no need to modify the VINDPM
+ * check for Hi-Z
+ */
+ if (chip->boost_mode) {
+ jiffy = CHARGER_HOST_JIFFIES;
+ bq2589x_enable_charger(chip, 0);
+ goto sched_task_work;
+ }
+
+ /*sometimes WDT interrupt won't assert, so charger parameters will be reseted to
+ * default value. we do check here, if the CV value is not correct, we reinit the
+ * charger parameters*/
+ bq2589x_cv_check_and_set(chip);
+ /* Modify the VINDPM */
+
+sched_task_work:
+ chip->batt_status = bq2589x_get_charging_status(chip);//fg_chip_get_property(POWER_SUPPLY_PROP_STATUS);
+ chip->batt_temp = fg_chip_get_property(POWER_SUPPLY_PROP_TEMP);
+ chip->batt_voltage = fg_chip_get_property(POWER_SUPPLY_PROP_VOLTAGE_NOW) / 1000;
+ if (chip->batt_voltage < 0) {
+ dev_err(&chip->client->dev, "Can't read voltage from FG\n");
+ }
+
+ /* convert voltage into millivolts */
+ if (POWER_SUPPLY_STATUS_FULL== chip->batt_status) {
+ if(soc != 100)
+ {
+ dev_warn(&chip->client->dev, "%s battery full,but soc is not 100", __func__);
+ bq2589x_enable_charging(chip, 0);
+ bq2589x_enable_charger(chip, 0);
+ msleep(500);
+ bq2589x_enable_charger(chip, 1);
+ bq2589x_enable_charging(chip, 1);
+ }
+ }
+
+ bq2589x_write_reg(chip->client,BQ2589X_REG_0D, 0x93);//vindpm 4.5V
+
+ bq2589x_temp_monitor(chip);
+
+ //pr_warn("bq2589x-dual battery voltage is %d, capacity is %d, temp is %d, status is %d\n", chip->batt_voltage, soc,chip->batt_temp,chip->batt_status);
+
+ bq2589x_dump_registers(chip);
+
+ if(soc < 5 || chip->batt_voltage < 3400)
+ jiffy = jiffy /3;
+
+ schedule_delayed_work(&chip->chrg_task_wrkr, jiffy);
+}
+
+static int bq2589x_get_chip_version(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ /* check chip model number */
+ ret = bq2589x_read_reg(chip->client, BQ2589X_REG_14);
+ if (ret < 0) {
+ dev_err(&chip->client->dev, "i2c read err:%d\n", ret);
+ return -EIO;
+ }
+ dev_info(&chip->client->dev, "version reg: 0x%x\n", ret);
+
+ ret = (ret&0x38) >> 3;
+ switch (ret) {
+ case BQ25852_IC_VERSION:
+ chip->chip_type = BQ25852;
+ memcpy(chip->ic_name, "BQ25852", sizeof("BQ25852"));
+ break;
+ case BQ25890_IC_VERSION:
+ chip->chip_type = BQ25890;
+ memcpy(chip->ic_name, "BQ25890", sizeof("BQ25890"));
+ break;
+ case bq25895_IC_VERSION:
+ chip->chip_type = BQ25895;
+ memcpy(chip->ic_name, "BQ25895", sizeof("BQ25895"));
+ break;
+ default:
+ dev_err(&chip->client->dev, "device version mismatch: 0x%x set default\n", ret);
+ chip->chip_type = BQ25852;
+ memcpy(chip->ic_name, "BQ25852", sizeof("BQ25852"));
+ break;
+ }
+
+ dev_info(&chip->client->dev, "chip type:%x\n", chip->chip_type);
+ return 0;
+}
+
+static ssize_t charger_ver_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret = 0;
+ struct i2c_client *client = container_of(dev, struct i2c_client, dev);
+ struct bq2589x_chip *chip = i2c_get_clientdata(client);
+
+ ret = sprintf(buf, "%s\n",chip->ic_name);
+
+ return ret;
+}
+
+static DEVICE_ATTR(chrg_version, S_IRUGO|S_IWUSR, charger_ver_get,NULL);
+
+static struct attribute *fs_attrs[] = {
+ &dev_attr_chrg_version.attr,
+ NULL,
+};
+
+static struct attribute_group fs_attr_group = {
+ .attrs = fs_attrs,
+};
+
+static int bq2589x_parse_dt(struct device *dev,struct bq2589x_chip *chip)
+{
+ struct device_node *np = dev->of_node;
+
+ chip->irq_gpio = of_get_named_gpio_flags(np, "charger,irq-gpio", 0, 0);
+ chip->chg_en_gpio = of_get_named_gpio_flags(np, "charger,en-gpio", 0, 0);
+ chip->otg_en_gpio = of_get_named_gpio_flags(np, "charger,otg-en-gpio", 0, 0);
+
+ of_property_read_u32(np,"charger,ichg-max" ,&chip->iusb_max);
+ of_property_read_u32(np,"charger,iterm-ma" ,&chip->iterm);
+ of_property_read_u32(np,"charger,ibat-max" ,&chip->ibat_normal);
+ of_property_read_u32(np,"charger,vbat-mv" ,&chip->vbat_normal);
+ of_property_read_u32(np,"charger,warm-temp" ,&chip->warm_temp);
+ of_property_read_u32(np,"charger,ibat-warm" ,&chip->ibat_warm);
+ of_property_read_u32(np,"charger,ibat-cool" ,&chip->ibat_cool);
+ of_property_read_u32(np,"charger,cool-temp" ,&chip->cool_temp);
+ of_property_read_u32(np,"charger,vbat-warm" ,&chip->vbat_warm);
+ of_property_read_u32(np,"charger,vbat-cool" ,&chip->vbat_cool);
+#ifdef CONFIG_FAKE_BATTERY
+ chip->sfi_tabl_present = 1;
+#else
+ chip->sfi_tabl_present = 0;
+#endif
+ chip->is_charging_enabled = true;
+ chip->bat_is_warm = false;
+ chip->bat_is_cool = false;
+
+ return 0;
+}
+
+//dummy function for unused static functions
+void dummy_function_dual(void)
+{
+ bq2589x_reg_read_mask(NULL, 0, 0, 0);
+ bq2589x_modify_vindpm(NULL, 0);
+ vindpm_to_reg(0);
+ bq2589x_is_charging_done(NULL);
+#ifdef CONFIG_CHARGING_APP_CONTROL
+ bq2589x_set_appropriate_cc(NULL);
+#endif
+}
+
+#ifdef EXT_CHARGER_POWER_SUPPLY
+static char *bq2589x_chip_name = "ext-charger-dual";
+//static char *bq2589x_chip_name = "ext-charger";
+
+static enum power_supply_property bq2589x_power_supply_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TEMP,
+// POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_CHARGE_ENABLED,
+ POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_FLASH_ACTIVE
+};
+
+static int bq2589x_power_supply_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq2589x_chip *chip = container_of(psy, struct bq2589x_chip, charger_psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ {
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = chip->batt_temp;
+ break;
+/* case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = chip->model;
+ break;*/
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ val->intval = (chip->is_charging_enabled)? 1:0;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = chip->iusb_max;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bq2589x_power_supply_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bq2589x_chip *chip = container_of(psy, struct bq2589x_chip, charger_psy);
+ int value;
+
+ switch (psp) {
+ //just for charging temp protect function debug
+ case POWER_SUPPLY_PROP_TEMP:
+ pr_err("%s debug charging temp, intval = %d\n", __func__, val->intval);
+ if((val->intval<-20)&&(val->intval>60))
+ chip->temp_debug_flag = false;
+ else
+ {
+ chip->temp_debug_flag = true;
+ chip->batt_temp = val->intval;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ bq2589x_enable_charger(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ value = val->intval;
+ bq2589x_set_iusb_max(chip, value);
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ bq2589x_enable_charging(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ value = val->intval;
+ bq2589x_set_ibat_max(chip, value);
+ break;
+ case POWER_SUPPLY_PROP_FLASH_ACTIVE:
+ pr_warn("%s not support POWER_SUPPLY_PROP_FLASH_ACTIVE \n", __func__);
+ break;
+ default:
+ pr_err("%s not support power_supply property cmd\n", __func__);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int bq2589x_power_supply_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_FLASH_ACTIVE:
+ return 1;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int bq2589x_power_supply_init(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ chip->charger_psy.name = bq2589x_chip_name;//chip->name;
+ chip->charger_psy.type = POWER_SUPPLY_TYPE_USB;
+ chip->charger_psy.properties = bq2589x_power_supply_props;
+ chip->charger_psy.num_properties = ARRAY_SIZE(bq2589x_power_supply_props);
+ chip->charger_psy.get_property = bq2589x_power_supply_get_property;
+ chip->charger_psy.set_property = bq2589x_power_supply_set_property;
+ chip->charger_psy.property_is_writeable = bq2589x_power_supply_property_is_writeable;
+
+ ret = power_supply_register(&chip->client->dev, &chip->charger_psy);
+ if (ret)
+ {
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static void bq2589x_power_supply_exit(struct bq2589x_chip *chip)
+{
+ //cancel_delayed_work_sync(&chip->work);
+ power_supply_unregister(&chip->charger_psy);
+
+}
+#endif
+
+static int bq2589x_regulator_get(struct bq2589x_chip *chip)
+{
+ int ret;
+
+ chip->vdd = regulator_get(&chip->client->dev, "vdd");
+ if (IS_ERR_OR_NULL(chip->vdd)) {
+ pr_err("%s: fail to get 1.8v LDO\n", __func__);
+ return -3;
+ }
+
+ if (regulator_count_voltages(chip->vdd) > 0) {
+ ret = regulator_set_voltage(chip->vdd, 1800000, 1800000);
+ if (ret) {
+ pr_err("%s: regulator set_vtg vdd_reg failed rc=%d\n", __func__, ret);
+ regulator_put(chip->vdd);
+ return -4;
+ }
+ }
+
+ ret = regulator_enable(chip->vdd);
+ if (ret) {
+ pr_err("%s: Regulator vdd_reg enable failed rc=%d\n", __func__, ret);
+ }
+
+ return 0;
+}
+static int bq2589x_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct bq2589x_chip *chip;
+ int ret;
+
+ printk("%s dual \n", __func__);
+
+ fg_psy = power_supply_get_by_name("battery");
+ if (!fg_psy) {
+ pr_err("bq25892-dual fg supply not found deferring probe\n");
+ return -EPROBE_DEFER;
+ }
+ chip = kzalloc(sizeof(struct bq2589x_chip), GFP_KERNEL);
+ if (!chip) {
+ dev_err(&client->dev, "bq25892-dual mem alloc failed\n");
+ return -ENOMEM;
+ }
+
+#ifdef CONFIG_OF
+ if (client->dev.of_node) {
+ ret = bq2589x_parse_dt(&client->dev, chip);
+ if (ret)
+ return ret;
+ }
+#endif
+
+ chip->client = client;
+
+ bq2589x_regulator_get(chip);
+
+ /*assigning default value for min and max temp*/
+ i2c_set_clientdata(client, chip);
+ chip->chgr_stat = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ /* check chip model number */
+ ret = bq2589x_get_chip_version(chip);
+ if (ret < 0) {
+ dev_err(&client->dev, "i2c read err:%d\n", ret);
+ i2c_set_clientdata(client, NULL);
+ kfree(chip);
+ regulator_disable(chip->vdd);
+ return -EIO;
+ }
+
+ printk("%s dual chip version %s \n", __func__, chip->ic_name);
+
+ INIT_DELAYED_WORK(&chip->chrg_task_wrkr, bq2589x_task_worker);
+ INIT_DELAYED_WORK(&chip->fault_work, bq2589x_fault_worker);
+ mutex_init(&chip->event_lock);
+
+ /* Initialize the wakelock */
+ wake_lock_init(&chip->wakelock, WAKE_LOCK_SUSPEND, "ctp_charger_wakelock");
+ wake_lock_init(&chip->irq_wk, WAKE_LOCK_SUSPEND, "charger_irq_wakelock");
+
+ /* Init Runtime PM State */
+ pm_runtime_put_noidle(&chip->client->dev);
+ pm_schedule_suspend(&chip->client->dev, MSEC_PER_SEC);
+
+ /* create debugfs for maxim registers */
+ ret = bq2589x_create_debugfs(chip);
+ if (ret < 0) {
+ dev_err(&client->dev, "debugfs create failed\n");
+ }
+
+#ifdef EXT_CHARGER_POWER_SUPPLY
+ ret = bq2589x_power_supply_init(chip);
+ if (ret) {
+ pr_err("failed to register power supply: %d\n", ret);
+ }
+ ret = sysfs_create_group(&chip->charger_psy.dev->kobj,&fs_attr_group);
+#else
+ ret = sysfs_create_group(&client->dev.kobj,&fs_attr_group);
+#endif
+
+ if (ret) {
+ dev_err(&client->dev, "failed to setup sysfs ret = %d\n", ret);
+ }
+ /*
+ * Request for charger chip gpio.This will be used to
+ * register for an interrupt handler for servicing charger
+ * interrupts
+ */
+ if (gpio_is_valid(chip->irq_gpio)) {
+ ret = gpio_request(chip->irq_gpio, "chg_irq_gpio");
+ ret = gpio_direction_input(chip->irq_gpio);
+ chip->irq = gpio_to_irq(chip->irq_gpio);
+ } else {
+ pr_err("invalid irq gpio\n");
+ chip->irq = -1;
+ }
+
+ if (chip->irq < 0) {
+ pr_err("%s-dual chgr_int_n GPIO is not available\n", __func__);
+ } else {
+ //pr_err("%s set pull up 1 %d\n", __func__, err);
+ ret = request_threaded_irq(chip->irq,
+ bq2589x_irq_isr, bq2589x_irq_thread,
+ IRQF_TRIGGER_FALLING, "bq2589x", chip);
+ if (ret) {
+ pr_warn("%s-dual failed to register irq for pin %d\n", __func__,
+ chip->irq_gpio);
+ } else {
+ pr_warn("%s-dual registered charger irq for pin %d\n", __func__,
+ chip->irq_gpio);
+ }
+ }
+
+#if 1
+ if(gpio_is_valid(chip->chg_en_gpio)) {
+ ret = gpio_request(chip->chg_en_gpio, "chg_en_gpio");
+ gpio_direction_output(chip->chg_en_gpio,0);
+ gpio_set_value(chip->chg_en_gpio,0);
+ }
+ else
+ pr_err("%s-dual chg_en_gpio is invalid\n", __func__);
+#endif
+
+ bq2589x_hw_init(chip);
+ bq2589x_enable_charger(chip, 1);
+
+ bq2589x_dump_registers(chip);
+
+ schedule_delayed_work(&chip->chrg_task_wrkr, msecs_to_jiffies(1000));
+
+ pr_info("%s dual prob success\n", __func__);
+
+ return 0;
+}
+
+static int bq2589x_remove(struct i2c_client *client)
+{
+ struct bq2589x_chip *chip = i2c_get_clientdata(client);
+
+ bq2589x_remove_debugfs(chip);
+
+#ifdef EXT_CHARGER_POWER_SUPPLY
+ bq2589x_power_supply_exit(chip);
+#endif
+
+ if (chip->irq > 0)
+ free_irq(chip->irq, chip);
+
+ i2c_set_clientdata(client, NULL);
+ wake_lock_destroy(&chip->wakelock);
+
+ kfree(chip);
+ return 0;
+}
+
+static void bq2589x_shutdown(struct i2c_client *client)
+{
+ struct bq2589x_chip *chip = i2c_get_clientdata(client);
+
+ printk("bq2589x-dual_shutdown \r\n");
+
+ wake_lock_destroy(&chip->wakelock);
+
+ if (chip->irq > 0) {
+ free_irq(chip->irq, chip);
+ }
+ cancel_delayed_work_sync(&chip->chrg_task_wrkr);
+ cancel_delayed_work_sync(&chip->fault_work);
+
+ if(1)
+ {
+ if(gpio_is_valid(chip->chg_en_gpio)) {
+ printk("disable charging for shutting down\r\n");
+ gpio_set_value(chip->chg_en_gpio,1);
+ }
+
+ bq2589x_set_iusb_max(chip, 0);
+
+ msleep(250);
+ }
+
+ bq2589x_reset_regs(chip);
+ bq2589x_enable_charger(chip, 0);
+ bq2589x_dump_registers(chip);
+
+ printk("bq2589x-dual_shutdown complete\r\n");
+}
+
+#ifdef CONFIG_PM
+static int bq2589x_suspend(struct device *dev)
+{
+ struct bq2589x_chip *chip = dev_get_drvdata(dev);
+
+ if (chip->irq > 0) {
+ /*
+ * Once the WDT is expired all bq2589x registers gets
+ * set to default which means WDT is programmed to 40s
+ * and if there is no charger connected, no point
+ * feeding the WDT. Since reg07[1] is set to default,
+ * charger will interrupt SOC every 40s which is not
+ * good for S3. In this case we need to free chgr_int_n
+ * interrupt so that no interrupt from charger wakes
+ * up the platform in case of S3. Interrupt will be
+ * re-enabled on charger connect.
+ */
+ free_irq(chip->irq, chip);
+ }
+ cancel_delayed_work_sync(&chip->chrg_task_wrkr);
+ cancel_delayed_work_sync(&chip->fault_work);
+
+ //regulator operation
+#if 1
+ {
+ int ret= regulator_disable(chip->vdd);
+ if (ret) {
+ pr_err("%s: Regulator vdd_reg disable failed rc=%d\n", __func__, ret);
+ }
+ }
+#endif
+
+ dev_dbg(&chip->client->dev, "bq2589x-dual suspend,cancel charger worker\n");
+ return 0;
+}
+
+static int bq2589x_resume(struct device *dev)
+{
+ struct bq2589x_chip *chip = dev_get_drvdata(dev);
+ int ret;
+ //regulator operation
+#if 1
+ ret = regulator_enable(chip->vdd);
+ if (ret) {
+ pr_err("%s: Regulator vdd_reg enable failed rc=%d\n", __func__, ret);
+ }
+#endif
+
+ if (chip->irq > 0) {
+ ret = request_threaded_irq(chip->irq,
+ bq2589x_irq_isr, bq2589x_irq_thread,
+ IRQF_TRIGGER_FALLING, "bq2589x", chip);
+ if (ret) {
+ dev_warn(dev, "failed to register irq for pin %d\n",
+ chip->irq_gpio);
+ } else {
+ dev_warn(dev, "registered charger irq for pin %d\n",
+ chip->irq_gpio);
+ }
+ }
+ schedule_delayed_work(&chip->fault_work, 0);
+ schedule_delayed_work(&chip->chrg_task_wrkr, msecs_to_jiffies(20));
+
+ dev_dbg(&chip->client->dev, "bq2589x-dual resume,shecdule charger work and fault work\n");
+ return 0;
+}
+#else
+#define bq2589x_suspend NULL
+#define bq2589x_resume NULL
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int bq2589x_runtime_suspend(struct device *dev)
+{
+
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int bq2589x_runtime_resume(struct device *dev)
+{
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+
+static int bq2589x_runtime_idle(struct device *dev)
+{
+
+ dev_dbg(dev, "%s called\n", __func__);
+ return 0;
+}
+#else
+#define bq2589x_runtime_suspend NULL
+#define bq2589x_runtime_resume NULL
+#define bq2589x_runtime_idle NULL
+#endif
+
+MODULE_DEVICE_TABLE(i2c, bq2589x_id);
+
+static const struct dev_pm_ops bq2589x_pm_ops = {
+ .suspend = bq2589x_suspend,
+ .resume = bq2589x_resume,
+ .runtime_suspend = bq2589x_runtime_suspend,
+ .runtime_resume = bq2589x_runtime_resume,
+ .runtime_idle = bq2589x_runtime_idle,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id bq2589x_match[] = {
+ { .compatible = "ti,bq2589x-dual" },
+ { },
+};
+#endif
+
+static const struct i2c_device_id bq2589x_id[] = {
+ { DEV_NAME, 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, bq2589x_id);
+
+static struct i2c_driver bq2589x_battery_driver = {
+ .driver = {
+ .name = DEV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &bq2589x_pm_ops,
+#ifdef CONFIG_OF
+ .of_match_table = of_match_ptr(bq2589x_match),
+#endif
+ },
+ .probe = bq2589x_charger_probe,
+ .remove = bq2589x_remove,
+ .shutdown = bq2589x_shutdown,
+ .id_table = bq2589x_id,
+};
+
+static inline int bq2589x_battery_i2c_init(void)
+{
+ int ret = i2c_add_driver(&bq2589x_battery_driver);
+ printk("%s:bq2589x-dual register_i2c driver\n",__func__);
+ if (ret)
+ printk(KERN_ERR "Unable to register bq2589x-dual i2c driver\n");
+
+ return ret;
+}
+
+static inline void bq2589x_battery_i2c_exit(void)
+{
+ i2c_del_driver(&bq2589x_battery_driver);
+}
+
+static int __init bq2589x_battery_init(void)
+{
+ int ret;
+ ret = bq2589x_battery_i2c_init();
+
+ pr_notice("bq2589x-dual %s %d\n", __func__, ret);
+
+ return ret;
+}
+module_init(bq2589x_battery_init);
+
+static void __exit bq2589x_battery_exit(void)
+{
+ bq2589x_battery_i2c_exit();
+}
+module_exit(bq2589x_battery_exit);
+
diff --git a/drivers/power/lenovo/smb1351_charger_lenovo.c b/drivers/power/lenovo/smb1351_charger_lenovo.c
new file mode 100644
index 00000000..3235171c
--- /dev/null
+++ b/drivers/power/lenovo/smb1351_charger_lenovo.c
@@ -0,0 +1,6382 @@
+/* Copyright (c) 2015-2016 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) "SMB1351 %s: " fmt, __func__
+//#define DEBUG
+
+//#define pr_fmt(fmt) "SMB1351 %s: %s: " fmt, dev_name(chip->dev), __func__
+
+#include <linux/i2c.h>
+#include <linux/debugfs.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include <linux/of.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/of_device.h>
+#include <linux/wakelock.h>
+#include <linux/reboot.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/msm_bcl.h>
+
+/*===============feature operation==================*/
+#define PSY_PARALLEL_NAME "ext-charger-dual"
+//#define PSY_PARALLEL_NAME "usb-parallel"
+#define EXT_CHARGER_POWER_SUPPLY
+#define MMI_TEST
+#define CHG_HEATBEAT_WORK
+#define APP_CONTROL
+#define LENOVO_OTG_USB_SHORT
+
+#define CHARGER_CHECK_JIFFIES (HZ * 2)
+#define VBUS_DIV_FACTOR 7
+/*==============feature operation end===============*/
+
+#ifdef DEBUG
+#ifdef pr_debug
+#undef pr_debug
+#define pr_debug(fmt, ...) \
+ printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
+#endif
+
+#ifdef pr_info
+#undef pr_info
+#define pr_info(fmt, ...) \
+ printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
+#endif
+#endif
+
+#define SMB1351_SHUTDOWN_BATTERY_VOLTGE 3400000
+
+/* Mask/Bit helpers */
+#define _SMB1351_MASK(BITS, POS) \
+ ((unsigned char)(((1 << (BITS)) - 1) << (POS)))
+#define SMB1351_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \
+ _SMB1351_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \
+ (RIGHT_BIT_POS))
+
+/* Configuration registers */
+#define CHG_CURRENT_CTRL_REG 0x0
+#define FAST_CHG_CURRENT_MASK SMB1351_MASK(7, 4)
+#define AC_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(3, 0)
+
+#define CHG_OTH_CURRENT_CTRL_REG 0x1
+#define PRECHG_CURRENT_MASK SMB1351_MASK(7, 5)
+#define ITERM_MASK SMB1351_MASK(4, 2)
+#define USB_2_3_MODE_SEL_BIT BIT(1)
+#define USB_2_3_MODE_SEL_BY_I2C 0
+#define USB_2_3_MODE_SEL_BY_PIN 0x2
+#define USB_5_1_CMD_POLARITY_BIT BIT(0)
+#define USB_CMD_POLARITY_500_1_100_0 0
+#define USB_CMD_POLARITY_500_0_100_1 0x1
+
+#define VARIOUS_FUNC_REG 0x2
+#define SUSPEND_MODE_CTRL_BIT BIT(7)
+#define SUSPEND_MODE_CTRL_BY_PIN 0
+#define SUSPEND_MODE_CTRL_BY_I2C 0x80
+#define BATT_TO_SYS_POWER_CTRL_BIT BIT(6)
+#define MAX_SYS_VOLTAGE BIT(5)
+#define AICL_EN_BIT BIT(4)
+#define AICL_DET_TH_BIT BIT(3)
+#define APSD_EN_BIT BIT(2)
+#define BATT_OV_BIT BIT(1)
+#define VCHG_FUNC_BIT BIT(0)
+
+#define VFLOAT_REG 0x3
+#define PRECHG_TO_FAST_VOLTAGE_CFG_MASK SMB1351_MASK(7, 6)
+#define VFLOAT_MASK SMB1351_MASK(5, 0)
+
+#define CHG_CTRL_REG 0x4
+#define AUTO_RECHG_BIT BIT(7)
+#define AUTO_RECHG_ENABLE 0
+#define AUTO_RECHG_DISABLE 0x80
+#define ITERM_EN_BIT BIT(6)
+#define ITERM_ENABLE 0
+#define ITERM_DISABLE 0x40
+#define MAPPED_AC_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(5, 4)
+#define AUTO_RECHG_TH_BIT BIT(3)
+#define AUTO_RECHG_TH_50MV 0
+#define AUTO_RECHG_TH_100MV 0x8
+#define AFCV_MASK SMB1351_MASK(2, 0)
+
+#define CHG_STAT_TIMERS_CTRL_REG 0x5
+#define STAT_OUTPUT_POLARITY_BIT BIT(7)
+#define STAT_OUTPUT_ACTIVE_HIGH BIT(7)
+#define STAT_OUTPUT_ACTIVE_LOW 0
+#define STAT_OUTPUT_MODE_BIT BIT(6)
+#define STAT_OUTPUT_CTRL_BIT BIT(5)
+#define OTH_CHG_IL_BIT BIT(4)
+#define COMPLETE_CHG_TIMEOUT_MASK SMB1351_MASK(3, 2)
+#define PRECHG_TIMEOUT_MASK SMB1351_MASK(1, 0)
+
+#define CHG_PIN_EN_CTRL_REG 0x6
+#define LED_BLINK_FUNC_BIT BIT(7)
+#define EN_PIN_CTRL_MASK SMB1351_MASK(6, 5)
+#define EN_BY_I2C_0_DISABLE 0
+#define EN_BY_I2C_0_ENABLE 0x20
+#define EN_BY_PIN_HIGH_ENABLE 0x40
+#define EN_BY_PIN_LOW_ENABLE 0x60
+#define USBCS_CTRL_BIT BIT(4)
+#define USBCS_CTRL_BY_I2C 0
+#define USBCS_CTRL_BY_PIN 0x10
+#define USBCS_INPUT_STATE_BIT BIT(3)
+#define CHG_ERR_BIT BIT(2)
+#define APSD_DONE_BIT BIT(1)
+#define USB_FAIL_BIT BIT(0)
+
+#define THERM_A_CTRL_REG 0x7
+#define MIN_SYS_VOLTAGE_MASK SMB1351_MASK(7, 6)
+#define LOAD_BATT_10MA_FVC_BIT BIT(5)
+#define THERM_MONITOR_BIT BIT(4)
+#define THERM_MONITOR_EN 0
+#define SOFT_COLD_TEMP_LIMIT_MASK SMB1351_MASK(3, 2)
+#define SOFT_HOT_TEMP_LIMIT_MASK SMB1351_MASK(1, 0)
+
+#define WDOG_SAFETY_TIMER_CTRL_REG 0x8
+#define AICL_FAIL_OPTION_BIT BIT(7)
+#define AICL_FAIL_TO_SUSPEND 0
+#define AICL_FAIL_TO_150_MA 0x80
+#define WDOG_TIMEOUT_MASK SMB1351_MASK(6, 5)
+#define WDOG_IRQ_SAFETY_TIMER_MASK SMB1351_MASK(4, 3)
+#define WDOG_IRQ_SAFETY_TIMER_EN_BIT BIT(2)
+#define WDOG_OPTION_BIT BIT(1)
+#define WDOG_TIMER_EN_BIT BIT(0)
+
+#define OTG_USBIN_AICL_CTRL_REG 0x9
+#define OTG_ID_PIN_CTRL_MASK SMB1351_MASK(7, 6)
+#define OTG_PIN_POLARITY_BIT BIT(5)
+#define DCIN_IC_GLITCH_FILTER_HV_ADAPTER_MASK SMB1351_MASK(4, 3)
+#define DCIN_IC_GLITCH_FILTER_LV_ADAPTER_BIT BIT(2)
+#define USBIN_AICL_CFG1_BIT BIT(1)
+#define USBIN_AICL_CFG0_BIT BIT(0)
+
+#define OTG_TLIM_CTRL_REG 0xA
+#define SWITCH_FREQ_MASK SMB1351_MASK(7, 6)
+#define SWITCH_FREQ_SHIFT 6
+#define THERM_LOOP_TEMP_SEL_MASK SMB1351_MASK(5, 4)
+#define OTG_OC_LIMIT_MASK SMB1351_MASK(3, 2)
+#define OTG_BATT_UVLO_TH_MASK SMB1351_MASK(1, 0)
+
+#define HARD_SOFT_LIMIT_CELL_TEMP_REG 0xB
+#define HARD_LIMIT_COLD_TEMP_ALARM_TRIP_MASK SMB1351_MASK(7, 6)
+#define HARD_LIMIT_HOT_TEMP_ALARM_TRIP_MASK SMB1351_MASK(5, 4)
+#define SOFT_LIMIT_COLD_TEMP_ALARM_TRIP_MASK SMB1351_MASK(3, 2)
+#define SOFT_LIMIT_HOT_TEMP_ALARM_TRIP_MASK SMB1351_MASK(1, 0)
+
+#define FAULT_INT_CFG_REG 0xC
+#define HOT_COLD_HARD_LIMIT_BIT BIT(7)
+#define HOT_COLD_SOFT_LIMIT_BIT BIT(6)
+#define BATT_UVLO_IN_OTG_BIT BIT(5)
+#define OTG_OC_BIT BIT(4)
+#define INPUT_OVLO_BIT BIT(3)
+#define INPUT_UVLO_BIT BIT(2)
+#define AICL_DONE_FAIL_BIT BIT(1)
+#define INTERNAL_OVER_TEMP_BIT BIT(0)
+
+#define STATUS_INT_CFG_REG 0xD
+#define CHG_OR_PRECHG_TIMEOUT_BIT BIT(7)
+#define RID_CHANGE_BIT BIT(6)
+#define BATT_OVP_BIT BIT(5)
+#define FAST_TERM_TAPER_RECHG_INHIBIT_BIT BIT(4)
+#define WDOG_TIMER_BIT BIT(3)
+#define POK_BIT BIT(2)
+#define BATT_MISSING_BIT BIT(1)
+#define BATT_LOW_BIT BIT(0)
+
+#define VARIOUS_FUNC_2_REG 0xE
+#define CHG_HOLD_OFF_TIMER_AFTER_PLUGIN_BIT BIT(7)
+#define CHG_INHIBIT_BIT BIT(6)
+#define FAST_CHG_CC_IN_BATT_SOFT_LIMIT_MODE_BIT BIT(5)
+#define FVCL_IN_BATT_SOFT_LIMIT_MODE_MASK SMB1351_MASK(4, 3)
+#define HARD_TEMP_LIMIT_BEHAVIOR_BIT BIT(2)
+#define PRECHG_TO_FASTCHG_BIT BIT(1)
+#define STAT_PIN_CONFIG_BIT BIT(0)
+
+#define FLEXCHARGER_REG 0x10
+#define AFVC_IRQ_BIT BIT(7)
+#define CHG_CONFIG_MASK SMB1351_MASK(6, 4)
+#define LOW_BATT_VOLTAGE_DET_TH_MASK SMB1351_MASK(3, 0)
+
+#define VARIOUS_FUNC_3_REG 0x11
+#define SAFETY_TIMER_EN_MASK SMB1351_MASK(7, 6)
+#define BLOCK_SUSPEND_DURING_VBATT_LOW_BIT BIT(5)
+#define TIMEOUT_SEL_FOR_APSD_BIT BIT(4)
+#define SDP_SUSPEND_BIT BIT(3)
+#define QC_2P1_AUTO_INCREMENT_MODE_BIT BIT(2)
+#define QC_2P1_AUTH_ALGO_BIT BIT(1)
+#define DCD_EN_BIT BIT(0)
+
+#define HVDCP_BATT_MISSING_CTRL_REG 0x12
+#define HVDCP_ADAPTER_SEL_MASK SMB1351_MASK(7, 6)
+#define HVDCP_EN_BIT BIT(5)
+#define HVDCP_AUTO_INCREMENT_LIMIT_BIT BIT(4)
+#define BATT_MISSING_ON_INPUT_PLUGIN_BIT BIT(3)
+#define BATT_MISSING_2P6S_POLLER_BIT BIT(2)
+#define BATT_MISSING_ALGO_BIT BIT(1)
+#define BATT_MISSING_THERM_PIN_SOURCE_BIT BIT(0)
+
+#define PON_OPTIONS_REG 0x13
+#define SYSOK_INOK_POLARITY_BIT BIT(7)
+#define SYSOK_INOK_POLARITY_INVERT BIT(7)
+#define SYSOK_OPTIONS_MASK SMB1351_MASK(6, 4)
+#define SYSOK_INOK_OPTION1 0x00
+#define INPUT_MISSING_POLLER_CONFIG_BIT BIT(3)
+#define VBATT_LOW_DISABLED_OR_RESET_STATE_BIT BIT(2)
+#define QC_2P1_AUTH_ALGO_IRQ_EN_BIT BIT(0)
+
+#define OTG_MODE_POWER_OPTIONS_REG 0x14
+#define ADAPTER_CONFIG_MASK SMB1351_MASK(7, 6)
+#define MAP_HVDCP_BIT BIT(5)
+#define SDP_LOW_BATT_FORCE_USB5_OVER_USB1_BIT BIT(4)
+#define OTG_HICCUP_MODE_BIT BIT(2)
+#define INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(1, 0)
+
+#define CHARGER_I2C_CTRL_REG 0x15
+#define FULLON_MODE_EN_BIT BIT(7)
+#define I2C_HS_MODE_EN_BIT BIT(6)
+#define SYSON_LDO_OUTPUT_SEL_BIT BIT(5)
+#define VBATT_TRACKING_VOLTAGE_DIFF_BIT BIT(4)
+#define DISABLE_AFVC_WHEN_ENTER_TAPER_BIT BIT(3)
+#define VCHG_IINV_BIT BIT(2)
+#define AFVC_OVERRIDE_BIT BIT(1)
+#define SYSOK_PIN_CONFIG_BIT BIT(0)
+
+#define VERSION_REG 0x2E
+#define VERSION_MASK BIT(1)
+
+/* Command registers */
+#define CMD_I2C_REG 0x30
+#define CMD_RELOAD_BIT BIT(7)
+#define CMD_BQ_CFG_ACCESS_BIT BIT(6)
+
+#define CMD_INPUT_LIMIT_REG 0x31
+#define CMD_OVERRIDE_BIT BIT(7)
+#define CMD_SUSPEND_MODE_BIT BIT(6)
+#define CMD_INPUT_CURRENT_MODE_BIT BIT(3)
+#define CMD_INPUT_CURRENT_MODE_APSD 0
+#define CMD_INPUT_CURRENT_MODE_CMD 0x08
+#define CMD_USB_2_3_SEL_BIT BIT(2)
+#define CMD_USB_2_MODE 0
+#define CMD_USB_3_MODE 0x4
+#define CMD_USB_1_5_AC_CTRL_MASK SMB1351_MASK(1, 0)
+#define CMD_USB_100_MODE 0
+#define CMD_USB_500_MODE 0x2
+#define CMD_USB_HC_MODE 0x1
+
+#define CMD_CHG_REG 0x32
+#define CMD_DISABLE_THERM_MONITOR_BIT BIT(4)
+#define CMD_TURN_OFF_STAT_PIN_BIT BIT(3)
+#define CMD_PRE_TO_FAST_EN_BIT BIT(2)
+#define CMD_CHG_EN_BIT BIT(1)
+#define CMD_CHG_DISABLE 0
+#define CMD_CHG_ENABLE 0x2
+#define CMD_OTG_EN_BIT BIT(0)
+
+#define CMD_DEAD_BATT_REG 0x33
+#define CMD_STOP_DEAD_BATT_TIMER_MASK SMB1351_MASK(7, 0)
+
+#define CMD_HVDCP_REG 0x34
+#define CMD_APSD_RE_RUN_BIT BIT(7)
+#define CMD_FORCE_HVDCP_2P0_BIT BIT(5)
+#define CMD_HVDCP_MODE_MASK SMB1351_MASK(5, 0)
+
+/* Status registers */
+#define STATUS_0_REG 0x36
+#define STATUS_AICL_DONE_BIT BIT(7)
+#define STATUS_INPUT_MODE_MASK SMB1351_MASK(6, 5)
+#define STATUS_INPUT_SUSPEND BIT(4)
+#define STATUS_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(3, 0)
+
+#define STATUS_1_REG 0x37
+#define STATUS_INPUT_RANGE_MASK SMB1351_MASK(7, 4)
+#define STATUS_INPUT_RANGE_12V BIT(7)
+#define STATUS_INPUT_RANGE_5V_9V BIT(6)
+#define STATUS_INPUT_RANGE_9V BIT(5)
+#define STATUS_INPUT_RANGE_5V BIT(4)
+#define STATUS_INPUT_USB_BIT BIT(0)
+
+#define STATUS_2_REG 0x38
+#define STATUS_FAST_CHG_BIT BIT(7)
+#define STATUS_HARD_LIMIT_BIT BIT(6)
+#define STATUS_FLOAT_VOLTAGE_MASK SMB1351_MASK(5, 0)
+
+#define STATUS_3_REG 0x39
+#define STATUS_CHG_BIT BIT(7)
+#define STATUS_PRECHG_CURRENT_MASK SMB1351_MASK(6, 4)
+#define STATUS_FAST_CHG_CURRENT_MASK SMB1351_MASK(3, 0)
+
+#define STATUS_4_REG 0x3A
+#define STATUS_OTG_BIT BIT(7)
+#define STATUS_AFVC_BIT BIT(6)
+#define STATUS_DONE_BIT BIT(5)
+#define STATUS_BATT_LESS_THAN_2V_BIT BIT(4)
+#define STATUS_HOLD_OFF_BIT BIT(3)
+#define STATUS_CHG_MASK SMB1351_MASK(2, 1)
+#define STATUS_NO_CHARGING 0
+#define STATUS_FAST_CHARGING 0x4
+#define STATUS_PRE_CHARGING 0x2
+#define STATUS_TAPER_CHARGING 0x6
+#define STATUS_CHG_EN_STATUS_BIT BIT(0)
+
+#define STATUS_5_REG 0x3B
+#define STATUS_SOURCE_DETECTED_MASK SMB1351_MASK(7, 0)
+#define STATUS_PORT_CDP 0x80
+#define STATUS_PORT_DCP 0x40
+#define STATUS_PORT_OTHER 0x20
+#define STATUS_PORT_SDP 0x10
+#define STATUS_PORT_ACA_A 0x8
+#define STATUS_PORT_ACA_B 0x4
+#define STATUS_PORT_ACA_C 0x2
+#define STATUS_PORT_ACA_DOCK 0x1
+
+#define STATUS_6_REG 0x3C
+#define STATUS_DCD_TIMEOUT_BIT BIT(7)
+#define STATUS_DCD_GOOD_DG_BIT BIT(6)
+#define STATUS_OCD_GOOD_DG_BIT BIT(5)
+#define STATUS_RID_ABD_DG_BIT BIT(4)
+#define STATUS_RID_FLOAT_STATE_MACHINE_BIT BIT(3)
+#define STATUS_RID_A_STATE_MACHINE_BIT BIT(2)
+#define STATUS_RID_B_STATE_MACHINE_BIT BIT(1)
+#define STATUS_RID_C_STATE_MACHINE_BIT BIT(0)
+
+#define STATUS_7_REG 0x3D
+#define STATUS_HVDCP_MASK SMB1351_MASK(7, 0)
+#define HVDCP_SEL_5V BIT(1)
+#define HVDCP_SEL_9V BIT(2)
+#define HVDCP_SEL_12V BIT(3)
+
+#define STATUS_8_REG 0x3E
+#define STATUS_USNIN_HV_INPUT_SEL_BIT BIT(5)
+#define STATUS_USBIN_LV_UNDER_INPUT_SEL_BIT BIT(4)
+#define STATUS_USBIN_LV_INPUT_SEL_BIT BIT(3)
+
+/* Revision register */
+#define CHG_REVISION_REG 0x3F
+#define GUI_REVISION_MASK SMB1351_MASK(7, 4)
+#define DEVICE_REVISION_MASK SMB1351_MASK(3, 0)
+
+/* IRQ status registers */
+#define IRQ_A_REG 0x40
+#define IRQ_HOT_HARD_BIT BIT(6)
+#define IRQ_COLD_HARD_BIT BIT(4)
+#define IRQ_HOT_SOFT_BIT BIT(2)
+#define IRQ_COLD_SOFT_BIT BIT(0)
+
+#define IRQ_B_REG 0x41
+#define IRQ_BATT_TERMINAL_REMOVED_BIT BIT(6)
+#define IRQ_BATT_MISSING_BIT BIT(4)
+#define IRQ_LOW_BATT_VOLTAGE_BIT BIT(2)
+#define IRQ_INTERNAL_TEMP_LIMIT_BIT BIT(0)
+
+#define IRQ_C_REG 0x42
+#define IRQ_PRE_TO_FAST_VOLTAGE_BIT BIT(6)
+#define IRQ_RECHG_BIT BIT(4)
+#define IRQ_TAPER_BIT BIT(2)
+#define IRQ_TERM_BIT BIT(0)
+
+#define IRQ_D_REG 0x43
+#define IRQ_BATT_OV_BIT BIT(6)
+#define IRQ_CHG_ERROR_BIT BIT(4)
+#define IRQ_CHG_TIMEOUT_BIT BIT(2)
+#define IRQ_PRECHG_TIMEOUT_BIT BIT(0)
+
+#define IRQ_E_REG 0x44
+#define IRQ_USBIN_OV_BIT BIT(6)
+#define IRQ_USBIN_UV_BIT BIT(4)
+#define IRQ_AFVC_BIT BIT(2)
+#define IRQ_POWER_OK_BIT BIT(0)
+
+#define IRQ_F_REG 0x45
+#define IRQ_OTG_OVER_CURRENT_BIT BIT(6)
+#define IRQ_OTG_FAIL_BIT BIT(4)
+#define IRQ_RID_BIT BIT(2)
+#define IRQ_OTG_OC_RETRY_BIT BIT(0)
+
+#define IRQ_G_REG 0x46
+#define IRQ_SOURCE_DET_BIT BIT(6)
+#define IRQ_AICL_DONE_BIT BIT(4)
+#define IRQ_AICL_FAIL_BIT BIT(2)
+#define IRQ_CHG_INHIBIT_BIT BIT(0)
+
+#define IRQ_H_REG 0x47
+#define IRQ_IC_LIMIT_STATUS_BIT BIT(5)
+#define IRQ_HVDCP_2P1_STATUS_BIT BIT(4)
+#define IRQ_HVDCP_AUTH_DONE_BIT BIT(2)
+#define IRQ_WDOG_TIMEOUT_BIT BIT(0)
+
+/* constants */
+#define USB2_MIN_CURRENT_MA 100
+#define USB2_MAX_CURRENT_MA 500
+#define USB3_MIN_CURRENT_MA 150
+#define USB3_MAX_CURRENT_MA 900
+#define SMB1351_IRQ_REG_COUNT 8
+#define SMB1351_CHG_PRE_MIN_MA 100
+#define SMB1351_CHG_FAST_MIN_MA 1000
+#define SMB1351_CHG_FAST_MAX_MA 5000
+#define SMB1351_CHG_PRE_SHIFT 5
+#define SMB1351_CHG_FAST_SHIFT 4
+#define DEFAULT_BATT_CAPACITY 50
+#define DEFAULT_BATT_TEMP 250
+#define DEFAULT_BATT_VOLTAGE 3800
+#define DEFAULT_VBUS_VOLTAGE 4000
+#define SUSPEND_CURRENT_MA 2
+
+#define CHG_ITERM_200MA 0x0
+#define CHG_ITERM_300MA 0x04
+#define CHG_ITERM_400MA 0x08
+#define CHG_ITERM_500MA 0x0C
+#define CHG_ITERM_600MA 0x10
+#define CHG_ITERM_700MA 0x14
+
+#define ADC_TM_WARM_COOL_THR_ENABLE ADC_TM_HIGH_LOW_THR_ENABLE
+
+enum reason {
+ USER = BIT(0),
+ THERMAL = BIT(1),
+ CURRENT = BIT(2),
+ SOC = BIT(3),
+ PARALLEL = BIT(4),
+ ITERM = BIT(5),
+};
+
+static char *pm_batt_supplied_to[] = {
+ "bms",
+};
+
+struct smb1351_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+};
+
+enum chip_version {
+ SMB_UNKNOWN = 0,
+ SMB1350,
+ SMB1351,
+ SMB_MAX_TYPE,
+};
+
+static const char *smb1351_version_str[SMB_MAX_TYPE] = {
+ [SMB_UNKNOWN] = "Unknown",
+ [SMB1350] = "SMB1350",
+ [SMB1351] = "SMB1351",
+};
+
+enum workaround_flags {
+ CHG_FORCE_ESR_WA = BIT(0),
+ CHG_FG_NOTIFY_JEITA = BIT(1),
+ CHG_ITERM_CHECK_SOC = BIT(2),
+ CHG_SOC_BASED_RESUME = BIT(3),
+};
+
+enum wakeup_src {
+ I2C_ACCESS,
+ HVDCP_DETECT,
+ REMOVAL_DETECT,
+ RERUN_APSD,
+ PARALLEL_CHARGE,
+ PARALLEL_TAPER,
+ FORCE_ESR_PULSE,
+ ITERM_CHECK_SOC,
+ WAKEUP_SRC_MAX,
+};
+#define WAKEUP_SRC_MASK GENMASK(WAKEUP_SRC_MAX, 0)
+
+struct smb1351_wakeup_source {
+ struct wakeup_source source;
+ unsigned long enabled_bitmap;
+ spinlock_t ws_lock;
+};
+
+/* parallel primary charger */
+struct parallel_main_cfg {
+ struct power_supply *psy;
+ struct mutex lock;
+
+ bool avail;
+ bool slave_detected;
+ int min_current_thr_ma;
+ int min_9v_current_thr_ma;
+ int min_12v_current_thr_ma;
+ int allowed_lowering_ma;
+ int main_chg_fcc_percent;
+ int main_chg_icl_percent;
+ int total_icl_ma;
+ int slave_icl_ma;
+
+ struct delayed_work parallel_work;
+};
+
+struct smb1351_charger {
+ struct i2c_client *client;
+ struct device *dev;
+
+ bool recharge_disabled;
+ int recharge_mv;
+ bool iterm_disabled;
+ int iterm_ma;
+ int vfloat_mv;
+ int switch_freq;
+ int chg_present;
+ int fake_battery_soc;
+ int resume_soc;
+ bool chg_autonomous_mode;
+ bool disable_apsd;
+ bool using_pmic_therm;
+ bool jeita_supported;
+ bool fg_notify_jeita;
+ bool battery_missing;
+ const char *bms_psy_name;
+ bool resume_completed;
+ bool irq_waiting;
+ struct delayed_work chg_remove_work;
+ struct delayed_work hvdcp_det_work;
+ struct delayed_work rerun_apsd_work;
+ struct delayed_work init_fg_work;
+ struct delayed_work iterm_check_soc_work;
+ struct smb1351_wakeup_source smb1351_ws;
+ struct delayed_work charger_check_work;
+
+ /* status tracking */
+ bool batt_full;
+ bool batt_hot;
+ bool batt_cold;
+ bool batt_warm;
+ bool batt_cool;
+
+ int battchg_disabled_status;
+ int usb_suspended_status;
+ int target_fastchg_current_max_ma;
+ int fastchg_current_max_ma;
+ int main_fcc_ma_before_esr;
+ int slave_fcc_ma_before_esr;
+ int workaround_flags;
+
+ int parallel_pin_polarity_setting;
+ bool is_slave;
+ bool use_external_fg;
+ bool parallel_charger_present;
+ bool bms_controlled_charging;
+ bool apsd_rerun;
+ bool usbin_ov;
+ int chg_remove_work_scheduled;
+ bool force_hvdcp_2p0;
+ bool check_parallel;
+ bool in_esr_pulse;
+ enum chip_version version;
+ u32 wa_flags;
+ int pre_soc;
+
+ /* psy */
+ struct power_supply *usb_psy;
+ int usb_psy_ma;
+ enum power_supply_type usb_psy_type;
+ struct power_supply *bms_psy;
+ struct power_supply batt_psy;
+ struct power_supply parallel_psy;
+#ifdef EXT_CHARGER_POWER_SUPPLY
+ struct power_supply ext_charger_psy;
+ bool temp_debug_flag;
+#endif
+#ifdef CHG_HEATBEAT_WORK
+ struct delayed_work heatbeat_work;
+#endif
+
+ bool handshake_state;
+ bool fast_charger_is_engineermode;
+
+ struct smb1351_regulator otg_vreg;
+ struct mutex irq_complete;
+ struct mutex fcc_lock;
+ struct mutex usb_suspend_status_lock;
+ struct wake_lock charging_lock;
+
+ struct dentry *debug_root;
+ u32 peek_poke_address;
+
+ /* adc_tm parameters */
+ struct qpnp_vadc_chip *vadc_dev;
+ struct qpnp_adc_tm_chip *adc_tm_dev;
+ struct qpnp_adc_tm_btm_param adc_param;
+
+ /* jeita parameters */
+ int batt_hot_decidegc;
+ int batt_cold_decidegc;
+ int batt_warm_decidegc;
+ int batt_cool_decidegc;
+ int batt_missing_decidegc;
+ unsigned int batt_warm_ma;
+ unsigned int batt_warm_mv;
+ unsigned int batt_cool_ma;
+ unsigned int batt_cool_mv;
+
+ /* pinctrl parameters */
+ const char *pinctrl_state_name;
+ struct pinctrl *smb_pinctrl;
+
+ /* parallel primary */
+ struct parallel_main_cfg parallel;
+
+ struct regulator *vdd;
+ int chg_en_gpio;
+ int chg_led_gpio;
+ int batt_temp;
+
+ struct qpnp_vadc_chip *chg_pm_vadc_dev;
+ struct qpnp_vadc_chip *chg_pmi_vadc_dev;
+ bool is_factory_mode;
+ bool is_factory_cable;
+ bool is_factory_need_write;
+ bool is_in_esr_state;
+ bool is_hv_fast_charging;
+
+ struct notifier_block mmi_reboot;
+
+#ifdef LENOVO_OTG_USB_SHORT
+ int otg_usb_short_gpio;
+ bool otg_usb_short_state;
+ bool is_otg_on;
+#endif
+};
+
+struct smb_irq_info {
+ const char *name;
+ int (*smb_irq)(struct smb1351_charger *chip, u8 rt_stat);
+ int high;
+ int low;
+};
+
+struct irq_handler_info {
+ u8 stat_reg;
+ u8 val;
+ u8 prev_val;
+ struct smb_irq_info irq_info[4];
+};
+
+/* USB input charge current */
+static int usb_chg_current[] = {
+ 500, 685, 1000, 1100, 1200, 1300, 1500, 1600,
+ 1700, 1800, 2000, 2200, 2500, 3000,
+};
+
+static int fast_chg_current[] = {
+ 1000, 1200, 1400, 1600, 1800, 2000, 2200,
+ 2400, 2600, 2800, 3000, 3400, 3600, 3800,
+ 4000, 4640,
+};
+
+static int pre_chg_current[] = {
+ 200, 300, 400, 500, 600, 700,
+};
+
+struct battery_status {
+ bool batt_hot;
+ bool batt_warm;
+ bool batt_cool;
+ bool batt_cold;
+ bool batt_present;
+};
+
+enum {
+ BATT_HOT = 0,
+ BATT_WARM,
+ BATT_NORMAL,
+ BATT_COOL,
+ BATT_COLD,
+ BATT_MISSING,
+ BATT_STATUS_MAX,
+};
+
+static struct battery_status batt_s[] = {
+ [BATT_HOT] = {1, 0, 0, 0, 1},
+ [BATT_WARM] = {0, 1, 0, 0, 1},
+ [BATT_NORMAL] = {0, 0, 0, 0, 1},
+ [BATT_COOL] = {0, 0, 1, 0, 1},
+ [BATT_COLD] = {0, 0, 0, 1, 1},
+ [BATT_MISSING] = {0, 0, 0, 1, 0},
+};
+
+static int smb1351_led_operation(struct smb1351_charger *chip, int en)
+{
+ pr_info("set led stat %d\n", en);
+
+ gpio_set_value(chip->chg_led_gpio, en);
+ return 0;
+}
+
+static int smb1351_led_state(struct smb1351_charger *chip)
+{
+ return gpio_get_value(chip->chg_led_gpio);
+}
+
+static void smb1351_stay_awake(struct smb1351_wakeup_source *source,
+ enum wakeup_src wk_src)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&source->ws_lock, flags);
+
+ if (!__test_and_set_bit(wk_src, &source->enabled_bitmap))
+ __pm_stay_awake(&source->source);
+
+ spin_unlock_irqrestore(&source->ws_lock, flags);
+}
+
+static void smb1351_relax(struct smb1351_wakeup_source *source,
+ enum wakeup_src wk_src)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&source->ws_lock, flags);
+ if (__test_and_clear_bit(wk_src, &source->enabled_bitmap) &&
+ !(source->enabled_bitmap & WAKEUP_SRC_MASK)) {
+ __pm_relax(&source->source);
+ }
+ spin_unlock_irqrestore(&source->ws_lock, flags);
+
+}
+
+static void smb1351_wakeup_src_init(struct smb1351_charger *chip)
+{
+ spin_lock_init(&chip->smb1351_ws.ws_lock);
+ wakeup_source_init(&chip->smb1351_ws.source, "smb1351");
+}
+
+static int smb1351_read_reg(struct smb1351_charger *chip, int reg, u8 *val)
+{
+ s32 ret;
+
+ smb1351_stay_awake(&chip->smb1351_ws, I2C_ACCESS);
+ ret = i2c_smbus_read_byte_data(chip->client, reg);
+ if (ret < 0) {
+ pr_err("%s i2c read fail: can't read from %02x: %d\n", dev_name(chip->dev), reg, ret);
+ smb1351_relax(&chip->smb1351_ws, I2C_ACCESS);
+ return ret;
+ } else {
+ *val = ret;
+ }
+ smb1351_relax(&chip->smb1351_ws, I2C_ACCESS);
+// pr_debug("%s Reading 0x%02x=0x%02x\n", dev_name(chip->dev), reg, *val);
+ return 0;
+}
+
+static int smb1351_write_reg(struct smb1351_charger *chip, int reg, u8 val)
+{
+ s32 ret;
+
+ smb1351_stay_awake(&chip->smb1351_ws, I2C_ACCESS);
+ ret = i2c_smbus_write_byte_data(chip->client, reg, val);
+ if (ret < 0) {
+ pr_err("%s, i2c write fail: can't write %02x to %02x: %d\n",
+ dev_name(chip->dev), val, reg, ret);
+ smb1351_relax(&chip->smb1351_ws, I2C_ACCESS);
+ return ret;
+ }
+ smb1351_relax(&chip->smb1351_ws, I2C_ACCESS);
+// pr_debug("%s Writing 0x%02x=0x%02x\n", dev_name(chip->dev), reg, val);
+ return 0;
+}
+
+static int smb1351_masked_write(struct smb1351_charger *chip, int reg,
+ u8 mask, u8 val)
+{
+ s32 rc;
+ u8 temp;
+
+ rc = smb1351_read_reg(chip, reg, &temp);
+ if (rc) {
+ pr_err("read failed: reg=%03X, rc=%d\n", reg, rc);
+ return rc;
+ }
+ temp &= ~mask;
+ temp |= val & mask;
+ rc = smb1351_write_reg(chip, reg, temp);
+ if (rc) {
+ pr_err("write failed: reg=%03X, rc=%d\n", reg, rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int smb1351_enable_volatile_writes(struct smb1351_charger *chip)
+{
+ int rc;
+
+ rc = smb1351_masked_write(chip, CMD_I2C_REG, CMD_BQ_CFG_ACCESS_BIT,
+ CMD_BQ_CFG_ACCESS_BIT);
+ if (rc)
+ pr_err("Couldn't write CMD_BQ_CFG_ACCESS_BIT rc=%d\n", rc);
+
+ return rc;
+}
+
+static int smb1351_usb_suspend(struct smb1351_charger *chip, int reason,
+ bool suspend)
+{
+ int rc = 0;
+ int suspended;
+
+ mutex_lock(&chip->usb_suspend_status_lock);
+ suspended = chip->usb_suspended_status;
+
+ pr_debug("%s reason = %d requested_suspend = %d suspended_status = %d\n",
+ dev_name(chip->dev), reason, suspend, suspended);
+
+ if (suspend == false)
+ suspended &= ~reason;
+ else
+ suspended |= reason;
+
+ pr_debug("new suspended_status = %d\n", suspended);
+
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG,
+ CMD_SUSPEND_MODE_BIT,
+ suspended ? CMD_SUSPEND_MODE_BIT : 0);
+ if (rc)
+ pr_err("Couldn't suspend rc = %d\n", rc);
+ else
+ chip->usb_suspended_status = suspended;
+
+ mutex_unlock(&chip->usb_suspend_status_lock);
+ return rc;
+}
+
+static int smb1351_battchg_disable(struct smb1351_charger *chip,
+ int reason, int disable)
+{
+ int rc = 0;
+ int disabled;
+
+ if (chip->chg_autonomous_mode) {
+ pr_debug("Charger in autonomous mode\n");
+ return 0;
+ }
+
+ disabled = chip->battchg_disabled_status;
+
+ pr_debug("%s reason = %d requested_disable = %d disabled_status = %d\n",
+ dev_name(chip->dev), reason, disable, disabled);
+ if (disable == true)
+ disabled |= reason;
+ else
+ disabled &= ~reason;
+
+ pr_debug("new disabled_status = %d\n", disabled);
+
+ rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_CHG_EN_BIT,
+ disabled ? 0 : CMD_CHG_ENABLE);
+ if (rc)
+ pr_err("Couldn't %s charging rc=%d\n",
+ disable ? "disable" : "enable", rc);
+ else
+ chip->battchg_disabled_status = disabled;
+
+ return rc;
+}
+
+static int smb1351_parallel_charger_disable_slave(struct smb1351_charger *chip);
+static int smb1351_set_hv_handshake_current(struct smb1351_charger *chip,
+ int current_ma)
+{
+ int rc = 0;
+
+ pr_debug("handshake current_ma = %d\n", current_ma);
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, 0x2, (current_ma>100)?0x2:0x0);
+ if (rc) {
+ pr_err("Set 0x31 1 failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+#ifdef MMI_TEST
+
+#define CHG_SHOW_MAX_SIZE 50
+
+static int factory_kill_disable;
+
+module_param(factory_kill_disable, int, 0644);
+
+/*motorola mmi test use*/
+static ssize_t force_chg_fail_clear_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ unsigned long r;
+ unsigned long mode;
+
+ r = kstrtoul(buf, 0, &mode);
+ if (r) {
+ pr_err("Invalid chg fail mode value = %lu\n", mode);
+ return -EINVAL;
+ }
+
+ /* do nothing for SMBCHG */
+ r = 0;
+
+ return r ? r : count;
+}
+
+static ssize_t force_chg_fail_clear_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ /* do nothing for SMBCHG */
+ return scnprintf(buf, CHG_SHOW_MAX_SIZE, "0\n");
+}
+
+static DEVICE_ATTR(force_chg_fail_clear, 0664,
+ force_chg_fail_clear_show,
+ force_chg_fail_clear_store);
+#endif
+
+#define INPUT_MODE_HC 0x00
+#define INPUT_MODE_USB100 (0x01 << 5)
+#define INPUT_MODE_USB500 (0x02 << 5)
+static int smb1351_get_usb_chg_current(struct smb1351_charger *chip,
+ int *icl_ma)
+{
+ int rc, i;
+ bool is_usb3;
+ u8 icl_status, usb_mode;
+
+ rc = smb1351_read_reg(chip, STATUS_0_REG, &icl_status);
+ if (rc) {
+ pr_err("read STATUS_0 failed, rc=%d\n", rc);
+ return rc;
+ }
+ if (icl_status & STATUS_INPUT_SUSPEND) {
+ pr_err("%s:USB suspended!\n", dev_name(chip->dev));
+ *icl_ma = 0;
+ return rc;
+ }
+ rc = smb1351_read_reg(chip, CMD_INPUT_LIMIT_REG, &usb_mode);
+ if (rc) {
+ pr_err("read CMD_IL failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ is_usb3 = !!(usb_mode & CMD_USB_2_3_SEL_BIT);
+ switch (icl_status & STATUS_INPUT_MODE_MASK) {
+ case INPUT_MODE_USB100:
+ if (is_usb3)
+ *icl_ma = USB3_MIN_CURRENT_MA;
+ else
+ *icl_ma = USB2_MIN_CURRENT_MA;
+ break;
+ case INPUT_MODE_USB500:
+ if (is_usb3)
+ *icl_ma = USB3_MAX_CURRENT_MA;
+ else
+ *icl_ma = USB2_MAX_CURRENT_MA;
+ break;
+ case INPUT_MODE_HC:
+ i = icl_status & STATUS_INPUT_CURRENT_LIMIT_MASK;
+ if (i >= ARRAY_SIZE(usb_chg_current))
+ i = ARRAY_SIZE(usb_chg_current) - 1;
+ *icl_ma = usb_chg_current[i];
+ break;
+ default:
+ break;
+ }
+ pr_debug("%s:USB ICL status: %d\n", dev_name(chip->dev), *icl_ma);
+
+ return rc;
+}
+
+static int smb1351_set_usb_chg_current(struct smb1351_charger *chip,
+ int current_ma)
+{
+ int i, rc = 0, icl_result_ma = 0;
+ u8 reg = 0, mask = 0;
+
+ pr_debug("%s:USB current_ma = %d\n", dev_name(chip->dev), current_ma);
+
+ if (chip->chg_autonomous_mode) {
+ pr_debug("Charger in autonomous mode\n");
+ return 0;
+ }
+
+#ifdef APP_CONTROL
+ if (!chip->is_slave) {
+ if ((current_ma < 500)&&(current_ma >= 100)) {
+ smb1351_set_hv_handshake_current(chip, current_ma);
+ return 0;
+ } else if(chip->handshake_state) {
+ pr_info ("in handshake state, do not set usb in (%d)\n", current_ma);
+ return 0;
+ }
+ }
+#endif
+
+
+ /* set suspend bit when urrent_ma <= 2 */
+ if (current_ma <= SUSPEND_CURRENT_MA) {
+ if(chip->is_slave) {
+ smb1351_usb_suspend(chip, CURRENT, true);
+ pr_err("USB suspend\n");
+ }
+ return 0;
+ }
+
+ if (current_ma > SUSPEND_CURRENT_MA &&
+ current_ma < USB2_MIN_CURRENT_MA)
+ current_ma = USB2_MIN_CURRENT_MA;
+
+ if (current_ma == USB2_MIN_CURRENT_MA) {
+ /* USB 2.0 - 100mA */
+ reg = CMD_USB_2_MODE | CMD_USB_100_MODE;
+ } else if (current_ma == USB3_MIN_CURRENT_MA) {
+ /* USB 3.0 - 150mA */
+ reg = CMD_USB_3_MODE | CMD_USB_100_MODE;
+ } else if (current_ma == USB2_MAX_CURRENT_MA) {
+ /* USB 2.0 - 500mA */
+ reg = CMD_USB_2_MODE | CMD_USB_500_MODE;
+ } else if (current_ma == USB3_MAX_CURRENT_MA) {
+ /* USB 3.0 - 900mA */
+ reg = CMD_USB_3_MODE | CMD_USB_500_MODE;
+ } else if (current_ma > USB2_MAX_CURRENT_MA) {
+ /* HC mode - if none of the above */
+ reg = CMD_USB_HC_MODE;
+ rc = smb1351_get_usb_chg_current(chip, &icl_result_ma);
+ if (rc) {
+ pr_err("Get ICL result failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ for (i = ARRAY_SIZE(usb_chg_current) - 1; i >= 0; i--) {
+ if (usb_chg_current[i] <= current_ma)
+ break;
+ }
+
+ if (i < 0)
+ i = 0;
+
+ rc = smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG,
+ AC_INPUT_CURRENT_LIMIT_MASK, i);
+ if (rc) {
+ pr_err("Couldn't set input mA rc=%d\n", rc);
+ return rc;
+ }
+ current_ma = usb_chg_current[i];
+ }
+
+ /* control input current mode by command */
+ reg |= CMD_INPUT_CURRENT_MODE_CMD;
+ mask = CMD_INPUT_CURRENT_MODE_BIT | CMD_USB_2_3_SEL_BIT |
+ CMD_USB_1_5_AC_CTRL_MASK;
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set charging mode rc = %d\n", rc);
+ return rc;
+ }
+
+ /* unset the suspend bit here */
+ smb1351_usb_suspend(chip, CURRENT, false);
+ /*
+ * AICL could be rerun if set a larger ICL
+ * and force a USB500 then USBAC mode.
+ */
+ if ((icl_result_ma < current_ma) && (reg & CMD_USB_HC_MODE)) {
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, mask,
+ CMD_USB_2_MODE | CMD_USB_500_MODE |
+ CMD_INPUT_CURRENT_MODE_CMD);
+ if (rc) {
+ pr_err("Set USB500 for AICL rerun failed, rc=%d\n", rc);
+ return rc;
+ }
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, mask, reg);
+ if (rc) {
+ pr_err("Set USBAC for AICL rerun failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int smb1351_fastchg_current_set(struct smb1351_charger *chip,
+ unsigned int fastchg_current)
+{
+ int i, rc;
+ bool is_pre_chg = false;
+
+ mutex_lock(&chip->fcc_lock);
+ if (fastchg_current < SMB1351_CHG_PRE_MIN_MA)
+ fastchg_current = SMB1351_CHG_PRE_MIN_MA;
+
+ if (fastchg_current > SMB1351_CHG_FAST_MAX_MA)
+ fastchg_current = SMB1351_CHG_FAST_MAX_MA;
+
+ pr_debug("set fastchg current mA=%d\n", fastchg_current);
+
+ /*
+ * fast chg current could not support less than 1000mA
+ * use pre chg to instead for the parallel charging
+ */
+ if (fastchg_current < SMB1351_CHG_FAST_MIN_MA) {
+ is_pre_chg = true;
+ pr_debug("is_pre_chg true, current is %d\n", fastchg_current);
+ }
+
+ if (is_pre_chg) {
+ /* set prechg current */
+ for (i = ARRAY_SIZE(pre_chg_current) - 1; i >= 0; i--) {
+ if (pre_chg_current[i] <= fastchg_current)
+ break;
+ }
+ if (i < 0)
+ i = 0;
+ chip->fastchg_current_max_ma = pre_chg_current[i];
+ pr_debug("prechg setting %02x\n", i);
+
+ i = i << SMB1351_CHG_PRE_SHIFT;
+
+ rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG,
+ PRECHG_CURRENT_MASK, i);
+ if (rc)
+ pr_err("Couldn't write CHG_OTH_CURRENT_CTRL_REG rc=%d\n",
+ rc);
+
+ rc = smb1351_masked_write(chip, VARIOUS_FUNC_2_REG,
+ PRECHG_TO_FASTCHG_BIT, PRECHG_TO_FASTCHG_BIT);
+ if (rc)
+ pr_err("Write VARIOUS_FUNC_2_REG failed, rc=%d\n", rc);
+ } else {
+ if (chip->version == SMB_UNKNOWN) {
+ rc = -EINVAL;
+ goto done;
+ }
+
+ /* SMB1350 supports FCC upto 2600 mA */
+ if (chip->version == SMB1350 && fastchg_current > 2600)
+ fastchg_current = 2600;
+
+ /* set fastchg current */
+ for (i = ARRAY_SIZE(fast_chg_current) - 1; i >= 0; i--) {
+ if (fast_chg_current[i] <= fastchg_current)
+ break;
+ }
+ if (i < 0)
+ i = 0;
+ chip->fastchg_current_max_ma = fast_chg_current[i];
+
+ i = i << SMB1351_CHG_FAST_SHIFT;
+ pr_debug("fastchg limit=%d setting %02x\n",
+ chip->fastchg_current_max_ma, i);
+
+ /* make sure pre chg mode is disabled */
+ rc = smb1351_masked_write(chip, VARIOUS_FUNC_2_REG,
+ PRECHG_TO_FASTCHG_BIT, 0);
+ if (rc)
+ pr_err("Couldn't write VARIOUS_FUNC_2_REG rc=%d\n", rc);
+
+ rc = smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG,
+ FAST_CHG_CURRENT_MASK, i);
+ if (rc)
+ pr_err("Write CURRENT_CTRL_REG failed, rc=%d\n", rc);
+ }
+
+done:
+ mutex_unlock(&chip->fcc_lock);
+ return rc;
+}
+
+static int smb1351_fastchg_hv_current_set(struct smb1351_charger *chip,
+ unsigned int fastchg_current)
+{
+ int rc;
+
+ pr_info("set hv fastchg current mA=%d-target fast max %d\n", fastchg_current, chip->target_fastchg_current_max_ma);
+
+ /*
+ * fast chg current could not support less than 1000mA
+ * use pre chg to instead for the parallel charging
+ */
+
+#ifdef APP_CONTROL
+ if(fastchg_current>2000) {
+ pr_info("current(%d)>2000, using parallel charger\n", fastchg_current);
+ chip->target_fastchg_current_max_ma = fastchg_current;
+ rc = 0;
+
+ schedule_delayed_work(&chip->parallel.parallel_work, (HZ*1));
+ }else {
+ smb1351_parallel_charger_disable_slave(chip);
+ smb1351_fastchg_current_set(chip, fastchg_current);
+ }
+#else
+ rc = 0;
+#endif
+
+ return rc;
+}
+
+#define charger_enabled(chip, sleep_time) {smb1351_set_hv_handshake_current(chip,500);msleep(sleep_time);}
+#define charger_disabled(chip, sleep_time) {smb1351_set_hv_handshake_current(chip,100);msleep(sleep_time);}
+int smb1351_hv_handshake(struct smb1351_charger *chip)
+{
+ int i;
+ int ret = 0;
+ int rc = 0;
+
+ pr_info("%s\n", __func__);
+
+ smb1351_fastchg_current_set(chip, 1000);
+ smb1351_set_usb_chg_current(chip, 500);
+
+ //mutex_lock(&chip->handshake_lock);
+ for(i=0;i<3;i++) {
+ charger_enabled(chip, 1000);
+ charger_disabled(chip, 100);
+ charger_enabled(chip, 100);
+ charger_disabled(chip, 100);
+
+ charger_enabled(chip, 100);
+ charger_disabled(chip, 100);
+ charger_enabled(chip, 300);
+ charger_disabled(chip, 100);
+ charger_enabled(chip, 300);
+ charger_disabled(chip, 100);
+ charger_enabled(chip, 300);
+ charger_disabled(chip, 100);
+ charger_enabled(chip, 500);
+ charger_disabled(chip, 150);
+ //charger_enabled(chip, 500);
+ //msleep(100);
+ }
+ //mutex_unlock(&chip->handshake_lock);
+
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, BIT(0), CMD_USB_HC_MODE);
+ if (rc) {
+ pr_err("Set 0x31 0 failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ return ret;
+}
+
+enum TEMP_TYPE {
+ BOARD_TEMP = 0,
+ CHARGE_TEMP,
+};
+
+static int smb1351_get_board_temp_voltage(struct smb1351_charger *chip, enum TEMP_TYPE type)
+{
+ int rc = 0;
+ struct qpnp_vadc_result results;
+ int chan;
+
+ if((chip == NULL)||(chip->chg_pm_vadc_dev == NULL))
+ return -1;
+
+ switch (type) {
+ case BOARD_TEMP:
+ chan = P_MUX4_1_1;
+ break;
+ case CHARGE_TEMP:
+ chan = P_MUX2_1_1;
+ break;
+ default:
+ pr_err("get ad type error %d\n", type);
+ return -2;
+ }
+
+ rc = qpnp_vadc_read(chip->chg_pm_vadc_dev, chan, &results);
+ if (rc) {
+ pr_err("Unable to read tboard rc=%d\n", rc);
+ return -1;
+ }
+
+ //pr_debug("get tboard ret2=%lld(%d)\n", results.physical, (int)(results.physical));
+
+ return (int)results.physical;
+}
+
+static int smb1351_get_board_temp(struct smb1351_charger *chip, enum TEMP_TYPE type)
+{
+ int i;
+ const int cnt_num = 6;
+ int sum, avr;
+ int max_val, min_val;
+ int ret;
+
+ sum = 0;
+ //get
+ for(i=0;i<cnt_num;i++) {
+ ret = smb1351_get_board_temp_voltage(chip, type);
+ if (ret<0) {
+ pr_err("Unable to read batt temperature rc=%d\n", ret);
+ return -1;
+ }
+
+ //pr_debug(" cnt %d vol = %d\n", i,ret);
+ if(i==0) {
+ max_val = ret;
+ min_val = ret;
+ } else {
+ if(ret>max_val)
+ max_val = ret;
+ else if(ret<min_val)
+ min_val = ret;
+ }
+ sum += ret;
+
+ msleep(1);
+ }
+
+ //sum
+ sum = sum - min_val - max_val;
+ avr = sum /(cnt_num-2);
+
+ return avr;
+}
+
+static int smb1351_get_vbus_voltage(struct smb1351_charger *chip)
+{
+ int rc = 0;
+ struct qpnp_vadc_result results;
+
+ if((chip == NULL)||(chip->chg_pmi_vadc_dev == NULL))
+ return -1;
+
+ rc = qpnp_vadc_read(chip->chg_pmi_vadc_dev, P_MUX1_1_1, &results);
+ if (rc) {
+ pr_err("Unable to read tboard rc=%d\n", rc);
+ return -1;
+ }
+
+ //pr_debug("get vbus ret2=%lld(%d)\n", results.physical, (int)(results.physical));
+
+ return ((int)results.physical) * VBUS_DIV_FACTOR / 3;
+}
+
+#define MIN_FLOAT_MV 3500
+#define MAX_FLOAT_MV 4500
+#define VFLOAT_STEP_MV 20
+static int smb1351_float_voltage_set(struct smb1351_charger *chip,
+ int vfloat_mv)
+{
+ u8 temp;
+
+ if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) {
+ pr_err("bad float voltage mv =%d asked to set\n", vfloat_mv);
+ return -EINVAL;
+ }
+
+ temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV;
+
+ return smb1351_masked_write(chip, VFLOAT_REG, VFLOAT_MASK, temp);
+}
+
+static int smb1351_chg_set_appropriate_battery_current(
+ struct smb1351_charger *chip)
+{
+ int rc;
+ unsigned int current_max = chip->target_fastchg_current_max_ma;
+
+ if (chip->batt_cool)
+ current_max = min(current_max, chip->batt_cool_ma);
+ if (chip->batt_warm)
+ current_max = min(current_max, chip->batt_warm_ma);
+
+ pr_debug("setting %dmA", current_max);
+
+ rc = smb1351_fastchg_current_set(chip, current_max);
+ if (rc)
+ pr_err("Couldn't set charging current rc = %d\n", rc);
+
+ return rc;
+}
+
+static int smb1351_chg_set_appropriate_vfloat(struct smb1351_charger *chip)
+{
+ int rc;
+ unsigned int vfloat = chip->vfloat_mv;
+
+ if (chip->batt_cool)
+ vfloat = min(vfloat, chip->batt_cool_mv);
+ if (chip->batt_warm)
+ vfloat = min(vfloat, chip->batt_warm_mv);
+
+ pr_debug("setting %dmV\n", vfloat);
+
+ rc = smb1351_float_voltage_set(chip, vfloat);
+ if (rc)
+ pr_err("Couldn't set float voltage rc = %d\n", rc);
+
+ return rc;
+}
+
+static int smb1351_iterm_set(struct smb1351_charger *chip, int iterm_ma)
+{
+ int rc;
+ u8 reg;
+
+ if (iterm_ma <= 200)
+ reg = CHG_ITERM_200MA;
+ else if (iterm_ma <= 300)
+ reg = CHG_ITERM_300MA;
+ else if (iterm_ma <= 400)
+ reg = CHG_ITERM_400MA;
+ else if (iterm_ma <= 500)
+ reg = CHG_ITERM_500MA;
+ else if (iterm_ma <= 600)
+ reg = CHG_ITERM_600MA;
+ else
+ reg = CHG_ITERM_700MA;
+
+ rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG,
+ ITERM_MASK, reg);
+ if (rc) {
+ pr_err("Couldn't set iterm rc = %d\n", rc);
+ return rc;
+ }
+ /* enable the iterm */
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ ITERM_EN_BIT, ITERM_ENABLE);
+ if (rc) {
+ pr_err("Couldn't enable iterm rc = %d\n", rc);
+ return rc;
+ }
+ return 0;
+}
+
+#define N_TYPE_BIT 8
+static int smb1351_get_usb_supply_type(struct smb1351_charger *chip,
+ enum power_supply_type *type)
+{
+ int rc, index;
+ u8 reg;
+ static const char * const usb_ps_type_str[] = {
+ "ACA_DOCK",
+ "ACA_C",
+ "ACA_B",
+ "ACA_A",
+ "SDP",
+ "OTHER",
+ "DCP",
+ "CDP",
+ };
+
+ rc = smb1351_read_reg(chip, STATUS_5_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read STATUS_5 rc = %d\n", rc);
+ return rc;
+ }
+
+ switch (reg) {
+ case STATUS_PORT_ACA_DOCK:
+ case STATUS_PORT_ACA_C:
+ case STATUS_PORT_ACA_B:
+ case STATUS_PORT_ACA_A:
+ *type = POWER_SUPPLY_TYPE_USB_ACA;
+ break;
+ case STATUS_PORT_CDP:
+ *type = POWER_SUPPLY_TYPE_USB_CDP;
+ break;
+ case STATUS_PORT_DCP:
+ *type = POWER_SUPPLY_TYPE_USB_DCP;
+ break;
+ case STATUS_PORT_SDP:
+ *type = POWER_SUPPLY_TYPE_USB;
+ break;
+ case STATUS_PORT_OTHER:
+ *type = POWER_SUPPLY_TYPE_USB_DCP;
+ break;
+ default:
+ *type = POWER_SUPPLY_TYPE_UNKNOWN;
+ break;
+ }
+
+ if (chip->is_factory_mode)
+ *type = POWER_SUPPLY_TYPE_USB;
+
+ if (*type == POWER_SUPPLY_TYPE_UNKNOWN) {
+ pr_err("Can't get correct power supply type!");
+ } else {
+ index = find_first_bit((unsigned long *)&reg, N_TYPE_BIT);
+ pr_debug("STATUS_5_REG(0x3B)=%x, USB type = %s\n",
+ reg, usb_ps_type_str[index]);
+ }
+
+ return rc;
+}
+
+static int smb1351_set_bms_property(struct smb1351_charger *chip,
+ enum power_supply_property prop, int value)
+{
+ int rc;
+ union power_supply_propval propval = {0, };
+
+ if (chip->bms_psy_name && !chip->bms_psy)
+ chip->bms_psy =
+ power_supply_get_by_name((char *)chip->bms_psy_name);
+
+ if (IS_ERR_OR_NULL(chip->bms_psy)) {
+ pr_debug("BMS(fg) power supply not present\n");
+ return -ENODEV;
+ }
+
+ propval.intval = value;
+ rc = chip->bms_psy->set_property(chip->bms_psy, prop, &propval);
+ if (rc) {
+ pr_err("Set BMS property %d failed, rc=%d\n", prop, rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smb1351_get_bms_property(struct smb1351_charger *chip,
+ enum power_supply_property prop, int *val)
+{
+ int rc;
+ union power_supply_propval propval = {0, };
+
+ if (chip->bms_psy_name && !chip->bms_psy)
+ chip->bms_psy =
+ power_supply_get_by_name((char *)chip->bms_psy_name);
+
+ if (IS_ERR_OR_NULL(chip->bms_psy)) {
+ pr_debug("BMS(fg) power supply not present\n");
+ return -ENODEV;
+ }
+
+ rc = chip->bms_psy->get_property(chip->bms_psy, prop, &propval);
+ if (rc) {
+ pr_err("Set BMS property %d failed, rc=%d\n", prop, rc);
+ return rc;
+ }
+ *val = propval.intval;
+
+ return rc;
+}
+
+int smb1351_set_battery_property(struct smb1351_charger *chip,
+ enum power_supply_property prop, int value)
+{
+ int rc;
+ union power_supply_propval propval = {0, };
+
+ propval.intval = value;
+ rc = chip->batt_psy.set_property(&chip->batt_psy, prop, &propval);
+ if (rc) {
+ pr_err("Set batter property %d failed, rc=%d\n", prop, rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smb1351_set_ext_charger_property(struct smb1351_charger *chip,
+ enum power_supply_property prop, int value)
+{
+ int rc;
+ union power_supply_propval propval = {0, };
+
+ propval.intval = value;
+ rc = chip->ext_charger_psy.set_property(&chip->ext_charger_psy, prop, &propval);
+ if (rc) {
+ pr_err("Set ext-charger property %d failed, rc=%d\n", prop, rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smb1351_chg_otg_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smb1351_charger *chip = rdev_get_drvdata(rdev);
+
+#ifdef LENOVO_OTG_USB_SHORT
+ chip->otg_usb_short_state = false;
+ chip->is_otg_on = true;
+#endif
+
+ rc = smb1351_masked_write(chip, OTG_TLIM_CTRL_REG, OTG_OC_LIMIT_MASK,
+ 0x00);
+ if (rc)
+ pr_err("Couldn't set OTG_OC_LIMIT rc=%d\n", rc);
+
+ rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT,
+ CMD_OTG_EN_BIT);
+ if (rc)
+ pr_err("Couldn't enable OTG mode rc=%d\n", rc);
+ return rc;
+}
+
+static int smb1351_chg_otg_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smb1351_charger *chip = rdev_get_drvdata(rdev);
+
+#ifdef LENOVO_OTG_USB_SHORT
+ {
+ int pre_state = chip->otg_usb_short_state;
+
+ gpio_set_value(chip->otg_usb_short_gpio, 0);
+ chip->otg_usb_short_state = false;
+ chip->is_otg_on = false;
+ pr_debug("smb1351_otg_short_config = %d", chip->otg_usb_short_state);
+ if (pre_state) {
+ pr_debug("update otg usb short state\n");
+ power_supply_changed(&chip->batt_psy);
+ }
+ }
+#endif
+
+ rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT, 0);
+ if (rc)
+ pr_err("Couldn't disable OTG mode rc=%d\n", rc);
+
+ return rc;
+}
+
+static int smb1351_chg_otg_regulator_is_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ u8 reg = 0;
+ struct smb1351_charger *chip = rdev_get_drvdata(rdev);
+
+ rc = smb1351_read_reg(chip, CMD_CHG_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read OTG enable bit rc=%d\n", rc);
+ return rc;
+ }
+
+ return (reg & CMD_OTG_EN_BIT) ? 1 : 0;
+}
+
+struct regulator_ops smb1351_chg_otg_reg_ops = {
+ .enable = smb1351_chg_otg_regulator_enable,
+ .disable = smb1351_chg_otg_regulator_disable,
+ .is_enabled = smb1351_chg_otg_regulator_is_enable,
+};
+
+static int smb1351_regulator_init(struct smb1351_charger *chip)
+{
+ int rc = 0;
+ struct regulator_init_data *init_data;
+ struct regulator_config cfg = {};
+
+ init_data = of_get_regulator_init_data(chip->dev, chip->dev->of_node);
+ if (!init_data) {
+ pr_err("Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ if (init_data->constraints.name) {
+ chip->otg_vreg.rdesc.owner = THIS_MODULE;
+ chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
+ chip->otg_vreg.rdesc.ops = &smb1351_chg_otg_reg_ops;
+ chip->otg_vreg.rdesc.name = init_data->constraints.name;
+
+ cfg.dev = chip->dev;
+ cfg.init_data = init_data;
+ cfg.driver_data = chip;
+ cfg.of_node = chip->dev->of_node;
+
+ init_data->constraints.valid_ops_mask
+ |= REGULATOR_CHANGE_STATUS;
+
+ chip->otg_vreg.rdev = regulator_register(
+ &chip->otg_vreg.rdesc, &cfg);
+ if (IS_ERR(chip->otg_vreg.rdev)) {
+ rc = PTR_ERR(chip->otg_vreg.rdev);
+ chip->otg_vreg.rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ pr_err("OTG reg failed, rc=%d\n", rc);
+ }
+ }
+
+ return rc;
+}
+
+static int smb_chip_get_version(struct smb1351_charger *chip)
+{
+ u8 ver;
+ int rc = 0;
+
+ if (chip->version == SMB_UNKNOWN) {
+ rc = smb1351_read_reg(chip, VERSION_REG, &ver);
+ if (rc) {
+ pr_err("Couldn't read version rc=%d\n", rc);
+ return rc;
+ }
+
+ /* If bit 1 is set, it is SMB1350 */
+ if (ver & VERSION_MASK)
+ chip->version = SMB1350;
+ else
+ chip->version = SMB1351;
+ }
+
+ return rc;
+}
+
+static int smb1351_disable_hw_jieta(struct smb1351_charger *chip, bool disable)
+{
+ int rc = 0;
+
+ rc = smb1351_masked_write(chip, THERM_A_CTRL_REG,
+ THERM_MONITOR_BIT, disable?THERM_MONITOR_BIT:THERM_MONITOR_EN);
+ if (rc) {
+ pr_err("Couldn't set THERM_A_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+#define INIT_FG_RETRY_MS 1000
+static int smb1351_hw_init(struct smb1351_charger *chip)
+{
+ int rc = 0;
+ u8 reg = 0, mask = 0;
+
+ /* configure smb_pinctrl to enable irqs */
+ if (chip->pinctrl_state_name) {
+ chip->smb_pinctrl = pinctrl_get_select(chip->dev,
+ chip->pinctrl_state_name);
+ if (IS_ERR(chip->smb_pinctrl)) {
+ pr_err("Could not get/set %s pinctrl state rc = %ld\n",
+ chip->pinctrl_state_name,
+ PTR_ERR(chip->smb_pinctrl));
+ return PTR_ERR(chip->smb_pinctrl);
+ }
+ }
+
+ /*
+ * If the charger is pre-configured for autonomous operation,
+ * do not apply additional settings
+ */
+ if (chip->chg_autonomous_mode) {
+ pr_debug("Charger configured for autonomous mode\n");
+ return 0;
+ }
+
+ rc = smb_chip_get_version(chip);
+ if (rc) {
+ pr_err("Couldn't get version rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = smb1351_enable_volatile_writes(chip);
+ if (rc) {
+ pr_err("Couldn't configure volatile writes rc=%d\n", rc);
+ return rc;
+ }
+
+ /* setup battery missing source */
+ reg = BATT_MISSING_THERM_PIN_SOURCE_BIT;
+ mask = BATT_MISSING_THERM_PIN_SOURCE_BIT;
+ rc = smb1351_masked_write(chip, HVDCP_BATT_MISSING_CTRL_REG,
+ mask, reg);
+ if (rc) {
+ pr_err("Couldn't set HVDCP_BATT_MISSING_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+
+ /* disable hvdcp */
+ mask = HVDCP_EN_BIT;
+ rc = smb1351_masked_write(chip, HVDCP_BATT_MISSING_CTRL_REG,
+ mask, 0);
+ if (rc) {
+ pr_err("Couldn't set HVDCP_BATT_MISSING_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+
+ /* setup defaults for CHG_PIN_EN_CTRL_REG */
+ reg = EN_BY_I2C_0_DISABLE | USBCS_CTRL_BY_I2C | CHG_ERR_BIT |
+ APSD_DONE_BIT | LED_BLINK_FUNC_BIT;
+ mask = EN_PIN_CTRL_MASK | USBCS_CTRL_BIT | CHG_ERR_BIT |
+ APSD_DONE_BIT | LED_BLINK_FUNC_BIT;
+ rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set CHG_PIN_EN_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+ /* setup USB 2.0/3.0 detection and USB 500/100 command polarity */
+ reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0;
+ mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT;
+ rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+ /* setup USB suspend, AICL and APSD */
+ reg = SUSPEND_MODE_CTRL_BY_I2C | AICL_EN_BIT;
+ if (!chip->disable_apsd)
+ reg |= APSD_EN_BIT;
+ mask = SUSPEND_MODE_CTRL_BIT | AICL_EN_BIT | APSD_EN_BIT;
+ rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set VARIOUS_FUNC_REG rc=%d\n", rc);
+ return rc;
+ }
+ /* Fault and Status IRQ configuration */
+ reg = HOT_COLD_HARD_LIMIT_BIT | HOT_COLD_SOFT_LIMIT_BIT | OTG_OC_BIT
+ | INPUT_OVLO_BIT | INPUT_UVLO_BIT | AICL_DONE_FAIL_BIT;
+ rc = smb1351_write_reg(chip, FAULT_INT_CFG_REG, reg);
+ if (rc) {
+ pr_err("Couldn't set FAULT_INT_CFG_REG rc=%d\n", rc);
+ return rc;
+ }
+ reg = CHG_OR_PRECHG_TIMEOUT_BIT | RID_CHANGE_BIT | BATT_OVP_BIT |
+ FAST_TERM_TAPER_RECHG_INHIBIT_BIT | BATT_MISSING_BIT |
+ BATT_LOW_BIT;
+ rc = smb1351_write_reg(chip, STATUS_INT_CFG_REG, reg);
+ if (rc) {
+ pr_err("Couldn't set STATUS_INT_CFG_REG rc=%d\n", rc);
+ return rc;
+ }
+ /* setup THERM Monitor */
+ if (!chip->using_pmic_therm) {
+ rc = smb1351_masked_write(chip, THERM_A_CTRL_REG,
+ THERM_MONITOR_BIT, THERM_MONITOR_EN);
+ if (rc) {
+ pr_err("Couldn't set THERM_A_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /*disable hw jieta anyway*/
+ rc = smb1351_masked_write(chip, THERM_A_CTRL_REG,
+ THERM_MONITOR_BIT, THERM_MONITOR_BIT);
+ if (rc) {
+ pr_err("Couldn't set THERM_A_CTRL_REG rc=%d\n", rc);
+ return rc;
+ }
+
+ /* set the fast charge current limit */
+ rc = smb1351_fastchg_current_set(chip,
+ chip->target_fastchg_current_max_ma);
+ if (rc) {
+ pr_err("Couldn't set fastchg current rc=%d\n", rc);
+ return rc;
+ }
+
+ /* set the float voltage */
+ if (chip->vfloat_mv != -EINVAL) {
+ rc = smb1351_float_voltage_set(chip, chip->vfloat_mv);
+ if (rc) {
+ pr_err("Couldn't set float voltage rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* set iterm */
+ if (chip->iterm_ma != -EINVAL) {
+ if (chip->iterm_disabled) {
+ pr_err("Error: Both iterm_disabled and iterm_ma set\n");
+ return -EINVAL;
+ } else {
+ rc = smb1351_iterm_set(chip, chip->iterm_ma);
+ if (rc) {
+ pr_err("Couldn't set iterm rc = %d\n", rc);
+ return rc;
+ }
+ }
+ } else if (chip->iterm_disabled) {
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ ITERM_EN_BIT, ITERM_DISABLE);
+ if (rc) {
+ pr_err("Couldn't set iterm rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* set recharge-threshold */
+ if (chip->recharge_mv != -EINVAL) {
+ if (chip->recharge_disabled) {
+ pr_err("Error: Both recharge_disabled and recharge_mv set\n");
+ return -EINVAL;
+ } else {
+ reg = AUTO_RECHG_ENABLE;
+ if (chip->recharge_mv > 50)
+ reg |= AUTO_RECHG_TH_100MV;
+ else
+ reg |= AUTO_RECHG_TH_50MV;
+
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ AUTO_RECHG_BIT |
+ AUTO_RECHG_TH_BIT, reg);
+ if (rc) {
+ pr_err("Couldn't set rechg-cfg rc = %d\n", rc);
+ return rc;
+ }
+ }
+ } else if (chip->recharge_disabled ||
+ (chip->wa_flags & CHG_SOC_BASED_RESUME)) {
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ AUTO_RECHG_BIT,
+ AUTO_RECHG_DISABLE);
+ if (rc) {
+ pr_err("Couldn't disable auto-rechg rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* Update switching frequency based on device tree entry */
+ if (chip->switch_freq != -EINVAL) {
+ pr_debug("set switch_freq\n");
+ rc = smb1351_masked_write(chip, OTG_TLIM_CTRL_REG,
+ SWITCH_FREQ_MASK,
+ (chip->switch_freq << SWITCH_FREQ_SHIFT));
+ if (rc) {
+ pr_err("Set switching frequency failed, r=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /* set STAT pin behavior: active low, indicate charging status */
+ rc = smb1351_masked_write(chip, CHG_STAT_TIMERS_CTRL_REG,
+ STAT_OUTPUT_POLARITY_BIT | STAT_OUTPUT_MODE_BIT
+ | STAT_OUTPUT_CTRL_BIT, 0);
+ if (rc) {
+ pr_err("Set STAT pin polarity failed, r=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->parallel.avail) {
+ rc = smb1351_masked_write(chip, PON_OPTIONS_REG,
+ SYSOK_INOK_POLARITY_BIT | SYSOK_OPTIONS_MASK,
+ SYSOK_INOK_POLARITY_INVERT | SYSOK_INOK_OPTION1);
+ if (rc) {
+ pr_err("Program PON option failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (chip->use_external_fg) {
+ pr_info("using externel fg\n");
+ rc = smb1351_set_bms_property(chip,
+ POWER_SUPPLY_PROP_IGNORE_FALSE_NEGATIVE_ISENSE, 0);
+ if (rc) {
+ pr_debug("set IGNORE_FALSE_NEGATIVE_ISENSE failed, rc=%d\n",
+ rc);
+ /* start a delay worker to do it again if failed */
+ schedule_delayed_work(&chip->init_fg_work,
+ msecs_to_jiffies(INIT_FG_RETRY_MS));
+ } else if (chip->fg_notify_jeita) {
+ rc = smb1351_set_bms_property(chip,
+ POWER_SUPPLY_PROP_ENABLE_JEITA_DETECTION, 1);
+ if (rc) {
+ pr_err("Set ENABLE_JEITA_DETECTION failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ if (chip->is_factory_mode) {
+ pr_info("set chg as factory mode\n");
+
+// smb1351_set_usb_chg_current(chip, 3000);
+// smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, BIT(0), CMD_USB_HC_MODE);
+ rc = smb1351_battchg_disable(chip, USER, 1);
+ if (rc)
+ pr_err("Couldn't %s charging rc = %d\n",
+ chip->usb_suspended_status ? "disable" : "enable", rc);
+ } else {
+ /* enable/disable charging by suspending usb */
+ rc = smb1351_usb_suspend(chip, USER, chip->usb_suspended_status);
+ if (rc) {
+ pr_err("Unable to %s USB input. rc=%d\n",
+ chip->usb_suspended_status ? "suspend" : "enable", rc);
+ return rc;
+ }
+
+ rc = smb1351_battchg_disable(chip, USER, chip->usb_suspended_status);
+ if (rc)
+ pr_err("Couldn't %s charging rc = %d\n",
+ chip->usb_suspended_status ? "disable" : "enable", rc);
+
+ rc = smb1351_masked_write(chip, FLEXCHARGER_REG, 0xff, 0x47);
+ if (rc)
+ pr_err("FLEXCHARGER_REG rc = %d\n",
+ rc);
+ rc = smb1351_masked_write(chip, OTG_MODE_POWER_OPTIONS_REG, 0xff, 0x9c);
+ if (rc)
+ pr_err("OTG_MODE_POWER_OPTIONS_REG rc = %d\n",
+ rc);
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG, AFCV_MASK, 0);
+ if (rc)
+ pr_err("CHG_CTRL_REG rc = %d\n",
+ rc);
+ rc = smb1351_masked_write(chip, WDOG_SAFETY_TIMER_CTRL_REG, AICL_FAIL_OPTION_BIT, AICL_FAIL_OPTION_BIT);
+ if (rc)
+ pr_err("WDOG_SAFETY_TIMER_CTRL_REG rc = %d\n",
+ rc);
+ }
+
+ return rc;
+}
+
+static enum power_supply_property smb1351_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION,
+ POWER_SUPPLY_PROP_HI_POWER,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+};
+
+static int smb1351_get_prop_batt_status(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg = 0;
+
+ if (chip->batt_full)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ rc = smb1351_read_reg(chip, STATUS_4_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read STATUS_4 rc = %d\n", rc);
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ pr_debug("STATUS_4_REG(0x3A)=%x\n", reg);
+
+ if (reg & STATUS_HOLD_OFF_BIT)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ if (reg & STATUS_CHG_MASK)
+ return POWER_SUPPLY_STATUS_CHARGING;
+
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+}
+
+static int smb1351_get_prop_batt_present(struct smb1351_charger *chip)
+{
+ return !chip->battery_missing;
+}
+
+static int smb1351_get_prop_batt_capacity(struct smb1351_charger *chip)
+{
+ int rc = 0, soc = 0;
+
+ if (chip->fake_battery_soc >= 0)
+ return chip->fake_battery_soc;
+
+ rc = smb1351_get_bms_property(chip, POWER_SUPPLY_PROP_CAPACITY, &soc);
+ if (rc) {
+ pr_debug("Get capacity from BMS failed, rc=%d\n", rc);
+ return DEFAULT_BATT_CAPACITY;
+ }
+ pr_debug("SoC = %d\n", soc);
+
+ return soc;
+}
+
+static int smb1351_get_prop_batt_voltage(struct smb1351_charger *chip)
+{
+ union power_supply_propval ret = {0, };
+
+ if (chip->fake_battery_soc >= 0)
+ return chip->fake_battery_soc;
+
+ if (chip->bms_psy) {
+ chip->bms_psy->get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, &ret);
+ return ret.intval;
+ }
+ pr_debug("return DEFAULT_BATT_VOLTAGE\n");
+ return DEFAULT_BATT_VOLTAGE;
+}
+
+static int smb1351_get_prop_batt_temp(struct smb1351_charger *chip)
+{
+ int rc = 0, temp = 0;
+ struct qpnp_vadc_result results;
+
+ rc = smb1351_get_bms_property(chip, POWER_SUPPLY_PROP_TEMP, &temp);
+ if (rc) {
+ temp = DEFAULT_BATT_TEMP;
+ pr_debug("Get tempertory from BMS failed, rc=%d\n", rc);
+ }
+ if (chip->vadc_dev) {
+ rc = qpnp_vadc_read(chip->vadc_dev,
+ LR_MUX1_BATT_THERM, &results);
+ if (rc)
+ pr_debug("Unable to read adc batt temp rc=%d\n", rc);
+ else
+ temp = (int)results.physical;
+ }
+ pr_debug("temperature: %d\n", temp);
+
+ if ( chip->temp_debug_flag)
+ temp = chip->batt_temp;
+
+ return temp;
+}
+
+static int smb1351_get_prop_charge_type(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg = 0;
+
+ rc = smb1351_read_reg(chip, STATUS_4_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read STATUS_4 rc = %d\n", rc);
+ return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+
+ pr_debug("STATUS_4_REG(0x3A)=%x\n", reg);
+
+ reg &= STATUS_CHG_MASK;
+
+ if (reg == STATUS_FAST_CHARGING)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else if (reg == STATUS_TAPER_CHARGING)
+ return POWER_SUPPLY_CHARGE_TYPE_TAPER;
+ else if (reg == STATUS_PRE_CHARGING)
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ else
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+static int smb1351_get_prop_batt_health(struct smb1351_charger *chip)
+{
+ union power_supply_propval ret = {0, };
+
+ if (chip->batt_hot)
+ ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (chip->batt_cold)
+ ret.intval = POWER_SUPPLY_HEALTH_COLD;
+ else if (chip->batt_warm) {
+ if (chip->batt_full) {
+ ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ } else {
+ ret.intval = POWER_SUPPLY_HEALTH_GOOD;
+ }
+ pr_info("batt_full %d ret %d\n", chip->batt_full, ret.intval);
+ } else if (chip->batt_cool)
+ ret.intval = POWER_SUPPLY_HEALTH_GOOD;
+ else
+ ret.intval = POWER_SUPPLY_HEALTH_GOOD;
+
+ return ret.intval;
+}
+
+static int smb1351_get_usbid_status(struct smb1351_charger *chip, bool *gnd)
+{
+ int rc;
+ u8 regval;
+
+ rc = smb1351_read_reg(chip, STATUS_6_REG, &regval);
+ if (rc) {
+ pr_err("Read STATUS_6 failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ *gnd = !regval;
+
+ return rc;
+}
+
+static struct power_supply *smb1351_get_parallel_slave(
+ struct smb1351_charger *chip)
+{
+ if (!chip->parallel.avail)
+ return NULL;
+ if (chip->parallel.psy)
+ return chip->parallel.psy;
+
+ chip->parallel.psy = power_supply_get_by_name(PSY_PARALLEL_NAME);
+ if (!chip->parallel.psy)
+ pr_debug("parallel slave not found\n");
+
+ return chip->parallel.psy;
+}
+/*
+ * Get the mininum parallel charging current threshold according to
+ * the voltage provided on the charger adapter
+ */
+static int smb1351_get_min_parallel_current_ma(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 value;
+
+ rc = smb1351_read_reg(chip, STATUS_1_REG, &value);
+ if (rc) {
+ pr_err("read STATUS_7_REG failed, rc = %d\n", rc);
+ return rc;
+ }
+ switch (value & STATUS_INPUT_RANGE_MASK) {
+ case STATUS_INPUT_RANGE_5V:
+ case STATUS_INPUT_RANGE_5V_9V:
+ rc = chip->parallel.min_current_thr_ma;
+ break;
+ case STATUS_INPUT_RANGE_9V:
+ rc = chip->parallel.min_9v_current_thr_ma;
+ break;
+ case STATUS_INPUT_RANGE_12V:
+ rc = chip->parallel.min_12v_current_thr_ma;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int smb1351_parallel_charger_disable_slave(
+ struct smb1351_charger *chip)
+{
+ int rc;
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+
+ pr_debug("Disable parallel slave!\n");
+
+ chip->parallel.total_icl_ma = 0;
+ chip->parallel.slave_icl_ma = 0;
+
+ /* Suspend USB and clear PRESENT to slave charger */
+ power_supply_set_current_limit(parallel_psy, SUSPEND_CURRENT_MA * 1000);
+ power_supply_set_present(parallel_psy, false);
+
+ /* Restore main charger FCC && ICL as the standalone */
+ rc = smb1351_chg_set_appropriate_battery_current(chip);
+ if (rc) {
+ pr_err("restore FCC to %d failed\n",
+ chip->target_fastchg_current_max_ma);
+ return rc;
+ }
+ rc = smb1351_set_usb_chg_current(chip, chip->usb_psy_ma);
+ if (rc) {
+ pr_err("restore ICL to %d failed\n", chip->usb_psy_ma);
+ return rc;
+ }
+ pr_debug("Restore ICL %dmA to the main charger\n", chip->usb_psy_ma);
+
+ return rc;
+}
+
+/*
+ * Enable slave, and split the total input current to main charger
+ * and the slave according to the percent.
+ */
+static int smb1351_parallel_charger_enable_slave(
+ struct smb1351_charger *chip,
+ int total_icl_ma)
+{
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+ union power_supply_propval pval = {0, };
+ int rc, slave_icl_ma, actual_slave_icl_ma, main_icl_ma;
+ int slave_fcc_ma, actual_slave_fcc_ma, main_fcc_ma;
+
+ if (!parallel_psy || !chip->parallel.slave_detected)
+ return 0;
+
+ if (chip->handshake_state) {
+ pr_err("in handshake state. Do not enable\n");
+ return 0;
+ }
+
+ pr_debug("Enable parallel slave!\n");
+ /*
+ * Set slave's float voltage a little high main charger
+ * to avoid slave trigger taper before the main charger
+ */
+ rc = power_supply_set_voltage_limit(parallel_psy, chip->vfloat_mv + 50);
+ if (rc) {
+ pr_err("Set slave VFLT failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ /* split the ICL for main and slave charger */
+ slave_icl_ma = total_icl_ma *
+ (100 - chip->parallel.main_chg_icl_percent) / 100;
+
+ /* set the allotted ICL for slave */
+ rc = power_supply_set_present(parallel_psy, true);
+ if (rc) {
+ pr_err("Set slave present failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = power_supply_set_current_limit(parallel_psy, slave_icl_ma * 1000);
+ if (rc) {
+ pr_err("Set slave ICL to %dmA failed, rc=%d\n",
+ slave_icl_ma, rc);
+ return rc;
+ }
+
+ chip->parallel.slave_icl_ma = slave_icl_ma;
+
+ /* get the ICL real set in HW */
+ parallel_psy->get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+ actual_slave_icl_ma = pval.intval / 1000;
+ pr_debug("parallel slave ICL, program %d, set %d\n",
+ slave_icl_ma, actual_slave_icl_ma);
+
+ /* set the allotted ICL for main charger */
+ main_icl_ma = max(total_icl_ma - actual_slave_icl_ma, 0);
+
+ rc = smb1351_set_usb_chg_current(chip, main_icl_ma);
+ if (rc) {
+ pr_err("set main charger ICL %d failed, rc=%d\n",
+ main_icl_ma, rc);
+ return rc;
+ }
+ pr_debug("ICL split, total %d: main %d, slave %d\n",
+ total_icl_ma, main_icl_ma, actual_slave_icl_ma);
+ /* split the FCC for main and slave charger */
+ slave_fcc_ma = chip->target_fastchg_current_max_ma *
+ (100 - chip->parallel.main_chg_fcc_percent) / 100;
+
+ /* set the allotted FCC to slave charger */
+ pval.intval = slave_fcc_ma * 1000;
+ parallel_psy->set_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ parallel_psy->get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ actual_slave_fcc_ma = pval.intval / 1000;
+
+ /* set the allotted FCC to main charger */
+ main_fcc_ma = chip->target_fastchg_current_max_ma
+ - actual_slave_fcc_ma;
+
+ rc = smb1351_fastchg_current_set(chip, main_fcc_ma);
+ if (rc) {
+ pr_err("Set main charger FCC %d failed, rc=%d\n",
+ main_fcc_ma, rc);
+ return rc;
+ }
+ pr_debug("FCC split, total %d: main %d, slave %d\n",
+ chip->target_fastchg_current_max_ma,
+ main_fcc_ma, actual_slave_fcc_ma);
+ return rc;
+}
+
+/*
+ * Check conditions if it's able to enable the slave for parallel
+ * charging, and also caculate the max current could be drawn from
+ * the charger adapter.
+ */
+static bool smb1351_attempt_enable_parallel_slave(
+ struct smb1351_charger *chip,
+ int *ret_total_icl_ma)
+{
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+ union power_supply_propval pval = {0, };
+ enum power_supply_type type;
+ int min_icl_thr_ma, main_current_icl_ma;
+ int slave_current_icl_ma, total_icl_ma;
+ int rc;
+
+ pr_debug("Check if it is OK to enable slave!\n");
+ if (!parallel_psy || !chip->parallel.slave_detected)
+ return false;
+ /*
+ * Report false to disable parallel charging if NOT in fastchg mode,
+ * only when battery is connected or parallel slave hasn't been
+ * enabled.
+ */
+ if (smb1351_get_prop_charge_type(chip) != POWER_SUPPLY_CHARGE_TYPE_FAST
+ && (smb1351_get_prop_batt_present(chip)
+ || chip->parallel.slave_icl_ma == 0)) {
+ pr_info("Not in fastchg mode, disable parallel!\n");
+ return false;
+ }
+ /* If battery health NOT good */
+ if (smb1351_get_prop_batt_health(chip) != POWER_SUPPLY_HEALTH_GOOD) {
+ pr_info("JEITA active, disable parallel!\n");
+ return false;
+ }
+ /* Only start parallel charger in DCP/HVDCP */
+ rc = smb1351_get_usb_supply_type(chip, &type);
+ if (rc) {
+ pr_err("Get USB power supply type failed, rc=%d\n", rc);
+ return false;
+ } else if (type == POWER_SUPPLY_TYPE_USB_CDP ||
+ type == POWER_SUPPLY_TYPE_USB) {
+ pr_debug("USB type %d, disable parallel!\n", type);
+ return false;
+ }
+
+ min_icl_thr_ma = smb1351_get_min_parallel_current_ma(chip);
+ if (min_icl_thr_ma <= 0) {
+ pr_info("Get min ICL threshold failed, disable parallel!\n");
+ return false;
+ }
+
+ rc = smb1351_get_usb_chg_current(chip, &main_current_icl_ma);
+ if (rc) {
+ pr_info("Get main charger ICL failed, skipping!\n");
+ return false;
+ }
+ pr_debug("main charger ICL = %d\n", main_current_icl_ma);
+ /*
+ * If parallel is not enabled, and main charger ICL is less than min
+ * parallel ICL threshold, disable parallel.
+ * Record the total ICL at 1st place.
+ */
+ if (chip->parallel.total_icl_ma == 0) {
+ if (main_current_icl_ma < min_icl_thr_ma) {
+ pr_info("Current ICL %d lower than threshold %d, skipping!\n",
+ main_current_icl_ma, min_icl_thr_ma);
+ return false;
+ }
+ chip->parallel.total_icl_ma = main_current_icl_ma;
+ pr_debug("Not in parallel charging previously, total_icl = %d\n",
+ main_current_icl_ma);
+ }
+
+ /* Check if parallel slave already draw some current */
+ parallel_psy->get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+ slave_current_icl_ma = pval.intval / 1000;
+ if (slave_current_icl_ma == SUSPEND_CURRENT_MA)
+ slave_current_icl_ma = 0;
+
+ pr_debug("slave charger ICL = %d\n", slave_current_icl_ma);
+ total_icl_ma = min(main_current_icl_ma + slave_current_icl_ma,
+ chip->usb_psy_ma);
+ pr_debug("calculated total ICL = %d\n", total_icl_ma);
+ /*
+ * If calculated total ICL is below than allowed_lowering,
+ * disable parallel
+ */
+ if (total_icl_ma <
+ chip->parallel.total_icl_ma -
+ chip->parallel.allowed_lowering_ma) {
+ pr_info("total ICL reduced to below the threshold: %d < (%d-%d)\n",
+ total_icl_ma, chip->parallel.total_icl_ma,
+ chip->parallel.allowed_lowering_ma);
+ return false;
+ }
+
+ *ret_total_icl_ma = total_icl_ma;
+
+ pr_debug("Final total ICL for parallel charging = %d\n", total_icl_ma);
+ return true;
+}
+
+#define MIN_SLAVER_FCC_MA 500
+#define SLAVE_CHG_FCC_REDUCTION_PERCENTAGE 75
+#define SLAVER_FCC_REDUCTION_MAX_TRIES 3
+static int smb1351_parallel_charger_taper_attempt(
+ struct smb1351_charger *chip)
+{
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+ union power_supply_propval pval = {0, };
+ int rc, slave_fcc_ma, tries = 0;
+ u8 value;
+
+ if (!parallel_psy || !chip->parallel.slave_detected)
+ return 0;
+
+ smb1351_stay_awake(&chip->smb1351_ws, PARALLEL_TAPER);
+
+try_again:
+ mutex_lock(&chip->parallel.lock);
+ if (chip->parallel.slave_icl_ma == 0) {
+ pr_debug("Parallel slave not enabled, skipping\n");
+ rc = 0;
+ goto done;
+ }
+ rc = parallel_psy->get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ if (rc) {
+ pr_err("Get slave FCC failed, rc=%d\n", rc);
+ goto done;
+ }
+
+ tries += 1;
+ slave_fcc_ma = pval.intval / 1000;
+
+ if (slave_fcc_ma < MIN_SLAVER_FCC_MA ||
+ tries > SLAVER_FCC_REDUCTION_MAX_TRIES) {
+ pr_debug("reduce slave FCC can't exist taper, disable slave\n");
+ smb1351_parallel_charger_disable_slave(chip);
+ goto done;
+ }
+
+ pval.intval = slave_fcc_ma * SLAVE_CHG_FCC_REDUCTION_PERCENTAGE / 100;
+ pval.intval *= 1000;
+ rc = parallel_psy->set_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ if (rc) {
+ pr_err("Set slave FCC %duA failed, rc=%d\n", pval.intval, rc);
+ goto done;
+ }
+
+ mutex_unlock(&chip->parallel.lock);
+ /*
+ * Sleep 100ms to make sure the charger has a chance to
+ * go back to fast charging state.
+ */
+ msleep(100);
+
+ mutex_lock(&chip->parallel.lock);
+ rc = smb1351_read_reg(chip, IRQ_C_REG, &value);
+ if (rc) {
+ pr_err("Read IRQ_C failed, rc=%d\n", rc);
+ goto done;
+ }
+
+ if (value & IRQ_TAPER_BIT) {
+ mutex_unlock(&chip->parallel.lock);
+ pr_debug("recheck: %d\n", tries);
+ goto try_again;
+ }
+done:
+ mutex_unlock(&chip->parallel.lock);
+ smb1351_relax(&chip->smb1351_ws, PARALLEL_TAPER);
+ return rc;
+}
+
+static void smb1351_parallel_work(struct work_struct *work)
+{
+ struct smb1351_charger *chip = container_of(work,
+ struct smb1351_charger, parallel.parallel_work.work);
+
+ int rc, pre_icl_ma, icl_ma, total_icl_ma = 0;
+
+ /* Get ICL twice with 500ms delays to check if ICl settled */
+ rc = smb1351_get_usb_chg_current(chip, &pre_icl_ma);
+ if (rc) {
+ pr_err("Get ICL result failed, rc = %d\n", rc);
+ return;
+ }
+
+ msleep(500);
+
+ rc = smb1351_get_usb_chg_current(chip, &icl_ma);
+ if (rc) {
+ pr_err("Get ICL result failed, rc = %d\n", rc);
+ return;
+ }
+
+ if ((pre_icl_ma > 0) && (pre_icl_ma == icl_ma)) {
+ pr_debug("AICL stable at %d\n", icl_ma);
+ } else {
+ pr_debug("AICL changed %d -> %d, double check\n",
+ pre_icl_ma, icl_ma);
+ goto recheck;
+ }
+ mutex_lock(&chip->parallel.lock);
+ if (smb1351_attempt_enable_parallel_slave(chip, &total_icl_ma)) {
+ pr_debug("Enable parallel slave, total_icl = %d!\n",
+ total_icl_ma);
+ smb1351_parallel_charger_enable_slave(chip, total_icl_ma);
+ } else {
+ if (chip->parallel.slave_icl_ma != 0) {
+ pr_debug("Disable parallel slave!\n");
+ smb1351_parallel_charger_disable_slave(chip);
+ }
+ }
+ mutex_unlock(&chip->parallel.lock);
+ smb1351_relax(&chip->smb1351_ws, PARALLEL_CHARGE);
+
+ return;
+recheck:
+ pr_debug("reschedule!\n");
+ schedule_delayed_work(&chip->parallel.parallel_work, 0);
+}
+
+static void smb1351_parallel_check_start(struct smb1351_charger *chip)
+{
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+
+ if (!parallel_psy || !chip->parallel.slave_detected)
+ return;
+
+ cancel_delayed_work_sync(&chip->parallel.parallel_work);
+ smb1351_stay_awake(&chip->smb1351_ws, PARALLEL_CHARGE);
+ schedule_delayed_work(&chip->parallel.parallel_work, 0);
+ pr_debug("parallel work scheduled\n");
+}
+
+static void smb1351_init_fg_work(struct work_struct *work)
+{
+ int rc;
+ struct smb1351_charger *chip = container_of(work,
+ struct smb1351_charger, init_fg_work.work);
+
+ rc = smb1351_set_bms_property(chip,
+ POWER_SUPPLY_PROP_IGNORE_FALSE_NEGATIVE_ISENSE, 0);
+ if (rc) {
+ pr_debug("Disable isense patch failed, rc=%d\n", rc);
+ goto recheck;
+ }
+
+ if (chip->fg_notify_jeita) {
+ rc = smb1351_set_bms_property(chip,
+ POWER_SUPPLY_PROP_ENABLE_JEITA_DETECTION, 1);
+ if (rc) {
+ pr_debug("Enable FG JEITA IRQ failed, rc=%d\n", rc);
+ goto recheck;
+ }
+ }
+ return;
+recheck:
+ /* start a delay worker to do it again if failed */
+ schedule_delayed_work(&chip->init_fg_work,
+ msecs_to_jiffies(INIT_FG_RETRY_MS));
+}
+
+static void smb1351_handle_jeita_from_fg(struct smb1351_charger *chip,
+ int health)
+{
+ int rc;
+#ifndef APP_CONTROL
+ bool disable;
+#endif
+
+ if (health <= POWER_SUPPLY_HEALTH_UNKNOWN
+ || health > POWER_SUPPLY_HEALTH_COOL) {
+ pr_err("health state not valid: %d\n", health);
+ return;
+ }
+
+ switch (health) {
+ case POWER_SUPPLY_HEALTH_GOOD:
+ chip->batt_hot = false;
+ chip->batt_cold = false;
+ chip->batt_warm = false;
+ chip->batt_cool = false;
+ break;
+ case POWER_SUPPLY_HEALTH_OVERHEAT:
+ chip->batt_hot = true;
+ chip->batt_cold = false;
+ chip->batt_warm = false;
+ chip->batt_cool = false;
+ break;
+ case POWER_SUPPLY_HEALTH_COLD:
+ chip->batt_hot = false;
+ chip->batt_cold = true;
+ chip->batt_cool = false;
+ chip->batt_warm = false;
+ break;
+ case POWER_SUPPLY_HEALTH_WARM:
+ chip->batt_hot = false;
+ chip->batt_cold = false;
+ chip->batt_warm = true;
+ chip->batt_cool = false;
+ break;
+ case POWER_SUPPLY_HEALTH_COOL:
+ chip->batt_hot = false;
+ chip->batt_cold = false;
+ chip->batt_warm = false;
+ chip->batt_cool = true;
+ break;
+ default:
+ pr_debug("health: %d, not a JEITA state\n", health);
+ break;
+ }
+ pr_debug("hot: %d, cold: %d, warm = %d, cool = %d\n",
+ chip->batt_hot, chip->batt_cold,
+ chip->batt_warm, chip->batt_cool);
+
+#ifndef APP_CONTROL
+ disable = (chip->batt_hot || chip->batt_cold) ? true : false;
+ rc = smb1351_battchg_disable(chip, THERMAL, disable);
+ if (rc) {
+ pr_err("%s charging for THERMAL failed, rc=%d\n",
+ disable ? "Disable" : "Enable", rc);
+ return;
+ }
+
+ rc = smb1351_chg_set_appropriate_battery_current(chip);
+ if (rc) {
+ pr_err("Set battery current failed\n");
+ return;
+ }
+#endif
+
+ rc = smb1351_chg_set_appropriate_vfloat(chip);
+ if (rc) {
+ pr_err("Set float voltage failed\n");
+ return;
+ }
+
+ smb1351_parallel_check_start(chip);
+
+ if (chip->use_external_fg) {
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_HEALTH,
+ smb1351_get_prop_batt_health(chip));
+ }
+
+ pr_debug("end!\n");
+}
+
+static void smb1351_handle_jeita_lenovo(struct smb1351_charger *chip,
+ int health)
+{
+ int rc;
+#ifndef APP_CONTROL
+ bool disable;
+#endif
+
+ if (health <= POWER_SUPPLY_HEALTH_UNKNOWN
+ || health > POWER_SUPPLY_HEALTH_COOL) {
+ pr_err("health state not valid: %d\n", health);
+ return;
+ }
+
+ switch (health) {
+ case POWER_SUPPLY_HEALTH_GOOD:
+ chip->batt_hot = false;
+ chip->batt_cold = false;
+ chip->batt_warm = false;
+ chip->batt_cool = false;
+ break;
+ case POWER_SUPPLY_HEALTH_OVERHEAT:
+ chip->batt_hot = true;
+ chip->batt_cold = false;
+ chip->batt_warm = false;
+ chip->batt_cool = false;
+ break;
+ case POWER_SUPPLY_HEALTH_COLD:
+ chip->batt_hot = false;
+ chip->batt_cold = true;
+ chip->batt_cool = false;
+ chip->batt_warm = false;
+ break;
+ case POWER_SUPPLY_HEALTH_WARM:
+ chip->batt_hot = false;
+ chip->batt_cold = false;
+ chip->batt_warm = true;
+ chip->batt_cool = false;
+ break;
+ case POWER_SUPPLY_HEALTH_COOL:
+ chip->batt_hot = false;
+ chip->batt_cold = false;
+ chip->batt_warm = false;
+ chip->batt_cool = true;
+ break;
+ default:
+ pr_debug("health: %d, not a JEITA state\n", health);
+ break;
+ }
+ pr_debug("hot: %d, cold: %d, warm = %d, cool = %d\n",
+ chip->batt_hot, chip->batt_cold,
+ chip->batt_warm, chip->batt_cool);
+
+#ifndef APP_CONTROL
+ disable = (chip->batt_hot || chip->batt_cold) ? true : false;
+ rc = smb1351_battchg_disable(chip, THERMAL, disable);
+ if (rc) {
+ pr_err("%s charging for THERMAL failed, rc=%d\n",
+ disable ? "Disable" : "Enable", rc);
+ return;
+ }
+
+ rc = smb1351_chg_set_appropriate_battery_current(chip);
+ if (rc) {
+ pr_err("Set battery current failed\n");
+ return;
+ }
+#endif
+
+ rc = smb1351_chg_set_appropriate_vfloat(chip);
+ if (rc) {
+ pr_err("Set float voltage failed\n");
+ return;
+ }
+
+ if (chip->use_external_fg) {
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_HEALTH,
+ smb1351_get_prop_batt_health(chip));
+ }
+
+ pr_debug("end!\n");
+}
+
+#define DEFAULT_CHARGE_FULL_DESIGN 5000000
+static int get_prop_charge_full_design(struct smb1351_charger *chip)
+{
+ int uah, rc;
+
+ rc = smb1351_get_bms_property(chip,
+ POWER_SUPPLY_PROP_CHARGE_FULL, &uah);
+ if (rc) {
+ pr_err("Couldn't get full design rc = %d\n", rc);
+ uah = DEFAULT_CHARGE_FULL_DESIGN;
+ }
+
+ if (uah == 0)
+ uah = DEFAULT_CHARGE_FULL_DESIGN;
+
+ return uah;
+}
+
+static int ibat_ocp_threshold_ua = 4500000;
+module_param(ibat_ocp_threshold_ua, int, 0644);
+
+#define UCONV 1000000LL
+#define MCONV 1000LL
+#define FLASH_V_THRESHOLD 3000000
+#define FLASH_VDIP_MARGIN 100000
+#define VPH_FLASH_VDIP (FLASH_V_THRESHOLD + FLASH_VDIP_MARGIN)
+#define BUCK_EFFICIENCY 800LL
+#define BATT_PARASITIC_R_UOHM 100000
+#define DEFAULT_VLED_MAX_UV 3500000
+static int smb1351_calc_max_flash_current(struct smb1351_charger *chip)
+{
+ int ocv_uv, esr_uohm, rbatt_uohm, ibat_now, rc;
+ int64_t ibat_flash_ua, avail_flash_ua, avail_flash_power_fw;
+ int64_t ibat_safe_ua, vin_flash_uv, vph_flash_uv;
+
+ rc = smb1351_get_bms_property(chip, POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ &ocv_uv);
+ if (rc) {
+ pr_err("Get OCV from BMS psy failed, rc=%d\n", rc);
+ return 0;
+ }
+
+ rc = smb1351_get_bms_property(chip, POWER_SUPPLY_PROP_RESISTANCE,
+ &esr_uohm);
+ if (rc) {
+ pr_err("Get ESR from BMS psy failed, rc=%d\n", rc);
+ return 0;
+ }
+
+ rc = msm_bcl_read(BCL_PARAM_CURRENT, &ibat_now);
+ if (rc) {
+ pr_err("BCL current read failed: %d\n", rc);
+ return 0;
+ }
+
+ rbatt_uohm = esr_uohm + BATT_PARASITIC_R_UOHM;
+
+ ibat_safe_ua = div_s64((ocv_uv - VPH_FLASH_VDIP) * UCONV,
+ rbatt_uohm);
+
+ if (ibat_safe_ua <= ibat_ocp_threshold_ua) {
+ /*
+ * If the calculated current is below the OCP threshold, then
+ * use it as the possible flash current.
+ */
+ ibat_flash_ua = ibat_safe_ua - ibat_now;
+ vph_flash_uv = VPH_FLASH_VDIP;
+ } else {
+ /*
+ * If the calculated current is above the OCP threshold, then
+ * use the ocp threshold instead.
+ *
+ * Any higher current will be tripping the battery OCP.
+ */
+ ibat_flash_ua = ibat_ocp_threshold_ua - ibat_now;
+ vph_flash_uv = ocv_uv - div64_s64((int64_t)rbatt_uohm
+ * ibat_ocp_threshold_ua, UCONV);
+ }
+ /* Calculate the input voltage of the flash module. */
+ vin_flash_uv = max((DEFAULT_VLED_MAX_UV + 500000LL),
+ div64_s64((vph_flash_uv * 1200), 1000));
+ /* Calculate the available power for the flash module. */
+ avail_flash_power_fw = BUCK_EFFICIENCY * vph_flash_uv * ibat_flash_ua;
+ /*
+ * Calculate the available amount of current the flash module can draw
+ * before collapsing the battery. (available power/ flash input voltage)
+ */
+ avail_flash_ua = div64_s64(avail_flash_power_fw, vin_flash_uv * MCONV);
+ pr_debug("avail_iflash=%lld, ocv=%d, ibat=%d, rbatt=%d\n",
+ avail_flash_ua, ocv_uv, ibat_now, rbatt_uohm);
+
+ return (int)avail_flash_ua;
+}
+
+static int smb1351_batt_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int smb1351_battery_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int rc;
+ struct smb1351_charger *chip = container_of(psy,
+ struct smb1351_charger, batt_psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!chip->bms_controlled_charging)
+ return -EINVAL;
+ switch (val->intval) {
+ case POWER_SUPPLY_STATUS_FULL:
+ rc = smb1351_battchg_disable(chip, SOC, true);
+ if (rc) {
+ pr_err("Couldn't disable charging rc = %d\n",
+ rc);
+ } else {
+ chip->batt_full = true;
+ pr_debug("status = FULL, batt_full = %d\n",
+ chip->batt_full);
+ }
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ chip->batt_full = false;
+ power_supply_changed(&chip->batt_psy);
+ pr_debug("status = DISCHARGING, batt_full = %d\n",
+ chip->batt_full);
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ rc = smb1351_battchg_disable(chip, SOC, false);
+ if (rc) {
+ pr_err("Couldn't enable charging rc = %d\n",
+ rc);
+ } else {
+ chip->batt_full = false;
+ pr_debug("status = CHARGING, batt_full = %d\n",
+ chip->batt_full);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ smb1351_usb_suspend(chip, USER, !val->intval);
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ if (chip->is_factory_mode) {
+ pr_err("factorymode need to disable chg, do not exc(%d)\n", val->intval);
+ break;
+ }
+ smb1351_battchg_disable(chip, USER, !val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ chip->fake_battery_soc = val->intval;
+ power_supply_changed(&chip->batt_psy);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (chip->fg_notify_jeita)
+ smb1351_handle_jeita_from_fg(chip, val->intval);
+ break;
+ /* placeholder for flash w/a */
+ case POWER_SUPPLY_PROP_FLASH_ACTIVE:
+ case POWER_SUPPLY_PROP_FLASH_TRIGGER:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int smb1351_battery_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb1351_charger *chip = container_of(psy,
+ struct smb1351_charger, batt_psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = smb1351_get_prop_batt_status(chip);
+ if ((val->intval == POWER_SUPPLY_STATUS_FULL) && (chip->batt_warm))
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = smb1351_get_prop_batt_present(chip);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = smb1351_get_prop_batt_capacity(chip);
+ if (val->intval < 1) {
+ int vol = smb1351_get_prop_batt_voltage(chip);
+ if (vol >= SMB1351_SHUTDOWN_BATTERY_VOLTGE)
+ val->intval = 1;
+ }
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = smb1351_get_prop_batt_voltage(chip);
+ if (chip->is_factory_mode) {
+ static int version = 0;
+ union power_supply_propval pval = {0,};
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+
+ pr_debug("in factory mode. check slave charger\n");
+ if (!version) {
+ parallel_psy->get_property(parallel_psy,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER, &pval);
+ version = pval.intval;
+ if (!version) {
+ pr_err("in factory mode. slave revision is null. Show error in vbus voltage *-1\n");
+ val->intval *= (-1);
+ }
+ }
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ val->intval = !chip->usb_suspended_status;
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ val->intval = !chip->battchg_disabled_status;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = smb1351_get_prop_charge_type(chip);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = smb1351_get_prop_batt_health(chip);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = smb1351_get_prop_batt_temp(chip);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = "smb1351";
+ break;
+ case POWER_SUPPLY_PROP_FLASH_CURRENT_MAX:
+ val->intval = smb1351_calc_max_flash_current(chip);
+ break;
+ case POWER_SUPPLY_PROP_FLASH_ACTIVE: /* placeholder for flash w/a */
+ case POWER_SUPPLY_PROP_FLASH_TRIGGER:
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION:
+ val->intval = smb1351_get_vbus_voltage(chip);
+ if (val->intval < 0)
+ val->intval = DEFAULT_VBUS_VOLTAGE;
+ break;
+ case POWER_SUPPLY_PROP_HI_POWER:
+ val->intval= chip->is_hv_fast_charging?
+ 1:0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = get_prop_charge_full_design(chip);
+ val->intval = DEFAULT_CHARGE_FULL_DESIGN;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = DEFAULT_CHARGE_FULL_DESIGN;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property smb1351_parallel_properties[] = {
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_FLASH_CURRENT_MAX,
+ POWER_SUPPLY_PROP_FLASH_ACTIVE,
+ POWER_SUPPLY_PROP_FLASH_TRIGGER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+};
+
+static int smb1351_parallel_set_chg_present(struct smb1351_charger *chip,
+ int present)
+{
+ int rc;
+ u8 reg, mask = 0;
+
+ pr_debug("set slave present = %d\n", present);
+ if (present == chip->parallel_charger_present) {
+ pr_debug("present %d -> %d, skipping\n",
+ chip->parallel_charger_present, present);
+ return 0;
+ }
+
+ chip->parallel_charger_present = present;
+
+ if (present) {
+ /* Check if SMB1351 is present */
+ rc = smb1351_read_reg(chip, CHG_REVISION_REG, &reg);
+ if (rc) {
+ pr_debug("Failed to detect smb1351-parallel-charger, may be absent\n");
+ return -ENODEV;
+ }
+
+ rc = smb_chip_get_version(chip);
+ if (rc) {
+ pr_err("Couldn't get version rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = smb1351_enable_volatile_writes(chip);
+ if (rc) {
+ pr_err("Couldn't configure for volatile rc = %d\n", rc);
+ return rc;
+ }
+
+ /* set the float voltage */
+ if (chip->vfloat_mv != -EINVAL) {
+ rc = smb1351_float_voltage_set(chip, chip->vfloat_mv);
+ if (rc) {
+ pr_err("Couldn't set float voltage rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* set recharge-threshold and enable auto recharge */
+ if (chip->recharge_mv != -EINVAL) {
+ reg = AUTO_RECHG_ENABLE;
+ if (chip->recharge_mv > 50)
+ reg |= AUTO_RECHG_TH_100MV;
+ else
+ reg |= AUTO_RECHG_TH_50MV;
+
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ AUTO_RECHG_BIT |
+ AUTO_RECHG_TH_BIT, reg);
+ if (rc) {
+ pr_err("Couldn't set rechg-cfg rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* set chg en by pin active low */
+ reg = chip->parallel_pin_polarity_setting | USBCS_CTRL_BY_I2C;
+ rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG,
+ EN_PIN_CTRL_MASK | USBCS_CTRL_BIT, reg);
+ if (rc) {
+ pr_err("Couldn't set en pin rc=%d\n", rc);
+ return rc;
+ }
+
+ /* control USB suspend via command bits */
+ rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG,
+ SUSPEND_MODE_CTRL_BIT,
+ SUSPEND_MODE_CTRL_BY_I2C);
+ if (rc) {
+ pr_err("Couldn't set USB suspend rc=%d\n", rc);
+ return rc;
+ }
+ /*
+ * setup USB 2.0/3.0 detection and USB 500/100
+ * command polarity
+ */
+ reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0;
+ mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT;
+ rc = smb1351_masked_write(chip,
+ CHG_OTH_CURRENT_CTRL_REG, mask, reg);
+ if (rc) {
+ pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /* set fast charging current limit */
+ chip->target_fastchg_current_max_ma = SMB1351_CHG_FAST_MIN_MA;
+ rc = smb1351_fastchg_current_set(chip,
+ chip->target_fastchg_current_max_ma);
+ if (rc) {
+ pr_err("Couldn't set fastchg current rc=%d\n", rc);
+ return rc;
+ }
+ /*
+ * Suspend USB input (CURRENT reason) to avoid slave start
+ * charging before any SW logic been run. USB input will be
+ * un-suspended (CURRENT reason) after allotted ICL being set.
+ */
+ chip->usb_psy_ma = SUSPEND_CURRENT_MA;
+ rc = smb1351_usb_suspend(chip, CURRENT, true);
+ if (rc) {
+ pr_err("Suspend USB (CURRENT) failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smb1351_usb_suspend(chip, PARALLEL, false);
+ if (rc) {
+ pr_err("Suspend USB (PARALLEL) failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ schedule_delayed_work(&chip->heatbeat_work, HZ * 1);
+ } else {
+ rc = smb1351_usb_suspend(chip, PARALLEL, true);
+ if (rc) {
+ pr_debug("Suspend USB (PARALLEL) failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ cancel_delayed_work_sync(&chip->heatbeat_work);
+ }
+
+ return 0;
+}
+
+static bool smb1351_is_input_current_limited(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb1351_read_reg(chip, IRQ_H_REG, &reg);
+ if (rc) {
+ pr_err("Failed to read IRQ_H_REG for ICL status: %d\n", rc);
+ return false;
+ }
+
+ return !!(reg & IRQ_IC_LIMIT_STATUS_BIT);
+}
+
+static int smb1351_parallel_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+ struct smb1351_charger *chip = container_of(psy,
+ struct smb1351_charger, parallel_psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ /*
+ *CHG EN is controlled by pin in the parallel charging.
+ *Use suspend if disable charging by command.
+ */
+ if (chip->parallel_charger_present) {
+ rc = smb1351_usb_suspend(chip, USER, !val->intval);
+ if (rc)
+ pr_err("%suspend charger failed\n",
+ val->intval ? "Un-s" : "S");
+ }
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ rc = smb1351_parallel_set_chg_present(chip, val->intval);
+ if (rc)
+ pr_err("Set charger %spresent failed\n",
+ val->intval ? "" : "un-");
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ if (chip->parallel_charger_present) {
+ chip->target_fastchg_current_max_ma =
+ val->intval / 1000;
+ rc = smb1351_fastchg_current_set(chip,
+ chip->target_fastchg_current_max_ma);
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (chip->parallel_charger_present) {
+ rc = smb1351_set_usb_chg_current(chip,
+ val->intval / 1000);
+ }
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (chip->parallel_charger_present &&
+ (chip->vfloat_mv != val->intval)) {
+ rc = smb1351_float_voltage_set(chip, val->intval);
+ if (!rc)
+ chip->vfloat_mv = val->intval;
+ } else {
+ chip->vfloat_mv = val->intval;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return rc;
+}
+
+static int smb1351_parallel_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static int smb1351_parallel_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb1351_charger *chip = container_of(psy,
+ struct smb1351_charger, parallel_psy);
+ int rc, icl;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ val->intval = !chip->usb_suspended_status;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (chip->parallel_charger_present) {
+ rc = smb1351_get_usb_chg_current(chip, &icl);
+ if (rc) {
+ pr_err("Get ICL result failed, rc=%d\n", rc);
+ return rc;
+ }
+ if (icl > 0)
+ val->intval = icl * 1000;
+ else
+ val->intval = 0;
+ } else {
+ val->intval = 0;
+ }
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = chip->vfloat_mv;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = chip->parallel_charger_present;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ if (chip->parallel_charger_present)
+ val->intval = chip->fastchg_current_max_ma * 1000;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (chip->parallel_charger_present)
+ val->intval = smb1351_get_prop_batt_status(chip);
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ if (chip->parallel_charger_present)
+ val->intval =
+ smb1351_is_input_current_limited(chip) ? 1 : 0;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ smb_chip_get_version(chip);
+ pr_debug("smb1351 chip revision is %d\n", chip->version);
+ val->intval = chip->version;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void smb1351_chg_ctrl_in_jeita(struct smb1351_charger *chip)
+{
+ union power_supply_propval ret = {0, };
+ int rc;
+
+ /* enable the iterm to prevent the reverse boost */
+ if (chip->iterm_disabled) {
+ if (chip->batt_cool || chip->batt_warm) {
+ rc = smb1351_iterm_set(chip, 100);
+ pr_debug("set the iterm due to JEITA\n");
+ } else {
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ ITERM_EN_BIT, ITERM_DISABLE);
+ pr_debug("disable the iterm when exits warm/cool\n");
+ }
+ if (rc) {
+ pr_err("Couldn't set iterm rc = %d\n", rc);
+ return;
+ }
+ }
+ /*
+ * When JEITA back to normal, the charging maybe disabled due to
+ * the current termination. So re-enable the charging if the soc
+ * is less than 100 in the normal mode. A 200ms delay is requred
+ * before the disabe and enable operation.
+ */
+ if (chip->bms_psy) {
+ rc = chip->bms_psy->get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_CAPACITY, &ret);
+ if (rc) {
+ pr_err("Couldn't read the bms capacity rc = %d\n",
+ rc);
+ return;
+ }
+ if (!chip->batt_cool && !chip->batt_warm
+ && !chip->batt_cold && !chip->batt_hot
+ && ret.intval < 100) {
+ rc = smb1351_battchg_disable(chip, THERMAL, true);
+ if (rc) {
+ pr_err("Couldn't disable charging rc = %d\n",
+ rc);
+ return;
+ }
+ /* delay for resetting the charging */
+ msleep(200);
+ rc = smb1351_battchg_disable(chip, THERMAL, false);
+ if (rc) {
+ pr_err("Couldn't enable charging rc = %d\n",
+ rc);
+ return;
+ } else {
+ chip->batt_full = false;
+ pr_debug("re-enable charging, batt_full = %d\n",
+ chip->batt_full);
+ }
+ pr_debug("batt psy changed\n");
+ power_supply_changed(&chip->batt_psy);
+ }
+ }
+}
+
+static void smb1351_reset(struct smb1351_charger *chip) {
+ int rc;
+
+ rc = smb1351_masked_write(chip, CMD_I2C_REG,
+ CMD_RELOAD_BIT, CMD_RELOAD_BIT);
+ if (rc) {
+ pr_err("%s, Couldn't reset charger = %d\n", dev_name(chip->dev), rc);
+ } else {
+ pr_info("%s, reset charger\n", dev_name(chip->dev));
+ }
+}
+
+#define HYSTERESIS_DECIDEGC 20
+static void smb1351_chg_adc_notification(enum qpnp_tm_state state, void *ctx)
+{
+ struct smb1351_charger *chip = ctx;
+ struct battery_status *cur;
+ int temp;
+
+ if (state >= ADC_TM_STATE_NUM) {
+ pr_err("invalid state parameter %d\n", state);
+ return;
+ }
+
+ temp = smb1351_get_prop_batt_temp(chip);
+
+ pr_debug("temp = %d state = %s\n", temp,
+ state == ADC_TM_WARM_STATE ? "hot" : "cold");
+
+ /* reset the adc status request */
+ chip->adc_param.state_request = ADC_TM_WARM_COOL_THR_ENABLE;
+
+ /* temp from low to high */
+ if (state == ADC_TM_WARM_STATE) {
+ /* WARM -> HOT */
+ if (temp >= chip->batt_hot_decidegc) {
+ cur = &batt_s[BATT_HOT];
+ chip->adc_param.low_temp =
+ chip->batt_hot_decidegc - HYSTERESIS_DECIDEGC;
+ chip->adc_param.state_request = ADC_TM_COOL_THR_ENABLE;
+ /* NORMAL -> WARM */
+ } else if (temp >= chip->batt_warm_decidegc &&
+ chip->jeita_supported) {
+ cur = &batt_s[BATT_WARM];
+ chip->adc_param.low_temp =
+ chip->batt_warm_decidegc - HYSTERESIS_DECIDEGC;
+ chip->adc_param.high_temp = chip->batt_hot_decidegc;
+ /* COOL -> NORMAL */
+ } else if (temp >= chip->batt_cool_decidegc &&
+ chip->jeita_supported) {
+ cur = &batt_s[BATT_NORMAL];
+ chip->adc_param.low_temp =
+ chip->batt_cool_decidegc - HYSTERESIS_DECIDEGC;
+ chip->adc_param.high_temp = chip->batt_warm_decidegc;
+ /* COLD -> COOL */
+ } else if (temp >= chip->batt_cold_decidegc) {
+ cur = &batt_s[BATT_COOL];
+ chip->adc_param.low_temp =
+ chip->batt_cold_decidegc - HYSTERESIS_DECIDEGC;
+ if (chip->jeita_supported)
+ chip->adc_param.high_temp =
+ chip->batt_cool_decidegc;
+ else
+ chip->adc_param.high_temp =
+ chip->batt_hot_decidegc;
+ /* MISSING -> COLD */
+ } else if (temp >= chip->batt_missing_decidegc) {
+ cur = &batt_s[BATT_COLD];
+ chip->adc_param.high_temp = chip->batt_cold_decidegc;
+ chip->adc_param.low_temp = chip->batt_missing_decidegc
+ - HYSTERESIS_DECIDEGC;
+
+ }
+ /* temp from high to low */
+ } else {
+ /* COLD -> MISSING */
+ if (temp <= chip->batt_missing_decidegc) {
+ cur = &batt_s[BATT_MISSING];
+ chip->adc_param.high_temp = chip->batt_missing_decidegc
+ + HYSTERESIS_DECIDEGC;
+ chip->adc_param.state_request = ADC_TM_WARM_THR_ENABLE;
+ /* COOL -> COLD */
+ } else if (temp <= chip->batt_cold_decidegc) {
+ cur = &batt_s[BATT_COLD];
+ chip->adc_param.high_temp =
+ chip->batt_cold_decidegc + HYSTERESIS_DECIDEGC;
+ /* add low_temp to enable batt present check */
+ chip->adc_param.low_temp = chip->batt_missing_decidegc;
+ /* NORMAL -> COOL */
+ } else if (temp <= chip->batt_cool_decidegc &&
+ chip->jeita_supported) {
+ cur = &batt_s[BATT_COOL];
+ chip->adc_param.high_temp =
+ chip->batt_cool_decidegc + HYSTERESIS_DECIDEGC;
+ chip->adc_param.low_temp = chip->batt_cold_decidegc;
+ /* WARM -> NORMAL */
+ } else if (temp <= chip->batt_warm_decidegc &&
+ chip->jeita_supported) {
+ cur = &batt_s[BATT_NORMAL];
+ chip->adc_param.high_temp =
+ chip->batt_warm_decidegc + HYSTERESIS_DECIDEGC;
+ chip->adc_param.low_temp = chip->batt_cool_decidegc;
+ /* HOT -> WARM */
+ } else if (temp <= chip->batt_hot_decidegc) {
+ cur = &batt_s[BATT_WARM];
+ if (chip->jeita_supported)
+ chip->adc_param.low_temp =
+ chip->batt_warm_decidegc;
+ else
+ chip->adc_param.low_temp =
+ chip->batt_cold_decidegc;
+ chip->adc_param.high_temp =
+ chip->batt_hot_decidegc + HYSTERESIS_DECIDEGC;
+ }
+ }
+
+ if (cur->batt_present)
+ chip->battery_missing = false;
+ else
+ chip->battery_missing = true;
+
+ if (cur->batt_hot ^ chip->batt_hot ||
+ cur->batt_cold ^ chip->batt_cold) {
+ chip->batt_hot = cur->batt_hot;
+ chip->batt_cold = cur->batt_cold;
+ /* stop charging explicitly since we use PMIC thermal pin*/
+ if (cur->batt_hot || cur->batt_cold ||
+ chip->battery_missing)
+ smb1351_battchg_disable(chip, THERMAL, 1);
+ else
+ smb1351_battchg_disable(chip, THERMAL, 0);
+ }
+
+ if ((chip->batt_warm ^ cur->batt_warm ||
+ chip->batt_cool ^ cur->batt_cool)
+ && chip->jeita_supported) {
+ chip->batt_warm = cur->batt_warm;
+ chip->batt_cool = cur->batt_cool;
+ smb1351_chg_set_appropriate_battery_current(chip);
+ smb1351_chg_set_appropriate_vfloat(chip);
+ smb1351_chg_ctrl_in_jeita(chip);
+ }
+
+ pr_debug("hot %d, cold %d, warm %d, cool %d, soft jeita supported %d, missing %d, low = %d deciDegC, high = %d deciDegC\n",
+ chip->batt_hot, chip->batt_cold, chip->batt_warm,
+ chip->batt_cool, chip->jeita_supported,
+ chip->battery_missing, chip->adc_param.low_temp,
+ chip->adc_param.high_temp);
+ if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev, &chip->adc_param))
+ pr_err("request ADC error\n");
+}
+
+static void smb1351_rerun_apsd_work(struct work_struct *work)
+{
+ int rc;
+ struct smb1351_charger *chip = container_of(work,
+ struct smb1351_charger,
+ rerun_apsd_work.work);
+ enum power_supply_type type = POWER_SUPPLY_TYPE_UNKNOWN;
+
+ pr_debug("Rerunning APSD\n");
+
+ rc = smb1351_get_usb_supply_type(chip, &type);
+ if ((type == POWER_SUPPLY_TYPE_USB) ||(type == POWER_SUPPLY_TYPE_USB_CDP)
+ ||(type == POWER_SUPPLY_TYPE_UNKNOWN)) {
+ chip->apsd_rerun = true;
+ rc = smb1351_masked_write(chip, CMD_HVDCP_REG, CMD_APSD_RE_RUN_BIT,
+ CMD_APSD_RE_RUN_BIT);
+ if (rc)
+ pr_err("Couldn't re-run APSD algo, rc = %d\n", rc);
+ }
+
+ smb1351_relax(&chip->smb1351_ws, RERUN_APSD);
+}
+
+static void smb1351_hvdcp_det_work(struct work_struct *work)
+{
+ int rc;
+ u8 reg;
+ bool is_hvdcp;
+ struct smb1351_charger *chip = container_of(work,
+ struct smb1351_charger,
+ hvdcp_det_work.work);
+
+ rc = smb1351_read_reg(chip, STATUS_7_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read STATUS_7_REG rc == %d\n", rc);
+ goto end;
+ }
+ pr_debug("STATUS_7_REG = 0x%02X\n", reg);
+
+ is_hvdcp = !!(reg & (HVDCP_SEL_5V | HVDCP_SEL_9V | HVDCP_SEL_12V));
+ if (is_hvdcp) {
+ pr_debug("HVDCP detected; notifying USB PSY\n");
+ power_supply_set_supply_type(chip->usb_psy,
+ POWER_SUPPLY_TYPE_USB_HVDCP);
+ }
+end:
+ smb1351_relax(&chip->smb1351_ws, HVDCP_DETECT);
+}
+
+#define HVDCP_NOTIFY_MS 2500
+static int smb1351_apsd_complete_handler(struct smb1351_charger *chip,
+ u8 status)
+{
+ int rc = 0;
+ enum power_supply_type type = POWER_SUPPLY_TYPE_UNKNOWN;
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+
+ /*
+ * If apsd is disabled, charger detection is done by
+ * USB phy driver.
+ */
+ if (chip->disable_apsd || chip->usbin_ov) {
+ pr_debug("APSD %s, status = %d\n",
+ chip->disable_apsd ? "disabled" : "enabled", !!status);
+ pr_debug("USBIN ov, status = %d\n", chip->usbin_ov);
+ return 0;
+ }
+
+ if (status) {
+ chip->chg_present = true;
+ rc = smb1351_get_usb_supply_type(chip, &type);
+ if (rc) {
+ pr_err("Get USB power supply type failed, rc=%d\n", rc);
+ return rc;
+ }
+ pr_debug("APSD complete. USB type detected=%d chg_present=%d\n",
+ type, chip->chg_present);
+ /*
+ * If defined force hvdcp 2p0 property,
+ * we force to hvdcp 2p0 in the APSD handler.
+ */
+ if (chip->force_hvdcp_2p0) {
+ pr_debug("Force set to HVDCP 2.0 mode\n");
+ smb1351_masked_write(chip, VARIOUS_FUNC_3_REG,
+ QC_2P1_AUTH_ALGO_BIT, 0);
+ smb1351_masked_write(chip, CMD_HVDCP_REG,
+ CMD_FORCE_HVDCP_2P0_BIT,
+ CMD_FORCE_HVDCP_2P0_BIT);
+ type = POWER_SUPPLY_TYPE_USB_HVDCP;
+ } else if (type == POWER_SUPPLY_TYPE_USB_DCP) {
+ pr_debug("schedule hvdcp detection worker\n");
+ smb1351_stay_awake(&chip->smb1351_ws, HVDCP_DETECT);
+ if (!wake_lock_active(&chip->charging_lock))
+ wake_lock(&chip->charging_lock);
+
+ schedule_delayed_work(&chip->hvdcp_det_work,
+ msecs_to_jiffies(HVDCP_NOTIFY_MS));
+ }
+
+ power_supply_set_supply_type(chip->usb_psy, type);
+ /*
+ * SMB is now done sampling the D+/D- lines,
+ * indicate USB driver
+ */
+ pr_debug("updating usb_psy present=%d\n", chip->chg_present);
+ power_supply_set_present(chip->usb_psy, chip->chg_present);
+ power_supply_set_online(chip->usb_psy, chip->chg_present);
+ chip->apsd_rerun = false;
+ /* set parallel slave PRESENT */
+ if (parallel_psy) {
+ pr_debug("set parallel charger present!\n");
+ rc = power_supply_set_present(parallel_psy, true);
+ if (rc)
+ pr_debug("parallel slave absent!\n");
+ else
+ chip->parallel.slave_detected = true;
+ }
+
+ schedule_delayed_work(&chip->heatbeat_work, (HZ * 1));
+ }
+
+ if(chip->is_factory_mode) {
+ schedule_delayed_work(&chip->charger_check_work, CHARGER_CHECK_JIFFIES);
+ }
+
+ return rc;
+}
+
+/*
+ * As source detect interrupt is not triggered on the falling edge,
+ * we need to schedule a work for checking source detect status after
+ * charger UV interrupt fired.
+ */
+#define FIRST_CHECK_DELAY_MS 100
+#define SECOND_CHECK_DELAY_MS 1000
+#define RERUN_CHG_REMOVE_DELAY_MS 1500
+static void smb1351_chg_remove_work(struct work_struct *work)
+{
+ int rc, vbus_uv = 0;
+ u8 reg;
+ struct smb1351_charger *chip = container_of(work,
+ struct smb1351_charger, chg_remove_work.work);
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+
+ if(chip->chg_present == false) {
+ pr_debug("chg already removed\n");
+ goto end;
+ }
+
+ rc = smb1351_read_reg(chip, IRQ_G_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_G_REG rc = %d\n", rc);
+ }
+
+ vbus_uv = smb1351_get_vbus_voltage(chip);
+
+ if (((!(reg & IRQ_SOURCE_DET_BIT) ) && (rc == 0))
+ ||((chip->chg_remove_work_scheduled >= 5) && (vbus_uv < 2000000) && (vbus_uv > 0))) {
+ pr_debug("chg removed, vbus_uv = %d\n",vbus_uv);
+ chip->chg_present = false;
+ chip->batt_full = false;
+ smb1351_battchg_disable(chip, SOC, false);
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ /* clear parallel slave PRESENT */
+ if (parallel_psy && chip->parallel.slave_detected) {
+ pr_debug("set parallel charger un-present!\n");
+ power_supply_set_present(parallel_psy, false);
+ }
+ power_supply_set_supply_type(chip->usb_psy,
+ POWER_SUPPLY_TYPE_UNKNOWN);
+ power_supply_set_present(chip->usb_psy,
+ chip->chg_present);
+ power_supply_set_online(chip->usb_psy,
+ chip->chg_present);
+
+ pr_debug("Set usb psy dp=r dm=r\n");
+ power_supply_set_dp_dm(chip->usb_psy,
+ POWER_SUPPLY_DP_DM_DPR_DMR);
+ chip->apsd_rerun = false;
+
+ if (wake_lock_active(&chip->charging_lock))
+ wake_unlock(&chip->charging_lock);
+
+ cancel_delayed_work_sync(&chip->heatbeat_work);
+ chip->is_hv_fast_charging = false;
+
+ if(chip->is_factory_mode) {
+ schedule_delayed_work(&chip->charger_check_work, CHARGER_CHECK_JIFFIES);
+ }
+ } else if (chip->chg_remove_work_scheduled < 5) {
+ chip->chg_remove_work_scheduled ++;
+ pr_debug("chg_remove_work_scheduled = %d\n",chip->chg_remove_work_scheduled);
+ goto reschedule;
+ } else {
+ pr_debug("charger is present\n");
+ }
+end:
+ chip->chg_remove_work_scheduled = 0;
+ smb1351_relax(&chip->smb1351_ws, REMOVAL_DETECT);
+ return;
+
+reschedule:
+ pr_debug("reschedule after 1s\n");
+ schedule_delayed_work(&chip->chg_remove_work,
+ msecs_to_jiffies(SECOND_CHECK_DELAY_MS));
+}
+
+static void smb1351_charger_check_worker(struct work_struct *work)
+{
+ struct smb1351_charger *chip = container_of(work, struct smb1351_charger, charger_check_work.work);
+ union power_supply_propval charger_type = {0, };
+
+ if (!chip->is_factory_cable) {
+ chip->usb_psy->get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &charger_type);
+ if((charger_type.intval==POWER_SUPPLY_TYPE_USB)&&(chip->is_factory_mode))
+ chip->is_factory_cable = true;
+ else
+ chip->is_factory_cable = false;
+
+ pr_info("CHG %s, charger type %d is_factorymode %d is_cable %d\n", __func__,
+ charger_type.intval, chip->is_factory_mode, chip->is_factory_cable);
+ } else {
+ if (chip->chg_present)
+ return;
+
+#ifdef MMI_TEST
+ //if (!factory_kill_disable && !reboot_in_progress()) {
+ if (!factory_kill_disable ) {
+#else
+ if (1) {
+#endif
+ pr_info("CHG - Factory Cable removed, power-off\n");
+ kernel_power_off();
+ } else
+ pr_info("CHG - Factory cable removed - kill disabled\n");
+
+ chip->is_factory_cable = false;
+ }
+}
+
+static int smb1351_usbin_uv_handler(struct smb1351_charger *chip, u8 status)
+{
+ int chg_remove_delay = FIRST_CHECK_DELAY_MS;
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+ int rc;
+
+ pr_debug("enter, status = %d\n", !!status);
+ /* use this to detect USB insertion only if !apsd */
+ if (chip->disable_apsd) {
+ /*
+ * If APSD is disabled, src det interrupt won't trigger.
+ * Hence use usbin_uv for removal and insertion notification
+ */
+ if (status == 0) {
+ chip->chg_present = true;
+ pr_debug("updating usb_psy present=%d\n",
+ chip->chg_present);
+ power_supply_set_supply_type(chip->usb_psy,
+ POWER_SUPPLY_TYPE_USB);
+ power_supply_set_present(chip->usb_psy,
+ chip->chg_present);
+ /* set parallel slave PRESENT */
+ if (parallel_psy) {
+ rc = power_supply_set_present(
+ parallel_psy, true);
+ if (rc)
+ pr_debug("parallel slave absent!\n");
+ else
+ chip->parallel.slave_detected = true;
+ }
+ } else {
+ chip->chg_present = false;
+ /* set parallel slave UN-PRESENT */
+ if (parallel_psy && chip->parallel.slave_detected)
+ power_supply_set_present(parallel_psy, false);
+ power_supply_set_supply_type(chip->usb_psy,
+ POWER_SUPPLY_TYPE_UNKNOWN);
+ power_supply_set_present(chip->usb_psy,
+ chip->chg_present);
+ pr_debug("updating usb_psy present=%d\n",
+ chip->chg_present);
+ }
+ return 0;
+ }
+
+ if (status) {
+ cancel_delayed_work_sync(&chip->hvdcp_det_work);
+ if (chip->wa_flags & CHG_ITERM_CHECK_SOC)
+ cancel_delayed_work_sync(&chip->iterm_check_soc_work);
+ smb1351_relax(&chip->smb1351_ws, REMOVAL_DETECT);
+ pr_debug("schedule charger remove worker\n");
+ if (chip->apsd_rerun)
+ chg_remove_delay = RERUN_CHG_REMOVE_DELAY_MS;
+ smb1351_stay_awake(&chip->smb1351_ws, REMOVAL_DETECT);
+ schedule_delayed_work(&chip->chg_remove_work,
+ msecs_to_jiffies(chg_remove_delay));
+ } else {
+ cancel_delayed_work_sync(&chip->chg_remove_work);
+ smb1351_relax(&chip->smb1351_ws, REMOVAL_DETECT);
+ pr_debug("Set usb psy dp=f dm=f\n");
+ power_supply_set_dp_dm(chip->usb_psy,
+ POWER_SUPPLY_DP_DM_DPF_DMF);
+ }
+
+ pr_info("chip->chg_present = %d\n", chip->chg_present);
+
+ return 0;
+}
+
+static int smb1351_usbin_ov_handler(struct smb1351_charger *chip, u8 status)
+{
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+ int health;
+ int rc;
+ u8 reg;
+
+ pr_debug("enter, status = %d\n", !!status);
+ rc = smb1351_read_reg(chip, IRQ_E_REG, &reg);
+ if (rc)
+ pr_err("Couldn't read IRQ_E rc = %d\n", rc);
+
+ if (status != 0) {
+ chip->chg_present = false;
+ chip->usbin_ov = true;
+ /* set parallel slave UN-PRESENT */
+ if (parallel_psy && chip->parallel.slave_detected)
+ power_supply_set_present(parallel_psy, false);
+ power_supply_set_supply_type(chip->usb_psy,
+ POWER_SUPPLY_TYPE_UNKNOWN);
+ power_supply_set_present(chip->usb_psy, chip->chg_present);
+ } else {
+ chip->usbin_ov = false;
+ if (reg & IRQ_USBIN_UV_BIT)
+ pr_debug("Charger unplugged from OV\n");
+ else
+ smb1351_apsd_complete_handler(chip, 1);
+ }
+
+ if (chip->usb_psy) {
+ health = status ? POWER_SUPPLY_HEALTH_OVERVOLTAGE
+ : POWER_SUPPLY_HEALTH_GOOD;
+ power_supply_set_health_state(chip->usb_psy, health);
+ pr_debug("chip ov status is %d\n", health);
+ }
+ pr_debug("chip->chg_present = %d\n", chip->chg_present);
+
+ return 0;
+}
+
+static int smb1351_usbid_handler(struct smb1351_charger *chip, u8 status)
+{
+ bool usbid_gnd;
+ int rc;
+
+ if (!!status) {
+ rc = smb1351_get_usbid_status(chip, &usbid_gnd);
+ if (rc) {
+ pr_err("Get usbid failed!");
+ return 0;
+ }
+ pr_debug("usbid grounded: %d\n", usbid_gnd);
+ if (chip->usb_psy)
+ power_supply_set_usb_otg(chip->usb_psy, usbid_gnd);
+ }
+ if (chip->use_external_fg)
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+
+ return 0;
+}
+
+static int smb1351_fast_chg_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ chip->check_parallel = true;
+ if (chip->use_external_fg)
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ return 0;
+}
+
+static int smb1351_taper_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ if (!!status)
+ smb1351_parallel_charger_taper_attempt(chip);
+
+ if (chip->use_external_fg)
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ return 0;
+}
+
+static int smb1351_recharge_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+// if (!chip->bms_controlled_charging)
+// chip->batt_full = !status;
+ chip->check_parallel = true;
+
+ if (chip->use_external_fg)
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ return 0;
+}
+
+#define FULL_SOC 100
+#define ITERM_CHECK_MS 10000
+#define ITERM_RECHECK_COUNT 3
+static void smb1351_iterm_check_soc_work(struct work_struct *work)
+{
+ int rc, current_ua, soc;
+ struct smb1351_charger *chip = container_of(work,
+ struct smb1351_charger, iterm_check_soc_work.work);
+
+ if (!chip->chg_present) {
+ pr_debug("Plugged out, ignore!\n");
+ goto done;
+ }
+
+ rc = smb1351_set_bms_property(chip,
+ POWER_SUPPLY_PROP_UPDATE_NOW, 1);
+ if (rc) {
+ pr_err("Update bms status failed, rc=%d\n", rc);
+ goto recheck;
+ }
+
+ rc = smb1351_get_bms_property(chip,
+ POWER_SUPPLY_PROP_CURRENT_NOW, &current_ua);
+ if (rc) {
+ pr_err("Get bms current now failed, rc=%d\n", rc);
+ goto recheck;
+ }
+
+ pr_debug("current: %d ua!", current_ua);
+ if (current_ua >= 0) {
+ pr_debug("Terminated? large current consumption?");
+ goto recheck;
+ }
+
+ if (-1 * current_ua / 1000 < chip->iterm_ma) {
+ pr_debug("Charging current is less than iterm, terminate!");
+ chip->batt_full = true;
+ goto done;
+ }
+
+ rc = smb1351_get_bms_property(chip, POWER_SUPPLY_PROP_CAPACITY, &soc);
+ if (rc) {
+ pr_err("Get bms capacity failed, rc=%d\n", rc);
+ goto recheck;
+ }
+
+ if (soc == FULL_SOC) {
+ pr_debug("FG has terminated, terminate charger!\n");
+ chip->batt_full = true;
+ goto done;
+ } else {
+ goto recheck;
+ }
+done:
+ if (chip->batt_full) {
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ if (chip->wa_flags & CHG_SOC_BASED_RESUME) {
+ rc = smb1351_battchg_disable(chip, SOC, true);
+ if (rc) {
+ pr_err("Disable charger for SOC failed, rc=%d\n",
+ rc);
+ goto recheck;
+ }
+ }
+ }
+
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ ITERM_EN_BIT, ITERM_ENABLE);
+ if (rc) {
+ pr_err("Re-enable iterm failed, rc=%d\n", rc);
+ goto recheck;
+ }
+
+ chip->iterm_disabled = false;
+ smb1351_relax(&chip->smb1351_ws, ITERM_CHECK_SOC);
+
+ return;
+
+recheck:
+ schedule_delayed_work(&chip->iterm_check_soc_work,
+ msecs_to_jiffies(ITERM_CHECK_MS));
+}
+
+static int smb1351_chg_term_handler(struct smb1351_charger *chip, u8 status)
+{
+ int rc, soc;
+
+ pr_debug("enter, status = 0x%02x\n", status);
+ if (!chip->bms_controlled_charging &&
+ !(chip->wa_flags & CHG_ITERM_CHECK_SOC))
+ chip->batt_full = !!status;
+
+ /* Only check parallel when falling */
+ if (!status)
+ chip->check_parallel = true;
+
+ if (!(chip->wa_flags & CHG_ITERM_CHECK_SOC))
+ return 0;
+
+ if (!!status && !chip->batt_full) {
+ rc = smb1351_get_bms_property(chip,
+ POWER_SUPPLY_PROP_CAPACITY, &soc);
+ if (rc) {
+ pr_err("Get capacity failed, rc=%d\n", rc);
+ return rc;
+ }
+ if (soc != FULL_SOC) {
+ pr_debug("FG haven't reported FULL, soc=%d\n", soc);
+ /*
+ * disable termination, restart charging,
+ * hold a wakelock and start a timer for
+ * checking SOC until the SOC reach to
+ * FULL_SOC.
+ */
+ rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+ ITERM_EN_BIT, ITERM_DISABLE);
+ if (rc) {
+ pr_err("disable iterm failed, rc=%d", rc);
+ return rc;
+ }
+ chip->iterm_disabled = true;
+ /* toggle charging disble bit for re-charge */
+ rc = smb1351_battchg_disable(chip, ITERM, true);
+ if (rc) {
+ pr_err("Disable charger for ITERM failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ rc = smb1351_battchg_disable(chip, ITERM, false);
+ if (rc) {
+ pr_err("Enable charger for ITERM failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ smb1351_stay_awake(&chip->smb1351_ws, ITERM_CHECK_SOC);
+ schedule_delayed_work(&chip->iterm_check_soc_work, 0);
+ } else {
+ pr_debug("Terminated, FG report full\n");
+ chip->batt_full = true;
+ }
+ }
+
+ return 0;
+}
+
+static int smb1351_safety_timeout_handler(struct smb1351_charger *chip,
+ u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ chip->check_parallel = true;
+ if (chip->use_external_fg)
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ return 0;
+}
+
+static int smb1351_chg_error_handler(struct smb1351_charger *chip,
+ u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ chip->check_parallel = true;
+ if (chip->use_external_fg)
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ return 0;
+}
+
+static int smb1351_aicl_done_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ chip->check_parallel = true;
+ return 0;
+}
+
+static int smb1351_hot_hard_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ chip->batt_hot = !!status;
+ chip->check_parallel = true;
+ return 0;
+}
+static int smb1351_cold_hard_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ chip->batt_cold = !!status;
+ chip->check_parallel = true;
+ return 0;
+}
+static int smb1351_hot_soft_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ chip->batt_warm = !!status;
+ chip->check_parallel = true;
+ return 0;
+}
+static int smb1351_cold_soft_handler(struct smb1351_charger *chip, u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ chip->batt_cool = !!status;
+ chip->check_parallel = true;
+ return 0;
+}
+
+static int smb1351_battery_missing_handler(struct smb1351_charger *chip,
+ u8 status)
+{
+ pr_debug("enter, status = 0x%02x\n", status);
+ if (status)
+ chip->battery_missing = true;
+ else
+ chip->battery_missing = false;
+
+ if (chip->use_external_fg)
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ return 0;
+}
+
+static struct irq_handler_info handlers[] = {
+ [0] = {
+ .stat_reg = IRQ_A_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "cold_soft",
+ .smb_irq = smb1351_cold_soft_handler,
+ },
+ { .name = "hot_soft",
+ .smb_irq = smb1351_hot_soft_handler,
+ },
+ { .name = "cold_hard",
+ .smb_irq = smb1351_cold_hard_handler,
+ },
+ { .name = "hot_hard",
+ .smb_irq = smb1351_hot_hard_handler,
+ },
+ },
+ },
+ [1] = {
+ .stat_reg = IRQ_B_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "internal_temp_limit",
+ },
+ { .name = "vbatt_low",
+ },
+ { .name = "battery_missing",
+ .smb_irq = smb1351_battery_missing_handler,
+ },
+ { .name = "batt_therm_removed",
+ },
+ },
+ },
+ [2] = {
+ .stat_reg = IRQ_C_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "chg_term",
+ .smb_irq = smb1351_chg_term_handler,
+ },
+ { .name = "taper",
+ .smb_irq = smb1351_taper_handler,
+ },
+ { .name = "recharge",
+ .smb_irq = smb1351_recharge_handler,
+ },
+ { .name = "fast_chg",
+ .smb_irq = smb1351_fast_chg_handler,
+ },
+ },
+ },
+ [3] = {
+ .stat_reg = IRQ_D_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "prechg_timeout",
+ },
+ { .name = "safety_timeout",
+ .smb_irq = smb1351_safety_timeout_handler,
+ },
+ { .name = "chg_error",
+ .smb_irq = smb1351_chg_error_handler,
+ },
+ { .name = "batt_ov",
+ },
+ },
+ },
+ [4] = {
+ .stat_reg = IRQ_E_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "power_ok",
+ },
+ { .name = "afvc",
+ },
+ { .name = "usbin_uv",
+ .smb_irq = smb1351_usbin_uv_handler,
+ },
+ { .name = "usbin_ov",
+ .smb_irq = smb1351_usbin_ov_handler,
+ },
+ },
+ },
+ [5] = {
+ .stat_reg = IRQ_F_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "otg_oc_retry",
+ },
+ { .name = "rid",
+ .smb_irq = smb1351_usbid_handler,
+ },
+ { .name = "otg_fail",
+ },
+ { .name = "otg_oc",
+ },
+ },
+ },
+ [6] = {
+ .stat_reg = IRQ_G_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "chg_inhibit",
+ },
+ { .name = "aicl_fail",
+ },
+ { .name = "aicl_done",
+ .smb_irq = smb1351_aicl_done_handler,
+ },
+ { .name = "apsd_complete",
+ .smb_irq = smb1351_apsd_complete_handler,
+ },
+ },
+ },
+ [7] = {
+ .stat_reg = IRQ_H_REG,
+ .val = 0,
+ .prev_val = 0,
+ .irq_info = {
+ { .name = "wdog_timeout",
+ },
+ { .name = "hvdcp_auth_done",
+ },
+ },
+ },
+};
+
+#define IRQ_LATCHED_MASK 0x02
+#define IRQ_STATUS_MASK 0x01
+#define BITS_PER_IRQ 2
+static irqreturn_t smb1351_chg_stat_handler(int irq, void *dev_id)
+{
+ struct smb1351_charger *chip = dev_id;
+ int i, j;
+ u8 triggered;
+ u8 changed;
+ u8 rt_stat, prev_rt_stat;
+ int rc;
+ int handler_count = 0;
+
+ pr_debug("%s IRQ triggered\n", __func__);
+
+ mutex_lock(&chip->irq_complete);
+
+ chip->irq_waiting = true;
+ if (!chip->resume_completed) {
+ pr_debug("IRQ triggered before device-resume\n");
+ disable_irq_nosync(irq);
+ mutex_unlock(&chip->irq_complete);
+ return IRQ_HANDLED;
+ }
+ chip->irq_waiting = false;
+
+ for (i = 0; i < ARRAY_SIZE(handlers); i++) {
+ rc = smb1351_read_reg(chip, handlers[i].stat_reg,
+ &handlers[i].val);
+ if (rc) {
+ pr_err("Couldn't read %d rc = %d\n",
+ handlers[i].stat_reg, rc);
+ continue;
+ }
+
+ for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) {
+ triggered = handlers[i].val
+ & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ));
+ rt_stat = handlers[i].val
+ & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+ prev_rt_stat = handlers[i].prev_val
+ & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+ changed = prev_rt_stat ^ rt_stat;
+
+ if (triggered || changed)
+ rt_stat ? handlers[i].irq_info[j].high++ :
+ handlers[i].irq_info[j].low++;
+
+ if ((triggered || changed)
+ && handlers[i].irq_info[j].smb_irq != NULL) {
+ handler_count++;
+ rc = handlers[i].irq_info[j].smb_irq(chip,
+ rt_stat);
+ if (rc)
+ pr_err("Couldn't handle %d irq for reg 0x%02x rc = %d\n",
+ j, handlers[i].stat_reg, rc);
+ }
+ }
+ handlers[i].prev_val = handlers[i].val;
+ }
+ if (chip->check_parallel) {
+ smb1351_parallel_check_start(chip);
+ chip->check_parallel = false;
+ }
+ pr_debug("handler count = %d\n", handler_count);
+ if (handler_count) {
+ pr_debug("batt psy changed\n");
+ power_supply_changed(&chip->batt_psy);
+ }
+
+ mutex_unlock(&chip->irq_complete);
+
+ return IRQ_HANDLED;
+}
+
+#define DEFAULT_SDP_MA 500
+#define DEFAULT_CDP_MA 1500
+//#define DEFAULT_DCP_MA 1800
+//#define DEFAULT_HVDCP_MA 1800
+#define DEFAULT_DCP_MA 2000
+#define DEFAULT_HVDCP_MA 2000
+#define DEFAULT_HVDCP3_MA 3000
+static int smb1351_update_usb_supply_icl(struct smb1351_charger *chip)
+{
+ int rc, type, icl;
+ union power_supply_propval pval = {0, };
+
+ rc = chip->usb_psy->get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_TYPE, &pval);
+ if (rc) {
+ pr_err("Get USB supply type failed, rc=%d\n", rc);
+ return rc;
+ }
+ type = pval.intval;
+ chip->usb_psy_type = type;
+ rc = chip->usb_psy->get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+ pr_debug("pro type = %d, icl = %d\n", type, icl);
+ if (!rc)
+ icl = pval.intval /1000;
+ else
+ return rc;
+ /*
+ * Only update ICL when DCP/HVDCP/HVDCP3 being detected
+ * For other types such as SDP/DCP, keep the ICL set in
+ * USB PHY to avoid violate USB spec.
+ */
+ switch (type) {
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ icl = DEFAULT_DCP_MA;
+ break;
+ case POWER_SUPPLY_TYPE_USB_HVDCP:
+ icl = DEFAULT_HVDCP_MA;
+ break;
+ case POWER_SUPPLY_TYPE_USB_HVDCP_3:
+ icl = DEFAULT_HVDCP3_MA;
+ break;
+ default:
+ break;
+ }
+ chip->usb_psy_ma = icl;
+ pr_info("type = %d, icl = %d\n", type, icl);
+
+ return rc;
+}
+
+#define ESR_PULSE_DELTA_MA 200
+static int smb1351_force_esr_pulse_en(struct smb1351_charger *chip, bool en)
+{
+ int rc = 0, current_in_esr = 0, fg_current_now = 0;
+ int main_fcc_in_esr = 0, slave_fcc_in_esr = 0;
+ int actual_slave_fcc_in_esr = 0;
+ union power_supply_propval pval = {0,};
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+
+ if (chip->in_esr_pulse == en) {
+ pr_err("ESR pulse is already %s\n",
+ en ? "enabled" : "disabled");
+ return 0;
+ }
+
+ if (en) {
+ rc = smb1351_get_bms_property(chip,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &fg_current_now);
+ if (rc) {
+ pr_debug("Get battery current from BMS(fg) failed, rc=%d\n",
+ rc);
+ return rc;
+ }
+ pr_debug("ibatt from FG reading: %duA\n", fg_current_now);
+ fg_current_now = abs(fg_current_now);
+ fg_current_now /= 1000;
+ chip->main_fcc_ma_before_esr = chip->fastchg_current_max_ma;
+ current_in_esr = max(chip->iterm_ma + ESR_PULSE_DELTA_MA,
+ fg_current_now - ESR_PULSE_DELTA_MA);
+ pr_debug("Force battery current to %d in ESR pulse\n",
+ current_in_esr);
+ }
+
+ mutex_lock(&chip->parallel.lock);
+ /*
+ * If parallel charging is not enabled, set the current_in_esr
+ * to main charger to achieve the ESR pulse
+ */
+ if (!parallel_psy || !chip->parallel.slave_detected
+ || chip->parallel.slave_icl_ma == 0) {
+ rc = smb1351_fastchg_current_set(chip,
+ en ? current_in_esr :
+ chip->main_fcc_ma_before_esr);
+ if (!rc)
+ chip->in_esr_pulse = en;
+ else
+ pr_err("set FCC for ESR failed, rc=%d\n", rc);
+
+ goto unlock;
+ }
+
+ /*
+ * If parallel charging is enabled, split current_in_esr and
+ * set it to main and slave charger accordingly
+ */
+ if (en) {
+ rc = parallel_psy->get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ if (rc) {
+ pr_err("get slave fcc failed, rc=%d\n", rc);
+ goto unlock;
+ }
+ chip->slave_fcc_ma_before_esr = pval.intval / 1000;
+ slave_fcc_in_esr = current_in_esr *
+ (100 - chip->parallel.main_chg_fcc_percent) / 100;
+ pval.intval = slave_fcc_in_esr * 1000;
+ rc = parallel_psy->set_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ if (rc) {
+ pr_err("set slave fcc for ESR failed, rc=%d\n", rc);
+ goto unlock;
+ }
+ rc = parallel_psy->get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ if (rc) {
+ pr_err("get slave fcc for ESR failed, rc=%d\n", rc);
+ goto unlock;
+ }
+ actual_slave_fcc_in_esr = pval.intval / 1000;
+ pr_debug("slave_fcc_in_esr = %dmA, actual_slave_fcc_in_esr = %dmA\n",
+ slave_fcc_in_esr, actual_slave_fcc_in_esr);
+ main_fcc_in_esr = current_in_esr - actual_slave_fcc_in_esr;
+ rc = smb1351_fastchg_current_set(chip, main_fcc_in_esr);
+ if (rc) {
+ pr_err("Set main fcc for ESR failed, rc=%d\n",
+ rc);
+ goto unlock;
+ }
+ pr_debug("main_fcc_in_esr = %dmA\n", main_fcc_in_esr);
+ } else {
+ pval.intval = chip->slave_fcc_ma_before_esr * 1000;
+ rc = parallel_psy->set_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ &pval);
+ if (rc) {
+ pr_err("restore fcc for slave failed, rc=%d\n", rc);
+ goto unlock;
+ }
+ rc = smb1351_fastchg_current_set(chip,
+ chip->main_fcc_ma_before_esr);
+ if (rc) {
+ pr_err("restore fcc for main failed, rc=%d\n", rc);
+ goto unlock;
+ }
+ pr_debug("restore, main_fcc = %dmA, slave_fcc = %dmA\n",
+ chip->main_fcc_ma_before_esr,
+ chip->slave_fcc_ma_before_esr);
+ }
+
+
+ chip->in_esr_pulse = en;
+unlock:
+ mutex_unlock(&chip->parallel.lock);
+
+ return rc;
+}
+
+#define ESR_PULSE_TIME_MS 1500
+static void smb1351_force_esr_for_fg(struct smb1351_charger *chip)
+{
+ int rc = 0, esr_count = 0;
+
+ if (!chip->chg_present) {
+ pr_debug("No force ESR pulse when no charging\n");
+ return;
+ }
+
+ pr_debug("start!\n");
+ chip->is_in_esr_state = true;
+
+ rc = smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_UPDATE_NOW, 1);
+ if (rc) {
+ pr_debug("Update BMS(fg) status failed, rc=%d\n", rc);
+ chip->is_in_esr_state = false;
+ return;
+ }
+ rc = smb1351_get_bms_property(chip, POWER_SUPPLY_PROP_ESR_COUNT,
+ &esr_count);
+ if (rc) {
+ pr_debug("Get ESR count from BMS(fg) failed, rc=%d\n", rc);
+ chip->is_in_esr_state = false;
+ return;
+ }
+ if (esr_count != 0) {
+ pr_debug("ESR count is not zero: %d, skipping\n",
+ esr_count);
+ chip->is_in_esr_state = false;
+ return;
+ }
+ smb1351_stay_awake(&chip->smb1351_ws, FORCE_ESR_PULSE);
+ rc = smb1351_force_esr_pulse_en(chip, true);
+ if (rc) {
+ pr_err("Force ESR pulse enable failed, rc=%d\n", rc);
+ chip->is_in_esr_state = false;
+ goto end;
+ }
+ msleep(ESR_PULSE_TIME_MS);
+ rc = smb1351_force_esr_pulse_en(chip, false);
+ if (rc) {
+ pr_err("Force ESR pulse disable failed, rc=%d\n", rc);
+ chip->is_in_esr_state = false;
+ goto end;
+ }
+
+end:
+ smb1351_relax(&chip->smb1351_ws, FORCE_ESR_PULSE);
+
+ chip->is_in_esr_state = false;
+ pr_debug("end!\n");
+}
+
+static void battery_soc_changed(struct smb1351_charger *chip)
+{
+ int rc, soc;
+
+ soc = smb1351_get_prop_batt_capacity(chip);
+ if (chip->pre_soc == soc)
+ return;
+
+ if (chip->wa_flags & CHG_FORCE_ESR_WA)
+ smb1351_force_esr_for_fg(chip);
+
+ if ((chip->wa_flags & CHG_SOC_BASED_RESUME)
+ && chip->batt_full && (soc <= chip->resume_soc)) {
+ rc = smb1351_battchg_disable(chip, SOC, false);
+ if (rc) {
+ pr_err("Enable charge for SOC failed, rc=%d\n",
+ rc);
+ return;
+ }
+ chip->batt_full = false;
+ smb1351_set_bms_property(chip, POWER_SUPPLY_PROP_STATUS,
+ smb1351_get_prop_batt_status(chip));
+ }
+
+ chip->pre_soc = soc;
+}
+
+static void smb1351_external_power_changed(struct power_supply *psy)
+{
+ struct smb1351_charger *chip = container_of(psy,
+ struct smb1351_charger, batt_psy);
+ union power_supply_propval prop = {0,};
+ int rc, online = 0;
+ struct power_supply *parallel_psy = smb1351_get_parallel_slave(chip);
+ int slave_present;
+
+
+ battery_soc_changed(chip);
+
+
+ parallel_psy->get_property(parallel_psy, POWER_SUPPLY_PROP_PRESENT, &prop);
+ slave_present = prop.intval;
+ /* Don't update ICL if slave is enabled */
+ pr_debug("slave_detected = %d, slave_present = %d, slave_icl_ma = %d\n",
+ chip->parallel.slave_detected, slave_present, chip->parallel.slave_icl_ma);
+ if (chip->parallel.slave_detected && slave_present
+ && chip->parallel.slave_icl_ma != 0)
+ return;
+
+ rc = chip->usb_psy->get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_ONLINE, &prop);
+ if (rc) {
+ pr_err("Couldn't read USB online property, rc=%d\n", rc);
+ return;
+ }
+
+ online = prop.intval;
+ rc = smb1351_update_usb_supply_icl(chip);
+ if (rc) {
+ pr_err("Update USB supply ICL failed!\n");
+ return;
+ }
+
+ pr_debug("online = %d, current_limit = %d\n", online, chip->usb_psy_ma);
+
+ smb1351_enable_volatile_writes(chip);
+ if (chip->is_factory_mode)
+ pr_info("in factory mode. do not set usbin\n");
+ else
+ smb1351_set_usb_chg_current(chip, chip->usb_psy_ma);
+
+ pr_debug("updating batt psy\n");
+}
+
+#define LAST_CNFG_REG 0x16
+static int show_cnfg_regs(struct seq_file *m, void *data)
+{
+ struct smb1351_charger *chip = m->private;
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (!rc)
+ seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ return 0;
+}
+
+static int cnfg_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb1351_charger *chip = inode->i_private;
+
+ return single_open(file, show_cnfg_regs, chip);
+}
+
+static const struct file_operations cnfg_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = cnfg_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#define FIRST_CMD_REG 0x30
+#define LAST_CMD_REG 0x34
+static int show_cmd_regs(struct seq_file *m, void *data)
+{
+ struct smb1351_charger *chip = m->private;
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (!rc)
+ seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ return 0;
+}
+
+static int cmd_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb1351_charger *chip = inode->i_private;
+
+ return single_open(file, show_cmd_regs, chip);
+}
+
+static const struct file_operations cmd_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = cmd_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+#define FIRST_STATUS_REG 0x36
+#define LAST_STATUS_REG 0x3F
+static int show_status_regs(struct seq_file *m, void *data)
+{
+ struct smb1351_charger *chip = m->private;
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (!rc)
+ seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ return 0;
+}
+
+static int status_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb1351_charger *chip = inode->i_private;
+
+ return single_open(file, show_status_regs, chip);
+}
+
+static const struct file_operations status_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = status_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int show_irq_count(struct seq_file *m, void *data)
+{
+ int i, j, total = 0;
+
+ for (i = 0; i < ARRAY_SIZE(handlers); i++)
+ for (j = 0; j < 4; j++) {
+ seq_printf(m, "%s=%d\t(high=%d low=%d)\n",
+ handlers[i].irq_info[j].name,
+ handlers[i].irq_info[j].high
+ + handlers[i].irq_info[j].low,
+ handlers[i].irq_info[j].high,
+ handlers[i].irq_info[j].low);
+ total += (handlers[i].irq_info[j].high
+ + handlers[i].irq_info[j].low);
+ }
+
+ seq_printf(m, "\n\tTotal = %d\n", total);
+
+ return 0;
+}
+
+static int irq_count_debugfs_open(struct inode *inode, struct file *file)
+{
+ struct smb1351_charger *chip = inode->i_private;
+
+ return single_open(file, show_irq_count, chip);
+}
+
+static const struct file_operations irq_count_debugfs_ops = {
+ .owner = THIS_MODULE,
+ .open = irq_count_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int get_reg(void *data, u64 *val)
+{
+ struct smb1351_charger *chip = data;
+ int rc;
+ u8 temp;
+
+ rc = smb1351_read_reg(chip, chip->peek_poke_address, &temp);
+ if (rc) {
+ pr_err("Couldn't read reg %x rc = %d\n",
+ chip->peek_poke_address, rc);
+ return -EAGAIN;
+ }
+ *val = temp;
+ return 0;
+}
+
+static int set_reg(void *data, u64 val)
+{
+ struct smb1351_charger *chip = data;
+ int rc;
+ u8 temp;
+
+ temp = (u8) val;
+ rc = smb1351_write_reg(chip, chip->peek_poke_address, temp);
+ if (rc) {
+ pr_err("Couldn't write 0x%02x to 0x%02x rc= %d\n",
+ temp, chip->peek_poke_address, rc);
+ return -EAGAIN;
+ }
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n");
+
+static int force_irq_set(void *data, u64 val)
+{
+ struct smb1351_charger *chip = data;
+
+ smb1351_chg_stat_handler(chip->client->irq, data);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n");
+
+#ifdef DEBUG
+static void dump_regs(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg;
+ u8 addr;
+
+ pr_debug("%s %s\n", dev_name(chip->dev), __func__);
+
+ for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (rc)
+ pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc);
+ else
+ pr_debug("0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (rc)
+ pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc);
+ else
+ pr_debug("0x%02x = 0x%02x\n", addr, reg);
+ }
+
+ for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+ rc = smb1351_read_reg(chip, addr, &reg);
+ if (rc)
+ pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc);
+ else
+ pr_debug("0x%02x = 0x%02x\n", addr, reg);
+ }
+}
+#else
+static void dump_regs(struct smb1351_charger *chip)
+{
+}
+#endif
+
+#define MAIN_CHG_DEFAULT_FCC_PERCENT 50
+#define MAIN_CHG_DEFAULT_ICL_PERCENT 50
+static int smb1351_parse_parallel_charger_dt(struct smb1351_charger *chip)
+{
+ int rc;
+ struct device_node *node = chip->dev->of_node;
+
+ rc = of_property_read_u32(node, "qcom,parallel-usb-min-current-ma",
+ &chip->parallel.min_current_thr_ma);
+ if (rc && rc != -EINVAL)
+ goto bail_out;
+
+ rc = of_property_read_u32(node, "qcom,parallel-usb-9v-min-current-ma",
+ &chip->parallel.min_9v_current_thr_ma);
+ if (rc && rc != -EINVAL)
+ goto bail_out;
+
+ rc = of_property_read_u32(node, "qcom,parallel-usb-12v-min-current-ma",
+ &chip->parallel.min_12v_current_thr_ma);
+ if (rc && rc != -EINVAL)
+ goto bail_out;
+
+ rc = of_property_read_u32(node, "qcom,parallel-allowed-lowering-ma",
+ &chip->parallel.allowed_lowering_ma);
+ if (rc && rc != -EINVAL)
+ goto bail_out;
+
+ rc = of_property_read_u32(node, "qcom,parallel-main-chg-fcc-percent",
+ &chip->parallel.main_chg_fcc_percent);
+ if (rc < 0)
+ chip->parallel.main_chg_fcc_percent =
+ MAIN_CHG_DEFAULT_FCC_PERCENT;
+
+ rc = of_property_read_u32(node, "qcom,parallel-main-chg-icl-percent",
+ &chip->parallel.main_chg_icl_percent);
+ if (rc < 0)
+ chip->parallel.main_chg_icl_percent =
+ MAIN_CHG_DEFAULT_ICL_PERCENT;
+
+ chip->parallel.avail = true;
+ pr_debug("parallel main charger settings: min_curr = %d, min_9v_curr = %d, min_12v_curr = %d, allowed_lowering = %d, fcc_percent = %d, icl_percent = %d\n",
+ chip->parallel.min_current_thr_ma,
+ chip->parallel.min_9v_current_thr_ma,
+ chip->parallel.min_12v_current_thr_ma,
+ chip->parallel.allowed_lowering_ma,
+ chip->parallel.main_chg_fcc_percent,
+ chip->parallel.main_chg_icl_percent);
+
+ return 0;
+
+bail_out:
+ chip->parallel.min_current_thr_ma = -EINVAL;
+ chip->parallel.min_9v_current_thr_ma = -EINVAL;
+ chip->parallel.min_12v_current_thr_ma = -EINVAL;
+ chip->parallel.allowed_lowering_ma = -EINVAL;
+ pr_debug("parallel charger not support!\n");
+
+ return 0;
+}
+
+static int smb1351_parse_dt(struct smb1351_charger *chip)
+{
+ int rc;
+ struct device_node *node = chip->dev->of_node;
+
+ if (!node) {
+ pr_err("device tree info. missing\n");
+ return -EINVAL;
+ }
+
+ chip->chg_en_gpio = of_get_named_gpio_flags(node, "qcom,en-gpio", 0, 0);
+ chip->chg_led_gpio = of_get_named_gpio_flags(node, "qcom,led-gpio", 0, 0);
+#ifdef LENOVO_OTG_USB_SHORT
+ chip->otg_usb_short_gpio = of_get_named_gpio_flags(node, "qcom,otg-short-gpio", 0, 0);
+#endif
+
+ chip->usb_suspended_status = of_property_read_bool(node,
+ "qcom,charging-disabled");
+
+ chip->chg_autonomous_mode = of_property_read_bool(node,
+ "qcom,chg-autonomous-mode");
+
+ chip->disable_apsd = of_property_read_bool(node, "qcom,disable-apsd");
+
+ chip->using_pmic_therm = of_property_read_bool(node,
+ "qcom,using-pmic-therm");
+ chip->bms_controlled_charging = of_property_read_bool(node,
+ "qcom,bms-controlled-charging");
+ chip->force_hvdcp_2p0 = of_property_read_bool(node,
+ "qcom,force-hvdcp-2p0");
+
+ rc = of_property_read_string(node, "qcom,bms-psy-name",
+ &chip->bms_psy_name);
+ if (rc)
+ chip->bms_psy_name = NULL;
+ chip->resume_soc = -EINVAL;
+ if (of_property_read_bool(node, "qcom,use-external-fg")) {
+ chip->use_external_fg = true;
+ chip->wa_flags |= CHG_FORCE_ESR_WA | CHG_FG_NOTIFY_JEITA
+ | CHG_ITERM_CHECK_SOC;
+ rc = of_property_read_u32(node, "qcom,resume-soc",
+ &chip->resume_soc);
+ if (!rc && chip->resume_soc > 0)
+ chip->wa_flags |= CHG_SOC_BASED_RESUME;
+ }
+
+ rc = of_property_read_u32(node, "qcom,fastchg-current-max-ma",
+ &chip->target_fastchg_current_max_ma);
+ if (rc)
+ chip->target_fastchg_current_max_ma = SMB1351_CHG_FAST_MAX_MA;
+
+ chip->iterm_disabled = of_property_read_bool(node,
+ "qcom,iterm-disabled");
+
+ rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma);
+ if (rc)
+ chip->iterm_ma = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+ &chip->vfloat_mv);
+ if (rc)
+ chip->vfloat_mv = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,recharge-mv",
+ &chip->recharge_mv);
+ if (rc)
+ chip->recharge_mv = -EINVAL;
+
+ chip->recharge_disabled = of_property_read_bool(node,
+ "qcom,recharge-disabled");
+
+ rc = of_property_read_u32(node, "qcom,switch-freq",
+ &chip->switch_freq);
+ if (rc < 0)
+ chip->switch_freq = -EINVAL;
+ /* thermal and jeita support */
+ rc = of_property_read_u32(node, "qcom,batt-cold-decidegc",
+ &chip->batt_cold_decidegc);
+ if (rc < 0)
+ chip->batt_cold_decidegc = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,batt-hot-decidegc",
+ &chip->batt_hot_decidegc);
+ if (rc < 0)
+ chip->batt_hot_decidegc = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,batt-warm-decidegc",
+ &chip->batt_warm_decidegc);
+ if (rc < 0)
+ chip->batt_warm_decidegc = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,batt-cool-decidegc",
+ &chip->batt_cool_decidegc);
+ if (rc < 0)
+ chip->batt_cool_decidegc = -EINVAL;
+
+ if ((chip->batt_warm_decidegc != -EINVAL
+ && chip->batt_cool_decidegc != -EINVAL)
+ || (chip->wa_flags & CHG_FG_NOTIFY_JEITA)) {
+ rc = of_property_read_u32(node, "qcom,batt-cool-mv",
+ &chip->batt_cool_mv);
+
+ rc |= of_property_read_u32(node, "qcom,batt-warm-mv",
+ &chip->batt_warm_mv);
+
+ rc |= of_property_read_u32(node, "qcom,batt-cool-ma",
+ &chip->batt_cool_ma);
+
+ rc |= of_property_read_u32(node, "qcom,batt-warm-ma",
+ &chip->batt_warm_ma);
+ if (rc)
+ chip->jeita_supported = false;
+ else if (chip->wa_flags & CHG_FG_NOTIFY_JEITA)
+ chip->fg_notify_jeita = true;
+ else
+ chip->jeita_supported = true;
+ }
+
+ pr_debug("wa_flag = %d, fg_notify_jeita = %d, jeita_supported = %d\n",
+ chip->wa_flags, chip->fg_notify_jeita, chip->jeita_supported);
+
+ rc = of_property_read_u32(node, "qcom,batt-missing-decidegc",
+ &chip->batt_missing_decidegc);
+
+ chip->pinctrl_state_name = of_get_property(node, "pinctrl-names", NULL);
+
+ smb1351_parse_parallel_charger_dt(chip);
+
+ return 0;
+}
+
+#define RERUN_APSD_DELAY_MS 1000
+static int smb1351_determine_initial_state(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg = 0;
+
+ /*
+ * It is okay to read the interrupt status here since
+ * interrupts aren't requested. Reading interrupt status
+ * clears the interrupt so be careful to read interrupt
+ * status only in interrupt handling code
+ */
+
+ rc = smb1351_read_reg(chip, IRQ_B_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_B rc = %d\n", rc);
+ goto fail_init_status;
+ }
+
+ chip->battery_missing = (reg & IRQ_BATT_MISSING_BIT) ? true : false;
+
+ rc = smb1351_read_reg(chip, IRQ_C_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_C rc = %d\n", rc);
+ goto fail_init_status;
+ }
+ chip->batt_full = (reg & IRQ_TERM_BIT) ? true : false;
+
+ rc = smb1351_read_reg(chip, IRQ_A_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read irq A rc = %d\n", rc);
+ return rc;
+ }
+
+ if (reg & IRQ_HOT_HARD_BIT)
+ chip->batt_hot = true;
+ if (reg & IRQ_COLD_HARD_BIT)
+ chip->batt_cold = true;
+ if (reg & IRQ_HOT_SOFT_BIT)
+ chip->batt_warm = true;
+ if (reg & IRQ_COLD_SOFT_BIT)
+ chip->batt_cool = true;
+
+ rc = smb1351_read_reg(chip, IRQ_E_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_E rc = %d\n", rc);
+ goto fail_init_status;
+ }
+
+ if (reg & IRQ_USBIN_UV_BIT) {
+ smb1351_usbin_uv_handler(chip, 1);
+ } else {
+ if (!chip->is_factory_mode) {
+ smb1351_stay_awake(&chip->smb1351_ws, RERUN_APSD);
+ schedule_delayed_work(&chip->rerun_apsd_work,
+ msecs_to_jiffies(RERUN_APSD_DELAY_MS));
+ }
+ }
+
+ rc = smb1351_read_reg(chip, IRQ_G_REG, &reg);
+ if (rc) {
+ pr_err("Couldn't read IRQ_G rc = %d\n", rc);
+ goto fail_init_status;
+ }
+
+ if (reg & IRQ_SOURCE_DET_BIT)
+ smb1351_apsd_complete_handler(chip, 1);
+
+ return 0;
+
+fail_init_status:
+ pr_err("Couldn't determine initial status\n");
+ return rc;
+}
+
+static int is_parallel_slave(struct i2c_client *client)
+{
+ struct device_node *node = client->dev.of_node;
+
+ return of_property_read_bool(node, "qcom,parallel-charger");
+}
+
+static int create_debugfs_entries(struct smb1351_charger *chip)
+{
+ struct dentry *ent;
+ char str[16];
+
+ snprintf(str, ARRAY_SIZE(str), "smb1351_%s",
+ chip->is_slave ? "slave" : "main");
+ chip->debug_root = debugfs_create_dir(str, NULL);
+ if (!chip->debug_root) {
+ pr_err("Couldn't create debug dir\n");
+ } else {
+ ent = debugfs_create_file("config_registers", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &cnfg_debugfs_ops);
+ if (!ent)
+ pr_err("Couldn't create cnfg debug file\n");
+
+ ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &status_debugfs_ops);
+ if (!ent)
+ pr_err("Couldn't create status debug file\n");
+
+ ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &cmd_debugfs_ops);
+ if (!ent)
+ pr_err("Couldn't create cmd debug file\n");
+
+ ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root,
+ &(chip->peek_poke_address));
+ if (!ent)
+ pr_err("Couldn't create address debug file\n");
+
+ ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root, chip,
+ &poke_poke_debug_ops);
+ if (!ent)
+ pr_err("Couldn't create data debug file\n");
+ if (!chip->is_slave) {
+ ent = debugfs_create_file("force_irq",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root, chip,
+ &force_irq_ops);
+ if (!ent)
+ pr_err("Couldn't create data debug file\n");
+
+ ent = debugfs_create_file("irq_count",
+ S_IFREG | S_IRUGO,
+ chip->debug_root, chip,
+ &irq_count_debugfs_ops);
+ if (!ent)
+ pr_err("Couldn't create count debug file\n");
+ }
+ }
+ return 0;
+}
+
+static int smb1351_regulator_get(struct smb1351_charger *chip)
+{
+ int ret;
+
+ pr_info("%s\n", __func__);
+
+ chip->vdd = regulator_get(&chip->client->dev, "vdd");
+ if (IS_ERR_OR_NULL(chip->vdd)) {
+ pr_err("%s: fail to get 1.8v LDO\n", __func__);
+ return -3;
+ }
+
+ if (regulator_count_voltages(chip->vdd) > 0) {
+ ret = regulator_set_voltage(chip->vdd, 1800000, 1800000);
+ if (ret) {
+ pr_err("%s: regulator set_vtg vdd_reg failed rc=%d\n", __func__, ret);
+ regulator_put(chip->vdd);
+ return -4;
+ }
+ }
+
+ ret = regulator_enable(chip->vdd);
+ if (ret) {
+ pr_err("%s: Regulator vdd_reg enable failed rc=%d\n", __func__, ret);
+ }
+
+ return 0;
+}
+
+static bool smb1351_charger_is_factory_mode(void)
+{
+ struct device_node *np = of_find_node_by_path("/chosen");
+ bool factory = false;
+
+ if (np)
+ factory = of_property_read_bool(np, "mmi,factory-cable");
+
+ of_node_put(np);
+
+ return factory;
+}
+
+static bool smb1351_is_usb_present(struct smb1351_charger *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smb1351_read_reg(chip, STATUS_1_REG, &reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read rt status rc = %d\n", rc);
+ return false;
+ }
+ reg &= 0xf0;
+
+ return reg?true:false;
+}
+
+static int smb1351_reboot(struct notifier_block *nb,
+ unsigned long event, void *unused)
+{
+ struct smb1351_charger *chip =
+ container_of(nb, struct smb1351_charger, mmi_reboot);
+
+ dev_dbg(chip->dev, "SMB Reboot\n");
+ if (!chip) {
+ dev_warn(chip->dev, "called before chip valid!\n");
+ return NOTIFY_DONE;
+ }
+
+ if (chip->is_factory_mode) {
+ dev_info(chip->dev, "SMB Reboot\n");
+
+ switch (event) {
+ case SYS_POWER_OFF:
+ /* Disable Charging */
+ smb1351_battchg_disable(chip, USER, true);
+
+ /* Suspend USB and DC */
+ smb1351_usb_suspend(chip, USER, true);
+
+ while (smb1351_is_usb_present(chip))
+ msleep(100);
+ dev_warn(chip->dev, "VBUS UV wait 1 sec!\n");
+ /* Delay 1 sec to allow more VBUS decay */
+ msleep(1000);
+ break;
+ default:
+ break;
+ }
+ }
+ return NOTIFY_DONE;
+}
+
+static int smb1351_jeita_check_lenovo(struct smb1351_charger *chip, int temp)
+{
+ int health;
+
+ pr_info("temp %d cool %d warm %d\n", temp, chip->batt_cool_decidegc, chip->batt_warm_decidegc);
+
+ if (temp <= chip->batt_cold_decidegc)
+ health = POWER_SUPPLY_HEALTH_COLD;
+ else if (temp < chip->batt_cool_decidegc)
+ health = POWER_SUPPLY_HEALTH_COOL;
+ else if (temp < chip->batt_warm_decidegc)
+ health = POWER_SUPPLY_HEALTH_GOOD;
+ else if (temp < chip->batt_hot_decidegc)
+ health = POWER_SUPPLY_HEALTH_WARM;
+ else
+ health = POWER_SUPPLY_HEALTH_OVERHEAT;
+
+ return health;
+}
+
+#ifdef CHG_HEATBEAT_WORK
+static void smb1351_heatbeat_work(struct work_struct *work)
+{
+ struct smb1351_charger *chip = container_of(work, struct smb1351_charger, heatbeat_work.work);
+ unsigned char state;
+ int vbus_uv = 0, temp_bat = 0, temp_b = 0, temp_c = 0, soc = 0, vol = 0, cur = 0;
+ static int pre_health = -1;
+ int health;
+
+ smb1351_read_reg(chip, STATUS_4_REG, &state);
+ if (!chip->is_slave) {
+ vbus_uv = smb1351_get_vbus_voltage(chip);
+ temp_b = smb1351_get_board_temp(chip, BOARD_TEMP);
+ temp_c = smb1351_get_board_temp(chip, CHARGE_TEMP);
+ if (vbus_uv > 5600000)
+ chip->is_hv_fast_charging = true;
+ else
+ chip->is_hv_fast_charging = false;
+ temp_bat = smb1351_get_prop_batt_temp(chip);
+ smb1351_get_bms_property(chip, POWER_SUPPLY_PROP_CAPACITY, &soc);
+ smb1351_get_bms_property(chip, POWER_SUPPLY_PROP_VOLTAGE_NOW, &vol);
+ smb1351_get_bms_property(chip, POWER_SUPPLY_PROP_CURRENT_NOW, &cur);
+ pr_info(" %s, chg state 0x%x, soc %d, vbat %d, cur %d, vbus %d, battemp %d, btemp %d, ctemp %d\n",
+ dev_name(chip->dev), state, soc, vol, cur, vbus_uv, temp_bat, temp_b, temp_c);
+
+ health = smb1351_jeita_check_lenovo(chip, temp_bat);
+ if (health != pre_health) {
+ pr_info("jeita change. bat heal %d pre %d\n", health, pre_health);
+ smb1351_set_ext_charger_property(chip, POWER_SUPPLY_PROP_HEALTH, health);
+ pre_health = health;
+ }
+
+ if((vbus_uv < 2000000) && (vbus_uv > 0) && (chip->chg_present == true)) {
+ pr_info("%s, chg remove but not do remove work\n",dev_name(chip->dev));
+ smb1351_usbin_uv_handler(chip, 1);
+ }
+ } else
+ pr_info(" %s, chg state 0x%x\n", dev_name(chip->dev), state);
+
+ schedule_delayed_work(&chip->heatbeat_work, (HZ * 10));
+}
+#endif
+
+#ifdef EXT_CHARGER_POWER_SUPPLY
+static char *ext_charger_chip_name = "ext-charger";
+static struct smb1351_charger *smb1351_charger_chip = NULL;
+
+static enum power_supply_property ext_charger_power_supply_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+// POWER_SUPPLY_PROP_ONLINE,
+// POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TEMP,
+// POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_CHARGE_ENABLED,
+ POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+// POWER_SUPPLY_PROP_FLASH_ACTIVE,
+// POWER_SUPPLY_PROP_USB_OTG,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+static int ext_charger_power_supply_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb1351_charger *chip = container_of(psy, struct smb1351_charger, ext_charger_psy);
+ int value;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = 25;//smb1351_get_board_temp(chip, PA1_TEMP);//board ntc temp
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ val->intval = !chip->usb_suspended_status;
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ val->intval = !chip->battchg_disabled_status;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ smb1351_get_usb_chg_current(chip, &value);
+ val->intval = value;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = chip->fastchg_current_max_ma;
+ break;
+ default:
+ pr_err("%s unsupported psy %d\n", __func__, psp);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ext_charger_power_supply_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smb1351_charger *chip = container_of(psy, struct smb1351_charger, ext_charger_psy);
+ int value;
+
+ switch (psp) {
+ //just for charging temp protect function debug
+ case POWER_SUPPLY_PROP_TEMP:
+ pr_err("%s debug charging temp, intval = %d\n", __func__, val->intval);
+ if ((val->intval<-200) ||(val->intval>600)) {
+ chip->temp_debug_flag = false;
+ } else {
+ chip->temp_debug_flag = true;
+ value = val->intval;
+ chip->batt_temp = val->intval;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ chip->is_factory_need_write = true;
+ smb1351_usb_suspend(chip, USER, !val->intval);
+ chip->is_factory_need_write = false;
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ chip->is_factory_need_write = true;
+ smb1351_battchg_disable(chip, USER, !val->intval);
+ chip->is_factory_need_write = false;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ value = val->intval;
+ chip->is_factory_need_write = true;
+ if (!value)
+ smb1351_usb_suspend(chip, USER, 1);
+ else {
+ if (chip->usb_suspended_status)
+ smb1351_usb_suspend(chip, USER, 0);
+
+ smb1351_set_usb_chg_current(chip, value);
+ }
+ chip->is_factory_need_write = false;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ value = val->intval;
+ chip->is_factory_need_write = true;
+ if (!value)
+ smb1351_battchg_disable(chip, USER, 1);
+ else {
+ if (!chip->battchg_disabled_status)
+ smb1351_battchg_disable(chip, USER, 0);
+
+ smb1351_fastchg_hv_current_set(chip, value);
+ }
+ chip->is_factory_need_write = false;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ value = val->intval;
+ chip->is_factory_need_write = true;
+ if (!value)
+ smb1351_battchg_disable(chip, USER, 1);
+ else {
+ if (chip->battchg_disabled_status)
+ smb1351_battchg_disable(chip, USER, 0);
+
+ smb1351_fastchg_hv_current_set(chip, value);
+ }
+ chip->is_factory_need_write = false;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (!chip->is_factory_mode) {
+ smb1351_handle_jeita_lenovo(chip, val->intval);
+ power_supply_changed(&chip->batt_psy);
+ }
+ break;
+ default:
+ pr_err("%s not support power_supply property cmd\n", __func__);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ext_charger_power_supply_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_HEALTH:
+ return 1;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int ext_charger_power_supply_init(struct smb1351_charger *chip)
+{
+ int ret;
+
+ chip->ext_charger_psy.name = ext_charger_chip_name;
+ chip->ext_charger_psy.type = POWER_SUPPLY_TYPE_BMS;
+ chip->ext_charger_psy.properties = ext_charger_power_supply_props;
+ chip->ext_charger_psy.num_properties = ARRAY_SIZE(ext_charger_power_supply_props);
+ chip->ext_charger_psy.get_property = ext_charger_power_supply_get_property;
+ chip->ext_charger_psy.set_property = ext_charger_power_supply_set_property;
+ chip->ext_charger_psy.property_is_writeable = ext_charger_power_supply_property_is_writeable;
+
+ ret = power_supply_register(&chip->client->dev, &chip->ext_charger_psy);
+ if (ret)
+ {
+ pr_err("psy register err %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static void ext_charger_power_supply_exit(struct smb1351_charger *chip)
+{
+ //cancel_delayed_work_sync(&chip->work);
+ power_supply_unregister(&chip->ext_charger_psy);
+
+}
+
+static ssize_t led_opt_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ ssize_t ret = strnlen(buf, PAGE_SIZE);
+ int cmd;
+ int value = -1;
+ struct smb1351_charger *chip = smb1351_charger_chip;
+
+ if (!chip) {
+ pr_err("no valid chip value, return\n");
+ return ret;
+ }
+
+ sscanf(buf, "%x", &cmd);
+ value = smb1351_led_operation(chip, cmd?1:0);
+
+ return ret;
+}
+
+
+static ssize_t led_opt_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct smb1351_charger *chip = smb1351_charger_chip;
+ int val;
+ int ret = 0;
+
+ if (!chip) {
+ pr_err("no valid chip value, return\n");
+ return ret;
+ }
+
+ val = smb1351_led_state(chip);
+
+ pr_info("led state is %d\n", val);
+ ret = sprintf(buf, "%d\n", val);
+
+ return ret;
+}
+
+static DEVICE_ATTR(led_opt, S_IRUGO|S_IWUSR, led_opt_get, led_opt_set);
+
+static ssize_t fast_charger_need_change_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ ssize_t ret = strnlen(buf, PAGE_SIZE);
+ int cmd;
+ s32 rc;
+ struct smb1351_charger *chip = smb1351_charger_chip;
+
+ if (!chip) {
+ pr_err("no valid chip value, return\n");
+ return ret;
+ }
+
+ while (chip->is_in_esr_state) {
+ msleep(500);
+ pr_info("in esr state. wait to handshake prepare\n");
+ }
+
+ sscanf(buf, "%x", &cmd);
+ chip->handshake_state = cmd;
+ if (chip->handshake_state) {
+ pr_info("handshake prepare\n");
+
+ smb1351_parallel_charger_disable_slave(chip);
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, 1, 0);
+ if (rc) {
+ pr_err("Set 0x31 0 failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->handshake_state = true;
+ } else {
+ pr_info("handshake finish\n");
+
+ rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, BIT(0), CMD_USB_HC_MODE);
+ if (rc) {
+ pr_err("Set 0x31 0 failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ chip->handshake_state = false;
+
+ rc = smb1351_get_vbus_voltage(chip);
+ if (rc > 6000000)
+ chip->is_hv_fast_charging = true;
+ else
+ chip->is_hv_fast_charging = false;
+ }
+
+ return ret;
+}
+
+
+static ssize_t fast_charger_need_change_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct smb1351_charger *chip = smb1351_charger_chip;
+ int val;
+ int ret = 0;
+
+ if (!chip) {
+ pr_err("no valid chip value, return\n");
+ return ret;
+ }
+
+ val = chip->handshake_state;
+
+ pr_info("handshake_state is %d\n", val);
+ ret = sprintf(buf, "%d\n", val);
+
+ return ret;
+}
+
+static DEVICE_ATTR(fast_charger_need_change, S_IRUGO|S_IWUSR, fast_charger_need_change_get, fast_charger_need_change_set);
+
+static ssize_t fast_charger_is_engineermode_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ ssize_t ret = strnlen(buf, PAGE_SIZE);
+ int cmd;
+ struct smb1351_charger *chip = smb1351_charger_chip;
+
+ sscanf(buf, "%x", &cmd);
+ chip->fast_charger_is_engineermode = cmd;
+
+ pr_info("fast charge engineer ret %d\n", chip->fast_charger_is_engineermode);
+
+ return ret;
+}
+
+ssize_t fast_charger_is_engineermode_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct smb1351_charger *chip = smb1351_charger_chip;
+
+ ret = sprintf(buf, "%d\n", chip->fast_charger_is_engineermode);
+
+ return ret;
+}
+
+static DEVICE_ATTR(fast_charger_is_engineermode, S_IRUGO|S_IWUSR, fast_charger_is_engineermode_get,fast_charger_is_engineermode_set);
+
+#ifdef LENOVO_OTG_USB_SHORT
+static int smb1351_turn_otg_vbus(struct smb1351_charger *chip, bool votg_on)
+{
+ int rc = 0;
+
+ if (votg_on) {
+ rc = smb1351_masked_write(chip, OTG_TLIM_CTRL_REG, OTG_OC_LIMIT_MASK,
+ 0x00);
+ if (rc)
+ pr_err("Couldn't set OTG_OC_LIMIT rc=%d\n", rc);
+
+ rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT,
+ CMD_OTG_EN_BIT);
+ if (rc)
+ pr_err("Couldn't enable OTG mode rc=%d\n", rc);
+ } else {
+ rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT, 0);
+ if (rc)
+ pr_err("Couldn't disable OTG mode rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+int smb1351_otg_short_config(struct smb1351_charger *chip, int en)
+{
+ int value;
+
+ if (en==1) {
+ if (gpio_is_valid(chip->otg_usb_short_gpio)) {
+ chip->otg_usb_short_state = true;
+
+ smb1351_turn_otg_vbus(chip, 0);
+ msleep(1000);
+ gpio_set_value(chip->otg_usb_short_gpio, 1);
+ msleep(1000);
+ smb1351_turn_otg_vbus(chip, 1);
+
+ if (!chip->otg_usb_short_state) {
+ pr_info("find otg cable out, turn off vbus\n");
+ gpio_set_value(chip->otg_usb_short_gpio, 0);
+ smb1351_turn_otg_vbus(chip, 0);
+ }
+ }else
+ chip->otg_usb_short_state = false;
+ } else {
+ if (gpio_is_valid(chip->otg_usb_short_gpio)) {
+ smb1351_turn_otg_vbus(chip, 0);
+ gpio_set_value(chip->otg_usb_short_gpio, 0);
+ msleep(2000);
+ smb1351_turn_otg_vbus(chip, 1);
+
+ if (!chip->otg_usb_short_state) {
+ pr_info("find otg cable out, turn off vbus\n");
+ gpio_set_value(chip->otg_usb_short_gpio, 0);
+ smb1351_turn_otg_vbus(chip, 0);
+ }
+
+ chip->otg_usb_short_state = false;
+ } else
+ chip->otg_usb_short_state = false;
+ }
+
+ value = gpio_get_value(chip->otg_usb_short_gpio);
+
+ pr_info("state is %d, en %d, ret %d\n",chip->otg_usb_short_state, en, value);
+
+ return value;
+}
+
+static ssize_t otg_usb_short_set(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ ssize_t ret = strnlen(buf, PAGE_SIZE);
+ int cmd;
+ int value = -1;
+ struct smb1351_charger *chip = smb1351_charger_chip;
+
+ sscanf(buf, "%x", &cmd);
+
+ if(chip->otg_usb_short_state==cmd)
+ {
+ pr_err("%s new cmd is as same as the old value(%d)\n", __func__, chip->otg_usb_short_state);
+ return ret;
+ }
+
+ if(!chip->is_otg_on)
+ {
+ pr_err("%s otg is off, ignore usb short config\n", __func__);
+ return ret;
+ }
+
+ value = smb1351_otg_short_config(chip, cmd);
+
+ pr_info("otg usb short state is %d, cmd %d, ret %d\n",chip->otg_usb_short_state, cmd, value);
+
+ return ret;
+}
+
+
+ssize_t otg_usb_short_get(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ struct smb1351_charger *chip = smb1351_charger_chip;
+
+ pr_info("otg usb short state is %d\n",chip->otg_usb_short_state);
+ ret = sprintf(buf, "%d\n", chip->otg_usb_short_state);
+
+ return ret;
+}
+
+static DEVICE_ATTR(otg_usb_short, S_IRUGO|S_IWUSR,otg_usb_short_get,otg_usb_short_set);
+#endif
+
+static struct attribute *fs_attrs[] = {
+ &dev_attr_led_opt.attr,
+ &dev_attr_fast_charger_need_change.attr,
+#ifdef LENOVO_OTG_USB_SHORT
+ &dev_attr_otg_usb_short.attr,
+#endif
+ &dev_attr_fast_charger_is_engineermode.attr,
+ NULL,
+};
+
+static struct attribute_group fs_attr_group = {
+ .attrs = fs_attrs,
+};
+#endif
+static int smb1351_main_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int rc;
+ struct smb1351_charger *chip;
+ struct power_supply *usb_psy;
+ u8 reg = 0;
+
+ usb_psy = power_supply_get_by_name("usb");
+ if (!usb_psy) {
+ pr_debug("USB psy not found; deferring probe\n");
+ return -EPROBE_DEFER;
+ }
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ pr_err("Couldn't allocate memory\n");
+ return -ENOMEM;
+ }
+
+ chip->client = client;
+ chip->dev = &client->dev;
+ chip->usb_psy = usb_psy;
+ chip->fake_battery_soc = -EINVAL;
+ INIT_DELAYED_WORK(&chip->chg_remove_work, smb1351_chg_remove_work);
+ INIT_DELAYED_WORK(&chip->hvdcp_det_work, smb1351_hvdcp_det_work);
+ INIT_DELAYED_WORK(&chip->rerun_apsd_work, smb1351_rerun_apsd_work);
+ smb1351_wakeup_src_init(chip);
+
+ smb1351_regulator_get(chip);
+
+ /* probe the device to check if its actually connected */
+ rc = smb1351_read_reg(chip, CHG_REVISION_REG, &reg);
+ if (rc) {
+ pr_err("Failed to detect smb1351, device may be absent\n");
+ rc = -ENODEV;
+ goto trash_ws;
+ }
+ pr_debug("smb1351 chip revision is %d\n", reg);
+
+ rc = smb1351_parse_dt(chip);
+ if (rc) {
+ pr_err("Couldn't parse DT nodes rc=%d\n", rc);
+ goto trash_ws;
+ }
+
+ /* using vadc and adc_tm for implementing pmic therm */
+ if (chip->using_pmic_therm) {
+ chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg");
+ if (IS_ERR(chip->vadc_dev)) {
+ rc = PTR_ERR(chip->vadc_dev);
+ if (rc != -EPROBE_DEFER)
+ pr_err("vadc property missing\n");
+ goto trash_ws;
+ }
+ chip->adc_tm_dev = qpnp_get_adc_tm(chip->dev, "chg");
+ if (IS_ERR(chip->adc_tm_dev)) {
+ rc = PTR_ERR(chip->adc_tm_dev);
+ if (rc != -EPROBE_DEFER)
+ pr_err("adc_tm property missing\n");
+ goto trash_ws;
+ }
+ }
+
+ i2c_set_clientdata(client, chip);
+ chip->is_in_esr_state = false;
+ chip->is_hv_fast_charging = false;
+ chip->fast_charger_is_engineermode = false;
+
+ chip->batt_psy.name = "battery";
+ chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->batt_psy.get_property = smb1351_battery_get_property;
+ chip->batt_psy.set_property = smb1351_battery_set_property;
+ chip->batt_psy.property_is_writeable =
+ smb1351_batt_property_is_writeable;
+ chip->batt_psy.properties = smb1351_battery_properties;
+ chip->batt_psy.num_properties =
+ ARRAY_SIZE(smb1351_battery_properties);
+ chip->batt_psy.external_power_changed =
+ smb1351_external_power_changed;
+ chip->batt_psy.supplied_to = pm_batt_supplied_to;
+ chip->batt_psy.num_supplicants = ARRAY_SIZE(pm_batt_supplied_to);
+
+ chip->resume_completed = true;
+ mutex_init(&chip->irq_complete);
+ mutex_init(&chip->fcc_lock);
+ mutex_init(&chip->parallel.lock);
+ mutex_init(&chip->usb_suspend_status_lock);
+
+ rc = power_supply_register(chip->dev, &chip->batt_psy);
+ if (rc) {
+ pr_err("Couldn't register batt psy rc=%d\n", rc);
+ goto destroy_mutex;
+ }
+
+ INIT_DELAYED_WORK(&chip->parallel.parallel_work, smb1351_parallel_work);
+ INIT_DELAYED_WORK(&chip->init_fg_work, smb1351_init_fg_work);
+ INIT_DELAYED_WORK(&chip->iterm_check_soc_work,
+ smb1351_iterm_check_soc_work);
+#ifdef CHG_HEATBEAT_WORK
+ INIT_DELAYED_WORK(&chip->heatbeat_work, smb1351_heatbeat_work);
+#endif
+
+ chip->is_slave = false;
+ chip->is_factory_cable = false;
+ chip->is_factory_need_write = false;
+ chip->is_factory_mode = smb1351_charger_is_factory_mode();
+ if (chip->is_factory_mode) {
+ dev_warn(&client->dev, "Entering Factory Mode\n");
+ rc = device_create_file(chip->dev, &dev_attr_force_chg_fail_clear);
+ if (rc) {
+ pr_err("couldn't create force_chg_fail_clear\n");
+ }
+
+ chip->mmi_reboot.notifier_call = smb1351_reboot;
+ chip->mmi_reboot.next = NULL;
+ chip->mmi_reboot.priority = 1;
+ rc = register_reboot_notifier(&chip->mmi_reboot);
+ if (rc)
+ dev_err(chip->dev, "register for reboot failed\n");
+
+ INIT_DELAYED_WORK(&chip->charger_check_work, smb1351_charger_check_worker);
+ }
+
+ dump_regs(chip);
+
+ rc = smb1351_regulator_init(chip);
+ if (rc) {
+ pr_err("Couldn't initialize smb1351 ragulator rc=%d\n", rc);
+ goto fail_smb1351_regulator_init;
+ }
+
+ rc = smb1351_hw_init(chip);
+ if (rc) {
+ pr_err("Couldn't intialize hardware rc=%d\n", rc);
+ goto fail_smb1351_hw_init;
+ }
+
+ rc = smb1351_determine_initial_state(chip);
+ if (rc) {
+ pr_err("Couldn't determine initial state rc=%d\n", rc);
+ goto fail_smb1351_hw_init;
+ }
+
+ chip->chg_pm_vadc_dev = NULL;
+ chip->chg_pm_vadc_dev = qpnp_get_vadc(chip->dev, "chg-pm");
+ if (IS_ERR(chip->chg_pm_vadc_dev)) {
+ pr_err("get chg_pm_vadc_dev fail\n");
+ rc = PTR_ERR(chip->chg_pm_vadc_dev);
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't get chg_pm_vadc_dev rc=%d\n",
+ rc);
+ goto fail_smb1351_hw_init;
+ }
+
+ chip->chg_pmi_vadc_dev = NULL;
+ chip->chg_pmi_vadc_dev = qpnp_get_vadc(chip->dev, "chg-pmi");
+ if (IS_ERR(chip->chg_pmi_vadc_dev)) {
+ pr_err("get chg_pmi_vadc_dev fail\n");
+ rc = PTR_ERR(chip->chg_pmi_vadc_dev);
+ if (rc != -EPROBE_DEFER)
+ pr_err("Couldn't get chg_pmi_vadc_dev rc=%d\n",
+ rc);
+ goto fail_smb1351_hw_init;
+ }
+
+#ifdef EXT_CHARGER_POWER_SUPPLY
+ smb1351_charger_chip = chip;
+
+ ext_charger_power_supply_init(chip);
+ rc = sysfs_create_group(&chip->ext_charger_psy.dev->kobj, &fs_attr_group);
+ if (rc) {
+ pr_err("Couldn't register fs group=%d\n", rc);
+ goto fail_smb1351_hw_init;
+ }
+#endif
+
+/* if (gpio_is_valid(chip->chg_en_gpio)) {
+ gpio_request(chip->chg_en_gpio, "chg_en_gpio");
+ gpio_direction_output(chip->chg_en_gpio,0);
+ gpio_set_value(chip->chg_en_gpio,0);
+ }
+ else
+ pr_err("%s chg_en_gpio is invalid\n", __func__);*/
+
+ if (gpio_is_valid(chip->chg_led_gpio)) {
+ gpio_request(chip->chg_led_gpio, "chg_led_gpio");
+ gpio_direction_output(chip->chg_led_gpio,0);
+ //gpio_set_value(chip->chg_en_gpio,0);
+ }
+ else
+ pr_err("%s chg_led_gpio is invalid\n", __func__);
+
+#ifdef LENOVO_OTG_USB_SHORT
+ rc = gpio_request_one(chip->otg_usb_short_gpio, GPIOF_DIR_OUT, "otg_usb_short");
+ if (rc)
+ pr_err("%s otg_usb_short_gpio is invalid\n", __func__);
+ else
+ gpio_set_value(chip->otg_usb_short_gpio, 0);
+#endif
+
+ /* STAT irq configuration */
+ if (client->irq) {
+ rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ smb1351_chg_stat_handler, IRQF_ONESHOT,
+ "smb1351_chg_stat_irq", chip);
+ if (rc) {
+ pr_err("Failed STAT irq=%d request rc = %d\n",
+ client->irq, rc);
+ goto fail_smb1351_hw_init;
+ }
+ enable_irq_wake(client->irq);
+ }
+
+ if (chip->using_pmic_therm) {
+ if (!chip->jeita_supported) {
+ /* add hot/cold temperature monitor */
+ chip->adc_param.low_temp = chip->batt_cold_decidegc;
+ chip->adc_param.high_temp = chip->batt_hot_decidegc;
+ } else {
+ chip->adc_param.low_temp = chip->batt_cool_decidegc;
+ chip->adc_param.high_temp = chip->batt_warm_decidegc;
+ }
+ chip->adc_param.timer_interval = ADC_MEAS2_INTERVAL_1S;
+ chip->adc_param.state_request = ADC_TM_WARM_COOL_THR_ENABLE;
+ chip->adc_param.btm_ctx = chip;
+ chip->adc_param.threshold_notification =
+ smb1351_chg_adc_notification;
+ chip->adc_param.channel = LR_MUX1_BATT_THERM;
+
+ rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev,
+ &chip->adc_param);
+ if (rc) {
+ pr_err("requesting ADC error %d\n", rc);
+ goto fail_smb1351_hw_init;
+ }
+ }
+
+ create_debugfs_entries(chip);
+
+ dump_regs(chip);
+
+ wake_lock_init(&chip->charging_lock, WAKE_LOCK_SUSPEND, "charger_handshake_wakelock");
+
+ pr_info("smb1351 successfully probed. charger=%d, batt=%d version=%s\n",
+ chip->chg_present,
+ smb1351_get_prop_batt_present(chip),
+ smb1351_version_str[chip->version]);
+ return 0;
+fail_smb1351_hw_init:
+ regulator_unregister(chip->otg_vreg.rdev);
+fail_smb1351_regulator_init:
+ power_supply_unregister(&chip->batt_psy);
+destroy_mutex:
+ ext_charger_power_supply_exit(chip);
+ mutex_destroy(&chip->irq_complete);
+ mutex_destroy(&chip->fcc_lock);
+ mutex_destroy(&chip->parallel.lock);
+ mutex_destroy(&chip->usb_suspend_status_lock);
+trash_ws:
+ wakeup_source_trash(&chip->smb1351_ws.source);
+
+ return rc;
+}
+
+static int smb1351_parallel_slave_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int rc;
+ struct smb1351_charger *chip;
+ struct device_node *node = client->dev.of_node;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip) {
+ pr_err("Couldn't allocate memory\n");
+ return -ENOMEM;
+ }
+
+ chip->client = client;
+ chip->dev = &client->dev;
+ chip->is_slave = true;
+
+ chip->usb_suspended_status = of_property_read_bool(node,
+ "qcom,charging-disabled");
+ rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+ &chip->vfloat_mv);
+ if (rc)
+ chip->vfloat_mv = -EINVAL;
+ rc = of_property_read_u32(node, "qcom,recharge-mv",
+ &chip->recharge_mv);
+ if (rc)
+ chip->recharge_mv = -EINVAL;
+
+ rc = of_property_read_u32(node, "qcom,parallel-en-pin-polarity",
+ &chip->parallel_pin_polarity_setting);
+ if (rc)
+ chip->parallel_pin_polarity_setting = EN_BY_PIN_LOW_ENABLE;
+ else
+ chip->parallel_pin_polarity_setting =
+ chip->parallel_pin_polarity_setting ?
+ EN_BY_PIN_HIGH_ENABLE : EN_BY_PIN_LOW_ENABLE;
+
+ i2c_set_clientdata(client, chip);
+
+ chip->parallel_psy.name = PSY_PARALLEL_NAME;//"usb-parallel";
+ chip->parallel_psy.type = POWER_SUPPLY_TYPE_USB_PARALLEL;
+ chip->parallel_psy.get_property = smb1351_parallel_get_property;
+ chip->parallel_psy.set_property = smb1351_parallel_set_property;
+ chip->parallel_psy.properties = smb1351_parallel_properties;
+ chip->parallel_psy.property_is_writeable
+ = smb1351_parallel_is_writeable;
+ chip->parallel_psy.num_properties
+ = ARRAY_SIZE(smb1351_parallel_properties);
+
+ mutex_init(&chip->fcc_lock);
+ mutex_init(&chip->irq_complete);
+ mutex_init(&chip->usb_suspend_status_lock);
+ smb1351_wakeup_src_init(chip);
+#ifdef CHG_HEATBEAT_WORK
+ INIT_DELAYED_WORK(&chip->heatbeat_work, smb1351_heatbeat_work);
+#endif
+
+ rc = power_supply_register(chip->dev, &chip->parallel_psy);
+ if (rc) {
+ pr_err("Couldn't register parallel psy rc=%d\n", rc);
+ goto fail_register_psy;
+ }
+
+ chip->resume_completed = true;
+ create_debugfs_entries(chip);
+
+ smb1351_regulator_get(chip);
+ smb1351_enable_volatile_writes(chip);
+ smb1351_disable_hw_jieta(chip, 1);
+
+ pr_info("smb1351 parallel successfully probed.\n");
+
+ return 0;
+
+fail_register_psy:
+ wakeup_source_trash(&chip->smb1351_ws.source);
+ mutex_destroy(&chip->irq_complete);
+ mutex_destroy(&chip->fcc_lock);
+ mutex_destroy(&chip->usb_suspend_status_lock);
+ return rc;
+}
+
+static int smb1351_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ if (is_parallel_slave(client))
+ return smb1351_parallel_slave_probe(client, id);
+ else
+ return smb1351_main_charger_probe(client, id);
+}
+
+static int smb1351_charger_remove(struct i2c_client *client)
+{
+ struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+ cancel_delayed_work_sync(&chip->chg_remove_work);
+ power_supply_unregister(&chip->batt_psy);
+#ifdef EXT_CHARGER_POWER_SUPPLY
+ if (!chip->is_slave)
+ ext_charger_power_supply_exit(chip);
+#endif
+
+ wakeup_source_trash(&chip->smb1351_ws.source);
+ mutex_destroy(&chip->irq_complete);
+ mutex_destroy(&chip->fcc_lock);
+ mutex_destroy(&chip->usb_suspend_status_lock);
+ if (is_parallel_slave(client))
+ mutex_destroy(&chip->parallel.lock);
+ debugfs_remove_recursive(chip->debug_root);
+ return 0;
+}
+
+static void smb1351_shutdown(struct i2c_client *client)
+{
+ struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+ pr_info("%s:smb1351_shutdown \r\n", dev_name(chip->dev));
+
+ if (chip->is_hv_fast_charging) {
+ pr_info("%s: reset hv charger \r\n", dev_name(chip->dev));
+ smb1351_set_hv_handshake_current(chip, 100);
+ msleep(250);
+ }
+ smb1351_reset(chip);
+
+ pr_info("smb1351_shutdown complete\r\n");
+}
+
+static int smb1351_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+ /* no suspend resume activities for parallel charger */
+ if (chip->is_slave)
+ return 0;
+
+ mutex_lock(&chip->irq_complete);
+ chip->resume_completed = false;
+ mutex_unlock(&chip->irq_complete);
+
+ return 0;
+}
+
+static int smb1351_suspend_noirq(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+ /* no suspend resume activities for parallel charger */
+ if (chip->is_slave)
+ return 0;
+
+ if (chip->irq_waiting) {
+ pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int smb1351_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+ /* no suspend resume activities for parallel charger */
+ if (chip->is_slave)
+ return 0;
+
+ mutex_lock(&chip->irq_complete);
+ chip->resume_completed = true;
+ if (chip->irq_waiting) {
+ mutex_unlock(&chip->irq_complete);
+ smb1351_chg_stat_handler(client->irq, chip);
+ enable_irq(client->irq);
+ } else {
+ mutex_unlock(&chip->irq_complete);
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops smb1351_pm_ops = {
+ .suspend = smb1351_suspend,
+ .suspend_noirq = smb1351_suspend_noirq,
+ .resume = smb1351_resume,
+};
+
+static struct of_device_id smb1351_match_table[] = {
+ { .compatible = "qcom,smb1351-charger",},
+ { },
+};
+
+static const struct i2c_device_id smb1351_charger_id[] = {
+ {"smb1351-charger", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, smb1351_charger_id);
+
+static struct i2c_driver smb1351_charger_driver = {
+ .driver = {
+ .name = "smb1351-charger",
+ .owner = THIS_MODULE,
+ .of_match_table = smb1351_match_table,
+ .pm = &smb1351_pm_ops,
+ },
+ .probe = smb1351_charger_probe,
+ .remove = smb1351_charger_remove,
+ .shutdown = smb1351_shutdown,
+ .id_table = smb1351_charger_id,
+};
+
+module_i2c_driver(smb1351_charger_driver);
+
+MODULE_DESCRIPTION("smb1351 Charger");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("i2c:smb1351-charger");
diff --git a/drivers/power/qcom/debug_core.c b/drivers/power/qcom/debug_core.c
index d3620bbb..ccef04ae 100644
--- a/drivers/power/qcom/debug_core.c
+++ b/drivers/power/qcom/debug_core.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -22,6 +22,8 @@
#include "soc/qcom/msm-core.h"
#define MAX_PSTATES 50
+#define NUM_OF_PENTRY 3 /* number of variables for ptable node */
+#define NUM_OF_EENTRY 2 /* number of variables for enable node */
enum arg_offset {
CPU_OFFSET,
@@ -82,15 +84,28 @@ static struct debugfs_blob_wrapper help_msg = {
};
-static void add_to_ptable(uint64_t *arg)
+static void add_to_ptable(unsigned int *arg)
{
struct core_debug *node;
int i, cpu = arg[CPU_OFFSET];
+ uint32_t freq = arg[FREQ_OFFSET];
+ uint32_t power = arg[POWER_OFFSET];
if (!cpu_possible(cpu))
return;
+ if ((freq == 0) || (power == 0)) {
+ pr_warn("Incorrect power data\n");
+ return;
+ }
+
node = &per_cpu(c_dgfs, cpu);
+
+ if (node->len >= MAX_PSTATES) {
+ pr_warn("Dropped ptable update - no space left.\n");
+ return;
+ }
+
if (!node->head) {
node->head = kzalloc(sizeof(struct cpu_pstate_pwr) *
(MAX_PSTATES + 1),
@@ -98,24 +113,18 @@ static void add_to_ptable(uint64_t *arg)
if (!node->head)
return;
}
- for (i = 0; i < MAX_PSTATES; i++) {
- if (node->head[i].freq == arg[FREQ_OFFSET]) {
- node->head[i].power = arg[POWER_OFFSET];
+
+ for (i = 0; i < node->len; i++) {
+ if (node->head[i].freq == freq) {
+ node->head[i].power = power;
return;
}
- if (node->head[i].freq == 0)
- break;
- }
-
- if (i == MAX_PSTATES) {
- pr_warn("Dropped ptable update - no space left.\n");
- return;
}
/* Insert a new frequency (may need to move things around to
keep in ascending order). */
for (i = MAX_PSTATES - 1; i > 0; i--) {
- if (node->head[i-1].freq > arg[FREQ_OFFSET]) {
+ if (node->head[i-1].freq > freq) {
node->head[i].freq = node->head[i-1].freq;
node->head[i].power = node->head[i-1].power;
} else if (node->head[i-1].freq != 0) {
@@ -123,23 +132,29 @@ static void add_to_ptable(uint64_t *arg)
}
}
- node->head[i].freq = arg[FREQ_OFFSET];
- node->head[i].power = arg[POWER_OFFSET];
- node->len++;
+ if (node->len < MAX_PSTATES) {
+ node->head[i].freq = freq;
+ node->head[i].power = power;
+ node->len++;
+ }
if (node->ptr)
node->ptr->len = node->len;
}
-static int split_ptable_args(char *line, uint64_t *arg)
+static int split_ptable_args(char *line, unsigned int *arg, uint32_t n)
{
char *args;
int i;
int ret = 0;
- for (i = 0; line; i++) {
+ for (i = 0; i < n; i++) {
+ if (!line)
+ break;
args = strsep(&line, " ");
- ret = kstrtoull(args, 10, &arg[i]);
+ ret = kstrtouint(args, 10, &arg[i]);
+ if (ret)
+ return ret;
}
return ret;
}
@@ -149,7 +164,7 @@ static ssize_t msm_core_ptable_write(struct file *file,
{
char *kbuf;
int ret;
- uint64_t arg[3];
+ unsigned int arg[3];
if (len == 0)
return 0;
@@ -163,7 +178,7 @@ static ssize_t msm_core_ptable_write(struct file *file,
goto done;
}
kbuf[len] = '\0';
- ret = split_ptable_args(kbuf, arg);
+ ret = split_ptable_args(kbuf, arg, NUM_OF_PENTRY);
if (!ret) {
add_to_ptable(arg);
ret = len;
@@ -201,7 +216,7 @@ static int msm_core_ptable_read(struct seq_file *m, void *data)
seq_printf(m, "--- CPU%d - Live numbers at %ldC---\n",
cpu, node->ptr->temp);
print_table(m, msm_core_data[cpu].ptable,
- msm_core_data[cpu].len);
+ node->driver_len);
}
}
return 0;
@@ -212,7 +227,7 @@ static ssize_t msm_core_enable_write(struct file *file,
{
char *kbuf;
int ret;
- uint64_t arg[3];
+ unsigned int arg[3];
int cpu;
if (len == 0)
@@ -227,7 +242,7 @@ static ssize_t msm_core_enable_write(struct file *file,
goto done;
}
kbuf[len] = '\0';
- ret = split_ptable_args(kbuf, arg);
+ ret = split_ptable_args(kbuf, arg, NUM_OF_EENTRY);
if (ret)
goto done;
cpu = arg[CPU_OFFSET];
diff --git a/drivers/power/qpnp-fg.c b/drivers/power/qpnp-fg.c
index d0d7fb6d..debffb70 100644
--- a/drivers/power/qpnp-fg.c
+++ b/drivers/power/qpnp-fg.c
@@ -9,8 +9,7 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
-
-#define pr_fmt(fmt) "FG: %s: " fmt, __func__
+#define pr_fmt(fmt) "FG : %s: " fmt, __func__
#include <linux/atomic.h>
#include <linux/delay.h>
@@ -79,6 +78,14 @@
_adc_val = (u8)((_current) * 100 / 976); \
}
+/*lenovo-sw weiwei added for psy name*/
+#define PSY_NAME "bms"
+#define CHARGER_PSY_NAME "battery"
+#define PSY_TYPE POWER_SUPPLY_TYPE_BMS;
+
+#define FG_HEATBEAT_WORK
+/*lenovo-sw weiwei added for psy name end*/
+
/* Debug Flag Definitions */
enum {
FG_SPMI_DEBUG_WRITES = BIT(0), /* Show SPMI writes */
@@ -504,6 +511,9 @@ struct fg_chip {
struct work_struct charge_full_work;
struct work_struct gain_comp_work;
struct work_struct bcl_hi_power_work;
+#ifdef FG_HEATBEAT_WORK
+ struct delayed_work battery_heatbeat_work;
+#endif
struct power_supply *batt_psy;
struct power_supply *usb_psy;
struct power_supply *dc_psy;
@@ -518,6 +528,7 @@ struct fg_chip {
struct fg_wakeup_source update_sram_wakeup_source;
bool fg_restarting;
bool profile_loaded;
+ bool soc_reporting_ready;
bool use_otp_profile;
bool battery_missing;
bool power_supply_registered;
@@ -528,6 +539,7 @@ struct fg_chip {
bool charge_done;
bool resume_soc_lowered;
bool vbat_low_irq_enabled;
+ bool full_soc_irq_enabled;
bool charge_full;
bool hold_soc_while_full;
bool input_present;
@@ -540,6 +552,7 @@ struct fg_chip {
bool fg_shutdown;
bool use_soft_jeita_irq;
bool allow_false_negative_isense;
+
struct delayed_work update_jeita_setting;
struct delayed_work update_sram_data;
struct delayed_work update_temp_work;
@@ -1331,8 +1344,11 @@ static void fg_enable_irqs(struct fg_chip *chip, bool enable)
if (enable) {
enable_irq(chip->soc_irq[DELTA_SOC].irq);
enable_irq_wake(chip->soc_irq[DELTA_SOC].irq);
- enable_irq(chip->soc_irq[FULL_SOC].irq);
- enable_irq_wake(chip->soc_irq[FULL_SOC].irq);
+ if (!chip->full_soc_irq_enabled) {
+ enable_irq(chip->soc_irq[FULL_SOC].irq);
+ enable_irq_wake(chip->soc_irq[FULL_SOC].irq);
+ chip->full_soc_irq_enabled = true;
+ }
enable_irq(chip->batt_irq[BATT_MISSING].irq);
if (!chip->vbat_low_irq_enabled) {
enable_irq(chip->batt_irq[VBATT_LOW].irq);
@@ -1347,8 +1363,11 @@ static void fg_enable_irqs(struct fg_chip *chip, bool enable)
} else {
disable_irq_wake(chip->soc_irq[DELTA_SOC].irq);
disable_irq_nosync(chip->soc_irq[DELTA_SOC].irq);
- disable_irq_wake(chip->soc_irq[FULL_SOC].irq);
- disable_irq_nosync(chip->soc_irq[FULL_SOC].irq);
+ if (chip->full_soc_irq_enabled) {
+ disable_irq_wake(chip->soc_irq[FULL_SOC].irq);
+ disable_irq_nosync(chip->soc_irq[FULL_SOC].irq);
+ chip->full_soc_irq_enabled = false;
+ }
disable_irq(chip->batt_irq[BATT_MISSING].irq);
if (chip->vbat_low_irq_enabled) {
disable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
@@ -2186,8 +2205,38 @@ static int get_prop_capacity(struct fg_chip *chip)
if (chip->battery_missing)
return MISSING_CAPACITY;
- if (!chip->profile_loaded && !chip->use_otp_profile)
+ if (!chip->profile_loaded && !chip->use_otp_profile) {
+/*lenovo-sw weiweij modified for booting default soc*/
+#if 0
return DEFAULT_CAPACITY;
+#else
+ static int shutdonw_soc = -22;
+ int j;
+ u8 reg_soc[4];
+ int64_t temp;
+
+ if (shutdonw_soc>=0) {
+ pr_info("using pre shutdown soc %d\n", shutdonw_soc);
+ return shutdonw_soc;
+ }
+
+ rc = fg_mem_read(chip, reg_soc, fg_data[FG_DATA_BATT_SOC].address,
+ fg_data[FG_DATA_BATT_SOC].len, fg_data[FG_DATA_BATT_SOC].offset, 0);
+ if (rc) {
+ pr_err("Failed to update soc sram data, using default soc\n");
+ return DEFAULT_CAPACITY;
+ }
+ temp = 0;
+ for (j = 0; j < fg_data[FG_DATA_BATT_SOC].len; j++)
+ temp |= reg_soc[j] << (8 * j);
+
+ shutdonw_soc = div64_s64((temp * 10000), FULL_PERCENT_3B) / 100;
+ pr_info("bat profile not ready, using shutdown soc %d\n", shutdonw_soc);
+
+ return shutdonw_soc;
+#endif
+/*lenovo-sw weiweij modified for booting default soc end*/
+ }
if (chip->charge_full)
return FULL_CAPACITY;
@@ -3242,6 +3291,29 @@ static void battery_age_work(struct work_struct *work)
estimate_battery_age(chip, &chip->actual_cap_uah);
}
+#ifdef FG_HEATBEAT_WORK
+static void battery_heatbeat_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work, struct fg_chip, battery_heatbeat_work.work);
+ int soc, vol, cur, batt_temp;
+ static int pre_soc = -1, pre_batt_temp = -1;
+
+ soc = get_prop_capacity(chip);
+ vol = get_sram_prop_now(chip, FG_DATA_VOLTAGE);
+ cur = get_sram_prop_now(chip, FG_DATA_CURRENT);
+ batt_temp = get_sram_prop_now(chip, FG_DATA_BATT_TEMP);
+
+ pr_info("soc %d pre_soc %d vol %d cur %d batt_temp %d\n", soc, pre_soc, vol, cur, batt_temp);
+ if ((soc!=pre_soc) || (batt_temp != pre_batt_temp)) {
+ pre_soc = soc;
+ pre_batt_temp = batt_temp;
+ power_supply_changed(&chip->bms_psy);
+ }
+
+ schedule_delayed_work(&chip->battery_heatbeat_work, (HZ * 5));
+}
+#endif
+
static int correction_times[] = {
1470,
2940,
@@ -3833,7 +3905,6 @@ static bool is_usb_present(struct fg_chip *chip)
union power_supply_propval prop = {0,};
if (!chip->usb_psy)
chip->usb_psy = power_supply_get_by_name("usb");
-
if (chip->usb_psy)
chip->usb_psy->get_property(chip->usb_psy,
POWER_SUPPLY_PROP_PRESENT, &prop);
@@ -3949,6 +4020,13 @@ static void status_change_work(struct work_struct *work)
enable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
chip->vbat_low_irq_enabled = true;
}
+
+ if (!chip->full_soc_irq_enabled) {
+ enable_irq(chip->soc_irq[FULL_SOC].irq);
+ enable_irq_wake(chip->soc_irq[FULL_SOC].irq);
+ chip->full_soc_irq_enabled = true;
+ }
+
if (!!(chip->wa_flag & PULSE_REQUEST_WA) && capacity == 100)
fg_configure_soc(chip);
} else if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) {
@@ -3958,6 +4036,12 @@ static void status_change_work(struct work_struct *work)
disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq);
chip->vbat_low_irq_enabled = false;
}
+
+ if (chip->full_soc_irq_enabled) {
+ disable_irq_wake(chip->soc_irq[FULL_SOC].irq);
+ disable_irq_nosync(chip->soc_irq[FULL_SOC].irq);
+ chip->full_soc_irq_enabled = false;
+ }
}
fg_cap_learning_check(chip);
schedule_work(&chip->update_esr_work);
@@ -4520,7 +4604,7 @@ static int fg_power_get_property(struct power_supply *psy,
val->intval = !!chip->bcl_lpm_disabled;
break;
case POWER_SUPPLY_PROP_SOC_REPORTING_READY:
- val->intval = !!chip->profile_loaded;
+ val->intval = !!chip->soc_reporting_ready;
break;
case POWER_SUPPLY_PROP_IGNORE_FALSE_NEGATIVE_ISENSE:
val->intval = !chip->allow_false_negative_isense;
@@ -5156,6 +5240,7 @@ static irqreturn_t fg_batt_missing_irq_handler(int irq, void *_chip)
fg_cap_learning_stop(chip);
chip->battery_missing = true;
chip->profile_loaded = false;
+ chip->soc_reporting_ready = false;
chip->batt_type = default_batt_type;
mutex_lock(&chip->cyc_ctr.lock);
if (fg_debug_mask & FG_IRQS)
@@ -6179,7 +6264,9 @@ fail:
#define PROFILE_COMPARE_LEN 32
#define THERMAL_COEFF_ADDR 0x444
#define THERMAL_COEFF_OFFSET 0x2
-#define BATTERY_PSY_WAIT_MS 2000
+/*lenovo-sw weiweij modified for battery profile re-check schedule time*/
+#define BATTERY_PSY_WAIT_MS 1000
+/*lenovo-sw weiweij modified for battery profile re-check schedule time end*/
static int fg_batt_profile_init(struct fg_chip *chip)
{
int rc = 0, ret;
@@ -6228,6 +6315,8 @@ wait:
if (fg_debug_mask & FG_STATUS)
pr_info("battery id = %d\n",
get_sram_prop_now(chip, FG_DATA_BATT_ID));
+/*lenovo-sw weiweij modified for psy name*/
+#if 0
profile_node = of_batterydata_get_best_profile(batt_node, "bms",
fg_batt_type);
if (IS_ERR_OR_NULL(profile_node)) {
@@ -6239,6 +6328,25 @@ wait:
goto no_profile;
}
}
+#else
+ pr_info("battery id = %d\n",
+ get_sram_prop_now(chip, FG_DATA_BATT_ID));
+
+ pr_err("psy name %s\n", PSY_NAME);
+ profile_node = of_batterydata_get_best_profile(batt_node, PSY_NAME,
+ fg_batt_type);
+
+ if (IS_ERR_OR_NULL(profile_node)) {
+ rc = PTR_ERR(profile_node);
+ if (rc == -EPROBE_DEFER) {
+ goto reschedule;
+ } else {
+ pr_err("couldn't find profile handle rc=%d\n", rc);
+ goto no_profile;
+ }
+ }
+#endif
+/*lenovo-sw weiweij modified for psy name end*/
/* read rslow compensation values if they're available */
rc = of_property_read_u32(profile_node, "qcom,chg-rs-to-rslow",
@@ -6440,6 +6548,7 @@ done:
chip->first_profile_loaded = true;
chip->profile_loaded = true;
+ chip->soc_reporting_ready = true;
chip->battery_missing = is_battery_missing(chip);
update_chg_iterm(chip);
update_cc_cv_setpoint(chip);
@@ -6458,6 +6567,7 @@ done:
complete_all(&chip->fg_reset_done);
return rc;
no_profile:
+ chip->soc_reporting_ready = true;
if (chip->charging_disabled) {
rc = set_prop_enable_charging(chip, true);
if (rc)
@@ -7190,7 +7300,6 @@ static int fg_init_irqs(struct fg_chip *chip)
}
enable_irq_wake(chip->soc_irq[DELTA_SOC].irq);
- enable_irq_wake(chip->soc_irq[FULL_SOC].irq);
if (!chip->use_vbat_low_empty_soc)
enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq);
break;
@@ -7212,7 +7321,7 @@ static int fg_init_irqs(struct fg_chip *chip)
chip->mem_irq[FG_MEM_AVAIL].irq, rc);
return rc;
}
- break;
+ break;
case FG_BATT:
chip->batt_irq[JEITA_SOFT_COLD].irq =
spmi_get_irq_byname(chip->spmi, spmi_resource,
@@ -8323,6 +8432,7 @@ static void ima_error_recovery_work(struct work_struct *work)
if (!chip->use_otp_profile) {
chip->battery_missing = true;
chip->profile_loaded = false;
+ chip->soc_reporting_ready = false;
chip->batt_type = default_batt_type;
fg_handle_battery_insertion(chip);
}
@@ -8511,6 +8621,11 @@ static void delayed_init_work(struct work_struct *work)
chip->otg_present = is_otg_present(chip);
chip->init_done = true;
pr_debug("FG: HW_init success\n");
+
+#ifdef FG_HEATBEAT_WORK
+ schedule_delayed_work(&chip->battery_heatbeat_work, 0);
+#endif
+
return;
done:
fg_cleanup(chip);
@@ -8603,6 +8718,10 @@ static int fg_probe(struct spmi_device *spmi)
INIT_WORK(&chip->slope_limiter_work, slope_limiter_work);
INIT_WORK(&chip->dischg_gain_work, discharge_gain_work);
INIT_WORK(&chip->cc_soc_store_work, cc_soc_store_work);
+#ifdef FG_HEATBEAT_WORK
+ INIT_DELAYED_WORK(&chip->battery_heatbeat_work, battery_heatbeat_work);
+#endif
+
alarm_init(&chip->fg_cap_learning_alarm, ALARM_BOOTTIME,
fg_cap_learning_alarm_cb);
alarm_init(&chip->hard_jeita_alarm, ALARM_BOOTTIME,
@@ -8713,8 +8832,15 @@ static int fg_probe(struct spmi_device *spmi)
chip->batt_type = default_batt_type;
+/*lenovo-sw weiweij modified for psy name*/
+#if 0
chip->bms_psy.name = "bms";
chip->bms_psy.type = POWER_SUPPLY_TYPE_BMS;
+#else
+ chip->bms_psy.name = PSY_NAME;
+ chip->bms_psy.type = PSY_TYPE;//POWER_SUPPLY_TYPE_BATTERY;
+#endif
+/*lenovo-sw weiweij modified for psy name end*/
chip->bms_psy.properties = fg_power_props;
chip->bms_psy.num_properties = ARRAY_SIZE(fg_power_props);
chip->bms_psy.get_property = fg_power_get_property;
@@ -8734,7 +8860,13 @@ static int fg_probe(struct spmi_device *spmi)
* Just initialize the batt_psy_name here. Power supply
* will be obtained later.
*/
+/*lenovo-sw weiweij modified for charger psy name*/
+#if 0
chip->batt_psy_name = "battery";
+#else
+ chip->batt_psy_name = CHARGER_PSY_NAME;
+#endif
+/*lenovo-sw weiweij modified for charger psy name end*/
if (chip->mem_base) {
rc = fg_dfs_create(chip);
@@ -8961,7 +9093,13 @@ static int fg_sense_type_set(const char *val, const struct kernel_param *kp)
if (fg_debug_mask & FG_STATUS)
pr_info("fg_sense_type set to %d\n", fg_sense_type);
+/*lenovo-sw weiweij modified for psy name*/
+#if 0
bms_psy = power_supply_get_by_name("bms");
+#else
+ bms_psy = power_supply_get_by_name(PSY_NAME);
+#endif
+/*lenovo-sw weiweij modified for psy name end*/
if (!bms_psy) {
pr_err("bms psy not found\n");
return 0;
@@ -8984,7 +9122,14 @@ static int fg_restart_set(const char *val, const struct kernel_param *kp)
struct power_supply *bms_psy;
struct fg_chip *chip;
+/*lenovo-sw weiweij modified for psy name*/
+#if 0
bms_psy = power_supply_get_by_name("bms");
+#else
+ bms_psy = power_supply_get_by_name(PSY_NAME);
+#endif
+/*lenovo-sw weiweij modified for psy name end*/
+
if (!bms_psy) {
pr_err("bms psy not found\n");
return 0;
diff --git a/drivers/power/qpnp-smbcharger.c b/drivers/power/qpnp-smbcharger.c
index 16820ae2..682dabe6 100644
--- a/drivers/power/qpnp-smbcharger.c
+++ b/drivers/power/qpnp-smbcharger.c
@@ -279,6 +279,7 @@ struct smbchg_chip {
struct votable *hw_aicl_rerun_disable_votable;
struct votable *hw_aicl_rerun_enable_indirect_votable;
struct votable *aicl_deglitch_short_votable;
+ struct votable *hvdcp_enable_votable;
};
enum qpnp_schg {
@@ -421,6 +422,12 @@ enum aicl_short_deglitch_voters {
HVDCP_SHORT_DEGLITCH_VOTER,
NUM_HW_SHORT_DEGLITCH_VOTERS,
};
+enum hvdcp_voters {
+ HVDCP_PMIC_VOTER,
+ HVDCP_OTG_VOTER,
+ HVDCP_PULSING_VOTER,
+ NUM_HVDCP_VOTERS,
+};
static int smbchg_debug_mask;
module_param_named(
debug_mask, smbchg_debug_mask, int, S_IRUSR | S_IWUSR
@@ -2472,6 +2479,26 @@ static int dc_suspend_vote_cb(struct device *dev, int suspend,
return rc;
}
+#define HVDCP_EN_BIT BIT(3)
+static int smbchg_hvdcp_enable_cb(struct device *dev, int enable,
+ int client, int last_enable,
+ int last_client)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = dev_get_drvdata(dev);
+
+ pr_err("smbchg_hvdcp_enable_cb enable %d last_enable %d\n",
+ enable, last_enable);
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_EN_BIT, enable ? HVDCP_EN_BIT : 0);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't %s HVDCP rc=%d\n",
+ enable ? "enable" : "disable", rc);
+
+ return rc;
+}
+
static int set_fastchg_current_vote_cb(struct device *dev,
int fcc_ma,
int client,
@@ -3836,7 +3863,6 @@ struct regulator_ops smbchg_otg_reg_ops = {
#define USBIN_ADAPTER_9V 0x3
#define USBIN_ADAPTER_5V_9V_CONT 0x2
#define USBIN_ADAPTER_5V_UNREGULATED_9V 0x5
-#define HVDCP_EN_BIT BIT(3)
static int smbchg_external_otg_regulator_enable(struct regulator_dev *rdev)
{
int rc = 0;
@@ -3860,9 +3886,7 @@ static int smbchg_external_otg_regulator_enable(struct regulator_dev *rdev)
* allowance to 9V, so that the audio boost operating in reverse never
* gets detected as a valid input
*/
- rc = smbchg_sec_masked_write(chip,
- chip->usb_chgpth_base + CHGPTH_CFG,
- HVDCP_EN_BIT, 0);
+ rc = vote(chip->hvdcp_enable_votable, HVDCP_OTG_VOTER, true, 0);
if (rc < 0) {
dev_err(chip->dev, "Couldn't disable HVDCP rc=%d\n", rc);
return rc;
@@ -3896,9 +3920,7 @@ static int smbchg_external_otg_regulator_disable(struct regulator_dev *rdev)
* value in order to allow normal USBs to be recognized as a valid
* input.
*/
- rc = smbchg_sec_masked_write(chip,
- chip->usb_chgpth_base + CHGPTH_CFG,
- HVDCP_EN_BIT, HVDCP_EN_BIT);
+ rc = vote(chip->hvdcp_enable_votable, HVDCP_OTG_VOTER, false, 1);
if (rc < 0) {
dev_err(chip->dev, "Couldn't enable HVDCP rc=%d\n", rc);
return rc;
@@ -4516,8 +4538,7 @@ static int smbchg_change_usb_supply_type(struct smbchg_chip *chip,
* and set type after the vote
*/
if (type == POWER_SUPPLY_TYPE_UNKNOWN) {
- rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, false,
- current_limit_ma);
+ rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, true, 0);
if (rc < 0)
pr_err("Couldn't remove ICL vote rc=%d\n", rc);
@@ -4636,9 +4657,7 @@ static void restore_from_hvdcp_detection(struct smbchg_chip *chip)
pr_err("Couldn't configure HVDCP 9V rc=%d\n", rc);
/* enable HVDCP */
- rc = smbchg_sec_masked_write(chip,
- chip->usb_chgpth_base + CHGPTH_CFG,
- HVDCP_EN_BIT, HVDCP_EN_BIT);
+ rc = vote(chip->hvdcp_enable_votable, HVDCP_PULSING_VOTER, false, 1);
if (rc < 0)
pr_err("Couldn't enable HVDCP rc=%d\n", rc);
@@ -5189,8 +5208,7 @@ static int smbchg_prepare_for_pulsing(struct smbchg_chip *chip)
/* disable HVDCP */
pr_smb(PR_MISC, "Disable HVDCP\n");
- rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
- HVDCP_EN_BIT, 0);
+ rc = vote(chip->hvdcp_enable_votable, HVDCP_PULSING_VOTER, true, 0);
if (rc < 0) {
pr_err("Couldn't disable HVDCP rc=%d\n", rc);
goto out;
@@ -5295,9 +5313,7 @@ static int smbchg_unprepare_for_pulsing(struct smbchg_chip *chip)
/* enable HVDCP */
pr_smb(PR_MISC, "Enable HVDCP\n");
- rc = smbchg_sec_masked_write(chip,
- chip->usb_chgpth_base + CHGPTH_CFG,
- HVDCP_EN_BIT, HVDCP_EN_BIT);
+ rc = vote(chip->hvdcp_enable_votable, HVDCP_PULSING_VOTER, false, 1);
if (rc < 0) {
pr_err("Couldn't enable HVDCP rc=%d\n", rc);
return rc;
@@ -6850,7 +6866,15 @@ static int smbchg_hw_init(struct smbchg_chip *chip)
chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR]);
/* Setup 9V HVDCP */
- if (!chip->hvdcp_not_supported) {
+ if (chip->hvdcp_not_supported) {
+ rc = vote(chip->hvdcp_enable_votable, HVDCP_PMIC_VOTER,
+ true, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't disable HVDCP rc=%d\n",
+ rc);
+ return rc;
+ }
+ } else {
rc = smbchg_sec_masked_write(chip,
chip->usb_chgpth_base + CHGPTH_CFG,
HVDCP_ADAPTER_SEL_MASK, HVDCP_9V);
@@ -7762,6 +7786,59 @@ static int smbchg_request_irqs(struct smbchg_chip *chip)
return rc;
}
+/*lenovo-sw weiweij added for disable pmi charger*/
+static int smbchg_request_irqs_simple(struct smbchg_chip *chip)
+{
+ int rc = 0;
+ struct resource *resource;
+ struct spmi_resource *spmi_resource;
+ u8 subtype;
+ struct spmi_device *spmi = chip->spmi;
+ unsigned long flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
+ | IRQF_ONESHOT;
+
+ pr_err("%s\n", __func__);
+ spmi_for_each_container_dev(spmi_resource, chip->spmi) {
+ if (!spmi_resource) {
+ dev_err(chip->dev, "spmi resource absent\n");
+ return rc;
+ }
+
+ resource = spmi_get_resource(spmi, spmi_resource,
+ IORESOURCE_MEM, 0);
+ if (!(resource && resource->start)) {
+ dev_err(chip->dev, "node %s IO resource absent!\n",
+ spmi->dev.of_node->full_name);
+ return rc;
+ }
+
+ rc = smbchg_read(chip, &subtype,
+ resource->start + SUBTYPE_REG, 1);
+ if (rc) {
+ dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ switch (subtype) {
+ case SMBCHG_USB_CHGPTH_SUBTYPE:
+ case SMBCHG_LITE_USB_CHGPTH_SUBTYPE:
+ REQUEST_IRQ(chip, spmi_resource, chip->src_detect_irq,
+ "usbin-src-det",
+ src_detect_handler, flags, rc);
+ enable_irq_wake(chip->src_detect_irq);
+ if (chip->parallel.avail && chip->usb_present) {
+ rc = enable_irq_wake(chip->aicl_done_irq);
+ chip->enable_aicl_wake = true;
+ }
+ break;
+ }
+ }
+
+ return rc;
+}
+/*lenovo-sw weiweij added for disable pmi charger end*/
+
#define REQUIRE_BASE(chip, base, rc) \
do { \
if (!rc && !chip->base) { \
@@ -8024,6 +8101,8 @@ static void rerun_hvdcp_det_if_necessary(struct smbchg_chip *chip)
}
}
+static int use_externel_charger = -1;
+
static int smbchg_probe(struct spmi_device *spmi)
{
int rc;
@@ -8032,6 +8111,13 @@ static int smbchg_probe(struct spmi_device *spmi)
struct qpnp_vadc_chip *vadc_dev = NULL, *vchg_vadc_dev = NULL;
const char *typec_psy_name;
+ if (of_find_property(spmi->dev.of_node, "qcom,use-external-charger", NULL)) {
+ use_externel_charger = 1;
+ pr_info("external charger used!\n");
+ return 0;
+ }else
+ use_externel_charger = 0;
+
usb_psy = power_supply_get_by_name("usb");
if (!usb_psy) {
pr_smb(PR_STATUS, "USB supply not found, deferring probe\n");
@@ -8147,6 +8233,13 @@ static int smbchg_probe(struct spmi_device *spmi)
if (IS_ERR(chip->aicl_deglitch_short_votable))
return PTR_ERR(chip->aicl_deglitch_short_votable);
+ chip->hvdcp_enable_votable = create_votable(&spmi->dev,
+ "SMBCHG: hvdcp_enable",
+ VOTE_MIN, NUM_HVDCP_VOTERS, 1,
+ smbchg_hvdcp_enable_cb);
+ if (IS_ERR(chip->hvdcp_enable_votable))
+ return PTR_ERR(chip->hvdcp_enable_votable);
+
INIT_WORK(&chip->usb_set_online_work, smbchg_usb_update_online_work);
INIT_DELAYED_WORK(&chip->parallel_en_work,
smbchg_parallel_usb_en_work);
@@ -8194,6 +8287,49 @@ static int smbchg_probe(struct spmi_device *spmi)
return rc;
}
+ /*lenovo-sw weiweij added for disable pmi charger*/
+ if(of_find_property(spmi->dev.of_node, "qcom,pmi-charger-disable", NULL)) {
+ unsigned char val;
+ pr_err("%s find pmi-charer-disable, return\n", __func__);
+#if 0
+ //wakeup_source_trash(&chip->smbchg_wake_source);
+ rc = smbchg_masked_write(chip, chip->chgr_base + CHGR_CFG2,
+ CHARGER_INHIBIT_BIT, CHARGER_INHIBIT_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base+ CMD_IL,
+ USBIN_SUSPEND_BIT, USBIN_SUSPEND_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set CMD_IL rc=%d\n", rc);
+ return rc;
+ }
+#endif
+ rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
+ EN_BAT_CHG_BIT, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set CMD_CHG_REG rc=%d\n", rc);
+ return rc;
+ }
+
+ smbchg_read(chip, &val, chip->chgr_base + CHGR_CFG2, 1);
+ dev_err(chip->dev, "read chgr_cfg2 0x%x\n", val);
+ smbchg_read(chip, &val, chip->usb_chgpth_base + CMD_IL, 1);
+ dev_err(chip->dev, "read CMD_IL 0x%x\n", val);
+ smbchg_read(chip, &val, chip->bat_if_base + CMD_CHG_REG, 1);
+ dev_err(chip->dev, "read chgr_cfg 0x%x\n", val);
+ smbchg_read(chip, &val, chip->chgr_base + RT_STS, 1);
+ dev_err(chip->dev, "read RT_STS 0x%x\n", val);
+ smbchg_read(chip, &val, chip->chgr_base + CHGR_STS, 1);
+ dev_err(chip->dev, "read CHGR_STS 0x%x\n", val);
+
+ smbchg_request_irqs_simple(chip);
+ return 0;//-EINVAL;
+ }
+ /*lenovo-sw weiweij added for disable pmi charger end*/
+
rc = smbchg_regulator_init(chip);
if (rc) {
dev_err(&spmi->dev,
@@ -8327,6 +8463,11 @@ static void smbchg_shutdown(struct spmi_device *spmi)
struct smbchg_chip *chip = dev_get_drvdata(&spmi->dev);
int i, rc;
+ if(use_externel_charger) {
+ pr_info("%s using externel charger return\n", __func__);
+ return;
+ }
+
if (!(chip->wa_flags & SMBCHG_RESTART_WA))
return;
@@ -8406,8 +8547,7 @@ static void smbchg_shutdown(struct spmi_device *spmi)
/* disable HVDCP */
pr_smb(PR_MISC, "Disable HVDCP\n");
- rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
- HVDCP_EN_BIT, 0);
+ rc = vote(chip->hvdcp_enable_votable, HVDCP_PMIC_VOTER, true, 0);
if (rc < 0)
pr_err("Couldn't disable HVDCP rc=%d\n", rc);
diff --git a/drivers/power/qpnp-typec.c b/drivers/power/qpnp-typec.c
index 37d5db33..8fc721fc 100644
--- a/drivers/power/qpnp-typec.c
+++ b/drivers/power/qpnp-typec.c
@@ -57,7 +57,7 @@
#define QPNP_TYPEC_DEV_NAME "qcom,qpnp-typec"
#define TYPEC_PSY_NAME "typec"
-#define DUAL_ROLE_DESC_NAME "dual_role"
+#define DUAL_ROLE_DESC_NAME "otg_default"
enum cc_line_state {
CC_1,
@@ -385,6 +385,9 @@ static int qpnp_typec_handle_detach(struct qpnp_typec_chip *chip)
pr_err("Failed to set DRP mode rc=%d\n", rc);
}
+ if (chip->dr_inst)
+ dual_role_instance_changed(chip->dr_inst);
+
return rc;
}
@@ -460,6 +463,9 @@ static irqreturn_t ufp_detect_handler(int irq, void *_chip)
if (rc)
pr_err("failed to set TYPEC MODE on battery psy rc=%d\n", rc);
+ if (chip->dr_inst)
+ dual_role_instance_changed(chip->dr_inst);
+
pr_debug("UFP status reg = 0x%x current = %dma\n",
reg, chip->current_ma);
@@ -517,6 +523,9 @@ static irqreturn_t dfp_detect_handler(int irq, void *_chip)
rc);
}
+ if (chip->dr_inst)
+ dual_role_instance_changed(chip->dr_inst);
+
pr_debug("UFP status reg = 0x%x DFP status reg = 0x%x\n",
reg[0], reg[1]);
@@ -733,6 +742,8 @@ static void qpnp_typec_role_check_work(struct work_struct *work)
enum dual_role_property qpnp_typec_dr_properties[] = {
DUAL_ROLE_PROP_SUPPORTED_MODES,
DUAL_ROLE_PROP_MODE,
+ DUAL_ROLE_PROP_PR,
+ DUAL_ROLE_PROP_DR,
};
static int qpnp_typec_dr_is_writeable(struct dual_role_phy_instance *dual_role,
@@ -816,19 +827,40 @@ static int qpnp_typec_dr_get_property(struct dual_role_phy_instance *dual_role,
unsigned int *val)
{
struct qpnp_typec_chip *chip = dual_role_get_drvdata(dual_role);
+ unsigned int mode, power_role, data_role;
if (!chip)
return -EINVAL;
+ switch (chip->typec_state) {
+ case POWER_SUPPLY_TYPE_UFP:
+ mode = DUAL_ROLE_PROP_MODE_UFP;
+ power_role = DUAL_ROLE_PROP_PR_SNK;
+ data_role = DUAL_ROLE_PROP_DR_DEVICE;
+ break;
+ case POWER_SUPPLY_TYPE_DFP:
+ mode = DUAL_ROLE_PROP_MODE_DFP;
+ power_role = DUAL_ROLE_PROP_PR_SRC;
+ data_role = DUAL_ROLE_PROP_DR_HOST;
+ break;
+ default:
+ mode = DUAL_ROLE_PROP_MODE_NONE;
+ power_role = DUAL_ROLE_PROP_PR_NONE;
+ data_role = DUAL_ROLE_PROP_DR_NONE;
+ };
+
switch (prop) {
case DUAL_ROLE_PROP_SUPPORTED_MODES:
*val = chip->dr_desc.supported_modes;
break;
case DUAL_ROLE_PROP_MODE:
- *val = (chip->typec_state == POWER_SUPPLY_TYPE_UFP)
- ? DUAL_ROLE_PROP_MODE_UFP
- : (chip->typec_state == POWER_SUPPLY_TYPE_DFP)
- ? DUAL_ROLE_PROP_MODE_DFP : DUAL_ROLE_PROP_MODE_NONE;
+ *val = mode;
+ break;
+ case DUAL_ROLE_PROP_PR:
+ *val = power_role;
+ break;
+ case DUAL_ROLE_PROP_DR:
+ *val = data_role;
break;
default:
return -EINVAL;
diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c
index ee3e72b3..55fef74b 100644
--- a/drivers/power/reset/msm-poweroff.c
+++ b/drivers/power/reset/msm-poweroff.c
@@ -264,6 +264,19 @@ static void halt_spmi_pmic_arbiter(void)
}
}
+static bool allow_to_edl(void)
+{
+ struct device_node *np = of_find_node_by_path("/chosen");
+ bool fact_cable = false;
+ u32 sec_version = 0;
+ fact_cable = of_property_read_bool(np, "mmi,factory-cable");
+ of_property_read_u32(np, "mmi,sec_ver", &sec_version);
+ if((sec_version < 2) || fact_cable)
+ return true;
+ else
+ return false;
+}
+
static void msm_restart_prepare(const char *cmd)
{
bool need_warm_reset = false;
@@ -281,14 +294,44 @@ static void msm_restart_prepare(const char *cmd)
if (qpnp_pon_check_hard_reset_stored()) {
/* Set warm reset as true when device is in dload mode */
+ /*lenovo-sw jixj2015.3.13 modify begin*/
+ #if 0
+ if (get_dload_mode() ||
+ ((cmd != NULL && cmd[0] != '\0') &&
+ strcmp(cmd, "recovery") &&
+ strcmp(cmd, "bootloader") &&
+ strcmp(cmd, "rtc")))
+ #else
if (get_dload_mode() ||
((cmd != NULL && cmd[0] != '\0') &&
- !strcmp(cmd, "edl")))
+ strcmp(cmd, "recovery") &&
+ strcmp(cmd, "bootloader") &&
+ strcmp(cmd, "testmode") &&
+ strcmp(cmd, "dloadmode") &&
+ strcmp(cmd, "rtc")))
+ #endif
+ /*lenovo-sw jixj2015.3.13 modify end*/
need_warm_reset = true;
} else {
need_warm_reset = (get_dload_mode() ||
(cmd != NULL && cmd[0] != '\0'));
}
+ /*lenovo-sw jixj2015.3.13 add begin, shutdown menu is reboot(GlobalActions)*/
+ if ((cmd != NULL && cmd[0] != '\0') &&
+ !strcmp(cmd, "GlobalActions")) {
+ need_warm_reset = false;
+ } else if(in_panic) {
+ need_warm_reset = true;
+ }
+
+ if(!in_panic) {
+ qpnp_pon_store_extra_reset_info(RESET_EXTRA_LAST_REBOOT_REASON,
+ RESET_EXTRA_LAST_REBOOT_REASON);
+ pr_crit("msm_restart_prepare normal reboot\n");
+ }
+
+ pr_crit("msm_restart_prepare need_warm_reset=%d\n",need_warm_reset);
+ /*lenovo-sw jixj2015.3.13 add begin*/
/* Hard reset the PMIC unless memory contents must be maintained. */
if (need_warm_reset) {
@@ -302,6 +345,14 @@ static void msm_restart_prepare(const char *cmd)
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_BOOTLOADER);
__raw_writel(0x77665500, restart_reason);
+ /* set reboot_bl flag in PMIC for cold reset */
+ qpnp_pon_store_extra_reset_info(RESET_EXTRA_REBOOT_BL_REASON,
+ RESET_EXTRA_REBOOT_BL_REASON);
+ /*
+ * force cold reboot here to avoid impaction from
+ * modem double reboot workaround solution.
+ */
+ qpnp_pon_system_pwr_off(PON_POWER_OFF_HARD_RESET);
} else if (!strncmp(cmd, "recovery", 8)) {
qpnp_pon_set_restart_reason(
PON_RESTART_REASON_RECOVERY);
@@ -329,8 +380,14 @@ static void msm_restart_prepare(const char *cmd)
if (!ret)
__raw_writel(0x6f656d00 | (code & 0xff),
restart_reason);
- } else if (!strncmp(cmd, "edl", 3)) {
+ } else if (!strncmp(cmd, "edl", 3) && allow_to_edl()) {
enable_emergency_dload_mode();
+ /*lenovo-sw jixj 2015.3.13 add begin*/
+ } else if (!strncmp(cmd, "testmode", 8)) {
+ __raw_writel(0x77665504, restart_reason);
+ } else if (!strncmp(cmd, "dloadmode", 9)) {
+ set_dload_mode(1);
+ /*lenovo-sw jixj 2015.3.13 add end*/
} else {
__raw_writel(0x77665501, restart_reason);
}
@@ -395,6 +452,8 @@ static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd)
static void do_msm_poweroff(void)
{
pr_notice("Powering off the SoC\n");
+ qpnp_pon_store_extra_reset_info(RESET_EXTRA_LAST_REBOOT_REASON,
+ RESET_EXTRA_LAST_REBOOT_REASON);
set_dload_mode(0);
scm_disable_sdi();
diff --git a/drivers/power/smb135x-charger.c b/drivers/power/smb135x-charger.c
index eb36c643..7f970066 100644
--- a/drivers/power/smb135x-charger.c
+++ b/drivers/power/smb135x-charger.c
@@ -112,6 +112,7 @@
#define BATT_MISSING_THERM_BIT BIT(1)
#define CFG_1A_REG 0x1A
+#define TEMP_MONITOR_EN_BIT BIT(6)
#define HOT_SOFT_VFLOAT_COMP_EN_BIT BIT(3)
#define COLD_SOFT_VFLOAT_COMP_EN_BIT BIT(2)
#define HOT_SOFT_CURRENT_COMP_EN_BIT BIT(1)
@@ -1814,6 +1815,15 @@ static int smb135x_parallel_set_chg_present(struct smb135x_chg *chip,
return rc;
}
+ /* disable thermal monitoring for parallel-charger */
+ rc = smb135x_masked_write(chip, CFG_1A_REG,
+ TEMP_MONITOR_EN_BIT, 0);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't disable temp-monitor rc=%d\n", rc);
+ return rc;
+ }
+
/* set the float voltage */
if (chip->vfloat_mv != -EINVAL) {
rc = smb135x_float_voltage_set(chip, chip->vfloat_mv);