Intel MDF battery support
authorsathya <sathyanarayanan.kuppuswamy@intel.com>
Wed, 9 Nov 2011 21:22:02 +0000 (13:22 -0800)
committerGross, Mark <mark.gross@intel.com>
Sat, 19 Nov 2011 07:57:45 +0000 (23:57 -0800)
Fork lifted the battery driver (intel_mdf_battery) from git://jfumg-gcrmirror.jf.intel.com/a/bsp/hardware/intel/linux-2.6.git

On top of that following 3.0 fixes are added.

1. 3.0 runtime pm fixes.
2. In kerenl 3.0, kfifo architechture is modified to support elements. So Irq kfifo in intel_mdf_battery.c is modified to use Int elements.

Change-Id: I650661992d062ad531ae8999432c4b3b7cae57c8
Reviewed-on: http://android.intel.com:8080/24602
Reviewed-by: Gross, Mark <mark.gross@intel.com>
Tested-by: Gross, Mark <mark.gross@intel.com>
arch/x86/platform/mrst/mrst.c
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/intel_mdf_battery.c [new file with mode: 0644]
drivers/power/intel_mdf_charger.h [new file with mode: 0644]
drivers/power/max17042_battery.c
include/linux/power/intel_mdf_battery.h [new file with mode: 0644]
include/linux/power/max17042_battery.h

index dd6ab18..1c2ac27 100644 (file)
@@ -24,6 +24,9 @@
 #include <linux/cyttsp.h>
 #include <linux/i2c.h>
 #include <linux/i2c/pca953x.h>
+#include <linux/power_supply.h>
+#include <linux/power/max17042_battery.h>
+#include <linux/power/intel_mdf_battery.h>
 #include <linux/gpio.h>
 #include <linux/gpio_keys.h>
 #include <linux/input.h>
@@ -722,6 +725,32 @@ static void *msic_gpio_platform_data(void *info)
        return msic_generic_platform_data(info, INTEL_MSIC_BLOCK_GPIO);
 }
 
+static void *max17042_platform_data(void *info)
+{
+       static struct max17042_platform_data platform_data;
+       struct i2c_board_info *i2c_info = (struct i2c_board_info *)info;
+       int intr = get_gpio_by_name("max17042");
+
+       i2c_info->irq = intr + MRST_IRQ_OFFSET;
+
+       platform_data.enable_current_sense = 0;
+       platform_data.is_init_done = 0;
+       platform_data.technology = POWER_SUPPLY_TECHNOLOGY_LION;
+
+#ifdef CONFIG_BATTERY_INTEL_MDF
+       platform_data.current_sense_enabled =
+           intel_msic_is_current_sense_enabled;
+       platform_data.battery_present = intel_msic_check_battery_present;
+       platform_data.battery_health = intel_msic_check_battery_health;
+       platform_data.battery_status = intel_msic_check_battery_status;
+       platform_data.battery_pack_temp = intel_msic_get_battery_pack_temp;
+       platform_data.save_config_data = intel_msic_save_config_data;
+       platform_data.restore_config_data = intel_msic_restore_config_data;
+#endif
+
+       return &platform_data;
+}
+
 static void *msic_audio_platform_data(void *info)
 {
        struct platform_device *pdev;
@@ -776,6 +805,7 @@ static const struct devs_id __initconst device_ids[] = {
        {"mpu3050", SFI_DEV_TYPE_I2C, 1, &mpu3050_platform_data},
        {"ektf2136_spi", SFI_DEV_TYPE_SPI, 0, &ektf2136_spi_platform_data},
        {"msic_adc", SFI_DEV_TYPE_IPC, 1, &msic_adc_platform_data},
+       {"max17042", SFI_DEV_TYPE_I2C, 1, &max17042_platform_data},
 
        /* MSIC subdevices */
        {"msic_battery", SFI_DEV_TYPE_IPC, 1, &msic_battery_platform_data},
index e57b50b..01eb36a 100644 (file)
@@ -203,6 +203,13 @@ config BATTERY_INTEL_MID
          Say Y here to enable the battery driver on Intel MID
          platforms.
 
+config BATTERY_INTEL_MDF
+        tristate "Battery driver for Intel Medfield MID platforms"
+        depends on INTEL_SCU_IPC && USB_PENWELL_OTG && MSIC_GPADC
+        help
+          Say Y here to enable the battery driver on Intel Medfield based MID
+          platforms.
+
 config CHARGER_ISP1704
        tristate "ISP1704 USB Charger Detection"
        depends on USB_OTG_UTILS
index 009a90f..45661cc 100644 (file)
@@ -32,6 +32,7 @@ obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o
 obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
 obj-$(CONFIG_BATTERY_JZ4740)   += jz4740-battery.o
 obj-$(CONFIG_BATTERY_INTEL_MID)        += intel_mid_battery.o
+obj-$(CONFIG_BATTERY_INTEL_MDF) += intel_mdf_battery.o
 obj-$(CONFIG_CHARGER_ISP1704)  += isp1704_charger.o
 obj-$(CONFIG_CHARGER_MAX8903)  += max8903_charger.o
 obj-$(CONFIG_CHARGER_TWL4030)  += twl4030_charger.o
diff --git a/drivers/power/intel_mdf_battery.c b/drivers/power/intel_mdf_battery.c
new file mode 100644 (file)
index 0000000..697b38a
--- /dev/null
@@ -0,0 +1,2765 @@
+/*
+ * intel_mdf_battery.c - Intel Medfield MSIC Internal charger and Battery Driver
+ *
+ * Copyright (C) 2010 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.        See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Author: Ananth Krishna <ananth.krishna.r@intel.com>,
+ *         Anantha Narayanan <anantha.narayanan@intel.com>
+ *         Ramakrishna Pallala <ramakrishna.pallala@intel.com>
+ */
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/slab.h>
+#include <linux/kfifo.h>
+#include <linux/param.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/pm_runtime.h>
+#include <linux/sfi.h>
+#include <linux/wakelock.h>
+#include <linux/async.h>
+#include <linux/reboot.h>
+
+#include <asm/intel_scu_ipc.h>
+#include <linux/usb/penwell_otg.h>
+#include <linux/power/intel_mdf_battery.h>
+#include <asm/intel_mid_gpadc.h>
+
+#include "intel_mdf_charger.h"
+
+#define DRIVER_NAME "intel_mdf_battery"
+#define CHARGER_PS_NAME "msic_charger"
+
+#define SFI_SIG_OEM0        "OEM0"
+#define IRQ_KFIFO_ELEMENT      1
+
+
+static void *otg_handle;
+static struct device *msic_dev;
+static struct power_supply *fg_psy;
+
+static char *msic_power_supplied_to[] = {
+                       "msic_battery",
+                       "max17042_battery",
+};
+
+static unsigned long long adc_ttl;
+static int adc_sensor_vals[MSIC_BATT_SENSORS];
+
+/*
+ * This array represents the Battery Pack thermistor
+ * temperature and corresponding ADC value limits
+ */
+static int const therm_curve_data[THERM_CURVE_MAX_SAMPLES]
+       [THERM_CURVE_MAX_VALUES] = {
+       /* {temp_max, temp_min, adc_max, adc_min} */
+       {-15, -20, 977, 961},
+       {-10, -15, 961, 941},
+       {-5, -10, 941, 917},
+       {0, -5, 917, 887},
+       {5, 0, 887, 853},
+       {10, 5, 853, 813},
+       {15, 10, 813, 769},
+       {20, 15, 769, 720},
+       {25, 20, 720, 669},
+       {30, 25, 669, 615},
+       {35, 30, 615, 561},
+       {40, 35, 561, 508},
+       {45, 40, 508, 456},
+       {50, 45, 456, 407},
+       {55, 50, 407, 357},
+       {60, 55, 357, 315},
+       {65, 60, 315, 277},
+       {70, 65, 277, 243},
+       {75, 70, 243, 212},
+       {80, 75, 212, 186},
+       {85, 80, 186, 162},
+       {90, 85, 162, 140},
+       {100, 90, 140, 107},
+};
+
+
+static struct batt_safety_thresholds *batt_thrshlds;
+
+
+static struct msic_batt_sfi_prop *sfi_table;
+
+static int error_count;
+
+
+/*
+ * All interrupt request are queued from interrupt
+ * handler and processed in the bottom half
+ */
+static DEFINE_KFIFO(irq_fifo, u32, IRQ_FIFO_MAX);
+
+
+/* Sysfs Entry for enable or disable Charging from user space */
+static ssize_t set_chrg_enable(struct device *device,
+                              struct device_attribute *attr, const char *buf,
+                              size_t count);
+static ssize_t get_chrg_enable(struct device *device,
+                              struct device_attribute *attr, char *buf);
+static DEVICE_ATTR(charge_enable, S_IRUGO | S_IWUSR, get_chrg_enable,
+                  set_chrg_enable);
+
+/* Sysfs Entry to show if lab power supply is used */
+static ssize_t get_is_power_supply_conn(struct device *device,
+                       struct device_attribute *attr, char *buf);
+static DEVICE_ATTR(power_supply_conn, S_IRUGO, get_is_power_supply_conn, NULL);
+
+/*
+ * msic usb properties
+ */
+static enum power_supply_property msic_usb_props[] = {
+       POWER_SUPPLY_PROP_TYPE,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_MODEL_NAME,
+       POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static int battery_reboot_notifier_callback(struct notifier_block *notifier,
+               unsigned long event, void *data);
+
+static struct notifier_block battery_reboot_notifier = {
+       .notifier_call = battery_reboot_notifier_callback,
+};
+
+/**
+ * check_batt_psy -check for whether power supply type is battery
+ * @dev : Power Supply dev structure
+ * @data : Power Supply Driver Data
+ * Context: can sleep
+ *
+ * Return true if power supply type is battery
+ *
+ */
+static int check_batt_psy(struct device *dev, void *data)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+
+       /* check for whether power supply type is battery */
+       if (psy->type == POWER_SUPPLY_TYPE_BATTERY) {
+               dev_info(msic_dev, "fg chip found:%s\n", psy->name);
+               fg_psy = psy;
+               return true;
+       }
+
+       return false;
+}
+
+/**
+ * get_fg_chip_psy - identify the Fuel Gauge Power Supply device
+ * Context: can sleep
+ *
+ * Return Fuel Gauge power supply structure
+ */
+static struct power_supply *get_fg_chip_psy(void)
+{
+       if (fg_psy)
+               return fg_psy;
+
+       /* loop through power supply class */
+       class_for_each_device(power_supply_class, NULL, NULL,
+                               check_batt_psy);
+       return fg_psy;
+}
+
+/**
+ * fg_chip_get_property - read a power supply property from Fuel Gauge driver
+ * @psp : Power Supply property
+ *
+ * Return power supply property value
+ *
+ */
+static int fg_chip_get_property(enum power_supply_property psp)
+{
+       union power_supply_propval val;
+       int ret = -ENODEV;
+
+       if (!fg_psy)
+               fg_psy = get_fg_chip_psy();
+       if (fg_psy) {
+               ret = fg_psy->get_property(fg_psy, psp, &val);
+               if (!ret)
+                       return val.intval;
+       }
+
+       return ret;
+}
+
+/* Exported Functions to use with Fuel Gauge driver */
+
+int intel_msic_is_current_sense_enabled(void)
+{
+       struct platform_device *pdev = container_of(msic_dev,
+                                       struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+       int val;
+
+       mutex_lock(&mbi->batt_lock);
+       val = mbi->current_sense_enabled;
+       mutex_unlock(&mbi->batt_lock);
+
+       return val;
+}
+EXPORT_SYMBOL(intel_msic_is_current_sense_enabled);
+
+int intel_msic_check_battery_present(void)
+{
+       struct platform_device *pdev = container_of(msic_dev,
+                                       struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+       int val;
+
+       mutex_lock(&mbi->batt_lock);
+       val = mbi->batt_props.present;
+       mutex_unlock(&mbi->batt_lock);
+
+       return val;
+}
+EXPORT_SYMBOL(intel_msic_check_battery_present);
+
+int intel_msic_check_battery_health(void)
+{
+       struct platform_device *pdev = container_of(msic_dev,
+                                       struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+       int val;
+
+       mutex_lock(&mbi->batt_lock);
+       val = mbi->batt_props.health;
+       mutex_unlock(&mbi->batt_lock);
+
+       return val;
+}
+EXPORT_SYMBOL(intel_msic_check_battery_health);
+
+int intel_msic_check_battery_status(void)
+{
+       struct platform_device *pdev = container_of(msic_dev,
+                                       struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+       int val;
+
+       mutex_lock(&mbi->batt_lock);
+       val = mbi->batt_props.status;
+       mutex_unlock(&mbi->batt_lock);
+
+       return val;
+}
+EXPORT_SYMBOL(intel_msic_check_battery_status);
+
+static int is_protected_regs(u16 addr)
+{
+       /* in unsigned kernel mode write to some registers are blocked */
+       if (addr == MSIC_BATT_CHR_CHRCVOLTAGE_ADDR ||
+               addr == MSIC_BATT_CHR_CHRCCURRENT_ADDR ||
+               addr == MSIC_BATT_CHR_PWRSRCLMT_ADDR ||
+               addr == MSIC_BATT_CHR_CHRCTRL1_ADDR)
+               return true;
+       else
+               return false;
+}
+
+/**
+ * handle_ipc_rw_status - handle msic ipc read/write status
+ * @error_val : ipc read/write status
+ * @address   : msic register address
+ * @rw       : read/write access
+ *
+ * If the error count is more than MAX_IPC_ERROR_COUNT, report
+ * charger and battery health as POWER_SUPPLY_HEALTH_UNSPEC_FAILURE
+ *
+ * returns error value in case of error else return 0
+ */
+
+static inline int handle_ipc_rw_status(int error_val,
+               const u16 address, char rw)
+{
+
+       struct platform_device *pdev = container_of(msic_dev,
+                                       struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+       /*
+       * Write to protected registers in unsigned kernel
+       * mode will return -EIO
+       */
+       /* Overwriting result value when failure is not a timeout, to support
+        * properly safety charging : we must ensure that when using
+        * an unsigned kernel, the failing access to protected registers
+        * (expected behaviour, returning -EIO)) will not block the accesses
+        * to the non protected registers.
+        * */
+       if ((is_protected_regs(address)) && (rw == MSIC_IPC_WRITE) &&
+                       (error_val == -EIO))
+               return 0;
+
+       if (error_count < MAX_IPC_ERROR_COUNT) {
+               error_count++;
+               dev_warn(msic_dev, "MSIC IPC %s access to %x failed",
+                       (rw == MSIC_IPC_WRITE ? "write" : "read"), address);
+       } else {
+
+               dev_crit(msic_dev, "MSIC IPC %s access to %x failed",
+                       (rw == MSIC_IPC_WRITE ? "write" : "read"), address);
+
+               mutex_lock(&mbi->batt_lock);
+               /* set battery health */
+               if (mbi->batt_props.health == POWER_SUPPLY_HEALTH_GOOD) {
+                       mbi->batt_props.health =
+                               POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+               }
+               mutex_unlock(&mbi->batt_lock);
+               /* set charger health */
+               mutex_lock(&mbi->usb_chrg_lock);
+               if (mbi->usb_chrg_props.charger_health ==
+                               POWER_SUPPLY_HEALTH_GOOD){
+                       mbi->usb_chrg_props.charger_health =
+                               POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+               }
+               mutex_unlock(&mbi->usb_chrg_lock);
+               power_supply_changed(&mbi->usb);
+       }
+
+       return error_val;
+}
+
+/**
+ * get_batt_fg_curve_index - get the fg curve ID from umip
+ * @name : Power Supply name
+ *
+ * Returns FG curve ID
+ *
+ */
+static int get_batt_fg_curve_index(const char *name)
+{
+       struct power_supply *psy;
+       int mip_offset, i, ret;
+       u8 batt_id[BATTID_STR_LEN];
+       u8 num_tbls = 0;
+       int num_supplicants;
+
+       /* check if we support the device in our supplied to list */
+       num_supplicants = ARRAY_SIZE(msic_power_supplied_to);
+       for (i = 0; i < num_supplicants; i++) {
+               if (!strncmp(name, msic_power_supplied_to[i], 16))
+                       break;
+       }
+
+       if (i >= num_supplicants)
+               return -ENXIO;
+
+       /* check if msic charger is ready */
+       psy = power_supply_get_by_name(CHARGER_PS_NAME);
+       if (!psy)
+               return -EAGAIN;
+
+       /* get the no.of tables from mip */
+       ret = intel_scu_ipc_read_mip((u8 *)&num_tbls, 1,
+                                       UMIP_NO_OF_CFG_TBLS, 0);
+       if (ret) {
+               dev_warn(msic_dev, "%s: umip read failed\n", __func__);
+               goto get_idx_failed;
+       }
+
+       /* compare the batt ID provided by SFI table and FG table in mip */
+       mip_offset = UMIP_BATT_FG_CFG_TBL1 + BATT_FG_TBL_BATTID;
+       for (i = 0; i < num_tbls; i++) {
+               ret = intel_scu_ipc_read_mip(batt_id, BATTID_STR_LEN,
+                                                       mip_offset, 0);
+               if (ret) {
+                       dev_warn(msic_dev, "%s: umip read failed\n", __func__);
+                       goto get_idx_failed;
+               }
+
+               if (!strncmp(batt_id, sfi_table->batt_id, BATTID_STR_LEN))
+                       break;
+
+               mip_offset += UMIP_FG_TBL_SIZE;
+               memset(batt_id, 0x0, BATTID_STR_LEN);
+       }
+
+       if (i < num_tbls)
+               ret = i;
+       else
+               ret = -ENXIO;
+
+get_idx_failed:
+       return ret;
+}
+
+/**
+ * intel_msic_restore_config_data - restore config data
+ * @name : Power Supply name
+ * @data : config data output pointer
+ * @len : length of config data
+ *
+ */
+int intel_msic_restore_config_data(const char *name, void *data, int len)
+{
+       int mip_offset, ret;
+
+       /* check if msic charger is ready */
+       if (!power_supply_get_by_name("msic_charger"))
+               return -EAGAIN;
+
+       /* Read the fuel gauge config data from umip */
+       mip_offset = UMIP_REF_FG_TBL + BATT_FG_TBL_BODY;
+       ret = intel_scu_ipc_read_mip((u8 *)data, len, mip_offset, 0);
+       if (ret)
+               dev_warn(msic_dev, "%s: umip read failed\n", __func__);
+
+       return ret;
+}
+EXPORT_SYMBOL(intel_msic_restore_config_data);
+
+/**
+ * intel_msic_save_config_data - save config data
+ * @name : Power Supply name
+ * @data : config data input pointer
+ * @len : length of config data
+ *
+ */
+int intel_msic_save_config_data(const char *name, void *data, int len)
+{
+       int mip_offset, ret;
+
+       /* check if msic charger is ready */
+       if (!power_supply_get_by_name("msic_charger"))
+               return -EAGAIN;
+
+       /* write the fuel gauge config data to umip */
+       mip_offset = UMIP_REF_FG_TBL + BATT_FG_TBL_BODY;
+       ret = intel_scu_ipc_write_umip((u8 *)data, len, mip_offset);
+       if (ret)
+               dev_warn(msic_dev, "%s: umip write failed\n", __func__);
+
+       return ret;
+}
+EXPORT_SYMBOL(intel_msic_save_config_data);
+
+/* Check for valid Temp ADC range */
+static bool is_valid_temp_adc(int adc_val)
+{
+       if (adc_val >= MSIC_BTP_ADC_MIN && adc_val <= MSIC_BTP_ADC_MAX)
+               return true;
+       else
+               return false;
+}
+
+/* Temperature conversion Macros */
+static int conv_adc_temp(int adc_val, int adc_max, int adc_diff, int temp_diff)
+{
+       int ret;
+
+       ret = (adc_max - adc_val) * temp_diff;
+       return ret / adc_diff;
+}
+
+/* Check if the adc value is in the curve sample range */
+static bool is_valid_temp_adc_range(int val, int min, int max)
+{
+       if (val > min && val <= max)
+               return true;
+       else
+               return false;
+}
+
+/**
+ * adc_to_temp - convert ADC code to temperature
+ * @adc_val : ADC sensor reading
+ *
+ * Returns temperature in Degree Celsius
+ */
+static int adc_to_temp(uint16_t adc_val)
+{
+       int temp = 0;
+       int i;
+
+       if (!is_valid_temp_adc(adc_val)) {
+               dev_warn(msic_dev, "Temperature out of Range: %u\n", adc_val);
+               return -ERANGE;
+       }
+
+       for (i = 0; i < THERM_CURVE_MAX_SAMPLES; i++) {
+               /* linear approximation for battery pack temperature */
+               if (is_valid_temp_adc_range(adc_val, therm_curve_data[i][3],
+                                           therm_curve_data[i][2])) {
+
+                       temp = conv_adc_temp(adc_val, therm_curve_data[i][2],
+                                            therm_curve_data[i][2] -
+                                            therm_curve_data[i][3],
+                                            therm_curve_data[i][0] -
+                                            therm_curve_data[i][1]);
+
+                       temp += therm_curve_data[i][1];
+                       break;
+               }
+       }
+
+       if (i >= THERM_CURVE_MAX_SAMPLES)
+               dev_warn(msic_dev, "Invalid temp adc range\n");
+
+       return temp;
+}
+
+static int is_ttl_valid(u64 ttl)
+{
+
+       if (time_before64(get_jiffies_64(), ttl))
+               return 1;
+       else
+               return 0;
+}
+
+/**
+ * mdf_multi_read_adc_regs - read multiple ADC sensors
+ * @mbi :  msic battery info pointer
+ * @sample_count: do sample serveral times and get the average value.
+ * @...: sensor numbers
+ *
+ * Returns 0 if success
+ */
+static int mdf_multi_read_adc_regs(struct msic_power_module_info *mbi,
+                                  int sample_count, int channel_count, ...)
+{
+       va_list args;
+       int ret = 0, i, sensor, tmp;
+       int *adc_val;
+       int temp_adc_val[MSIC_BATT_SENSORS];
+
+
+       mutex_lock(&mbi->adc_val_lock);
+       if (!is_ttl_valid(adc_ttl) || (sample_count > 1)) {
+               ret =
+                   intel_mid_gpadc_sample(mbi->adc_handle, sample_count,
+                                          &adc_sensor_vals[MSIC_ADC_TEMP_IDX],
+                                          &adc_sensor_vals
+                                          [MSIC_ADC_USB_VOL_IDX],
+                                          &adc_sensor_vals
+                                          [MSIC_ADC_BATTID_IDX]);
+               if (ret) {
+                       dev_err(&mbi->pdev->dev,
+                               "adc driver api returned error(%d)\n", ret);
+                       mutex_unlock(&mbi->adc_val_lock);
+                       goto adc_multi_exit;
+               }
+               adc_ttl = get_jiffies_64() + ADC_TIME_TO_LIVE;
+       }
+       memcpy(temp_adc_val, adc_sensor_vals, sizeof(temp_adc_val));
+       mutex_unlock(&mbi->adc_val_lock);
+
+       va_start(args, channel_count);
+       for (i = 0; i < channel_count; i++) {
+               /* get sensor number */
+               sensor = va_arg(args, int);
+               /* get value pointer */
+               adc_val = va_arg(args, int *);
+               if (adc_val == NULL) {
+                       ret = -EINVAL;
+                       goto adc_multi_exit;
+               }
+               *adc_val = temp_adc_val[sensor];
+               switch (sensor) {
+               case MSIC_ADC_TEMP_IDX:
+                       tmp = adc_to_temp(*adc_val);
+                       break;
+               case MSIC_ADC_USB_VOL_IDX:
+                       tmp = MSIC_ADC_TO_VBUS_VOL(*adc_val);
+                       break;
+               case MSIC_ADC_BATTID_IDX:
+                       tmp = *adc_val;
+                       break;
+               default:
+                       dev_err(&mbi->pdev->dev, "invalid sensor%d", sensor);
+                       return -EINVAL;
+               }
+               *adc_val = tmp;
+       }
+       va_end(args);
+
+adc_multi_exit:
+       return ret;
+}
+
+static int mdf_read_adc_regs(int sensor, int *sensor_val,
+               struct msic_power_module_info *mbi)
+{
+       int ret;
+       ret = mdf_multi_read_adc_regs(mbi, 1, 1, sensor, sensor_val);
+
+       if (ret)
+               dev_err(&mbi->pdev->dev, "%s:mdf_multi_read_adc_regs failed",
+                       __func__);
+       return ret;
+}
+
+int intel_msic_get_battery_pack_temp(int *temp)
+{
+       struct platform_device *pdev = container_of(msic_dev,
+                                       struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+       /* check if msic charger is ready */
+       if (!power_supply_get_by_name(CHARGER_PS_NAME))
+               return -EAGAIN;
+
+       if (!mbi->current_sense_enabled)
+               return -ENODEV;
+
+       return mdf_read_adc_regs(MSIC_ADC_TEMP_IDX, temp, mbi);
+}
+EXPORT_SYMBOL(intel_msic_get_battery_pack_temp);
+
+static void dump_registers(void)
+{
+       int i, retval = 0;
+       uint8_t reg_val;
+       uint16_t chk_reg_addr;
+       uint16_t reg_addr[] = {MSIC_BATT_CHR_CHRCTRL_ADDR,
+               CHR_STATUS_FAULT_REG, MSIC_BATT_CHR_PWRSRCLMT_ADDR,
+               MSIC_BATT_CHR_CHRCVOLTAGE_ADDR, MSIC_BATT_CHR_CHRCCURRENT_ADDR};
+       char *reg_str[] = {"Chrctrl_reg", "Fault_reg", "Pwrsrclmt_reg",
+               "Chrvolt_reg", "Chrcur_reg"};
+
+       for (i = 0; i < 5; i++) {
+               retval = intel_scu_ipc_ioread8(reg_addr[i], &reg_val);
+               if (retval) {
+                       chk_reg_addr = reg_addr[i];
+                       goto ipcread_err;
+               }
+
+               dev_info(msic_dev, "%s val: %x\n", reg_str[i], reg_val);
+       }
+
+       return;
+
+ipcread_err:
+       handle_ipc_rw_status(retval, chk_reg_addr, MSIC_IPC_READ);
+}
+
+static int is_charger_fault(void)
+{
+       uint8_t fault_reg, chrctrl_reg, stat;
+       int retval = 0;
+       int adc_temp, adc_usb_volt, batt_volt;
+       struct platform_device *pdev = container_of(msic_dev,
+                                       struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+       retval = intel_scu_ipc_ioread8(MSIC_BATT_CHR_CHRCTRL_ADDR,
+                       &chrctrl_reg);
+       if (retval) {
+               retval = handle_ipc_rw_status(retval,
+                               MSIC_BATT_CHR_CHRCTRL_ADDR, MSIC_IPC_READ);
+               if (retval)
+                       return retval;
+       }
+       /* if charger is disabled report false*/
+       if (chrctrl_reg & CHRCNTL_CHRG_DISABLE)
+               return false;
+
+       retval = intel_scu_ipc_ioread8(CHR_STATUS_FAULT_REG, &fault_reg);
+       if (retval) {
+               retval = handle_ipc_rw_status(retval, CHR_STATUS_FAULT_REG,
+                               MSIC_IPC_READ);
+               if (retval)
+                       return retval;
+       }
+
+       /*If charger is enabled and STAT(0:1) shows charging progress or
+        * charging done then we report false*/
+       stat = (fault_reg & CHR_STATUS_BIT_MASK) >> CHR_STATUS_BIT_POS;
+       if (stat == CHR_STATUS_BIT_PROGRESS ||
+               stat == CHR_STATUS_BIT_CYCLE_DONE)
+               return false;
+       else {
+               dev_info(msic_dev, "Charger fault occured\n");
+               if (!mdf_read_adc_regs(MSIC_ADC_USB_VOL_IDX,
+                                       &adc_usb_volt, mbi) &&
+                               !mdf_read_adc_regs(MSIC_ADC_TEMP_IDX,
+                                       &adc_temp, mbi)) {
+                       batt_volt = fg_chip_get_property(
+                                       POWER_SUPPLY_PROP_VOLTAGE_NOW);
+                       if (batt_volt != -ENODEV && batt_volt != -EINVAL) {
+                               dev_info(msic_dev, "Temp: %d, char_volt: %d, "
+                                               "batt_volt: %d\n", adc_temp,
+                                               adc_usb_volt, batt_volt/1000);
+                       } else
+                               dev_info(msic_dev, "Temp: %d, char_volt: %d\n",
+                                               adc_temp, adc_usb_volt);
+               }
+
+               dump_registers();
+
+               return true;
+       }
+}
+
+/**
+ * msic_usb_get_property - usb power source get property
+ * @psy: usb power supply context
+ * @psp: usb power source property
+ * @val: usb power source property value
+ * Context: can sleep
+ *
+ * MSIC usb power source property needs to be provided to power_supply
+ * subsystem for it to provide the information to users.
+ */
+static int msic_usb_get_property(struct power_supply *psy,
+                                enum power_supply_property psp,
+                                union power_supply_propval *val)
+{
+       struct msic_power_module_info *mbi =
+           container_of(psy, struct msic_power_module_info, usb);
+       int retval = 0;
+
+       mutex_lock(&mbi->usb_chrg_lock);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = mbi->usb_chrg_props.charger_present;
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               if (mbi->batt_props.status != POWER_SUPPLY_STATUS_NOT_CHARGING)
+                       val->intval = mbi->usb_chrg_props.charger_present;
+               else
+                       val->intval = 0;
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               val->intval = mbi->usb_chrg_props.charger_health;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               retval = mdf_read_adc_regs(MSIC_ADC_USB_VOL_IDX,
+                                   &mbi->usb_chrg_props.vbus_vol, mbi);
+               if (retval)
+                       break;
+               val->intval = mbi->usb_chrg_props.vbus_vol * 1000;
+               break;
+       case POWER_SUPPLY_PROP_MODEL_NAME:
+               val->strval = mbi->usb_chrg_props.charger_model;
+               break;
+       case POWER_SUPPLY_PROP_MANUFACTURER:
+               val->strval = mbi->usb_chrg_props.charger_vender;
+               break;
+       default:
+               mutex_unlock(&mbi->usb_chrg_lock);
+               return -EINVAL;
+       }
+
+       mutex_unlock(&mbi->usb_chrg_lock);
+       return retval;
+}
+
+/**
+ * msic_log_exception_event - log battery events
+ * @event: msic event to be logged
+ * Context: can sleep
+ *
+ * There are multiple battery and internal charger events
+ * which may be of interest to users.
+ * this battery function logs the different events onto the
+ * kernel log messages.
+ */
+static void msic_log_exception_event(enum msic_event event)
+{
+       switch (event) {
+       case MSIC_EVENT_BATTOCP_EXCPT:
+               dev_warn(msic_dev,
+                        "over battery charge current condition detected\n");
+               break;
+       case MSIC_EVENT_BATTOTP_EXCPT:
+               dev_warn(msic_dev,
+                        "battery out of acceptable temp range condition detected\n");
+               break;
+       case MSIC_EVENT_LOWBATT_EXCPT:
+               dev_warn(msic_dev, "Low battery voltage condition detected\n");
+               break;
+       case MSIC_EVENT_BATTOVP_EXCPT:
+               dev_warn(msic_dev, "battery over voltage condition detected\n");
+               break;
+       case MSIC_EVENT_CHROTP_EXCPT:
+               dev_warn(msic_dev,
+                        "charger high temperature condition detected\n");
+               break;
+       case MSIC_EVENT_USBOVP_EXCPT:
+               dev_warn(msic_dev, "USB over voltage condition detected\n");
+               break;
+       case MSIC_EVENT_USB_VINREG_EXCPT:
+               dev_warn(msic_dev, "USB Input voltage regulation "
+                        "condition detected\n");
+               break;
+       case MSIC_EVENT_WEAKVIN_EXCPT:
+               dev_warn(msic_dev, "USB Weak VBUS voltage "
+                        "condition detected\n");
+               break;
+       case MSIC_EVENT_TIMEEXP_EXCPT:
+               dev_warn(msic_dev, "Charger Total Time Expiration "
+                        "condition detected\n");
+               break;
+       case MSIC_EVENT_WDTIMEEXP_EXCPT:
+               dev_warn(msic_dev, "Watchdog Time Expiration "
+                        "condition detected\n");
+               break;
+       default:
+               dev_warn(msic_dev, "unknown error %u detected\n", event);
+               break;
+       }
+}
+
+/**
+ * msic_handle_exception - handle any exception scenario
+ * @mbi: device info structure to update the information
+ * Context: can sleep
+ *
+ */
+
+static void msic_handle_exception(struct msic_power_module_info *mbi,
+                                 uint8_t CHRINT_reg_value,
+                                 uint8_t CHRINT1_reg_value)
+{
+       enum msic_event exception;
+       int temp, retval;
+
+       /* Battery Events */
+       mutex_lock(&mbi->batt_lock);
+       if (CHRINT_reg_value & MSIC_BATT_CHR_BATTOCP_MASK) {
+               exception = MSIC_EVENT_BATTOCP_EXCPT;
+               msic_log_exception_event(exception);
+       }
+
+       if (CHRINT_reg_value & MSIC_BATT_CHR_BATTOTP_MASK) {
+               retval = mdf_read_adc_regs(MSIC_ADC_TEMP_IDX, &temp, mbi);
+               if (retval) {
+                       dev_err(msic_dev, "%s(): Error in reading"
+                              " temperature. Setting health as OVERHEAT\n",
+                              __func__);
+               }
+               if (retval || (temp > batt_thrshlds->temp_high) ||
+                       (temp < batt_thrshlds->temp_low))
+                       mbi->batt_props.health = POWER_SUPPLY_HEALTH_OVERHEAT;
+               exception = MSIC_EVENT_BATTOTP_EXCPT;
+               msic_log_exception_event(exception);
+       }
+
+       if (CHRINT_reg_value & MSIC_BATT_CHR_LOWBATT_MASK) {
+               mbi->batt_props.health = POWER_SUPPLY_HEALTH_DEAD;
+               exception = MSIC_EVENT_LOWBATT_EXCPT;
+               msic_log_exception_event(exception);
+       }
+       if (CHRINT_reg_value & MSIC_BATT_CHR_TIMEEXP_MASK) {
+               exception = MSIC_EVENT_TIMEEXP_EXCPT;
+               msic_log_exception_event(exception);
+       }
+
+       if (CHRINT_reg_value & MSIC_BATT_CHR_WDTIMEEXP_MASK) {
+               exception = MSIC_EVENT_WDTIMEEXP_EXCPT;
+               msic_log_exception_event(exception);
+       }
+
+       if (CHRINT1_reg_value & MSIC_BATT_CHR_BATTOVP_MASK) {
+               mbi->batt_props.health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+               exception = MSIC_EVENT_BATTOVP_EXCPT;
+               msic_log_exception_event(exception);
+       }
+       mutex_unlock(&mbi->batt_lock);
+
+       /* Charger Events */
+       mutex_lock(&mbi->usb_chrg_lock);
+       if (CHRINT1_reg_value & MSIC_BATT_CHR_CHROTP_MASK) {
+               exception = MSIC_EVENT_CHROTP_EXCPT;
+               msic_log_exception_event(exception);
+       }
+
+       if (CHRINT1_reg_value & MSIC_BATT_CHR_USBOVP_MASK) {
+               mbi->usb_chrg_props.charger_health =
+                   POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+               exception = MSIC_EVENT_USBOVP_EXCPT;
+               msic_log_exception_event(exception);
+       }
+       if (CHRINT1_reg_value & MSIC_BATT_CHR_WKVINDET_MASK) {
+               mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_DEAD;
+               exception = MSIC_EVENT_WEAKVIN_EXCPT;
+               msic_log_exception_event(exception);
+       }
+       if (CHRINT1_reg_value & MSIC_BATT_CHR_VINREGMINT_MASK) {
+               mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_DEAD;
+               exception = MSIC_EVENT_USB_VINREG_EXCPT;
+               msic_log_exception_event(exception);
+       }
+       mutex_unlock(&mbi->usb_chrg_lock);
+}
+
+/**
+ *     msic_write_multi        -       multi-write helper
+ *     @mbi: msic power module
+ *     @address: addresses of IPC writes
+ *     @data: data for IPC writes
+ *     @n: size of write table
+ *
+ *     Write a series of values to the SCU while respecting the ipc_rw_lock
+ *     across the entire sequence. Handle any error reporting and pass back
+ *     error codes on failure
+ */
+static int msic_write_multi(struct msic_power_module_info *mbi,
+                           const u16 *address, const u8 *data, int n)
+{
+       int retval = 0, i;
+       mutex_lock(&mbi->ipc_rw_lock);
+       for (i = 0; i < n; i++) {
+               retval = intel_scu_ipc_iowrite8(*address++, *data++);
+               if (retval) {
+                       retval = handle_ipc_rw_status(retval,
+                                       *(address-1), MSIC_IPC_WRITE);
+                       if (retval)
+                               break;
+               }
+       }
+       mutex_unlock(&mbi->ipc_rw_lock);
+       return retval;
+}
+
+/**
+ *     ipc_read_modify_chr_param_reg - read and modify charger registers
+ *     @mbi: msic power module
+ *     @address:  charger register address
+ *     @data: value to be set/reset
+ *     @n: set or reset
+ *
+ */
+static int ipc_read_modify_chr_param_reg(struct msic_power_module_info *mbi,
+                                        uint16_t addr, uint8_t val, int set)
+{
+       int ret = 0;
+       static u16 address[2] = {
+               MSIC_BATT_CHR_WDTWRITE_ADDR, 0
+       };
+       static u8 data[2] = {
+               WDTWRITE_UNLOCK_VALUE, 0
+       };
+
+       address[1] = addr;
+
+       /* Unlock Charge parameter registers before reading */
+       ret = intel_scu_ipc_iowrite8(address[0], data[0]);
+       if (ret) {
+               ret = handle_ipc_rw_status(ret,
+                               address[0], MSIC_IPC_WRITE);
+               if (ret)
+                       return ret;
+       }
+
+       ret = intel_scu_ipc_ioread8(address[1], &data[1]);
+       if (ret) {
+               ret = handle_ipc_rw_status(ret, address[1], MSIC_IPC_READ);
+               if (ret)
+                       return ret;
+       }
+
+       if (set)
+               data[1] |= val;
+       else
+               data[1] &= (~val);
+
+       return msic_write_multi(mbi, address, data, 2);
+}
+
+/**
+ *     ipc_read_modify__reg - read and modify MSIC registers
+ *     @mbi: msic power module
+ *     @address:  charger register address
+ *     @data: value to be set/reset
+ *     @n: set or reset
+ *
+ */
+static int ipc_read_modify_reg(struct msic_power_module_info *mbi,
+                              uint16_t addr, uint8_t val, int set)
+{
+       int ret;
+       u8 data;
+
+       ret = intel_scu_ipc_ioread8(addr, &data);
+       if (ret) {
+               ret = handle_ipc_rw_status(ret, addr, MSIC_IPC_READ);
+               if (ret)
+                       return ret;
+       }
+
+       if (set)
+               data |= val;
+       else
+               data &= (~val);
+
+       ret = intel_scu_ipc_iowrite8(addr, data);
+       if (ret)
+               ret = handle_ipc_rw_status(ret, addr, MSIC_IPC_WRITE);
+
+       return ret;
+}
+
+/**
+ * update_usb_ps_info - update usb power supply parameters
+ * @mbi: msic power module structure
+ * @cap: charger capability structure
+ * @event: USB OTG events
+ * Context: can sleep
+ *
+ * Updates the USB power supply parameters based on otg event.
+ */
+static void update_usb_ps_info(struct msic_power_module_info *mbi,
+                               struct otg_bc_cap *cap, int event)
+{
+       mutex_lock(&mbi->usb_chrg_lock);
+       switch (event) {
+       case USBCHRG_EVENT_DISCONN:
+               mbi->usb.type = POWER_SUPPLY_TYPE_USB;
+       case USBCHRG_EVENT_SUSPEND:
+               dev_dbg(msic_dev, "Charger Disconnected or Suspended\n");
+               mbi->usb_chrg_props.charger_health =
+                                       POWER_SUPPLY_HEALTH_UNKNOWN;
+               memcpy(mbi->usb_chrg_props.charger_model, "Unknown",
+                                                       sizeof("Unknown"));
+               memcpy(mbi->usb_chrg_props.charger_vender, "Unknown",
+                                                       sizeof("Unknown"));
+               if (event == USBCHRG_EVENT_SUSPEND)
+                       mbi->usb_chrg_props.charger_present =
+                                               MSIC_USB_CHARGER_PRESENT;
+               else
+                       mbi->usb_chrg_props.charger_present =
+                                               MSIC_USB_CHARGER_NOT_PRESENT;
+               break;
+       case USBCHRG_EVENT_CONNECT:
+       case USBCHRG_EVENT_UPDATE:
+       case USBCHRG_EVENT_RESUME:
+               dev_dbg(msic_dev, "Charger Connected or Updated\n");
+               if (cap->chrg_type == CHRG_CDP) {
+                       mbi->usb.type = POWER_SUPPLY_TYPE_USB_CDP;
+                       dev_info(msic_dev, "Charger type: CDP, "
+                                       "current-val: %d", cap->mA);
+               } else if (cap->chrg_type == CHRG_DCP) {
+                       mbi->usb.type = POWER_SUPPLY_TYPE_USB_DCP;
+                       dev_info(msic_dev, "Charger type: DCP, "
+                                       "current-val: %d", cap->mA);
+               } else if (cap->chrg_type == CHRG_ACA) {
+                       mbi->usb.type = POWER_SUPPLY_TYPE_USB_ACA;
+                       dev_info(msic_dev, "Charger type: ACA, "
+                                       "current-val: %d", cap->mA);
+               } else if (cap->chrg_type == CHRG_SDP) {
+                       mbi->usb.type = POWER_SUPPLY_TYPE_USB;
+                       dev_info(msic_dev, "Charger type: SDP, "
+                                       "current negotiated: %d", cap->mA);
+               } else {
+                       /* CHRG_UNKNOWN */
+                       dev_warn(msic_dev, "Charger type:%d unknown\n",
+                                       cap->chrg_type);
+                       mbi->usb.type = POWER_SUPPLY_TYPE_USB;
+                       goto update_usb_ps_exit;
+               }
+               mbi->usb_chrg_props.charger_present = MSIC_USB_CHARGER_PRESENT;
+               mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_GOOD;
+               memcpy(mbi->usb_chrg_props.charger_model, "msic",
+                                                       sizeof("msic"));
+               memcpy(mbi->usb_chrg_props.charger_vender, "Intel",
+                                                       sizeof("Intel"));
+               break;
+       default:
+               dev_warn(msic_dev, "Invalid OTG event\n");
+       }
+
+update_usb_ps_exit:
+       mutex_unlock(&mbi->usb_chrg_lock);
+
+       power_supply_changed(&mbi->usb);
+}
+
+static int msic_batt_stop_charging(struct msic_power_module_info *mbi)
+{
+       static const u16 address[] = {
+               MSIC_BATT_CHR_WDTWRITE_ADDR,
+               MSIC_BATT_CHR_CHRCTRL_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR,
+               MSIC_BATT_CHR_CHRSTWDT_ADDR,
+       };
+       static const u8 data[] = {
+               WDTWRITE_UNLOCK_VALUE,  /* Unlock chrg params */
+               /*Disable Charging,Enable Low Power Mode */
+               CHRCNTL_CHRG_DISABLE | CHRCNTL_CHRG_LOW_PWR_ENBL,
+               WDTWRITE_UNLOCK_VALUE,  /* Unlock chrg params */
+               CHR_WDT_DISABLE,        /*  Disable WDT Timer */
+       };
+
+       /*
+        * Charger connect handler delayed work also modifies the
+        * MSIC charger parameter registers.To avoid concurrent
+        * read writes to same set of registers  locking applied by
+        * msic_write_multi
+        */
+       return msic_write_multi(mbi, address, data, 4);
+}
+
+/**
+ * msic_batt_do_charging - set battery charger
+ * @mbi: device info structure
+ * @chrg: charge mode to set battery charger in
+ * Context: can sleep
+ *
+ * MsIC battery charger needs to be enabled based on the charger
+ * capabilities connected to the platform.
+ */
+static int msic_batt_do_charging(struct msic_power_module_info *mbi,
+                                struct charge_params *params,
+                                int is_maint_mode)
+{
+       int retval;
+       static u8 data[] = {
+               WDTWRITE_UNLOCK_VALUE, 0,
+               WDTWRITE_UNLOCK_VALUE, 0,
+               WDTWRITE_UNLOCK_VALUE, 0,
+               WDTWRITE_UNLOCK_VALUE, CHR_WDT_SET_60SEC,
+               WDTWRITE_UNLOCK_VALUE, 0
+       };
+       static const u16 address[] = {
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCCURRENT_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCVOLTAGE_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCTRL_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRSTWDT_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_SPCHARGER_ADDR
+       };
+
+       data[1] = params->ccur;
+       data[3] = params->cvol; /* charge voltage 4.14V */
+       data[5] = params->vinilmt;
+       data[9] = params->weakvin;
+
+       /*
+        * Charger disconnect handler also modifies the
+        * MSIC charger parameter registers.To avoid concurrent
+        * read writes to same set of registers  locking applied
+        */
+       retval = msic_write_multi(mbi, address, data, 10);
+       if (retval < 0) {
+               dev_warn(msic_dev, "ipc multi write failed:%s\n", __func__);
+               return retval;
+       }
+
+       dev_dbg(msic_dev, "Charger Enabled\n");
+       /* prevent system from entering s3 while charger is connected */
+       if (!wake_lock_active(&mbi->wakelock))
+               wake_lock(&mbi->wakelock);
+
+       mutex_lock(&mbi->batt_lock);
+       if (is_maint_mode)
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_FULL;
+       else
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_CHARGING;
+       mutex_unlock(&mbi->batt_lock);
+
+       power_supply_changed(&mbi->usb);
+       return 0;
+}
+
+static void reset_wdt_timer(struct msic_power_module_info *mbi)
+{
+       static const u16 address[2] = {
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRSTWDT_ADDR
+       };
+       static const u8 data[2] = {
+               WDTWRITE_UNLOCK_VALUE, CHR_WDT_SET_60SEC
+       };
+
+       /*
+        * Charger disconnect handler also modifies the
+        * MSIC charger parameter registers.To avoid concurrent
+        * read writes to same set of registers  locking applied
+        */
+       msic_write_multi(mbi, address, data, 2);
+}
+
+/**
+ * check_charge_full -  check battery is full or not
+ * @mbi: device info structure
+ * @vref: battery voltage
+ * @temp_idx : temperature range index
+ * Context: can sleep
+ *
+ * Return true if full
+ *
+ */
+static int check_charge_full(struct msic_power_module_info *mbi,
+                            int vref, int temp_idx)
+{
+       static int volt_prev;
+       int is_full = false;
+       int volt_now;
+       int cur_avg;
+
+       /* Read voltage and current from FG driver */
+       volt_now = fg_chip_get_property(POWER_SUPPLY_PROP_VOLTAGE_NOW);
+       if (volt_now == -ENODEV || volt_now == -EINVAL) {
+               dev_warn(msic_dev, "Can't read voltage from FG\n");
+               return false;
+       }
+       /* convert to milli volts */
+       volt_now /= 1000;
+
+       /* Using Current-avg instead of Current-now to take care of
+        * instantaneous spike or dip */
+       cur_avg = fg_chip_get_property(POWER_SUPPLY_PROP_CURRENT_AVG);
+       if (cur_avg == -ENODEV || cur_avg == -EINVAL) {
+               dev_warn(msic_dev, "Can't read current-avg from FG\n");
+               return false;
+       }
+       /* convert to milli amps */
+       cur_avg /= 1000;
+
+       if ((volt_now > (vref)) &&
+               (volt_prev > (vref))) {
+               if (cur_avg >= FULL_CURRENT_AVG_LOW  &&
+                               cur_avg <= FULL_CURRENT_AVG_HIGH)
+                       is_full = true;
+               else
+                       is_full = false;
+       } else {
+               is_full = false;
+       }
+
+
+       if (is_full) {
+               dev_info(msic_dev, "Charge full detected\n");
+               dev_dbg(msic_dev, "volt_now:%d, volt_prev:%d, "
+                               "volt_ref:%d, cur_avg:%d\n",
+                               volt_now, volt_prev, vref, cur_avg);
+               /* Disable Charging */
+               msic_batt_stop_charging(mbi);
+       }
+
+       volt_prev = volt_now;
+
+       return is_full;
+}
+
+/**
+* sfi_temp_range_lookup - lookup SFI table to find the temperature range index
+* @adc_temp : temperature in Degree Celcius
+*
+* Returns temperature range index
+*/
+static unsigned int sfi_temp_range_lookup(int adc_temp)
+{
+       int i;
+
+       for (i = 0; i < sfi_table->temp_mon_ranges; i++) {
+               if (adc_temp <= sfi_table->temp_mon_range[i].temp_up_lim &&
+                   adc_temp > sfi_table->temp_mon_range[i].temp_low_lim) {
+                       dev_dbg(msic_dev, "Temp Range %d\n", i);
+                       break;
+               }
+       }
+
+       return i;
+}
+
+/**
+* msic_batt_temp_charging - manages the charging based on temperature
+* @charge_param: charging parameter
+* @sfi_table: SFI table structure
+*
+* To manage the charging based on the
+* temperature of the battery
+*/
+static void msic_batt_temp_charging(struct work_struct *work)
+{
+       int ret, i, is_maint_chrg = false, is_lowchrg_enbl;
+       static int iprev = -1, is_chrg_enbl;
+       short int cv = 0, cc = 0, vinlimit = 0, cvref;
+       int adc_temp, adc_vol;
+       struct charge_params charge_param;
+       struct msic_power_module_info *mbi =
+           container_of(work, struct msic_power_module_info,
+                        connect_handler.work);
+       struct temp_mon_table *temp_mon = NULL;
+
+       memset(&charge_param, 0x0, sizeof(struct charge_params));
+       charge_param.vinilmt = mbi->ch_params.vinilmt;
+       charge_param.chrg_type = mbi->ch_params.chrg_type;
+
+       mutex_lock(&mbi->event_lock);
+       if (mbi->refresh_charger) {
+               /*
+                * If the charger type is unknown or None
+                * better start the charging again and compute
+                * the properties again.
+                */
+               mbi->refresh_charger = 0;
+               iprev = -1;
+               is_chrg_enbl = false;
+       }
+       mutex_unlock(&mbi->event_lock);
+
+       if (mdf_read_adc_regs(MSIC_ADC_TEMP_IDX, &adc_temp, mbi)) {
+               dev_err(msic_dev, "Error in reading temperature\n");
+               goto lbl_sched_work;
+       }
+
+       /* find the temperature range */
+       i = sfi_temp_range_lookup(adc_temp);
+
+       if (i >= sfi_table->temp_mon_ranges || is_charger_fault()) {
+               if ((adc_temp > batt_thrshlds->temp_high) ||
+                       (adc_temp < batt_thrshlds->temp_low)) {
+                       dev_warn(msic_dev,
+                                       "TEMP RANGE DOES NOT EXIST FOR %d\n",
+                                               adc_temp);
+                       mutex_lock(&mbi->batt_lock);
+                       mbi->batt_props.health = POWER_SUPPLY_HEALTH_OVERHEAT;
+                       mutex_unlock(&mbi->batt_lock);
+               }
+               /* Check charger Status bits */
+               if (is_charger_fault()) {
+                       mutex_lock(&mbi->batt_lock);
+                       mbi->batt_props.status =
+                                       POWER_SUPPLY_STATUS_NOT_CHARGING;
+                       mutex_unlock(&mbi->batt_lock);
+               }
+               /*
+                * If we are in middle of charge cycle is safer to Reset WDT
+                * Timer Register.Because battery temperature and charge
+                * status register are not related.
+                */
+               reset_wdt_timer(mbi);
+               dev_dbg(msic_dev, "Charger Watchdog timer reset for 60sec\n");
+               goto lbl_sched_work;
+       }
+
+       /* Set charger parameters */
+       cv = sfi_table->temp_mon_range[i].full_chrg_vol;
+       cc = sfi_table->temp_mon_range[i].full_chrg_cur;
+       cvref = cv;
+       dev_dbg(msic_dev, "cc:%d  cv:%d\n", cc, cv);
+
+       mutex_lock(&mbi->event_lock);
+       /* Check on user setting for charge current */
+       if (mbi->usr_chrg_enbl == USER_SET_CHRG_LMT1)
+               vinlimit = CHRCNTL_VINLMT_100;  /* VINILMT set to 100mA */
+       else if (mbi->usr_chrg_enbl == USER_SET_CHRG_LMT2)
+               vinlimit = CHRCNTL_VINLMT_500;  /* VINILMT set to 500mA */
+       else if (mbi->usr_chrg_enbl == USER_SET_CHRG_LMT3)
+               vinlimit = CHRCNTL_VINLMT_950;  /* VINILMT set to 950mA */
+       else {
+               /* D7,D6 bits of CHRCNTL will set the VINILMT */
+               if (charge_param.vinilmt > 950)
+                       vinlimit = CHRCNTL_VINLMT_NOLMT;
+               else if (charge_param.vinilmt > 500)
+                       vinlimit = CHRCNTL_VINLMT_950;
+               else if (charge_param.vinilmt > 100)
+                       vinlimit = CHRCNTL_VINLMT_500;
+               else
+                       vinlimit = CHRCNTL_VINLMT_100;
+       }
+       /*
+        * Check for Charge full condition and set the battery
+        * properties accordingly. Also check for charging mode
+        * whether it is normal or maintenance mode.
+        */
+       if (mbi->charging_mode == BATT_CHARGING_MODE_MAINTENANCE) {
+               cvref = sfi_table->temp_mon_range[i].maint_chrg_vol_ul;
+               is_maint_chrg = true;
+       }
+       mutex_unlock(&mbi->event_lock);
+
+       /* Check full detection only if we are charging */
+       if (is_chrg_enbl)
+               ret = check_charge_full(mbi, cvref, i);
+       else
+               ret = is_chrg_enbl;
+
+       if (ret) {
+               is_chrg_enbl = false;
+               if (!is_maint_chrg) {
+                       dev_dbg(msic_dev, "Going to Maintenance CHRG Mode\n");
+
+                       mutex_lock(&mbi->event_lock);
+                       mbi->charging_mode = BATT_CHARGING_MODE_MAINTENANCE;
+                       mutex_unlock(&mbi->event_lock);
+
+                       mutex_lock(&mbi->batt_lock);
+                       mbi->batt_props.status = POWER_SUPPLY_STATUS_FULL;
+                       mutex_unlock(&mbi->batt_lock);
+
+                       is_maint_chrg = true;
+                       power_supply_changed(&mbi->usb);
+               }
+       }
+
+       /*
+        * If we are in same Temperature range check for the
+        * maintenance charging mode and enable the charging depending
+        * on the voltage.If Temperature range is changed then anyways
+        * we need to set charging parameters and enable charging.
+        */
+       if (i == iprev) {
+               /*
+                * Check if the voltage falls below lower threshold
+                * if we are in maintenance mode charging.
+                */
+               if (is_maint_chrg && !is_chrg_enbl) {
+                       temp_mon = &sfi_table->temp_mon_range[i];
+                       /* Read battery Voltage */
+                       adc_vol = fg_chip_get_property(
+                                       POWER_SUPPLY_PROP_VOLTAGE_NOW);
+                       if (adc_vol == -ENODEV || adc_vol == -EINVAL) {
+                               dev_warn(msic_dev, "Can't read voltage from FG\n");
+                               goto lbl_sched_work;
+                       }
+                       /* convert to milli volts */
+                       adc_vol /= 1000;
+
+                       if ((adc_vol <= temp_mon->maint_chrg_vol_ll)) {
+                               dev_dbg(msic_dev, "restart charging\n");
+                               cv = temp_mon->maint_chrg_vol_ul;
+                       } else {
+                               dev_dbg(msic_dev, "vbat is more than ll\n");
+                               goto lbl_sched_work;
+                       }
+               } else {
+                       /* Reset WDT Timer Register for 60 Sec */
+                       reset_wdt_timer(mbi);
+                       dev_dbg(msic_dev, "Charger Watchdog timer reset for 60sec\n");
+                       goto lbl_sched_work;
+               }
+       } else {
+               temp_mon = &sfi_table->temp_mon_range[i];
+               dev_info(msic_dev, "Current Temp zone is %d, "
+                               "it's parameters are:\n", i);
+               dev_info(msic_dev, "full_volt:%d, full_cur:%d\n",
+                               temp_mon->full_chrg_vol,
+                               temp_mon->full_chrg_cur);
+               dev_info(msic_dev, "maint_vol_ll:%d, maint_vol_ul:%d, "
+                               "maint_cur:%d\n", temp_mon->maint_chrg_vol_ll,
+                               temp_mon->maint_chrg_vol_ul,
+                               temp_mon->maint_chrg_cur);
+               dump_registers();
+       }
+
+       iprev = i;
+       mbi->ch_params.cvol = cv;
+       charge_param.cvol = CONV_VOL_DEC_MSICREG(cv);
+       /* CHRCC_MIN_CURRENT is th lowet value */
+       cc = cc - CHRCC_MIN_CURRENT;
+       /*
+        * If charge current parameter is less than 550mA we should
+        * enable LOW CHARGE mode which will limit the charge current to 325mA.
+        */
+       if (cc <= 0) {
+               dev_dbg(msic_dev, "LOW CHRG mode enabled\n");
+               cc = 0;
+               is_lowchrg_enbl = BIT_SET;
+       } else {
+               dev_dbg(msic_dev, "LOW CHRG mode NOT enabled\n");
+               cc = cc / 100;
+               is_lowchrg_enbl = BIT_RESET;
+       }
+       cc = cc << 3;
+
+       charge_param.ccur = cc;
+       charge_param.vinilmt = vinlimit;
+
+       dev_dbg(msic_dev, "params  vol: %x  cur:%x vinilmt:%x\n",
+               charge_param.cvol, charge_param.ccur, charge_param.vinilmt);
+
+       if (cv > WEAKVIN_VOLTAGE_LEVEL)
+               charge_param.weakvin = CHR_SPCHRGER_WEAKVIN_LVL1;
+       else
+               charge_param.weakvin = CHR_SPCHRGER_WEAKVIN_LVL2;
+
+       if (is_lowchrg_enbl)
+               charge_param.weakvin |= CHR_SPCHRGER_LOWCHR_ENABLE;
+
+       /* enable charging here */
+       ret = msic_batt_do_charging(mbi, &charge_param, is_maint_chrg);
+       if (ret) {
+               dev_warn(msic_dev, "msic_batt_do_charging failed\n");
+               goto lbl_sched_work;
+       }
+       is_chrg_enbl = true;
+
+lbl_sched_work:
+       /* Schedule the work after 30 Seconds */
+       schedule_delayed_work(&mbi->connect_handler, TEMP_CHARGE_DELAY_JIFFIES);
+}
+static void update_charger_health(struct msic_power_module_info *mbi)
+{
+       int vbus_volt;
+       unsigned char dummy_val;
+
+       /* We don't get an interrupt once charger returns from
+         error state. So check current status by reading voltage
+         and report health as good if recovered from error state */
+
+
+       /* Read charger data from ADC channels */
+       if (mdf_read_adc_regs(MSIC_ADC_USB_VOL_IDX, &vbus_volt, mbi)) {
+               dev_warn(msic_dev, "Error in reading charger"
+                               " voltage:%s\n", __func__);
+               return;
+       }
+
+       /* Compute Charger health */
+       mutex_lock(&mbi->usb_chrg_lock);
+       /* check system recovered from overvoltage and dead conditions */
+       if ((mbi->usb_chrg_props.charger_health ==
+                       POWER_SUPPLY_HEALTH_OVERVOLTAGE ||
+               mbi->usb_chrg_props.charger_health
+                       == POWER_SUPPLY_HEALTH_DEAD) &&
+           vbus_volt >= MSIC_VBUS_LOW_VOLTAGE &&
+           vbus_volt <= MSIC_VBUS_OVER_VOLTAGE) {
+
+               mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_GOOD;
+
+       } else if (mbi->batt_props.health ==
+                       POWER_SUPPLY_HEALTH_UNSPEC_FAILURE){
+
+               /* do a dummy IPC read to see IPC recovered form IPC error.
+                * If recovered reset error_count*/
+
+               if (!intel_scu_ipc_ioread8(MSIC_BATT_CHR_CHRCTRL_ADDR,
+                                       &dummy_val)) {
+                       error_count = 0;
+                       mbi->usb_chrg_props.charger_health =
+                                       POWER_SUPPLY_HEALTH_GOOD;
+
+               }
+       }
+       mutex_unlock(&mbi->usb_chrg_lock);
+}
+
+static void update_battery_health(struct msic_power_module_info *mbi)
+{
+       int temp, volt, chr_mode, max_volt;
+       unsigned char dummy_val;
+
+       mutex_lock(&mbi->event_lock);
+       chr_mode = mbi->charging_mode;
+       mutex_unlock(&mbi->event_lock);
+
+       /* We don't get an interrupt once battery returns from
+         error state. So check current status by reading voltagei and
+         temperature and report health as good if recovered from error state */
+
+       volt = fg_chip_get_property(POWER_SUPPLY_PROP_VOLTAGE_NOW);
+
+       if (volt == -ENODEV || volt == -EINVAL) {
+               dev_warn(msic_dev, "Can't read voltage from FG\n");
+               return;
+       }
+
+       temp = fg_chip_get_property(POWER_SUPPLY_PROP_TEMP);
+
+       if (temp == -ENODEV || temp == -EINVAL) {
+               dev_warn(msic_dev, "Can't read temp from FG\n");
+               return;
+       }
+
+       /* convert to milli volts */
+       volt /= 1000;
+
+       /*convert to degree Celcius from tenths of degree Celsius */
+       temp = temp / 10;
+
+
+
+       if (chr_mode != BATT_CHARGING_MODE_NONE)
+               max_volt = (mbi->ch_params.cvol * OVP_VAL_MULT_FACTOR) / 10;
+       else
+               max_volt = BATT_OVERVOLTAGE_CUTOFF_VOLT;
+
+       /* Check for fault and update health */
+       mutex_lock(&mbi->batt_lock);
+       /*
+        * Valid temperature window is 0 to 60 Degrees
+        * and thermistor has 2 degree hysteresis and considering
+        * 2 degree adc error, fault revert temperature will
+        * be 4 to 56 degrees.
+        */
+       if (mbi->batt_props.health == POWER_SUPPLY_HEALTH_GOOD) {
+               /* if BATTOTP is masked check temperature and
+                * update BATT HEALTH */
+               if (mbi->chrint_mask & MSIC_BATT_CHR_BATTOTP_MASK) {
+                       if ((temp > (batt_thrshlds->temp_high)) ||
+                                       (temp < (batt_thrshlds->temp_low))) {
+                               /* We report OVERHEAT in both cases*/
+                               mbi->batt_props.health =
+                                       POWER_SUPPLY_HEALTH_OVERHEAT;
+                               dev_dbg(msic_dev, "Battery pack temp: %d, "
+                                               "too hot or too cold\n", temp);
+                       }
+               }
+       } else {
+               /* check for battery overvoltage,overheat and dead  health*/
+               if ((mbi->batt_props.health !=
+                                     POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) &&
+                       (temp <= (batt_thrshlds->temp_high -
+                                MSIC_TEMP_HYST_ERR)) &&
+                       (temp >= (batt_thrshlds->temp_low
+                                 + MSIC_TEMP_HYST_ERR)) &&
+                       (volt >= batt_thrshlds->vbatt_crit) &&
+                       (volt <= max_volt)) {
+
+                       mbi->batt_props.health = POWER_SUPPLY_HEALTH_GOOD;
+                       dev_dbg(msic_dev, "Setting battery-health, power-supply good");
+                       dump_registers();
+               } else if (mbi->batt_props.health ==
+                               POWER_SUPPLY_HEALTH_UNSPEC_FAILURE){
+
+                       /* do a dummy IPC read to see IPC recovered from
+                        * IPC error. If recovered reset error_count*/
+
+                       if (!intel_scu_ipc_ioread8(MSIC_BATT_CHR_CHRCTRL_ADDR,
+                                               &dummy_val)) {
+                               error_count = 0;
+                               mbi->batt_props.health =
+                                       POWER_SUPPLY_HEALTH_GOOD;
+
+                       }
+               }
+       }
+
+       mutex_unlock(&mbi->batt_lock);
+
+}
+
+static void msic_batt_disconn(struct work_struct *work)
+{
+       int ret, event;
+       struct msic_power_module_info *mbi =
+           container_of(work, struct msic_power_module_info,
+                        disconn_handler.work);
+
+       mutex_lock(&mbi->event_lock);
+       event = mbi->batt_event;
+       mutex_unlock(&mbi->event_lock);
+
+       if (event != USBCHRG_EVENT_SUSPEND &&
+               event != USBCHRG_EVENT_DISCONN) {
+               dev_warn(msic_dev, "%s:Not a Disconn or Suspend event\n",
+                                                       __func__);
+               return ;
+       }
+
+       dev_dbg(msic_dev, "Stopping charging due to charger event: %s\n",
+                       (event == USBCHRG_EVENT_SUSPEND) ? "SUSPEND" :
+                       "DISCONNECT");
+       dump_registers();
+       ret = msic_batt_stop_charging(mbi);
+       if (ret) {
+               dev_warn(msic_dev, "%s: failed\n", __func__);
+               return;
+       }
+
+       mutex_lock(&mbi->batt_lock);
+       if (event == USBCHRG_EVENT_SUSPEND)
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+       else
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_DISCHARGING;
+       mutex_unlock(&mbi->batt_lock);
+
+       /* release the wake lock when charger is unplugged */
+       if (wake_lock_active(&mbi->wakelock))
+               wake_unlock(&mbi->wakelock);
+
+       power_supply_changed(&mbi->usb);
+}
+
+/**
+* msic_event_handler - msic event handler
+* @arg : private  data pointer
+* @event: MSIC event
+* @cap : otg capabilities
+*
+*/
+static int msic_event_handler(void *arg, int event, struct otg_bc_cap *cap)
+{
+       struct msic_power_module_info *mbi =
+           (struct msic_power_module_info *)arg;
+
+       /* Update USB power supply info */
+       update_usb_ps_info(mbi, cap, event);
+
+       /* check for valid battery condition */
+       if (!mbi->is_batt_valid)
+               return 0;
+
+       mutex_lock(&mbi->event_lock);
+       if ((mbi->batt_event == event && event != USBCHRG_EVENT_UPDATE) ||
+           (!mbi->usr_chrg_enbl)) {
+               mutex_unlock(&mbi->event_lock);
+               return 0;
+       }
+       mbi->batt_event = event;
+       mutex_unlock(&mbi->event_lock);
+
+       switch (event) {
+       case USBCHRG_EVENT_CONNECT:
+               pm_runtime_get_sync(&mbi->pdev->dev);
+       case USBCHRG_EVENT_RESUME:
+       case USBCHRG_EVENT_UPDATE:
+               /*
+                * If previous event CONNECT and current is event is
+                * UPDATE, we have already queued the work.
+                * Its better to dequeue the previous work
+                * and add the new work to the queue.
+                */
+               cancel_delayed_work_sync(&mbi->connect_handler);
+               mbi->ch_params.vinilmt = cap->mA;
+               mbi->ch_params.chrg_type = cap->chrg_type;
+               dev_dbg(msic_dev, "CHRG TYPE:%d %d\n", cap->chrg_type, cap->mA);
+               mutex_lock(&mbi->event_lock);
+               mbi->refresh_charger = 1;
+               if (mbi->charging_mode == BATT_CHARGING_MODE_NONE)
+                       mbi->charging_mode = BATT_CHARGING_MODE_NORMAL;
+               mutex_unlock(&mbi->event_lock);
+
+               /* Enable charger LED */
+               ipc_read_modify_chr_param_reg(mbi, MSIC_CHRG_LED_CNTL_REG,
+                                             MSIC_CHRG_LED_ENBL, 1);
+               /*Disable charger  LOW Power Mode */
+               ipc_read_modify_chr_param_reg(mbi, MSIC_BATT_CHR_CHRCTRL_ADDR,
+                                             CHRCNTL_CHRG_LOW_PWR_ENBL, 0);
+
+               schedule_delayed_work(&mbi->connect_handler, 0);
+               break;
+       case USBCHRG_EVENT_DISCONN:
+               pm_runtime_put_sync(&mbi->pdev->dev);
+       case USBCHRG_EVENT_SUSPEND:
+               dev_dbg(msic_dev, "USB DISCONN or SUSPEND\n");
+               cancel_delayed_work_sync(&mbi->connect_handler);
+               schedule_delayed_work(&mbi->disconn_handler, 0);
+
+               mutex_lock(&mbi->event_lock);
+               mbi->refresh_charger = 0;
+               mbi->charging_mode = BATT_CHARGING_MODE_NONE;
+               mutex_unlock(&mbi->event_lock);
+
+               /* Disable charger LED */
+               ipc_read_modify_chr_param_reg(mbi, MSIC_CHRG_LED_CNTL_REG,
+                                             MSIC_CHRG_LED_ENBL, 0);
+               /*Enable charger LOW Power Mode */
+               ipc_read_modify_chr_param_reg(mbi, MSIC_BATT_CHR_CHRCTRL_ADDR,
+                                             CHRCNTL_CHRG_LOW_PWR_ENBL, 1);
+               break;
+       default:
+               dev_warn(msic_dev, "Invalid OTG Event:%s\n", __func__);
+       }
+       return 0;
+}
+
+static void msic_chrg_callback_worker(struct work_struct *work)
+{
+       struct otg_bc_cap cap;
+       struct msic_power_module_info *mbi =
+           container_of(work, struct msic_power_module_info,
+                        chrg_callback_dwrk.work);
+       penwell_otg_query_charging_cap(&cap);
+       msic_event_handler(mbi, cap.current_event, &cap);
+}
+
+/*
+ * msic_charger_callback - callback for USB OTG
+ * @arg: device info structure
+ * @event: USB event
+ * @cap: charging capabilities
+ * Context: Interrupt Context can not sleep
+ *
+ * Will be called from the OTG driver.Depending on the event
+ * schedules a bottom half to enable or disable the charging.
+ */
+static int msic_charger_callback(void *arg, int event, struct otg_bc_cap *cap)
+{
+       struct msic_power_module_info *mbi =
+           (struct msic_power_module_info *)arg;
+
+       schedule_delayed_work(&mbi->chrg_callback_dwrk, 0);
+       return 0;
+}
+
+/**
+ * msic_status_monitor - worker function to monitor status
+ * @work: delayed work handler structure
+ * Context: Can sleep
+ *
+ * Will be called from the threaded IRQ function.
+ * Monitors status of the charge register and temperature.
+ */
+static void msic_status_monitor(struct work_struct *work)
+{
+       int chr_mode, chr_event;
+       unsigned int delay = CHARGE_STATUS_DELAY_JIFFIES;
+       struct msic_power_module_info *mbi =
+           container_of(work, struct msic_power_module_info,
+                        chr_status_monitor.work);
+
+       pm_runtime_get_sync(&mbi->pdev->dev);
+
+       mutex_lock(&mbi->event_lock);
+       chr_mode = mbi->charging_mode;
+       chr_event = mbi->batt_event;
+       mutex_unlock(&mbi->event_lock);
+
+       /*update charger and battery health */
+       update_charger_health(mbi);
+       update_battery_health(mbi);
+
+       mutex_lock(&mbi->batt_lock);
+
+       if (chr_mode == BATT_CHARGING_MODE_MAINTENANCE)
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_FULL;
+       else if (chr_mode == BATT_CHARGING_MODE_NORMAL)
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_CHARGING;
+       else if (chr_event == USBCHRG_EVENT_SUSPEND)
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+       else
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+
+       /* Check charger Status bits */
+       if (is_charger_fault()) {
+
+               dev_warn(msic_dev, "charger fault detected\n");
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+       }
+
+       mutex_unlock(&mbi->batt_lock);
+
+       power_supply_changed(&mbi->usb);
+       schedule_delayed_work(&mbi->chr_status_monitor, delay);
+
+       pm_runtime_put_sync(&mbi->pdev->dev);
+}
+
+/**
+ * msic_battery_interrupt_handler - msic battery interrupt handler
+ * Context: interrupt context
+ *
+ * MSIC battery interrupt handler which will be called on insertion
+ * of valid power source to charge the battery or an exception
+ * condition occurs.
+ */
+static irqreturn_t msic_battery_interrupt_handler(int id, void *dev)
+{
+       struct msic_power_module_info *mbi = dev;
+       u32 reg_int_val;
+
+       /* We have only one concurrent fifo reader
+        * and only one concurrent writer, so we are not
+        * using any lock to protect fifo.
+        */
+       if (unlikely(kfifo_is_full(&irq_fifo))) {
+               dev_warn(&mbi->pdev->dev, "KFIFO Full\n");
+               return IRQ_WAKE_THREAD;
+       }
+       /* Copy Interrupt registers locally */
+       reg_int_val = readl(mbi->msic_intr_iomap);
+       /* Add the Interrupt regs to  FIFO */
+       kfifo_in(&irq_fifo, &reg_int_val, IRQ_KFIFO_ELEMENT);
+
+       return IRQ_WAKE_THREAD;
+}
+
+/**
+ * msic_battery_thread_handler - msic battery threaded IRQ function
+ * Context: can sleep
+ *
+ * MSIC battery needs to either update the battery status as full
+ * if it detects battery full condition caused the interrupt or needs
+ * to enable battery charger if it detects usb and battery detect
+ * caused the source of interrupt.
+ */
+static irqreturn_t msic_battery_thread_handler(int id, void *dev)
+{
+       int ret;
+       unsigned char data[2];
+       struct msic_power_module_info *mbi = dev;
+       u32 tmp;
+       unsigned char intr_stat;
+       u32 log_intr;
+
+       /* We have only one concurrent fifo reader
+        * and only one concurrent writer, we are not
+        * using any lock to protect fifo.
+        */
+       if (unlikely(kfifo_is_empty(&irq_fifo))) {
+               dev_warn(msic_dev, "KFIFO Empty\n");
+               return IRQ_NONE;
+       }
+       /* Get the Interrupt regs state from FIFO */
+       ret = kfifo_out(&irq_fifo, &tmp, IRQ_KFIFO_ELEMENT);
+       if (ret != IRQ_KFIFO_ELEMENT) {
+               dev_warn(msic_dev, "KFIFO underflow\n");
+               return IRQ_NONE;
+       }
+
+       /* Even though some second level charger interrupts are masked by SCU
+        * the flags for these interrupts will be set in second level interrupt
+        * status register when SCU forwards the unmasked interrupts. Kernel
+        * should ignore the status for masked registers.*/
+
+       /* CHRINT Register */
+       data[0] = ((tmp & 0x00ff0000) >> 16) & ~(mbi->chrint_mask);
+       /* CHRINT1 Register */
+       data[1] = (tmp & 0xff000000) >> 24 & ~(mbi->chrint1_mask);
+
+       dev_dbg(msic_dev, "PWRSRC Int %x %x\n", tmp & 0xff,
+               (tmp & 0xff00) >> 8);
+       dev_dbg(msic_dev, "CHR Int %x %x\n", data[0], data[1]);
+
+       /* Saving the read interrupt value for printing later */
+       log_intr = tmp;
+
+       mutex_lock(&mbi->event_lock);
+       tmp = mbi->charging_mode;
+       mutex_unlock(&mbi->event_lock);
+
+       /* Check if charge complete */
+       if (data[1] & MSIC_BATT_CHR_CHRCMPLT_MASK)
+               dev_dbg(msic_dev, "CHRG COMPLT\n");
+
+       if ((data[0] & MSIC_BATT_CHR_TIMEEXP_MASK) &&
+                       (tmp == BATT_CHARGING_MODE_NORMAL)) {
+               dev_dbg(msic_dev, "force suspend event\n");
+               msic_event_handler(mbi, USBCHRG_EVENT_SUSPEND, NULL);
+       }
+
+       if (data[1] & MSIC_BATT_CHR_WKVINDET_MASK) {
+               dev_dbg(msic_dev, "CHRG WeakVIN Detected\n");
+
+               /* Sometimes for USB unplug we are receiving WeakVIN
+                * interrupts,So as SW work around we will check the
+                * SPWRSRCINT SUSBDET bit to know the USB connection.
+                */
+               ret = intel_scu_ipc_ioread8(MSIC_BATT_CHR_SPWRSRCINT_ADDR,
+                                           &intr_stat);
+               if (ret)
+                       handle_ipc_rw_status(ret, MSIC_BATT_CHR_SPWRSRCINT_ADDR,
+                                       MSIC_IPC_READ);
+               else if (!(intr_stat & MSIC_BATT_CHR_USBDET_MASK) &&
+                   (tmp != BATT_CHARGING_MODE_NONE)) {
+                       data[1] &= ~MSIC_BATT_CHR_WKVINDET_MASK;
+                       dev_dbg(msic_dev, "force disconnect event\n");
+                       msic_event_handler(mbi, USBCHRG_EVENT_DISCONN, NULL);
+               }
+       }
+
+       /* Check if an exception occurred */
+       if (data[0] || (data[1] & ~(MSIC_BATT_CHR_CHRCMPLT_MASK)))
+               msic_handle_exception(mbi, data[0], data[1]);
+
+       /* Check charger Status bits */
+       if (is_charger_fault()) {
+               mutex_lock(&mbi->batt_lock);
+               mbi->batt_props.status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               mutex_unlock(&mbi->batt_lock);
+
+               dev_info(msic_dev, "PWRSRC Int %x %x\n", log_intr & 0xff,
+                               (log_intr & 0xff00) >> 8);
+               dev_info(msic_dev, "CHR Int %x %x\n", data[0], data[1]);
+       }
+
+       power_supply_changed(&mbi->usb);
+       return IRQ_HANDLED;
+}
+
+/**
+ * check_charger_conn - check charger connection and handle the state
+ * @mbi : charger device info
+ * Context: can sleep
+ *
+ */
+static int check_charger_conn(struct msic_power_module_info *mbi)
+{
+       int retval;
+       struct otg_bc_cap cap;
+       unsigned char data;
+
+       retval = intel_scu_ipc_ioread8(MSIC_BATT_CHR_SPWRSRCINT_ADDR,
+                       &data);
+       if (retval) {
+               retval = handle_ipc_rw_status(retval,
+                               MSIC_BATT_CHR_SPWRSRCINT_ADDR, MSIC_IPC_READ);
+               if (retval)
+                       goto disable_chrg_block;
+       }
+
+       if (data & MSIC_BATT_CHR_USBDET_MASK) {
+               retval = penwell_otg_query_charging_cap(&cap);
+               if (retval) {
+                       dev_warn(msic_dev, "%s(): usb otg power query "
+                                "failed with error code %d\n", __func__,
+                                retval);
+                       goto disable_chrg_block;
+               }
+               /* Enable charging only if vinilmt is >= 100mA */
+               if (cap.mA >= 100) {
+                       msic_event_handler(mbi, USBCHRG_EVENT_CONNECT, &cap);
+                       return retval;
+               }
+       }
+
+disable_chrg_block:
+       /* Disable the charging block */
+       msic_batt_stop_charging(mbi);
+       /*Putting the charger in LOW Power Mode */
+       ipc_read_modify_chr_param_reg(mbi, MSIC_BATT_CHR_CHRCTRL_ADDR,
+                                     CHRCNTL_CHRG_LOW_PWR_ENBL, 1);
+       return retval;
+}
+
+/**
+ * set_chrg_enable - sysfs set api for charge_enable attribute
+ * Parameter as define by sysfs interface
+ * Context: can sleep
+ *
+ */
+static ssize_t set_chrg_enable(struct device *dev,
+                              struct device_attribute *attr, const char *buf,
+                              size_t count)
+{
+       struct platform_device *pdev =
+           container_of(dev, struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+       unsigned long value;
+       int retval, chr_mode;
+
+       if (strict_strtoul(buf, 10, &value))
+               return -EINVAL;
+
+       /* Allow only 0 to 4 for writing */
+       if (value < USER_SET_CHRG_DISABLE || value > USER_SET_CHRG_NOLMT)
+               return -EINVAL;
+
+       mutex_lock(&mbi->event_lock);
+       chr_mode = mbi->charging_mode;
+       mutex_unlock(&mbi->event_lock);
+
+       if (!value && (chr_mode != BATT_CHARGING_MODE_NONE)) {
+               dev_dbg(msic_dev, "User App charger disable !\n");
+               /* Disable charger before setting the usr_chrg_enbl */
+               msic_event_handler(mbi, USBCHRG_EVENT_SUSPEND, NULL);
+
+               mutex_lock(&mbi->event_lock);
+               mbi->usr_chrg_enbl = value;
+               mutex_unlock(&mbi->event_lock);
+
+       } else if (value && (chr_mode == BATT_CHARGING_MODE_NONE)) {
+               dev_dbg(msic_dev, "User App charger enable!\n");
+
+               /* enable usr_chrg_enbl before checking charger connection */
+               mutex_lock(&mbi->event_lock);
+               mbi->usr_chrg_enbl = value;
+               mutex_unlock(&mbi->event_lock);
+
+               retval = check_charger_conn(mbi);
+               if (retval)
+                       dev_warn(msic_dev, "check_charger_conn failed\n");
+       } else {
+               mutex_lock(&mbi->event_lock);
+               mbi->refresh_charger = 1;
+               mbi->usr_chrg_enbl = value;
+               mutex_unlock(&mbi->event_lock);
+       }
+
+       return count;
+}
+
+/**
+ * get_chrg_enable - sysfs get api for charge_enable attribute
+ * Parameter as define by sysfs interface
+ * Context: can sleep
+ *
+ */
+static ssize_t get_chrg_enable(struct device *dev,
+                              struct device_attribute *attr, char *buf)
+{
+       struct platform_device *pdev =
+           container_of(dev, struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+       unsigned int val;
+
+       mutex_lock(&mbi->event_lock);
+       val = mbi->usr_chrg_enbl;
+       mutex_unlock(&mbi->event_lock);
+
+       return sprintf(buf, "%d\n", val);
+}
+
+/* get_is_power_supply_conn - sysfs get api for power_supply_conn attribute
+ * Parameter as defined by sysfs interface
+ * Context: can sleep
+ *
+ */
+static ssize_t get_is_power_supply_conn(struct device *dev,
+                               struct device_attribute *attr, char *buf)
+{
+       return sprintf(buf, "%d\n", !(intel_msic_is_current_sense_enabled()));
+}
+/**
+ * sfi_table_invalid_batt - default battery SFI table values  to be
+ * used in case of invalid battery
+ *
+ * @sfi_table : sfi table pointer
+ * Context: can sleep
+ *
+ */
+static void sfi_table_invalid_batt(struct msic_batt_sfi_prop *sfi_table)
+{
+
+       /*
+        * In case of invalid battery we manually set
+        * the SFI parameters and limit the battery from
+        * charging, so platform will be in discharging mode
+        */
+       memcpy(sfi_table->batt_id, "UNKNOWN", sizeof("UNKNOWN"));
+       sfi_table->voltage_max = CHR_CHRVOLTAGE_SET_DEF;
+       sfi_table->capacity = DEFAULT_MAX_CAPACITY;
+       sfi_table->battery_type = POWER_SUPPLY_TECHNOLOGY_LION;
+       sfi_table->temp_mon_ranges = 0;
+
+}
+
+/**
+ * sfi_table_populate - Simple Firmware Interface table Populate
+ * @sfi_table: Simple Firmware Interface table structure
+ *
+ * SFI table has entries for the temperature limits
+ * which is populated in a local structure
+ */
+static int __init sfi_table_populate(struct sfi_table_header *table)
+{
+       struct sfi_table_simple *sb;
+       struct msic_batt_sfi_prop *pentry;
+       struct platform_device *pdev =
+           container_of(msic_dev, struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+       int totentrs = 0, totlen = 0;
+
+       sb = (struct sfi_table_simple *)table;
+       if (!sb) {
+               dev_warn(msic_dev, "SFI: Unable to map BATT signature\n");
+               return -ENODEV;
+       }
+
+       totentrs = SFI_GET_NUM_ENTRIES(sb, struct msic_batt_sfi_prop);
+       if (totentrs) {
+               pentry = (struct msic_batt_sfi_prop *)sb->pentry;
+               totlen = totentrs * sizeof(*pentry);
+               memcpy(sfi_table, pentry, totlen);
+               mbi->is_batt_valid = true;
+       } else {
+               dev_warn(msic_dev, "Invalid battery detected\n");
+               sfi_table_invalid_batt(sfi_table);
+               mbi->is_batt_valid = false;
+       }
+
+       return 0;
+}
+
+/**
+ * init_batt_props - initialize battery properties
+ * @mbi: msic module device structure
+ * Context: can sleep
+ *
+ * init_batt_props function initializes the
+ * MSIC battery properties.
+ */
+static void init_batt_props(struct msic_power_module_info *mbi)
+{
+       unsigned char data;
+       int retval;
+
+       mbi->batt_event = USBCHRG_EVENT_DISCONN;
+       mbi->charging_mode = BATT_CHARGING_MODE_NONE;
+       mbi->usr_chrg_enbl = USER_SET_CHRG_NOLMT;
+
+       mbi->batt_props.status = POWER_SUPPLY_STATUS_DISCHARGING;
+       mbi->batt_props.health = POWER_SUPPLY_HEALTH_GOOD;
+       mbi->batt_props.present = MSIC_BATT_NOT_PRESENT;
+
+       /* read specific to determine the status */
+       retval = intel_scu_ipc_ioread8(MSIC_BATT_CHR_SPWRSRCINT_ADDR, &data);
+       if (retval)
+               handle_ipc_rw_status(retval, MSIC_BATT_CHR_SPWRSRCINT_ADDR,
+                               MSIC_IPC_READ);
+
+       /* determine battery Presence */
+       if (data & MSIC_BATT_CHR_BATTDET_MASK)
+               mbi->batt_props.present = MSIC_BATT_PRESENT;
+       else
+               mbi->batt_props.present = MSIC_BATT_NOT_PRESENT;
+
+       /* Enable Status Register */
+       retval = intel_scu_ipc_iowrite8(CHR_STATUS_FAULT_REG,
+                                       CHR_STATUS_TMR_RST |
+                                       CHR_STATUS_STAT_ENBL);
+       if (retval)
+               handle_ipc_rw_status(retval,
+                               CHR_STATUS_FAULT_REG, MSIC_IPC_WRITE);
+
+       /* Disable charger LED */
+       ipc_read_modify_chr_param_reg(mbi, MSIC_CHRG_LED_CNTL_REG,
+                                     MSIC_CHRG_LED_ENBL, 0);
+
+       /*
+        * Mask Battery Detect Interrupt, we are not
+        * handling battery jarring in the driver.
+        */
+       ipc_read_modify_reg(mbi, MSIC_BATT_CHR_MPWRSRCINT_ADDR,
+                           MSIC_MPWRSRCINT_BATTDET, 1);
+}
+
+/**
+ * init_batt_thresholds - initialize battery thresholds
+ * @mbi: msic module device structure
+ * Context: can sleep
+ */
+static void init_batt_thresholds(struct msic_power_module_info *mbi)
+{
+       int ret;
+
+       batt_thrshlds->vbatt_sh_min = BATT_DEAD_CUTOFF_VOLT;
+       batt_thrshlds->vbatt_crit = BATT_CRIT_CUTOFF_VOLT;
+       batt_thrshlds->temp_high = MSIC_BATT_TEMP_MAX;
+       batt_thrshlds->temp_low = MSIC_BATT_TEMP_MIN;
+
+       /* Read the threshold data from SMIP */
+       dev_dbg(msic_dev, "[SMIP Read] offset: %x\n", BATT_SMIP_BASE_OFFSET);
+       ret = intel_scu_ipc_read_mip((u8 *) batt_thrshlds,
+                         sizeof(struct batt_safety_thresholds),
+                         BATT_SMIP_BASE_OFFSET, 1);
+       if (ret)
+               dev_warn(msic_dev, "%s: smip read failed\n", __func__);
+
+       dev_dbg(msic_dev, "vbatt shutdown: %d\n", batt_thrshlds->vbatt_sh_min);
+       dev_dbg(msic_dev, "vbatt_crit: %d\n", batt_thrshlds->vbatt_crit);
+       dev_dbg(msic_dev, "Temp High Lmt: %d\n", batt_thrshlds->temp_high);
+       dev_dbg(msic_dev, "Temp Low Lmt: %d\n", batt_thrshlds->temp_low);
+}
+
+/**
+ * init_charger_props - initialize charger properties
+ * @mbi: msic module device structure
+ * Context: can sleep
+ *
+ * init_charger_props function initializes the
+ * MSIC usb charger properties.
+ */
+static void init_charger_props(struct msic_power_module_info *mbi)
+{
+       mbi->usb_chrg_props.charger_present = MSIC_USB_CHARGER_NOT_PRESENT;
+       mbi->usb_chrg_props.charger_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+       memcpy(mbi->usb_chrg_props.charger_model, "Unknown", sizeof("Unknown"));
+       memcpy(mbi->usb_chrg_props.charger_vender, "Unknown",
+              sizeof("Unknown"));
+}
+
+/**
+ * init_msic_regs - initialize msic registers
+ * @mbi: msic module device structure
+ * Context: can sleep
+ *
+ * init_msic_regs function initializes the
+ * MSIC registers like CV,Power Source LMT,etc..
+ */
+static int init_msic_regs(struct msic_power_module_info *mbi)
+{
+       static const u16 address[] = {
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_PWRSRCLMT_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCVOLTAGE_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRTTIME_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_SPCHARGER_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRSTWDT_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_CHRCTRL1_ADDR,
+               MSIC_BATT_CHR_WDTWRITE_ADDR, MSIC_BATT_CHR_LOWBATTDET_ADDR
+       };
+       static u8 data[] = {
+               WDTWRITE_UNLOCK_VALUE, CHR_PWRSRCLMT_SET_RANGE,
+               WDTWRITE_UNLOCK_VALUE,
+               CONV_VOL_DEC_MSICREG(CHR_CHRVOLTAGE_SET_DEF),
+               WDTWRITE_UNLOCK_VALUE, CHR_CHRTIME_SET_13HRS,
+               WDTWRITE_UNLOCK_VALUE,
+               (~CHR_SPCHRGER_LOWCHR_ENABLE & CHR_SPCHRGER_WEAKVIN_LVL1),
+               WDTWRITE_UNLOCK_VALUE, CHR_WDT_DISABLE,
+               WDTWRITE_UNLOCK_VALUE, MSIC_CHRG_EXTCHRDIS,
+               WDTWRITE_UNLOCK_VALUE, MSIC_BATT_CHR_SET_LOWBATTREG
+       };
+
+       return msic_write_multi(mbi, address, data, 14);
+}
+
+/**
+ * msic_battery_probe - msic battery initialize
+ * @pdev: msic battery platform device structure
+ * Context: can sleep
+ *
+ * MSIC battery initializes its internal data structure and other
+ * infrastructure components for it to work as expected.
+ */
+static int msic_battery_probe(struct platform_device *pdev)
+{
+       int retval;
+       int read_temp, batt_id_res;
+       uint8_t data;
+       struct msic_power_module_info *mbi = NULL;
+
+       mbi = kzalloc(sizeof(struct msic_power_module_info), GFP_KERNEL);
+       if (!mbi) {
+               dev_err(&pdev->dev, "%s(): memory allocation failed\n",
+                       __func__);
+               return -ENOMEM;
+       }
+
+       sfi_table = kzalloc(sizeof(struct msic_batt_sfi_prop), GFP_KERNEL);
+       if (!sfi_table) {
+               dev_err(&pdev->dev, "%s(): memory allocation failed\n",
+                       __func__);
+               kfree(mbi);
+               return -ENOMEM;
+       }
+       batt_thrshlds = kzalloc(sizeof(struct batt_safety_thresholds),
+                               GFP_KERNEL);
+       if (!batt_thrshlds) {
+               dev_err(&pdev->dev, "%s(): memory allocation failed\n",
+                       __func__);
+               kfree(sfi_table);
+               kfree(mbi);
+               return -ENOMEM;
+       }
+
+       mbi->pdev = pdev;
+       mbi->irq = platform_get_irq(pdev, 0);
+       platform_set_drvdata(pdev, mbi);
+       msic_dev = &pdev->dev;
+
+       /* initialize all required framework before enabling interrupts */
+
+       /* OTG Disconnect is being called from IRQ context
+        * so calling ipc function is not appropriate from otg callback
+        */
+       INIT_DELAYED_WORK(&mbi->disconn_handler, msic_batt_disconn);
+       INIT_DELAYED_WORK(&mbi->connect_handler, msic_batt_temp_charging);
+       INIT_DELAYED_WORK_DEFERRABLE(&mbi->chr_status_monitor,
+                                    msic_status_monitor);
+       INIT_DELAYED_WORK(&mbi->chrg_callback_dwrk, msic_chrg_callback_worker);
+       wake_lock_init(&mbi->wakelock, WAKE_LOCK_SUSPEND,
+                      "msicbattery_wakelock");
+
+       /* Initialize mutex locks */
+       mutex_init(&mbi->usb_chrg_lock);
+       mutex_init(&mbi->batt_lock);
+       mutex_init(&mbi->ipc_rw_lock);
+       mutex_init(&mbi->event_lock);
+       mutex_init(&mbi->adc_val_lock);
+
+       /* Allocate ADC Channels */
+       mbi->adc_handle =
+           intel_mid_gpadc_alloc(MSIC_BATT_SENSORS,
+                                 MSIC_BATT_PACK_TEMP | CH_NEED_VCALIB |
+                                 CH_NEED_VREF,
+                                 MSIC_USB_VOLTAGE | CH_NEED_VCALIB,
+                                 MSIC_BATTID | CH_NEED_VREF | CH_NEED_VCALIB);
+       if (mbi->adc_handle == NULL)
+               dev_err(&pdev->dev, "ADC allocation failed\n");
+
+       mdf_multi_read_adc_regs(mbi, HYSTR_SAMPLE_MAX, 2, MSIC_ADC_BATTID_IDX,
+                               &batt_id_res, MSIC_ADC_TEMP_IDX, &read_temp);
+
+       /* When no battery is present and the board is operated from
+        * a lab power supply, the battery thermistor is absent or
+        * the BATTID is less than 24kOhm */
+       if ((read_temp == -ERANGE) || (batt_id_res <= ADC_BATTID_24KOHM)) {
+               if (read_temp == -ERANGE)
+                       dev_warn(msic_dev, "Power Supply detected on PR2B\n");
+               else
+                       dev_warn(msic_dev,
+                                "Power Supply detected on iCDKa/iCDKb\n");
+               mbi->current_sense_enabled = 0;
+       } else
+               mbi->current_sense_enabled = 1;
+
+       /* Invalid battery is reported when Populate
+        *  data from SFI Table is failed*/
+       if (sfi_table_parse(SFI_SIG_OEM0, NULL, NULL, sfi_table_populate)
+           || (!mbi->current_sense_enabled)) {
+               sfi_table_invalid_batt(sfi_table);
+               mbi->is_batt_valid = false;
+       }
+
+       /* Initialize battery and charger Properties */
+       init_batt_props(mbi);
+       init_charger_props(mbi);
+       init_batt_thresholds(mbi);
+
+       /* Re Map Phy address space for MSIC regs */
+       mbi->msic_intr_iomap = ioremap_nocache(MSIC_SRAM_INTR_ADDR, 8);
+       if (!mbi->msic_intr_iomap) {
+               dev_err(&pdev->dev, "battery: ioremap Failed\n");
+               retval = -ENOMEM;
+               goto ioremap_intr_failed;
+       }
+
+       /* Init MSIC Registers */
+       retval = init_msic_regs(mbi);
+       if (retval < 0)
+               dev_err(&pdev->dev, "MSIC registers init failed\n");
+
+       /* register msic-usb with power supply subsystem */
+       mbi->usb.name = CHARGER_PS_NAME;
+       mbi->usb.type = POWER_SUPPLY_TYPE_USB;
+       mbi->usb.supplied_to = msic_power_supplied_to;
+       mbi->usb.num_supplicants = ARRAY_SIZE(msic_power_supplied_to);
+       mbi->usb.properties = msic_usb_props;
+       mbi->usb.num_properties = ARRAY_SIZE(msic_usb_props);
+       mbi->usb.get_property = msic_usb_get_property;
+       retval = power_supply_register(&pdev->dev, &mbi->usb);
+       if (retval) {
+               dev_err(&pdev->dev, "%s(): failed to register msic usb "
+                       "device with power supply subsystem\n", __func__);
+               goto power_reg_failed_usb;
+       }
+
+       retval = device_create_file(&pdev->dev, &dev_attr_charge_enable);
+       if (retval)
+               goto  sysfs1_create_failed;
+       retval = device_create_file(&pdev->dev, &dev_attr_power_supply_conn);
+       if (retval)
+               goto sysfs2_create_failed;
+       /* Register with OTG */
+       otg_handle = penwell_otg_register_bc_callback(msic_charger_callback,
+                                                     (void *)mbi);
+       if (!otg_handle) {
+               dev_err(&pdev->dev, "battery: OTG Registration failed\n");
+               retval = -EBUSY;
+               goto otg_failed;
+       }
+
+       /* Init Runtime PM State */
+       pm_runtime_put_noidle(&mbi->pdev->dev);
+       pm_schedule_suspend(&mbi->pdev->dev, MSEC_PER_SEC);
+
+       /* Check if already exist a Charger connection */
+       retval = check_charger_conn(mbi);
+       if (retval)
+               dev_err(&pdev->dev, "check charger Conn failed\n");
+
+       mbi->chrint_mask = CHRINT_MASK;
+       mbi->chrint1_mask = CHRINT1_MASK;
+
+       retval = intel_scu_ipc_iowrite8(MSIC_BATT_CHR_MCHRINT_ADDR,
+                                      mbi->chrint_mask);
+       if (retval)
+               handle_ipc_rw_status(retval,
+                              MSIC_BATT_CHR_MCHRINT_ADDR, MSIC_IPC_WRITE);
+
+       retval = intel_scu_ipc_iowrite8(MSIC_BATT_CHR_MCHRINT1_ADDR,
+                                      mbi->chrint1_mask);
+       if (retval)
+               handle_ipc_rw_status(retval,
+                               MSIC_BATT_CHR_MCHRINT1_ADDR, MSIC_IPC_WRITE);
+
+       /* register interrupt */
+       retval = request_threaded_irq(mbi->irq, msic_battery_interrupt_handler,
+                                     msic_battery_thread_handler,
+                                     0, DRIVER_NAME, mbi);
+       if (retval) {
+               dev_err(&pdev->dev, "%s(): cannot get IRQ\n", __func__);
+               goto requestirq_failed;
+       }
+
+       /*
+        * When no battery is present and the board is operated from
+        * a lab power supply, the battery thermistor is absent.
+        * In this case, the MSIC reports emergency temperature warnings,
+        * which must be ignored, to avoid a rain of interrupts
+        * (KFIFO_FULL messages)
+        * By reading the thermistor value on BPTHERM1 during driver probe
+        * it is possible to detect operation without a battery and
+        * mask the undesired MSIC interrupt in this case
+        *
+        */
+
+       if (read_temp == -ERANGE) {
+               dev_warn(msic_dev,
+                        "Temp read out of range:"
+                               "disabling BATTOTP interrupts");
+
+               retval = intel_scu_ipc_ioread8(MSIC_BATT_CHR_MCHRINT_ADDR,
+                                              &data);
+               if (retval) {
+                       retval = handle_ipc_rw_status(retval,
+                               MSIC_BATT_CHR_MCHRINT_ADDR, MSIC_IPC_READ);
+                       if (retval)
+                               return retval;
+               }
+
+               /* Applying BATTOTP INT mask */
+               data |= MSIC_BATT_CHR_BATTOTP_MASK;
+               retval = intel_scu_ipc_iowrite8(MSIC_BATT_CHR_MCHRINT_ADDR,
+                                               data);
+               if (retval) {
+                       retval = handle_ipc_rw_status(retval,
+                              MSIC_BATT_CHR_MCHRINT_ADDR, MSIC_IPC_WRITE);
+                       return retval;
+               }
+       }
+
+       /* Start the status monitoring worker */
+       schedule_delayed_work(&mbi->chr_status_monitor, 0);
+       return retval;
+
+requestirq_failed:
+       penwell_otg_unregister_bc_callback(otg_handle);
+otg_failed:
+       device_remove_file(&pdev->dev, &dev_attr_power_supply_conn);
+sysfs2_create_failed:
+       device_remove_file(&pdev->dev, &dev_attr_charge_enable);
+sysfs1_create_failed:
+       power_supply_unregister(&mbi->usb);
+power_reg_failed_usb:
+       iounmap(mbi->msic_intr_iomap);
+ioremap_intr_failed:
+       kfree(batt_thrshlds);
+       kfree(sfi_table);
+       kfree(mbi);
+
+       return retval;
+}
+
+static void do_exit_ops(struct msic_power_module_info *mbi)
+{
+       /* disable MSIC Charger */
+       mutex_lock(&mbi->batt_lock);
+       if (mbi->batt_props.status != POWER_SUPPLY_STATUS_DISCHARGING)
+               msic_batt_stop_charging(mbi);
+       mutex_unlock(&mbi->batt_lock);
+}
+
+/**
+ * msic_battery_remove - msic battery finalize
+ * @pdev: msic battery platform  device structure
+ * Context: can sleep
+ *
+ * MSIC battery finalizes its internal data structure and other
+ * infrastructure components that it initialized in
+ * msic_battery_probe.
+ */
+static int msic_battery_remove(struct platform_device *pdev)
+{
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+       if (mbi) {
+               penwell_otg_unregister_bc_callback(otg_handle);
+               flush_scheduled_work();
+               intel_mid_gpadc_free(mbi->adc_handle);
+               free_irq(mbi->irq, mbi);
+               pm_runtime_get_noresume(&mbi->pdev->dev);
+               do_exit_ops(mbi);
+               if (mbi->msic_intr_iomap != NULL)
+                       iounmap(mbi->msic_intr_iomap);
+               device_remove_file(&pdev->dev, &dev_attr_charge_enable);
+               device_remove_file(&pdev->dev, &dev_attr_power_supply_conn);
+               power_supply_unregister(&mbi->usb);
+               wake_lock_destroy(&mbi->wakelock);
+
+               kfree(batt_thrshlds);
+               kfree(sfi_table);
+               kfree(mbi);
+       }
+
+       return 0;
+}
+
+static int battery_reboot_notifier_callback(struct notifier_block *notifier,
+               unsigned long event, void *data)
+{
+       struct platform_device *pdev = container_of(msic_dev,
+                                       struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+
+       if (mbi)
+               do_exit_ops(mbi);
+
+       return NOTIFY_OK;
+}
+
+#ifdef CONFIG_PM
+static int msic_battery_suspend(struct device *dev)
+{
+       struct msic_power_module_info *mbi = dev_get_drvdata(dev);
+       int event;
+
+       mutex_lock(&mbi->event_lock);
+       event = mbi->batt_event;
+       mutex_unlock(&mbi->event_lock);
+
+       if (event == USBCHRG_EVENT_CONNECT ||
+           event == USBCHRG_EVENT_UPDATE || event == USBCHRG_EVENT_RESUME) {
+
+               msic_event_handler(mbi, USBCHRG_EVENT_SUSPEND, NULL);
+               dev_dbg(&mbi->pdev->dev, "Forced suspend\n");
+       }
+
+       cancel_delayed_work_sync(&mbi->chr_status_monitor);
+
+       return 0;
+}
+
+static int msic_battery_resume(struct device *dev)
+{
+       int retval = 0;
+       struct msic_power_module_info *mbi = dev_get_drvdata(dev);
+       int event;
+
+       mutex_lock(&mbi->event_lock);
+       event = mbi->batt_event;
+       mutex_unlock(&mbi->event_lock);
+
+       if (event == USBCHRG_EVENT_SUSPEND || event == USBCHRG_EVENT_DISCONN) {
+               /* Check if already exist a Charger connection */
+               retval = check_charger_conn(mbi);
+               if (retval)
+                       dev_warn(msic_dev, "check_charger_conn failed\n");
+       }
+
+       schedule_delayed_work(&mbi->chr_status_monitor, 0);
+       return retval;
+}
+#else
+#define msic_battery_suspend    NULL
+#define msic_battery_resume     NULL
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int msic_runtime_suspend(struct device *dev)
+{
+
+       /* ToDo: Check for MSIC Power rails */
+       dev_dbg(dev, "%s called\n", __func__);
+       return 0;
+}
+
+static int msic_runtime_resume(struct device *dev)
+{
+       /* ToDo: Check for MSIC Power rails */
+       dev_dbg(dev, "%s called\n", __func__);
+       return 0;
+}
+
+static int msic_runtime_idle(struct device *dev)
+{
+       struct platform_device *pdev =
+           container_of(dev, struct platform_device, dev);
+       struct msic_power_module_info *mbi = platform_get_drvdata(pdev);
+       int event;
+
+       dev_dbg(dev, "%s called\n", __func__);
+
+       mutex_lock(&mbi->event_lock);
+       event = mbi->batt_event;
+       mutex_unlock(&mbi->event_lock);
+
+       if (event == USBCHRG_EVENT_CONNECT ||
+           event == USBCHRG_EVENT_UPDATE || event == USBCHRG_EVENT_RESUME) {
+
+               dev_warn(&mbi->pdev->dev, "%s: device busy\n", __func__);
+
+               return -EBUSY;
+       }
+
+       return 0;
+}
+#else
+#define msic_runtime_suspend   NULL
+#define msic_runtime_resume    NULL
+#define msic_runtime_idle      NULL
+#endif
+/*********************************************************************
+ *             Driver initialisation and finalization
+ *********************************************************************/
+
+static const struct platform_device_id battery_id_table[] = {
+       {"msic_battery", 1},
+};
+
+static const struct dev_pm_ops msic_batt_pm_ops = {
+       .suspend = msic_battery_suspend,
+       .resume = msic_battery_resume,
+       .runtime_suspend = msic_runtime_suspend,
+       .runtime_resume = msic_runtime_resume,
+       .runtime_idle = msic_runtime_idle,
+};
+
+static struct platform_driver msic_battery_driver = {
+       .driver = {
+                  .name = DRIVER_NAME,
+                  .owner = THIS_MODULE,
+                  .pm = &msic_batt_pm_ops,
+                  },
+       .probe = msic_battery_probe,
+       .remove = __devexit_p(msic_battery_remove),
+       .id_table = battery_id_table,
+};
+
+static int __init msic_battery_module_init(void)
+{
+       int ret;
+
+       ret = platform_driver_register(&msic_battery_driver);
+       if (ret)
+               dev_err(msic_dev, "driver_register failed");
+
+       if (register_reboot_notifier(&battery_reboot_notifier))
+               dev_warn(msic_dev, "Battery: Unable to register reboot notifier");
+
+       return ret;
+}
+
+static void __exit msic_battery_module_exit(void)
+{
+       unregister_reboot_notifier(&battery_reboot_notifier);
+       platform_driver_unregister(&msic_battery_driver);
+}
+
+late_initcall_async(msic_battery_module_init);
+module_exit(msic_battery_module_exit);
+
+MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
+MODULE_AUTHOR("Anantha Narayanan <anantha.narayanan@intel.com>");
+MODULE_AUTHOR("Ananth Krishna <ananth.krishna.r@intel.com>");
+MODULE_DESCRIPTION("Intel Medfield MSIC Battery Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/intel_mdf_charger.h b/drivers/power/intel_mdf_charger.h
new file mode 100644 (file)
index 0000000..6c7c6c5
--- /dev/null
@@ -0,0 +1,464 @@
+/*
+ * intel_mdf_charger.h - Intel Medfield MSIC Internal charger Driver header file
+ *
+ * Copyright (C) 2011 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.        See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Author: Ananth Krishna <ananth.krishna.r@intel.com>,
+ *         Ramakrishna Pallala <ramakrishna.pallala@intel.com>
+ *         Jenny TC <jenny.tc@intel.com>
+ */
+
+#ifndef __INTEL_MDF_CHARGER_H_
+#define __INTEL_MDF_CHARGER_H_
+
+
+/*********************************************************************
+ *             Generic defines
+ *********************************************************************/
+
+#define MSIC_BATT_PRESENT              1
+#define MSIC_BATT_NOT_PRESENT          0
+#define MSIC_USB_CHARGER_PRESENT       1
+#define MSIC_USB_CHARGER_NOT_PRESENT   0
+
+/* Interrupt registers*/
+#define MSIC_BATT_CHR_PWRSRCINT_ADDR   0x005
+#define MSIC_BATT_CHR_BATTDET_MASK     (1 << 0)
+#define MSIC_BATT_CHR_USBDET_MASK      (1 << 1)
+#define MSIC_BATT_CHR_ADPLVDET_MASK    (1 << 3)
+#define MSIC_BATT_CHR_ADPHVDET_MASK    (1 << 4)
+
+#define MSIC_BATT_CHR_PWRSRCINT1_ADDR  0x006
+#define MSIC_BATT_CHR_USBDCDET_MASK    (1 << 2)
+#define MSIC_BATT_CHR_USBCHPDET_MASK   (1 << 6)
+
+#define MSIC_BATT_CHR_CHRINT_ADDR      0x007
+#define MSIC_BATT_CHR_BATTOCP_MASK     (1 << 1)
+#define MSIC_BATT_CHR_BATTOTP_MASK     (1 << 2)
+#define MSIC_BATT_CHR_LOWBATT_MASK     (1 << 3)
+#define MSIC_BATT_CHR_WDTIMEEXP_MASK   (1 << 5)
+#define MSIC_BATT_CHR_ADPOVP_MASK      (1 << 6)
+#define MSIC_BATT_CHR_TIMEEXP_MASK     (1 << 7)
+
+#define MSIC_BATT_CHR_CHRINT1_ADDR     0x008
+#define MSIC_BATT_CHR_WKVINDET_MASK    (1 << 2)
+#define MSIC_BATT_CHR_CHROTP_MASK      (1 << 3)
+#define MSIC_BATT_CHR_VINREGMINT_MASK  (1 << 4)
+#define MSIC_BATT_CHR_BATTOVP_MASK     (1 << 5)
+#define MSIC_BATT_CHR_USBOVP_MASK      (1 << 6)
+#define MSIC_BATT_CHR_CHRCMPLT_MASK    (1 << 7)
+#define MSIC_BATT_CHRINT1_EXCP_MASK    0x68
+
+/* Interrupts need to be masked */
+#define CHRINT1_MASK MSIC_BATT_CHR_WKVINDET_MASK
+#define CHRINT_MASK (MSIC_BATT_CHR_BATTOTP_MASK |\
+               MSIC_BATT_CHR_BATTOCP_MASK)
+
+/* Interrupt Mask registers */
+#define MSIC_BATT_CHR_MPWRSRCINT_ADDR  0x014
+#define MSIC_MPWRSRCINT_BATTDET                (1 << 0)
+#define MSIC_BATT_CHR_MPWRSRCINT1_ADDR 0x015
+#define MSIC_BATT_CHR_MCHRINT_ADDR     0x016
+#define MSIC_BATT_CHR_MCHRINT1_ADDR    0x017
+
+/* Low Battery detect register */
+#define MSIC_BATT_CHR_LOWBATTDET_ADDR  0x187
+#define MSIC_BATT_CHR_SET_LOWBATTREG   0x06
+
+/* Internal charger control registers */
+#define MSIC_BATT_CHR_CHRCTRL_ADDR     0x188
+#define CHRCNTL_CHRG_LOW_PWR_ENBL      (1 << 1)
+#define CHRCNTL_CHRG_DISABLE           (1 << 2)
+#define CHRCNTL_CHRITERMEN             (1 << 3)
+#define CHRCNTL_VINLMT_100             0x0
+#define CHRCNTL_VINLMT_500             (1 << 6)
+#define CHRCNTL_VINLMT_950             (2 << 6)
+#define CHRCNTL_VINLMT_NOLMT           (3 << 6)
+
+#define MSIC_BATT_CHR_CHRCVOLTAGE_ADDR 0x189
+
+/* Set Charger Voltage to 4200 mV */
+#define CHR_CHRVOLTAGE_SET_DEF         4200
+
+#define MSIC_BATT_CHR_CHRCCURRENT_ADDR 0x18A
+#define CHRCC_MIN_CURRENT              500
+
+#define MSIC_BATT_CHR_SPCHARGER_ADDR   0x18B
+#define CHR_SPCHRGER_LOWCHR_ENABLE     (1 << 5)
+#define WEAKVIN_VOLTAGE_LEVEL          4000
+#define CHR_SPCHRGER_WEAKVIN_LVL1      0x00
+#define CHR_SPCHRGER_WEAKVIN_LVL2      0x07
+
+#define MSIC_BATT_CHR_CHRTTIME_ADDR    0x18C
+#define CHR_CHRTIME_SET_13HRS          0x0F
+
+#define MSIC_BATT_CHR_CHRCTRL1_ADDR    0x18D
+#define MSIC_EMRG_CHRG_ENBL            (1 << 3)
+#define MSIC_EMRG_CHRG_TEMP            (1 << 4)
+#define MSIC_CHRG_EXTCHRDIS            (1 << 5)
+
+/* Temperature limit registers */
+#define MSIC_BATT_CHR_PWRSRCLMT_ADDR   0x18E   /* Temperature limits */
+#define CHR_PWRSRCLMT_SET_RANGE                0xC0
+
+#define MSIC_BATT_CHR_CHRSTWDT_ADDR    0x18F   /* Watchdog timer */
+#define CHR_WDT_DISABLE                        0x0
+#define CHR_WDT_SET_60SEC              (1 << 4)
+#define CHR_WDT_SET_120SEC             (2 << 4)
+#define CHR_WDT_SET_180SEC             (3 << 4)
+
+#define MSIC_BATT_CHR_WDTWRITE_ADDR    0x190
+#define WDTWRITE_UNLOCK_VALUE          0x01
+
+#define MSIC_BATT_CHR_CHRSAFELMT_ADDR  0x191   /* Maximum safe charging
+                                                  voltage and current */
+/* Status registers */
+#define MSIC_BATT_CHR_SPWRSRCINT_ADDR  0x192
+#define MSIC_BATT_CHR_SPWRSRCINT1_ADDR 0x193
+#define MSIC_BATT_CHR_USBLOWBATT_MASK  (1 << 0)
+
+
+/* ADC Channel Numbers */
+#define MSIC_BATT_SENSORS      3
+#define MSIC_BATT_PACK_TEMP    0x7
+#define MSIC_USB_VOLTAGE       0x5
+#define MSIC_BATTID            0x2
+#define MSIC_ADC_TEMP_IDX      0
+#define MSIC_ADC_USB_VOL_IDX   1
+#define MSIC_ADC_BATTID_IDX    2
+
+/*MSIC battery temperature  attributes*/
+#define MSIC_BTP_ADC_MIN       107
+#define MSIC_BTP_ADC_MAX       977
+
+
+/* SRAM Addresses for INTR and OCV locations */
+#define MSIC_SRAM_INTR_ADDR            0xFFFF7FC3
+
+/* Battery Charger Status Register */
+#define CHR_STATUS_FAULT_REG   0x37D
+#define CHR_STATUS_STAT_ENBL   (1 << 6)
+#define CHR_STATUS_TMR_RST     (1 << 7)
+#define CHR_STATUS_VOTG_ENBL   (1 << 7)
+
+#define CHR_STATUS_BIT_MASK    0x30
+#define CHR_STATUS_BIT_POS     0x04
+#define CHR_STATUS_BIT_READY   0x0
+#define CHR_STATUS_BIT_PROGRESS        0x1
+#define CHR_STATUS_BIT_CYCLE_DONE      0x2
+#define CHR_STATUS_BIT_FAULT   0x03
+
+#define CHR_FAULT_BIT_MASK     0x7
+#define CHR_FAULT_BIT_NORMAL   0x0
+#define CHR_FAULT_BIT_VBUS_OVP 0x1
+#define CHR_FAULT_BIT_SLEEP    0x2
+#define CHR_FAULT_BIT_LOW_VBUS 0x3
+#define CHR_FAULT_BIT_BATT_OVP 0x4
+#define CHR_FAULT_BIT_THRM     0x5
+#define CHR_FAULT_BIT_TIMER    0x6
+#define CHR_FAULT_BIT_NO_BATT  0x7
+
+/* Battery data Offset range in SMIP */
+#define BATT_SMIP_BASE_OFFSET          0x314
+#define BATT_SMIP_END_OFFSET           0x3F8
+
+/* Battery data Offset range in UMIP */
+#define BATT_UMIP_BASE_OFFSET          0x800
+#define BATT_UMIP_END_OFFSET           0xBFF
+/* UMIP parameter Offsets from UMIP base */
+#define UMIP_REV_MAJ_MIN_NUMBER                0x800
+#define UMIP_SIZE_IN_BYTES             0x802
+
+#define UMIP_FG_TBL_SIZE               144
+#define UMIP_REF_FG_TBL                        0x806   /* 2 bytes */
+
+#define UMIP_BATT_FG_TABLE_SIZE                0x90
+#define UMIP_NO_OF_CFG_TBLS_SIZE       0x01
+#define UMIP_BATT_FG_TABLE_OFFSET      0x896
+
+#define UMIP_NO_OF_CFG_TBLS            UMIP_BATT_FG_TABLE_OFFSET
+#define UMIP_BATT_FG_CFG_TBL1 \
+       (UMIP_NO_OF_CFG_TBLS + UMIP_NO_OF_CFG_TBLS)
+#define UMIP_BATT_FG_CFG_TBL2 \
+       (UMIP_BATT_FG_CFG_TBL1 + UMIP_BATT_FG_TABLE_SIZE)
+#define UMIP_BATT_FG_CFG_TBL3 \
+       (UMIP_BATT_FG_CFG_TBL2 + UMIP_BATT_FG_TABLE_SIZE)
+#define UMIP_BATT_FG_CFG_TBL4 \
+       (UMIP_BATT_FG_CFG_TBL3 + UMIP_BATT_FG_TABLE_SIZE)
+#define UMIP_BATT_FG_CFG_TBL5 \
+       (UMIP_BATT_FG_CFG_TBL4 + UMIP_BATT_FG_TABLE_SIZE)
+
+/* UMIP BATT or FG Table definition */
+#define BATT_FG_TBL_REV                        0       /* 2 bytes */
+#define BATT_FG_TBL_NAME               2       /* 4 bytes */
+#define BATT_FG_TBL_BATTID             6       /* 8 bytes */
+#define BATT_FG_TBL_SIZE               14      /* 2 bytes */
+#define BATT_FG_TBL_TYPE               16      /* 1 bytes */
+#define BATT_FG_TBL_BODY               14      /* 130 bytes */
+
+#define UMIP_READ      0
+#define UMIP_WRITE     1
+#define SMIP_READ      2
+#define MSIC_IPC_READ  0
+#define MSIC_IPC_WRITE 1
+#define MAX_IPC_ERROR_COUNT 20
+/*
+ * Each LSB of Charger LED PWM register
+ * contributes to 0.39% of duty cycle
+ */
+#define MSIC_CHRG_LED_PWM_REG  0x194
+
+#define MSIC_CHRG_LED_CNTL_REG 0x195
+#define MSIC_CHRG_LED_ENBL             (1 << 0)
+
+/* LED DC Current settings */
+#define MSIC_CHRG_LED_DCCUR1   0x0     /* 0.5 mA */
+#define MSIC_CHRG_LED_DCCUR2   0x1     /* 1.0 mA */
+#define MSIC_CHRG_LED_DCCUR3   0x2     /* 2.5 mA */
+#define MSIC_CHRG_LED_DCCUR4   0x3     /* 5.0 mA */
+
+/* Charger LED Frequency settings */
+#define MSIC_CHRG_LED_FREQ1            0x0     /* 0.25 Hz */
+#define MSIC_CHRG_LED_FREQ2            0x1     /* 0.50 Hz */
+#define MSIC_CHRG_LED_FREQ3            0x2     /* 1.00 Hz */
+#define MSIC_CHRG_LED_FREQ4            0x3     /* 2.00 Hz */
+
+/*
+ * Convert the voltage form decimal to
+ * Register writable format
+ */
+#define CONV_VOL_DEC_MSICREG(a)        (((a - 3500) / 20) << 2)
+
+#define BATT_LOWBATT_CUTOFF_VOLT       3750    /* 3750 mV */
+#define BATT_OVERVOLTAGE_CUTOFF_VOLT   5040    /* 1.2 times of Max voltage */
+#define BATT_CRIT_CUTOFF_VOLT          3700    /* 3700 mV */
+#define BATT_DEAD_CUTOFF_VOLT          3400    /* 3400 mV */
+#define DEFAULT_MAX_CAPACITY           1500
+
+#define COLMB_TO_MAHRS_CONV_FCTR       3600
+#define MSIC_VBUS_LOW_VOLTAGE          4200    /* 4200 mV */
+#define MSIC_VBUS_OVER_VOLTAGE         6300    /* 6300 mV */
+
+#define MSIC_BATT_TEMP_MAX             60      /* 60 degrees */
+#define MSIC_BATT_TEMP_MIN             0
+#define MSIC_TEMP_HYST_ERR             4       /* 4 degrees */
+
+/* internal return values */
+#define BIT_SET                1
+#define BIT_RESET      0
+
+#define TEMP_CHARGE_DELAY_JIFFIES      (HZ * 30)       /*30 sec */
+#define CHARGE_STATUS_DELAY_JIFFIES    (HZ * 60)       /*60 sec */
+
+#define IRQ_FIFO_MAX           16
+#define THERM_CURVE_MAX_SAMPLES 23
+#define THERM_CURVE_MAX_VALUES 4
+#define BATT_STRING_MAX                8
+#define HYSTR_SAMPLE_MAX       4
+
+#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 BATTID_STR_LEN         8
+#define MANFCT_STR_LEN         2
+#define MODEL_STR_LEN          4
+#define SFI_TEMP_NR_RNG                4
+
+#define DISCHRG_CURVE_MAX_SAMPLES 17
+#define DISCHRG_CURVE_MAX_COLUMNS 2
+#define CC_TIME_TO_LIVE (HZ/8) /* 125 ms */
+#define ADC_TIME_TO_LIVE (HZ/8)        /* 125 ms */
+
+#define ADC_BATTID_24KOHM      108
+
+#define FULL_CURRENT_AVG_LOW   0
+#define FULL_CURRENT_AVG_HIGH  50
+
+/*using 1.2*10 to avoid float operations */
+#define OVP_VAL_MULT_FACTOR (12)
+
+/* Convert ADC value to VBUS voltage */
+#define MSIC_ADC_TO_VBUS_VOL(adc_val)  ((6843 * (adc_val)) / 1000)
+/* Valid msic exception events */
+enum msic_event {
+       MSIC_EVENT_BATTOCP_EXCPT,
+       MSIC_EVENT_BATTOTP_EXCPT,
+       MSIC_EVENT_LOWBATT_EXCPT,
+       MSIC_EVENT_BATTOVP_EXCPT,
+       MSIC_EVENT_ADPOVP_EXCPT,
+       MSIC_EVENT_CHROTP_EXCPT,
+       MSIC_EVENT_USBOVP_EXCPT,
+       MSIC_EVENT_USB_VINREG_EXCPT,
+       MSIC_EVENT_WEAKVIN_EXCPT,
+       MSIC_EVENT_TIMEEXP_EXCPT,
+       MSIC_EVENT_WDTIMEEXP_EXCPT,
+};
+
+/* Valid Charging modes */
+enum {
+       BATT_CHARGING_MODE_NONE = 0,
+       BATT_CHARGING_MODE_NORMAL,
+       BATT_CHARGING_MODE_MAINTENANCE,
+};
+
+/* Battery Thresholds info which need to get from SMIP area */
+struct batt_safety_thresholds {
+       u8 smip_rev;
+       u8 fpo;         /* fixed implementation options */
+       u8 fpo1;        /* fixed implementation options1 */
+       u8 rsys;        /* System Resistance for Fuel gauging */
+
+       /* Minimum voltage necessary to
+        * be able to safely shut down */
+       short int vbatt_sh_min;
+
+       /* Voltage at which the battery driver
+        * should report the LEVEL as CRITICAL */
+       short int vbatt_crit;
+
+       short int itc;          /* Charge termination current */
+       short int temp_high;    /* Safe Temp Upper Limit */
+       short int temp_low;     /* Safe Temp lower Limit */
+       u8 brd_id;              /* Unique Board ID */
+} __packed;
+
+/*********************************************************************
+ *             SFI table entries Structures
+ *********************************************************************/
+
+/* Parameters defining the range */
+struct temp_mon_table {
+       short int temp_up_lim;
+       short int temp_low_lim;
+       short int rbatt;
+       short int full_chrg_vol;
+       short int full_chrg_cur;
+       short int maint_chrg_vol_ll;
+       short int maint_chrg_vol_ul;
+       short int maint_chrg_cur;
+} __packed;
+
+/* SFI table entries structure.*/
+struct msic_batt_sfi_prop {
+       char batt_id[BATTID_STR_LEN];
+       unsigned short int voltage_max;
+       unsigned int capacity;
+       u8 battery_type;
+       u8 temp_mon_ranges;
+       struct temp_mon_table temp_mon_range[SFI_TEMP_NR_RNG];
+} __packed;
+
+/*********************************************************************
+ *             Battery properties
+ *********************************************************************/
+struct charge_params {
+       short int cvol;
+       short int ccur;
+       short int vinilmt;
+       short int weakvin;
+       enum usb_charger_type chrg_type;
+};
+
+struct msic_batt_props {
+       unsigned int status;
+       unsigned int health;
+       unsigned int present;
+};
+
+struct msic_charg_props {
+       unsigned int charger_present;
+       unsigned int charger_health;
+       unsigned int vbus_vol;
+       char charger_model[BATT_STRING_MAX];
+       char charger_vender[BATT_STRING_MAX];
+};
+
+/*
+ * msic battery info
+ */
+struct msic_power_module_info {
+
+       struct platform_device *pdev;
+       bool is_batt_valid;
+
+       /* msic charger data */
+       /* lock to protect usb charger properties
+        * locking is applied wherever read or write
+        * operation is being performed to the msic usb
+        * charger property structure.
+        */
+       struct mutex usb_chrg_lock;
+       struct msic_charg_props usb_chrg_props;
+       struct power_supply usb;
+
+       /* msic battery data */
+       /* lock to protect battery  properties
+        * locking is applied wherever read or write
+        * operation is being performed to the msic battery
+        * property structure.
+        */
+       struct mutex batt_lock;
+       struct msic_batt_props batt_props;
+
+       uint16_t adc_index;     /* ADC Channel Index */
+       int irq;                /* GPE_ID or IRQ# */
+
+       struct delayed_work connect_handler;
+       struct delayed_work disconn_handler;
+       struct charge_params ch_params; /* holds the charge parameters */
+
+       unsigned long update_time;      /* jiffies when data read */
+
+       void __iomem *msic_intr_iomap;
+
+       /* mutex lock to protect driver event related variables
+        * these events can happen to due OTG as well as charger block
+        */
+       struct mutex event_lock;
+       int batt_event;
+       int charging_mode;
+       int usr_chrg_enbl;      /* User Charge Enable or Disable */
+       int refresh_charger;    /* Refresh charger parameters */
+
+       /* Worker to monitor status and faults */
+       struct delayed_work chr_status_monitor;
+
+       /* Worker to handle otg callback events */
+       struct delayed_work chrg_callback_dwrk;
+
+       /* lock to avoid concurrent  access to HW Registers.
+        * As some charger control and parameter registers
+        * can be read or write at same time, ipc_rw_lock lock
+        * is used to synchronize those IPC read or write calls.
+        */
+       struct mutex ipc_rw_lock;
+       struct wake_lock wakelock;
+       /* Handle for gpadc requests */
+       void *adc_handle;
+       int current_sense_enabled;
+       /* Lock for ADC reading */
+       struct mutex adc_val_lock;
+       /* interrupt masks*/
+       uint8_t chrint_mask, chrint1_mask;
+
+};
+#endif
index c5c8805..9a51077 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Fuel gauge driver for Maxim 17042 / 8966 / 8997
+ * max17042_battery.c - Fuel gauge driver for Maxim 17042 / 8966 / 8997
  *  Note that Maxim 8966 and 8997 are mfd and this is its subdevice.
  *
  * Copyright (C) 2011 Samsung Electronics
  * This driver is based on max17040_battery.c
  */
 
+#include <linux/module.h>
 #include <linux/init.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
 #include <linux/slab.h>
 #include <linux/i2c.h>
-#include <linux/mod_devicetable.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/pm_runtime.h>
 #include <linux/power_supply.h>
 #include <linux/power/max17042_battery.h>
+#include <linux/reboot.h>
+#include <linux/delay.h>
+#include <linux/notifier.h>
+
+/* Status register bits */
+#define STATUS_POR_BIT         (1 << 1)
+#define STATUS_BST_BIT         (1 << 3)
+#define STATUS_VMN_BIT         (1 << 8)
+#define STATUS_TMN_BIT         (1 << 9)
+#define STATUS_SMN_BIT         (1 << 10)
+#define STATUS_BI_BIT          (1 << 11)
+#define STATUS_VMX_BIT         (1 << 12)
+#define STATUS_TMX_BIT         (1 << 13)
+#define STATUS_SMX_BIT         (1 << 14)
+#define STATUS_BR_BIT          (1 << 15)
+
+#define MAX17042_IC_VERSION    0x0092
+
+#define MAX_VOLT_THRLD         0xD2    /* 4200mv */
+#define MIN_VOLT_THRLD         0xBC    /* 3760mv */
+
+#define MAX_TEMP_THRLD         0x3C    /* 60 Degrees */
+#define MIN_TEMP_THRLD         0x0     /* 0 Degrees */
+
+/* Interrupt mask bits */
+#define CONFIG_ALRT_BIT_ENBL   (1 << 2)
+#define STATUS_INTR_VOLT_BIT   (1 << 12)
+#define STATUS_INTR_TEMP_BIT   (1 << 13)
+#define STATUS_INTR_SOC_BIT    (1 << 14)
+
+#define VFSOC0_LOCK            0x0000
+#define VFSOC0_UNLOCK          0x0080
+#define FG_MODEL_UNLOCK1       0X0059
+#define FG_MODEL_UNLOCK2       0X00C4
+#define FG_MODEL_LOCK1         0X0000
+#define FG_MODEL_LOCK2         0X0000
+
+#define dQ_ACC_DIV     0x4
+#define dP_ACC_100     0x1900
+#define dP_ACC_200     0x3200
+
+#define        NTC_47K_TGAIN   0xE4E4
+#define        NTC_47K_TOFF    0x2F1D
+
+#define BATT_CHRG_FULL_DES             1550000
+#define MAX17042_VOLT_CONV_FCTR                625
+#define MAX17042_CURR_CONV_FCTR                156
+#define MAX17042_CHRG_CONV_FCTR                500
+
+#define MAX17042_TEMP_SIGN_MASK                0x8000
+
+#define MAX17042_MAX_MEM       (0xFF + 1)
+
+#define MAX17042_MODEL_MUL_FACTOR(a)   ((a * 10) / 7)
+#define MAX17042_MODEL_DIV_FACTOR(a)   ((a * 7) / 10)
+#define CONSTANT_TEMP_IN_POWER_SUPPLY  350
+#define POWER_SUPPLY_VOLT_MIN_THRESHOLD        3500000
+
+#define CYCLES_ROLLOVER_CUTOFF         0x0100
+#define MAX17042_DEF_RO_LRNCFG         0x0076
+
+#define MAX17042_SIGN_INDICATOR                0x8000
+
+#define BYTE_VALUE                     1
+#define WORD_VALUE                     0
 
 enum max17042_register {
        MAX17042_STATUS         = 0x00,
@@ -46,7 +117,7 @@ enum max17042_register {
        MAX17042_SOC            = 0x0D,
        MAX17042_AvSOC          = 0x0E,
        MAX17042_RemCap         = 0x0F,
-       MAX17402_FullCAP        = 0x10,
+       MAX17042_FullCAP        = 0x10,
        MAX17042_TTE            = 0x11,
        MAX17042_V_empty        = 0x12,
 
@@ -65,6 +136,7 @@ enum max17042_register {
        MAX17042_ManName        = 0x20,
        MAX17042_DevName        = 0x21,
        MAX17042_DevChem        = 0x22,
+       MAX17042_FullCAPNom     = 0x23,
 
        MAX17042_TempNom        = 0x24,
        MAX17042_TempCold       = 0x25,
@@ -79,55 +151,298 @@ enum max17042_register {
        MAX17042_CGAIN          = 0x2E,
        MAX17042_COFF           = 0x2F,
 
-       MAX17042_Q_empty        = 0x33,
+       MAX17042_SOCempty       = 0x33,
        MAX17042_T_empty        = 0x34,
+       MAX17042_FullCAP0       = 0x35,
 
+       MAX17042_LAvg_empty     = 0x36,
+       MAX17042_FCTC           = 0x37,
        MAX17042_RCOMP0         = 0x38,
        MAX17042_TempCo         = 0x39,
-       MAX17042_Rx             = 0x3A,
-       MAX17042_T_empty0       = 0x3B,
+       MAX17042_ETC            = 0x3A,
+       MAX17042_K_empty0       = 0x3B,
        MAX17042_TaskPeriod     = 0x3C,
        MAX17042_FSTAT          = 0x3D,
 
        MAX17042_SHDNTIMER      = 0x3F,
 
+       MAX17042_dQacc          = 0x45,
+       MAX17042_dPacc          = 0x46,
+       MAX17042_VFSOC0         = 0x48,
        MAX17042_VFRemCap       = 0x4A,
 
        MAX17042_QH             = 0x4D,
        MAX17042_QL             = 0x4E,
+
+       MAX17042_VFSOC0Enable   = 0x60,
+       MAX17042_MLOCKReg1      = 0x62,
+       MAX17042_MLOCKReg2      = 0x63,
+       MAX17042_MODELChrTbl    = 0x80,
+       MAX17042_OCV            = 0xEE,
+       MAX17042_OCVInternal    = 0xFB,
+       MAX17042_VFSOC          = 0xFF,
+
+};
+
+#define DRV_NAME "max17042_battery"
+#define DEV_NAME "max17042"
+
+/* No of times we should retry on -EAGAIN error */
+#define NR_RETRY_CNT   3
+
+/* No of cell characterization words to be written to max17042 */
+#define CELL_CHAR_TBL_SAMPLES  48
+
+static uint16_t cell_char_tbl[] = {
+       /* Data to be written from 0x80h */
+       0xA250, 0xB720, 0xB800, 0xB880, 0xB920, 0xBA00, 0xBA60, 0xBBF0,
+       0xBCF0, 0xBE50, 0xC060, 0xC2D0, 0xC520, 0xC750, 0xCA00, 0xD090,
+       /* Data to be written from 0x90h */
+       0x0120, 0x1C80, 0x0470, 0x0440, 0x0100, 0x5500, 0x0960, 0x2410,
+       0x2250, 0x15F0, 0x0BD0, 0x0D00, 0x0B00, 0x0BB0, 0x08A0, 0x08A0,
+       /* Data to be written from 0xA0h */
+       0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
+       0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100, 0x0100,
 };
 
+struct max17042_config_data {
+       /*
+        * if config_init is 0, which means new
+        * configuration has been loaded in that case
+        * we need to perform complete init of chip
+        */
+       u16     size;
+       u8      table_type;
+       u8      config_init;
+
+       u16     rcomp0;
+       u16     tempCo;
+       u16     kempty0;
+       u16     full_cap;
+       u16     cycles;
+       u16     full_capnom;
+       u16     soc_empty;
+       u16     ichgt_term;
+       u16     design_cap;
+       u16     etc;
+       u16     rsense;
+       u16     cfg;
+       u16     learn_cfg;
+       u16     filter_cfg;
+       u16     relax_cfg;
+       u16     cell_char_tbl[CELL_CHAR_TBL_SAMPLES];
+} __packed;
+
 struct max17042_chip {
        struct i2c_client *client;
        struct power_supply battery;
        struct max17042_platform_data *pdata;
+       struct mutex batt_lock;
+       struct mutex init_lock;
+
+       int present;
+       int status;
+       int health;
+       int technology;
+       int charge_full_des;
+       struct delayed_work init_worker;
+};
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *max17042_dbgfs_root;
+static char max17042_dbg_regs[MAX17042_MAX_MEM][4];
+#endif
+
+static int max17042_reboot_callback(struct notifier_block *nfb,
+                                       unsigned long event, void *data);
+
+static struct notifier_block max17042_reboot_notifier_block = {
+       .notifier_call = max17042_reboot_callback,
+       .priority = 0,
+};
+
+static void set_soc_intr_thresholds(struct max17042_chip *chip, u16 off);
+static void save_runtime_params(struct max17042_chip *chip);
+static u16 fg_vfSoc;
+static struct max17042_config_data *fg_conf_data;
+static struct i2c_client *max17042_client;
+
+static enum power_supply_property max17042_battery_props[] = {
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_TECHNOLOGY,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_VOLTAGE_AVG,
+       POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+       POWER_SUPPLY_PROP_CURRENT_AVG,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_TEMP,
+       POWER_SUPPLY_PROP_CHARGE_NOW,
+       POWER_SUPPLY_PROP_CHARGE_FULL,
+       POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
 };
 
 static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value)
 {
-       int ret = i2c_smbus_write_word_data(client, reg, value);
+       int ret, i;
+
+       for (i = 0; i < NR_RETRY_CNT; i++) {
+               ret = i2c_smbus_write_word_data(client, reg, value);
+               if (ret == -EAGAIN || ret == -ETIMEDOUT)
+                       continue;
+               else
+                       break;
+       }
 
        if (ret < 0)
-               dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+               dev_err(&client->dev, "I2C SMbus Write error:%d\n", ret);
 
        return ret;
 }
 
 static int max17042_read_reg(struct i2c_client *client, u8 reg)
 {
-       int ret = i2c_smbus_read_word_data(client, reg);
+       int ret, i;
+
+       for (i = 0; i < NR_RETRY_CNT; i++) {
+               ret = i2c_smbus_read_word_data(client, reg);
+               if (ret == -EAGAIN || ret == -ETIMEDOUT)
+                       continue;
+               else
+                       break;
+       }
 
        if (ret < 0)
-               dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+               dev_err(&client->dev, "I2C SMbus Read error:%d\n", ret);
 
        return ret;
 }
 
-static enum power_supply_property max17042_battery_props[] = {
-       POWER_SUPPLY_PROP_VOLTAGE_NOW,
-       POWER_SUPPLY_PROP_VOLTAGE_AVG,
-       POWER_SUPPLY_PROP_CAPACITY,
-};
+static int max17042_write_verify_reg(struct i2c_client *client,
+                                               u8 reg, u16 value)
+{
+       int ret;
+
+       /* Write the value to register */
+       ret = max17042_write_reg(client, reg, value);
+
+       /* Read the value from register */
+       ret = max17042_read_reg(client, reg);
+
+       /* compare the both the values */
+       if (value != ret)
+               dev_err(&client->dev,
+                       "write verify failed on Register:0x%x\n", reg);
+
+       return ret;
+}
+
+static int max17042_reg_read_modify(struct i2c_client *client, u8 reg,
+                                                       u16 val, int bit_set)
+{
+       u16 data;
+       int ret;
+
+       data = max17042_read_reg(client, reg);
+
+       if (bit_set)
+               data |= val;
+       else
+               data &= (~val);
+
+       ret = max17042_write_reg(client, reg, data);
+       return ret;
+}
+
+static irqreturn_t max17042_intr_handler(int id, void *dev)
+{
+       return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t max17042_thread_handler(int id, void *dev)
+{
+       struct max17042_chip *chip = dev;
+       u16 val;
+
+       pm_runtime_get_sync(&chip->client->dev);
+
+       mutex_lock(&chip->batt_lock);
+       val = max17042_read_reg(chip->client, MAX17042_STATUS);
+       if (val & STATUS_INTR_VOLT_BIT)
+               dev_info(&chip->client->dev, "Volatge threshold INTR\n");
+
+       if (val & STATUS_INTR_TEMP_BIT)
+               dev_info(&chip->client->dev, "Temperature threshold INTR\n");
+
+       if (val & STATUS_INTR_SOC_BIT) {
+               dev_info(&chip->client->dev, "SOC threshold INTR\n");
+               set_soc_intr_thresholds(chip, 1);
+       }
+       mutex_unlock(&chip->batt_lock);
+
+       power_supply_changed(&chip->battery);
+       pm_runtime_put_sync(&chip->client->dev);
+       return IRQ_HANDLED;
+}
+
+static short adjust_sign_value(int value, int is_byte)
+{
+       short result, temp = (short)value;
+       if (temp & MAX17042_SIGN_INDICATOR) {
+
+               if (is_byte) {
+                       result = (~temp) >> 8;
+                       result &= 0xff;
+               } else {
+                       result = ~temp;
+               }
+
+               result++;
+               result *= -1;
+       } else {
+               if (is_byte)
+                       result = temp >> 8;
+               else
+                       result = temp;
+       }
+
+       return result;
+}
+
+static int read_batt_pack_temp(struct max17042_chip *chip, int *temp)
+{
+       int ret;
+       u16 val;
+
+       /* Read battery pack temperature */
+       if (chip->pdata->battery_pack_temp) {
+               ret = chip->pdata->battery_pack_temp(temp);
+               if (ret < 0)
+                       goto temp_read_err;
+               val = (0xff & (char)*temp) << 8;
+               ret = max17042_write_reg(chip->client, MAX17042_TEMP, val);
+               if (ret < 0)
+                       goto temp_read_err;
+       } else {
+               ret = max17042_read_reg(chip->client, MAX17042_TEMP);
+               if (ret < 0)
+                       goto temp_read_err;
+
+               /* MAX17042_TEMP register gives the signed
+                * value and we are ignoring the lower byte
+                * which represents the decimal point */
+
+               *temp = adjust_sign_value(ret, BYTE_VALUE);
+       }
+
+       return 0;
+
+temp_read_err:
+       dev_err(&chip->client->dev, "BP Temp read error:%d", ret);
+       return ret;
+}
 
 static int max17042_get_property(struct power_supply *psy,
                            enum power_supply_property psp,
@@ -135,48 +450,881 @@ static int max17042_get_property(struct power_supply *psy,
 {
        struct max17042_chip *chip = container_of(psy,
                                struct max17042_chip, battery);
+       short int cur;
+       int volt_ocv, ret, batt_temp;
 
+       mutex_lock(&chip->batt_lock);
        switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               val->intval = chip->status;
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               val->intval = chip->health;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = chip->present;
+               break;
+       case POWER_SUPPLY_PROP_TECHNOLOGY:
+               val->intval = chip->technology;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+               val->intval = chip->charge_full_des;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_NOW:
+               ret = max17042_read_reg(chip->client, MAX17042_RepCap);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+               val->intval = ret * MAX17042_CHRG_CONV_FCTR;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               ret = max17042_read_reg(chip->client, MAX17042_FullCAP);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+               val->intval = ret * MAX17042_CHRG_CONV_FCTR;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               ret = max17042_read_reg(chip->client, MAX17042_Current);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+
+               cur = adjust_sign_value(ret, WORD_VALUE);
+
+               if (fg_conf_data->rsense)
+                       val->intval = (cur * MAX17042_CURR_CONV_FCTR)
+                                               / fg_conf_data->rsense;
+               else
+                       val->intval = cur * MAX17042_CURR_CONV_FCTR;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+               ret = max17042_read_reg(chip->client, MAX17042_AvgCurrent);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+
+               cur = adjust_sign_value(ret, WORD_VALUE);
+
+               if (fg_conf_data->rsense)
+                       val->intval = (cur * MAX17042_CURR_CONV_FCTR)
+                                               / fg_conf_data->rsense;
+               else
+                       val->intval = cur * MAX17042_CURR_CONV_FCTR;
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+               if (!chip->pdata->enable_current_sense) {
+                       val->intval = CONSTANT_TEMP_IN_POWER_SUPPLY;
+                       break;
+               }
+               ret = read_batt_pack_temp(chip, &batt_temp);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+               /*
+                * Temperature is measured in units of degrees celcius, the
+                * power_supply class measures temperature in tenths of degrees
+                * celsius.
+                */
+               val->intval = batt_temp * 10;
+               break;
        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_VCELL) * 83; /* 1000 / 12 = 83 */
+               ret = max17042_read_reg(chip->client, MAX17042_OCVInternal);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+               val->intval = (ret >> 3) * MAX17042_VOLT_CONV_FCTR;
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_AVG:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_AvgVCELL) * 83;
+               ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+               val->intval = (ret >> 3) * MAX17042_VOLT_CONV_FCTR;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+               ret = max17042_read_reg(chip->client, MAX17042_V_empty);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+               val->intval = (ret >> 7) * 10000; /* Units of LSB = 10mV */
                break;
        case POWER_SUPPLY_PROP_CAPACITY:
-               val->intval = max17042_read_reg(chip->client,
-                               MAX17042_SOC) / 256;
+               /* FIXME: WA to avoid modem crash, should be removed once
+                * everybody starts using the PR2 POR batteries */
+               ret = max17042_read_reg(chip->client, MAX17042_OCVInternal);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+               volt_ocv = (ret >> 3) * MAX17042_VOLT_CONV_FCTR;
+               /* Check if the OCV is less than
+                * IA_APPS_RUN(3.6V) Threshold */
+               if ((chip->pdata->enable_current_sense && volt_ocv <= 3600000)
+                               || (chip->health == POWER_SUPPLY_HEALTH_DEAD)) {
+                       val->intval = 0;
+                       break;
+               }
+               if (!chip->pdata->enable_current_sense &&
+                               volt_ocv <= POWER_SUPPLY_VOLT_MIN_THRESHOLD) {
+                       val->intval = 0;
+                       break;
+               }
+               /* If current sensing is not enabled then read the
+                * voltage based fuel gauge register for SOC */
+               if (chip->pdata->enable_current_sense)
+                       ret = max17042_read_reg(chip->client, MAX17042_RepSOC);
+               else
+                       ret = max17042_read_reg(chip->client, MAX17042_VFSOC);
+               if (ret < 0)
+                       goto ps_prop_read_err;
+               val->intval = ret >> 8;
+               /* Check if MSB of lower byte is set
+                * then round off the SOC to higher digit
+                */
+               if (ret & 0x80)
+                       val->intval += 1;
+
+               if (val->intval > 100)
+                       val->intval = 100;
                break;
        default:
+               mutex_unlock(&chip->batt_lock);
                return -EINVAL;
        }
+
+       mutex_unlock(&chip->batt_lock);
+       return 0;
+
+ps_prop_read_err:
+       mutex_unlock(&chip->batt_lock);
+       return ret;
+}
+
+static void init_fg_config_data(void)
+{
+       fg_conf_data->cfg = 0x2210;
+       fg_conf_data->learn_cfg = 0x0076;
+       fg_conf_data->filter_cfg = 0x87A4;
+       fg_conf_data->relax_cfg = 0x506B;
+       memcpy(&fg_conf_data->cell_char_tbl, cell_char_tbl,
+                                       sizeof(cell_char_tbl));
+       fg_conf_data->rcomp0 = 0x0056;
+       fg_conf_data->tempCo = 0x1621;
+       fg_conf_data->etc = 0x2D51;
+       fg_conf_data->kempty0 = 0x0350;
+       fg_conf_data->ichgt_term = 0x0140;
+       fg_conf_data->full_cap = 3100;
+       fg_conf_data->design_cap = 3100;
+       fg_conf_data->full_capnom = 3100;
+       fg_conf_data->rsense = 1;
+}
+
+static void dump_fg_conf_data(struct max17042_chip *chip)
+{
+       int i;
+
+       dev_info(&chip->client->dev, "size:%x\n", fg_conf_data->size);
+       dev_info(&chip->client->dev, "table_type:%x\n",
+                                       fg_conf_data->table_type);
+       dev_info(&chip->client->dev, "config_init:%x\n",
+                                       fg_conf_data->config_init);
+       dev_info(&chip->client->dev, "rcomp0:%x\n", fg_conf_data->rcomp0);
+       dev_info(&chip->client->dev, "tempCo:%x\n", fg_conf_data->tempCo);
+       dev_info(&chip->client->dev, "kempty0:%x\n", fg_conf_data->kempty0);
+       dev_info(&chip->client->dev, "full_cap:%x\n", fg_conf_data->full_cap);
+       dev_info(&chip->client->dev, "cycles:%x\n", fg_conf_data->cycles);
+       dev_info(&chip->client->dev, "full_capnom:%x\n",
+                                               fg_conf_data->full_capnom);
+       dev_info(&chip->client->dev, "soc_empty:%x\n",
+                                               fg_conf_data->soc_empty);
+       dev_info(&chip->client->dev, "ichgt_term:%x\n",
+                                               fg_conf_data->ichgt_term);
+       dev_info(&chip->client->dev, "design_cap:%x\n",
+                                               fg_conf_data->design_cap);
+       dev_info(&chip->client->dev, "etc:%x\n", fg_conf_data->etc);
+       dev_info(&chip->client->dev, "rsense:%x\n", fg_conf_data->rsense);
+       dev_info(&chip->client->dev, "cfg:%x\n", fg_conf_data->cfg);
+       dev_info(&chip->client->dev, "learn_cfg:%x\n",
+                                               fg_conf_data->learn_cfg);
+       dev_info(&chip->client->dev, "filter_cfg:%x\n",
+                                               fg_conf_data->filter_cfg);
+       dev_info(&chip->client->dev, "relax_cfg:%x\n", fg_conf_data->relax_cfg);
+
+       for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+               dev_info(&chip->client->dev, "%x, ",
+                               fg_conf_data->cell_char_tbl[i]);
+       dev_info(&chip->client->dev, "\n");
+}
+
+static void enable_soft_POR(struct max17042_chip *chip)
+{
+       u16 val = 0x0000;
+
+       max17042_write_reg(chip->client, MAX17042_MLOCKReg1, val);
+       max17042_write_reg(chip->client, MAX17042_MLOCKReg2, val);
+       max17042_write_reg(chip->client, MAX17042_STATUS, val);
+
+       val = max17042_read_reg(chip->client, MAX17042_MLOCKReg1);
+       if (val)
+               dev_err(&chip->client->dev, "MLOCKReg1 read failed\n");
+
+       val = max17042_read_reg(chip->client, MAX17042_MLOCKReg2);
+       if (val)
+               dev_err(&chip->client->dev, "MLOCKReg2 read failed\n");
+
+       val = max17042_read_reg(chip->client, MAX17042_STATUS);
+       if (val)
+               dev_err(&chip->client->dev, "STATUS read failed\n");
+
+       /* send POR command */
+       max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, 0x000F);
+       mdelay(2);
+
+       val = max17042_read_reg(chip->client, MAX17042_STATUS);
+       if (val & STATUS_POR_BIT)
+               dev_info(&chip->client->dev, "SoftPOR done!\n");
+       else
+               dev_err(&chip->client->dev, "SoftPOR failed\n");
+}
+
+static int write_characterization_data(struct max17042_chip *chip)
+{
+       uint16_t cell_data[CELL_CHAR_TBL_SAMPLES];
+       uint16_t temp_data[CELL_CHAR_TBL_SAMPLES];
+       int i;
+       u8 addr;
+
+       memset(cell_data, 0x0, sizeof(cell_data));
+       /* Unlock model access */
+       max17042_write_reg(chip->client, MAX17042_MLOCKReg1, FG_MODEL_UNLOCK1);
+       max17042_write_reg(chip->client, MAX17042_MLOCKReg2, FG_MODEL_UNLOCK2);
+       addr = MAX17042_MODELChrTbl;
+
+       /* write the 48 words */
+       for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+               max17042_write_reg(chip->client, addr + i,
+                               fg_conf_data->cell_char_tbl[i]);
+
+       /* read the 48 words */
+       for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+               cell_data[i] = max17042_read_reg(chip->client, addr + i);
+
+       /* compare the data */
+       if (memcmp(cell_data, fg_conf_data->cell_char_tbl, sizeof(cell_data))) {
+               dev_err(&chip->client->dev, "%s write failed\n", __func__);
+               for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+                       dev_err(&chip->client->dev, "0x%x,0x%x\n", cell_data[i],
+                                               fg_conf_data->cell_char_tbl[i]);
+               /* Lock Model access regs */
+               max17042_write_reg(chip->client, MAX17042_MLOCKReg1,
+                                                               FG_MODEL_LOCK1);
+               max17042_write_reg(chip->client, MAX17042_MLOCKReg2,
+                                                               FG_MODEL_LOCK2);
+               return -EIO;
+       }
+
+       memset(temp_data, 0x0, sizeof(temp_data));
+       /* Lock Model access regs */
+       max17042_write_reg(chip->client, MAX17042_MLOCKReg1, FG_MODEL_LOCK1);
+       max17042_write_reg(chip->client, MAX17042_MLOCKReg2, FG_MODEL_LOCK2);
+
+       /* read the 48 words */
+       for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+               cell_data[i] = max17042_read_reg(chip->client, addr + i);
+
+       /* compare the data */
+       if (memcmp(cell_data, temp_data, sizeof(temp_data))) {
+               dev_err(&chip->client->dev, "%s verify failed\n", __func__);
+               for (i = 0; i < CELL_CHAR_TBL_SAMPLES; i++)
+                       dev_err(&chip->client->dev, "0x%x, ", cell_data[i]);
+               dev_err(&chip->client->dev, "\n");
+               return -EIO;
+       }
+
+       return 0;
+}
+
+static void configure_learncfg(struct max17042_chip *chip)
+{
+
+       u16 cycles;
+
+       cycles = max17042_read_reg(chip->client, MAX17042_Cycles);
+       if (cycles > CYCLES_ROLLOVER_CUTOFF)
+               max17042_write_reg(chip->client, MAX17042_LearnCFG,
+                                               MAX17042_DEF_RO_LRNCFG);
+       else
+               max17042_write_reg(chip->client, MAX17042_LearnCFG,
+                                               fg_conf_data->learn_cfg);
+
+}
+
+static void write_config_regs(struct max17042_chip *chip)
+{
+       max17042_write_reg(chip->client, MAX17042_CONFIG, fg_conf_data->cfg);
+       configure_learncfg(chip);
+
+       max17042_write_reg(chip->client, MAX17042_SHFTCFG,
+                                               fg_conf_data->filter_cfg);
+       max17042_write_reg(chip->client, MAX17042_RelaxCFG,
+                                               fg_conf_data->relax_cfg);
+}
+
+static void write_custom_regs(struct max17042_chip *chip)
+{
+       max17042_write_verify_reg(chip->client, MAX17042_RCOMP0,
+                                               fg_conf_data->rcomp0);
+       max17042_write_verify_reg(chip->client, MAX17042_TempCo,
+                                               fg_conf_data->tempCo);
+       max17042_write_reg(chip->client, MAX17042_ETC, fg_conf_data->etc);
+       max17042_write_verify_reg(chip->client, MAX17042_K_empty0,
+                                               fg_conf_data->kempty0);
+       max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm,
+                                               fg_conf_data->ichgt_term);
+       max17042_write_verify_reg(chip->client, MAX17042_SOCempty,
+                                               fg_conf_data->soc_empty);
+}
+
+static void update_capacity_regs(struct max17042_chip *chip)
+{
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+                       MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap)
+                       * fg_conf_data->rsense);
+       max17042_write_reg(chip->client, MAX17042_DesignCap,
+                       fg_conf_data->design_cap * fg_conf_data->rsense);
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+                       MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_capnom)
+                       * fg_conf_data->rsense);
+}
+
+static void reset_vfsoc0_reg(struct max17042_chip *chip)
+{
+       fg_vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC);
+       max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK);
+       max17042_write_verify_reg(chip->client, MAX17042_VFSOC0, fg_vfSoc);
+       max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK);
+}
+
+static void load_new_capacity_params(struct max17042_chip *chip, bool is_por)
+{
+       u16 full_cap0, rem_cap, rep_cap, dq_acc;
+
+       if (is_por) {
+               full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0);
+
+               /* fg_vfSoc needs to shifted by 8 bits to get the
+                * perc in 1% accuracy, to get the right rem_cap multiply
+                * full_cap0, fg_vfSoc and devide by 100
+                */
+               rem_cap = ((fg_vfSoc >> 8) * (u32)full_cap0) / 100;
+               max17042_write_verify_reg(chip->client,
+                                       MAX17042_RemCap, rem_cap);
+
+               rep_cap = rem_cap;
+               max17042_write_verify_reg(chip->client,
+                                       MAX17042_RepCap, rep_cap);
+       }
+
+       /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */
+       dq_acc = MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap) / dQ_ACC_DIV;
+       max17042_write_verify_reg(chip->client, MAX17042_dQacc, dq_acc);
+       max17042_write_verify_reg(chip->client, MAX17042_dPacc, dP_ACC_200);
+
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+                       MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap)
+                       * fg_conf_data->rsense);
+       max17042_write_reg(chip->client, MAX17042_DesignCap,
+                       fg_conf_data->design_cap * fg_conf_data->rsense);
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+                       MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_capnom)
+                       * fg_conf_data->rsense);
+}
+
+static void save_runtime_params(struct max17042_chip *chip)
+{
+       int size, retval;
+
+       dev_dbg(&chip->client->dev, "%s\n", __func__);
+
+       if (!chip->pdata->save_config_data || !chip->pdata->is_init_done)
+               return ;
+
+       fg_conf_data->rcomp0 = max17042_read_reg(chip->client,
+                                                       MAX17042_RCOMP0);
+       fg_conf_data->tempCo = max17042_read_reg(chip->client,
+                                                       MAX17042_TempCo);
+       fg_conf_data->kempty0 = max17042_read_reg(chip->client,
+                                                       MAX17042_K_empty0);
+       fg_conf_data->full_capnom = max17042_read_reg(chip->client,
+                                                       MAX17042_FullCAPNom);
+       fg_conf_data->full_cap = max17042_read_reg(chip->client,
+                                                       MAX17042_FullCAP);
+       if (fg_conf_data->rsense) {
+               fg_conf_data->full_capnom = MAX17042_MODEL_DIV_FACTOR(
+                       fg_conf_data->full_capnom) / fg_conf_data->rsense;
+               fg_conf_data->full_cap /= fg_conf_data->rsense;
+       }
+       fg_conf_data->cycles = max17042_read_reg(chip->client,
+                                                       MAX17042_Cycles);
+
+       /* Dump data before saving */
+       dump_fg_conf_data(chip);
+
+       size = sizeof(*fg_conf_data) - sizeof(fg_conf_data->cell_char_tbl);
+       retval = chip->pdata->save_config_data(DRV_NAME, fg_conf_data, size);
+       if (retval < 0) {
+               dev_err(&chip->client->dev, "%s failed\n", __func__);
+               return ;
+       }
+
+}
+
+static void restore_runtime_params(struct max17042_chip *chip)
+{
+       u16 full_cap0, rem_cap, soc, dq_acc;
+       int size, retval;
+
+       if (!chip->pdata->restore_config_data)
+               return ;
+
+       size = sizeof(*fg_conf_data) - sizeof(fg_conf_data->cell_char_tbl);
+       retval = chip->pdata->restore_config_data(DRV_NAME, fg_conf_data, size);
+       if (retval < 0) {
+               dev_err(&chip->client->dev, "%s failed\n", __func__);
+               return ;
+       }
+
+       /* Dump data after restoring */
+       dump_fg_conf_data(chip);
+
+       max17042_write_verify_reg(chip->client, MAX17042_RCOMP0,
+                                               fg_conf_data->rcomp0);
+       max17042_write_verify_reg(chip->client, MAX17042_TempCo,
+                                               fg_conf_data->tempCo);
+       max17042_write_verify_reg(chip->client, MAX17042_K_empty0,
+                                               fg_conf_data->kempty0);
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+                       MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_capnom)
+                       * fg_conf_data->rsense);
+       max17042_write_verify_reg(chip->client, MAX17042_Cycles,
+                                               fg_conf_data->cycles);
+
+       /* delay must be atleast 350mS to allow SOC
+        * to be calculated from the restored configuration
+        */
+       msleep(350);
+
+       /* restore full capacity value */
+       full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0);
+       soc = max17042_read_reg(chip->client, MAX17042_SOC);
+
+       rem_cap = (soc * (u32)full_cap0) / 25600;
+       max17042_write_verify_reg(chip->client, MAX17042_RemCap, rem_cap);
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+                       fg_conf_data->full_cap * fg_conf_data->rsense);
+
+       /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */
+       dq_acc = MAX17042_MODEL_MUL_FACTOR(fg_conf_data->full_cap) / dQ_ACC_DIV;
+       max17042_write_verify_reg(chip->client, MAX17042_dQacc, dq_acc);
+       max17042_write_verify_reg(chip->client, MAX17042_dPacc, dP_ACC_200);
+
+       /* delay must be atleast 350mS to write Cycles
+        * value from the restored configuration
+        */
+       msleep(350);
+
+       max17042_write_verify_reg(chip->client, MAX17042_Cycles,
+                                               fg_conf_data->cycles);
+}
+
+static int max17042_reboot_callback(struct notifier_block *nfb,
+                                       unsigned long event, void *data)
+{
+       struct max17042_chip *chip = i2c_get_clientdata(max17042_client);
+
+       save_runtime_params(chip);
+       return NOTIFY_OK;
+}
+
+static int init_max17042_chip(struct max17042_chip *chip)
+{
+       int ret = 0, val;
+       bool is_por;
+
+       val = max17042_read_reg(chip->client, MAX17042_STATUS);
+       dev_info(&chip->client->dev, "Status reg: %x\n", val);
+
+       if (val & STATUS_POR_BIT)
+               is_por = true;
+       else
+               is_por = false;
+
+       /* Initialize configuration */
+       write_config_regs(chip);
+
+       /* write cell characterization data */
+       ret = write_characterization_data(chip);
+       if (ret < 0)
+               return ret;
+
+       /* write custom parameters */
+       write_custom_regs(chip);
+
+       /* update capacity params */
+       update_capacity_regs(chip);
+
+       /* delay must be atleast 350mS to allow VFSOC
+        * to be calculated from the new configuration
+        */
+       msleep(350);
+
+       /* reset vfsoc0 reg */
+       reset_vfsoc0_reg(chip);
+
+       /* advance to coulomb counter mode */
+       max17042_write_verify_reg(chip->client,
+                       MAX17042_Cycles, fg_conf_data->cycles);
+
+       /* adjust Temperature gain and offset */
+       max17042_write_reg(chip->client,
+                       MAX17042_TGAIN, NTC_47K_TGAIN);
+       max17042_write_reg(chip->client,
+                       MAx17042_TOFF, NTC_47K_TOFF);
+
+       /* load new capacity params */
+       load_new_capacity_params(chip, is_por);
+
+       if (is_por) {
+               /* Init complete, Clear the POR bit */
+               val = max17042_read_reg(chip->client, MAX17042_STATUS);
+               max17042_write_reg(chip->client, MAX17042_STATUS,
+                                               val & (~STATUS_POR_BIT));
+       }
+
+       /* reset FullCap to non inflated value */
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+                       fg_conf_data->full_cap * fg_conf_data->rsense);
+
+       return ret;
+}
+
+static void reset_max17042(struct max17042_chip *chip)
+{
+       int val;
+
+       /* do soft power reset */
+       enable_soft_POR(chip);
+
+       /* After Power up, the MAX17042 requires 500mS in order
+        * to perform signal debouncing and initial SOC reporting
+        */
+       msleep(500);
+
+       max17042_write_reg(chip->client, MAX17042_CONFIG, 0x2210);
+
+       /* adjust Temperature gain and offset */
+       max17042_write_reg(chip->client, MAX17042_TGAIN, NTC_47K_TGAIN);
+       max17042_write_reg(chip->client, MAx17042_TOFF, NTC_47K_TOFF);
+
+       /* Init complete, Clear the POR bit */
+       val = max17042_read_reg(chip->client, MAX17042_STATUS);
+       max17042_write_reg(chip->client, MAX17042_STATUS,
+                                       val & (~STATUS_POR_BIT));
+
+}
+
+static void max17042_restore_conf_data(struct max17042_chip *chip)
+{
+       int retval = 0, val, size;
+
+       /* return if lock already acquired */
+       if (!mutex_trylock(&chip->init_lock))
+               return;
+
+       if (!chip->pdata->is_init_done && chip->pdata->restore_config_data) {
+               retval = chip->pdata->restore_config_data(DRV_NAME,
+                                       fg_conf_data, sizeof(*fg_conf_data));
+
+               if (retval == -ENXIO) {         /* no device found */
+                       dev_err(&chip->client->dev, "device not found\n");
+                       chip->pdata->is_init_done = 1;
+                       chip->pdata->save_config_data = NULL;
+               } else if (retval < 0) {        /* device not ready */
+                       dev_warn(&chip->client->dev, "device not ready\n");
+               } else {                        /* device ready */
+                       /* Dump data after restoring */
+                       dump_fg_conf_data(chip);
+
+                       val = max17042_read_reg(chip->client, MAX17042_STATUS);
+                       dev_info(&chip->client->dev, "Status reg: %x\n", val);
+                       if (!fg_conf_data->config_init ||
+                                               (val & STATUS_POR_BIT)) {
+                               dev_info(&chip->client->dev,
+                                       "config data needs to be loaded\n");
+                               retval = init_max17042_chip(chip);
+                               if (retval < 0) {
+                                       dev_err(&chip->client->dev,
+                                               "maxim chip init failed\n");
+                                       reset_max17042(chip);
+                                       chip->pdata->save_config_data = NULL;
+                               }
+                       }
+                       chip->pdata->is_init_done = 1;
+
+                       /* mark the dirty byte in non-volatile memory */
+                       if (!fg_conf_data->config_init && retval >= 0) {
+                               fg_conf_data->config_init = 0x1;
+                               size = sizeof(*fg_conf_data) -
+                                       sizeof(fg_conf_data->cell_char_tbl);
+                               retval = chip->pdata->save_config_data(
+                                       DRV_NAME, fg_conf_data, size);
+                               if (retval < 0)
+                                       dev_err(&chip->client->dev,
+                                               "%s failed\n", __func__);
+                       }
+               }
+       }
+
+       /* Check if current sensing is enabled */
+       if (chip->pdata->is_init_done && (retval == 0)) {
+               if (chip->pdata->current_sense_enabled)
+                       chip->pdata->enable_current_sense =
+                               chip->pdata->current_sense_enabled();
+               else
+                       chip->pdata->enable_current_sense = 1;
+       }
+
+       if (chip->pdata->enable_current_sense) {
+               dev_info(&chip->client->dev, "current sensing enabled\n");
+               /* enable coulomb counter based fuel gauging */
+               configure_learncfg(chip);
+
+               /* enable Alerts for SOCRep */
+               max17042_write_reg(chip->client, MAX17042_MiscCFG, 0x0000);
+
+               chip->technology = chip->pdata->technology;
+       } else {
+               dev_info(&chip->client->dev, "current sensing NOT enabled\n");
+               /* Enable voltage based Fuel Gauging */
+               max17042_write_reg(chip->client, MAX17042_LearnCFG, 0x0007);
+               /* configure interrupts for SOCvf */
+               max17042_write_reg(chip->client, MAX17042_MiscCFG, 0x0003);
+
+               chip->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+       }
+
+       mutex_unlock(&chip->init_lock);
+}
+
+static void max17042_init_worker(struct work_struct *work)
+{
+       struct max17042_chip *chip = container_of(work,
+                               struct max17042_chip, init_worker.work);
+
+       dev_info(&chip->client->dev, "%s\n", __func__);
+       max17042_restore_conf_data(chip);
+}
+
+static void set_soc_intr_thresholds(struct max17042_chip *chip, u16 off)
+{
+       u16 soc, soc_tr;
+
+       /* program interrupt thesholds such that we should
+        * get interrupt for every 'off' perc change in the soc
+        */
+       soc = max17042_read_reg(chip->client, MAX17042_RepSOC) >> 8;
+       soc_tr = (soc + off) << 8;
+       soc_tr |= (soc - off);
+       max17042_write_reg(chip->client, MAX17042_SALRT_Th, soc_tr);
+}
+
+static void set_intr_thresholds(struct max17042_chip *chip)
+{
+       u16 volt_tr, temp_tr;
+
+       /* Max voltage threshold set to 4200mV */
+       volt_tr = MAX_VOLT_THRLD << 8;
+       /* Min voltage threshold set to 3760mV */
+       volt_tr |= MIN_VOLT_THRLD;
+       max17042_write_reg(chip->client, MAX17042_VALRT_Th, volt_tr);
+
+       /* Max temperature threshold set 60 Degrees */
+       temp_tr = MAX_TEMP_THRLD << 8;
+       /* Min temperature threshold set 0 Degrees */
+       temp_tr |= MIN_TEMP_THRLD;
+       max17042_write_reg(chip->client, MAX17042_TALRT_Th, temp_tr);
+
+       /* set soc threshold */
+       set_soc_intr_thresholds(chip, 1);
+}
+
+static void max17042_external_power_changed(struct power_supply *psy)
+{
+       struct max17042_chip *chip = container_of(psy,
+                               struct max17042_chip, battery);
+
+       pm_runtime_get_sync(&chip->client->dev);
+
+       mutex_lock(&chip->batt_lock);
+       /* get the battery status */
+       if (chip->pdata->battery_status)
+               chip->status = chip->pdata->battery_status();
+
+       /* get the battery health */
+       if (chip->pdata->battery_health)
+               chip->health = chip->pdata->battery_health();
+       mutex_unlock(&chip->batt_lock);
+
+       /* Init maxim chip if it is not already initialized */
+       if (!chip->pdata->is_init_done)
+               schedule_delayed_work(&chip->init_worker, 0);
+
+       power_supply_changed(&chip->battery);
+       pm_runtime_put_sync(&chip->client->dev);
+}
+
+static void init_battery_props(struct max17042_chip *chip)
+{
+       u16 val;
+
+       val = max17042_read_reg(chip->client, MAX17042_STATUS);
+       /* check battery present bit */
+       if (val & STATUS_BST_BIT)
+               chip->present = 0;
+       else
+               chip->present = 1;
+
+       chip->status = POWER_SUPPLY_STATUS_UNKNOWN;
+       chip->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+       chip->technology = chip->pdata->technology;
+       chip->charge_full_des = BATT_CHRG_FULL_DES;
+}
+
+#ifdef CONFIG_DEBUG_FS
+/**
+ * max17042_show - debugfs: show the state of an endpoint.
+ * @seq: The seq_file to write data to.
+ * @unused: not used
+ *
+ * This debugfs entry shows the content of the register
+ * given in the data parameter.
+*/
+static int max17042_show(struct seq_file *seq, void *unused)
+{
+       u16 val;
+       long addr;
+
+       if (strict_strtol((char *)seq->private, 16, &addr))
+               return -EINVAL;
+
+       val = max17042_read_reg(max17042_client, addr);
+       seq_printf(seq, "%x\n", val);
+
        return 0;
 }
 
+static int max17042_dbgfs_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, max17042_show, inode->i_private);
+}
+
+static const struct file_operations max17042_dbgfs_fops = {
+       .owner          = THIS_MODULE,
+       .open           = max17042_dbgfs_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+static void max17042_create_debugfs(struct max17042_chip *chip)
+{
+       int i;
+       struct dentry *entry;
+
+       max17042_dbgfs_root = debugfs_create_dir(DEV_NAME, NULL);
+       if (IS_ERR(max17042_dbgfs_root)) {
+               dev_warn(&chip->client->dev, "DEBUGFS DIR create failed\n");
+               return ;
+       }
+
+       for (i = 0; i < MAX17042_MAX_MEM; i++) {
+               sprintf((char *)&max17042_dbg_regs[i], "%x", i);
+               entry = debugfs_create_file(
+                                       (const char *)&max17042_dbg_regs[i],
+                                       S_IRUGO,
+                                       max17042_dbgfs_root,
+                                       &max17042_dbg_regs[i],
+                                       &max17042_dbgfs_fops);
+               if (IS_ERR(entry)) {
+                       debugfs_remove_recursive(max17042_dbgfs_root);
+                       max17042_dbgfs_root = NULL;
+                       dev_warn(&chip->client->dev,
+                                       "DEBUGFS entry Create failed\n");
+                       return ;
+               }
+       }
+}
+static inline void max17042_remove_debugfs(struct max17042_chip *chip)
+{
+       if (max17042_dbgfs_root)
+               debugfs_remove_recursive(max17042_dbgfs_root);
+}
+#else
+static inline void max17042_create_debugfs(struct max17042_chip *chip)
+{
+}
+static inline void max17042_remove_debugfs(struct max17042_chip *chip)
+{
+}
+#endif
+
 static int __devinit max17042_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
 {
        struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
        struct max17042_chip *chip;
        int ret;
+       if (!client->dev.platform_data) {
+               dev_err(&client->dev, "Platform Data is NULL");
+               return -EFAULT;
+       }
 
-       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
+               dev_err(&client->dev,
+                               "SM bus doesn't support DWORD transactions\n");
                return -EIO;
+       }
 
        chip = kzalloc(sizeof(*chip), GFP_KERNEL);
-       if (!chip)
+       if (!chip) {
+               dev_err(&client->dev, "mem alloc failed\n");
                return -ENOMEM;
+       }
 
+       fg_conf_data = kzalloc(sizeof(*fg_conf_data), GFP_KERNEL);
+       if (!fg_conf_data) {
+               dev_err(&client->dev, "mem alloc failed\n");
+               kfree(chip);
+               return -ENOMEM;
+       }
        chip->client = client;
        chip->pdata = client->dev.platform_data;
 
        i2c_set_clientdata(client, chip);
+       max17042_client = client;
+
+       ret = max17042_read_reg(chip->client, MAX17042_DevName);
+       if (ret != MAX17042_IC_VERSION) {
+               dev_err(&client->dev, "device version mismatch: %x\n", ret);
+               kfree(chip);
+               kfree(fg_conf_data);
+               return -EIO;
+       }
+
+       /* init battery properties */
+       init_battery_props(chip);
+       INIT_DELAYED_WORK(&chip->init_worker, max17042_init_worker);
+       mutex_init(&chip->batt_lock);
+       mutex_init(&chip->init_lock);
+
+       /* Initialize the chip with battery config data */
+       max17042_restore_conf_data(chip);
 
        chip->battery.name              = "max17042_battery";
        chip->battery.type              = POWER_SUPPLY_TYPE_BATTERY;
        chip->battery.get_property      = max17042_get_property;
+       chip->battery.external_power_changed = max17042_external_power_changed;
        chip->battery.properties        = max17042_battery_props;
        chip->battery.num_properties    = ARRAY_SIZE(max17042_battery_props);
 
@@ -185,14 +1333,35 @@ static int __devinit max17042_probe(struct i2c_client *client,
                dev_err(&client->dev, "failed: power supply register\n");
                i2c_set_clientdata(client, NULL);
                kfree(chip);
+               kfree(fg_conf_data);
                return ret;
        }
 
-       if (!chip->pdata->enable_current_sense) {
-               max17042_write_reg(client, MAX17042_CGAIN, 0x0000);
-               max17042_write_reg(client, MAX17042_MiscCFG, 0x0003);
-               max17042_write_reg(client, MAX17042_LearnCFG, 0x0007);
-       }
+       /* Init Runtime PM State */
+       pm_runtime_put_noidle(&chip->client->dev);
+       pm_schedule_suspend(&chip->client->dev, MSEC_PER_SEC);
+
+
+       /* register interrupt */
+       ret = request_threaded_irq(client->irq, max17042_intr_handler,
+                                               max17042_thread_handler,
+                                               0, DRV_NAME, chip);
+       if (ret)
+               dev_warn(&client->dev, "%s(): cannot get IRQ\n", __func__);
+       else
+               dev_info(&client->dev, "IRQ No:%d\n", client->irq);
+
+       /* Enable Interrupts */
+       max17042_reg_read_modify(client, MAX17042_CONFIG,
+                                               CONFIG_ALRT_BIT_ENBL, 1);
+       /* set the Interrupt threshold registers */
+       set_intr_thresholds(chip);
+
+       /* Create debugfs for maxim registers */
+       max17042_create_debugfs(chip);
+
+       /* Register reboot notifier callback */
+       register_reboot_notifier(&max17042_reboot_notifier_block);
 
        return 0;
 }
@@ -201,21 +1370,92 @@ static int __devexit max17042_remove(struct i2c_client *client)
 {
        struct max17042_chip *chip = i2c_get_clientdata(client);
 
+       unregister_reboot_notifier(&max17042_reboot_notifier_block);
+       max17042_remove_debugfs(chip);
+       free_irq(client->irq, chip);
        power_supply_unregister(&chip->battery);
+       pm_runtime_get_noresume(&chip->client->dev);
        i2c_set_clientdata(client, NULL);
        kfree(chip);
+       kfree(fg_conf_data);
        return 0;
 }
 
+#ifdef CONFIG_PM
+static int max17042_suspend(struct device *dev)
+{
+       struct max17042_chip *chip = dev_get_drvdata(dev);
+
+       /* max17042 IC automatically goes into shutdown mode
+        * if the SCL and SDA were held low for more than
+        * timeout of SHDNTIMER register value
+        */
+       dev_dbg(&chip->client->dev, "max17042 suspend\n");
+
+       return 0;
+}
+
+static int max17042_resume(struct device *dev)
+{
+       struct max17042_chip *chip = dev_get_drvdata(dev);
+
+       /* max17042 IC automatically wakes up if any edge
+        * on SDCl or SDA if we set I2CSH of CONFG reg
+        */
+       dev_dbg(&chip->client->dev, "max17042 resume\n");
+
+       return 0;
+}
+#else
+#define max17042_suspend NULL
+#define max17042_resume NULL
+#endif
+
+#ifdef CONFIG_PM_RUNTIME
+static int max17042_runtime_suspend(struct device *dev)
+{
+
+       dev_dbg(dev, "%s called\n", __func__);
+       return 0;
+}
+
+static int max17042_runtime_resume(struct device *dev)
+{
+       dev_dbg(dev, "%s called\n", __func__);
+       return 0;
+}
+
+static int max17042_runtime_idle(struct device *dev)
+{
+
+       dev_dbg(dev, "%s called\n", __func__);
+       return 0;
+}
+#else
+#define max17042_runtime_suspend       NULL
+#define max17042_runtime_resume                NULL
+#define max17042_runtime_idle          NULL
+#endif
+
 static const struct i2c_device_id max17042_id[] = {
-       { "max17042", 0 },
-       { }
+       { DEV_NAME, 0 },
+       { },
 };
 MODULE_DEVICE_TABLE(i2c, max17042_id);
 
+static const struct dev_pm_ops max17042_pm_ops = {
+       .suspend                = max17042_suspend,
+       .resume                 = max17042_resume,
+       .runtime_suspend        = max17042_runtime_suspend,
+       .runtime_resume         = max17042_runtime_resume,
+       .runtime_idle           = max17042_runtime_idle,
+};
+
 static struct i2c_driver max17042_i2c_driver = {
        .driver = {
-               .name   = "max17042",
+               .name   = DEV_NAME,
+               .owner  = THIS_MODULE,
+               .pm     = &max17042_pm_ops,
        },
        .probe          = max17042_probe,
        .remove         = __devexit_p(max17042_remove),
diff --git a/include/linux/power/intel_mdf_battery.h b/include/linux/power/intel_mdf_battery.h
new file mode 100644 (file)
index 0000000..97af447
--- /dev/null
@@ -0,0 +1,37 @@
+
+/*
+ * intel_mdf_battery.h - Intel Medfield MSIC Internal charger and Battery Driver
+ *
+ * Copyright (C) 2011 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.        See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
+ */
+
+#ifndef __INTEL_MDF_BATTERY_H_
+#define __INTEL_MDF_BATTERY_H_
+
+extern int intel_msic_is_current_sense_enabled(void);
+extern int intel_msic_check_battery_present(void);
+extern int intel_msic_check_battery_health(void);
+extern int intel_msic_check_battery_status(void);
+extern int intel_msic_get_battery_pack_temp(int *val);
+extern int intel_msic_save_config_data(const char *name, void *data, int len);
+extern int intel_msic_restore_config_data(const char *name, void *data,
+                                         int len);
+#endif /* __INTEL_MDF_BATTERY_H_ */
index 7995deb..2ea2d1f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Fuel gauge driver for Maxim 17042 / 8966 / 8997
+ * max17042_battery.h - Fuel gauge driver for Maxim 17042 / 8966 / 8997
  *  Note that Maxim 8966 and 8997 are mfd and this is its subdevice.
  *
  * Copyright (C) 2011 Samsung Electronics
 
 struct max17042_platform_data {
        bool enable_current_sense;
+       bool is_init_done;
+       int technology;
+
+       int (*current_sense_enabled)(void);
+       int (*battery_present)(void);
+       int (*battery_health)(void);
+       int (*battery_status)(void);
+       int (*battery_pack_temp)(int *);
+       int (*save_config_data)(const char *name, void *data, int len);
+       int (*restore_config_data)(const char *name, void *data, int len);
 };
 
 #endif /* __MAX17042_BATTERY_H_ */