Merge tag 'for-v3.4-rc1' of git://git.infradead.org/battery-2.6
authorLinus Torvalds <torvalds@linux-foundation.org>
Fri, 30 Mar 2012 23:09:02 +0000 (16:09 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 30 Mar 2012 23:09:02 +0000 (16:09 -0700)
Pull battery updates from Anton Vorontsov:
 "Various small bugfixes and enhancements, plus two new drivers:
   - A quite complex ab8500 charger driver, submitted by Arun Murthy @
     ST-Ericsson;
   - Summit Microelectronics SMB347 Battery Charger, submitted by Bruce
     E Robertson and Alan Cox @ Intel.

  And that's all."

* tag 'for-v3.4-rc1' of git://git.infradead.org/battery-2.6: (36 commits)
  max17042_battery: Clean up interrupt handling
  Revert "max8998_charger: Include linux/module.h just once"
  ab8500_fg: Fix some build warnings on x86_64
  max17042_battery: Fix CHARGE_FULL representation.
  max8998_charger: Include linux/module.h just once
  power_supply: Convert i2c drivers to module_i2c_driver
  lp8727_charger: Add MODULE_DEVICE_TABLE
  charger-manager: Simplify charger_get_property(), get rid of a warning
  charger-manager: Clean up for better readability
  da9052-battery: Convert to use module_platform_driver
  da9052-battery: Fix a memory leak when unload the module
  da9052-battery: Add missing platform_set_drvdata
  ab8500: Turn unneeded global symbols into local ones
  ab8500_fg: Fix copy-paste error
  ab8500_fg: Get rid of 'struct battery_type'
  ab8500_fg: Get rid of 'struct v_to_cap'
  ab8500_btemp: Get rid of 'enum adc_therm'
  ab8500_charger: Convert to the new USB OTG calls
  ab8500-btemp: AB8500 battery temperature driver
  ab8500-fg: A8500 fuel gauge driver
  ...

23 files changed:
Documentation/devicetree/bindings/power_supply/max17042_battery.txt [new file with mode: 0644]
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/ab8500_btemp.c [new file with mode: 0644]
drivers/power/ab8500_charger.c [new file with mode: 0644]
drivers/power/ab8500_fg.c [new file with mode: 0644]
drivers/power/abx500_chargalg.c [new file with mode: 0644]
drivers/power/charger-manager.c
drivers/power/da9052-battery.c
drivers/power/ds2782_battery.c
drivers/power/isp1704_charger.c
drivers/power/lp8727_charger.c
drivers/power/max17040_battery.c
drivers/power/max17042_battery.c
drivers/power/sbs-battery.c
drivers/power/smb347-charger.c [new file with mode: 0644]
drivers/power/z2_battery.c
include/linux/lp8727.h
include/linux/mfd/abx500.h
include/linux/mfd/abx500/ab8500-bm.h [new file with mode: 0644]
include/linux/mfd/abx500/ux500_chargalg.h [new file with mode: 0644]
include/linux/power/max17042_battery.h
include/linux/power/smb347-charger.h [new file with mode: 0644]

diff --git a/Documentation/devicetree/bindings/power_supply/max17042_battery.txt b/Documentation/devicetree/bindings/power_supply/max17042_battery.txt
new file mode 100644 (file)
index 0000000..5bc9b68
--- /dev/null
@@ -0,0 +1,18 @@
+max17042_battery
+~~~~~~~~~~~~~~~~
+
+Required properties :
+ - compatible : "maxim,max17042"
+
+Optional properties :
+ - maxim,rsns-microohm : Resistance of rsns resistor in micro Ohms
+                         (datasheet-recommended value is 10000).
+   Defining this property enables current-sense functionality.
+
+Example:
+
+       battery-charger@36 {
+               compatible = "maxim,max17042";
+               reg = <0x36>;
+               maxim,rsns-microohm = <10000>;
+       };
index 459f66437fe93159bb696efad3cb3e093eea6dfa..99dc29f2f2f2ba84b16430a51548817b094b9c3a 100644 (file)
@@ -249,7 +249,7 @@ config CHARGER_TWL4030
          Say Y here to enable support for TWL4030 Battery Charge Interface.
 
 config CHARGER_LP8727
-       tristate "National Semiconductor LP8727 charger driver"
+       tristate "TI/National Semiconductor LP8727 charger driver"
        depends on I2C
        help
          Say Y here to enable support for LP8727 Charger Driver.
@@ -288,4 +288,23 @@ config CHARGER_MAX8998
          Say Y to enable support for the battery charger control sysfs and
          platform data of MAX8998/LP3974 PMICs.
 
+config CHARGER_SMB347
+       tristate "Summit Microelectronics SMB347 Battery Charger"
+       depends on I2C
+       help
+         Say Y to include support for Summit Microelectronics SMB347
+         Battery Charger.
+
+config AB8500_BM
+       bool "AB8500 Battery Management Driver"
+       depends on AB8500_CORE && AB8500_GPADC
+       help
+         Say Y to include support for AB5500 battery management.
+
+config AB8500_BATTERY_THERM_ON_BATCTRL
+       bool "Thermistor connected on BATCTRL ADC"
+       depends on AB8500_BM
+       help
+         Say Y to enable battery temperature measurements using
+         thermistor connected on BATCTRL ADC.
 endif # POWER_SUPPLY
index c590fa5334066b398213e2e1e0850a4eb27335cb..b6b243416c0ee1947a47efd85b205341dc0f1463 100644 (file)
@@ -34,6 +34,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_AB8500_BM)                += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o
 obj-$(CONFIG_CHARGER_ISP1704)  += isp1704_charger.o
 obj-$(CONFIG_CHARGER_MAX8903)  += max8903_charger.o
 obj-$(CONFIG_CHARGER_TWL4030)  += twl4030_charger.o
@@ -42,3 +43,4 @@ obj-$(CONFIG_CHARGER_GPIO)    += gpio-charger.o
 obj-$(CONFIG_CHARGER_MANAGER)  += charger-manager.o
 obj-$(CONFIG_CHARGER_MAX8997)  += max8997_charger.o
 obj-$(CONFIG_CHARGER_MAX8998)  += max8998_charger.o
+obj-$(CONFIG_CHARGER_SMB347)   += smb347-charger.o
diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c
new file mode 100644 (file)
index 0000000..d8bb993
--- /dev/null
@@ -0,0 +1,1124 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Battery temperature driver for AB8500
+ *
+ * License Terms: GNU General Public License v2
+ * Author:
+ *     Johan Palsson <johan.palsson@stericsson.com>
+ *     Karl Komierowski <karl.komierowski@stericsson.com>
+ *     Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/jiffies.h>
+
+#define VTVOUT_V                       1800
+
+#define BTEMP_THERMAL_LOW_LIMIT                -10
+#define BTEMP_THERMAL_MED_LIMIT                0
+#define BTEMP_THERMAL_HIGH_LIMIT_52    52
+#define BTEMP_THERMAL_HIGH_LIMIT_57    57
+#define BTEMP_THERMAL_HIGH_LIMIT_62    62
+
+#define BTEMP_BATCTRL_CURR_SRC_7UA     7
+#define BTEMP_BATCTRL_CURR_SRC_20UA    20
+
+#define to_ab8500_btemp_device_info(x) container_of((x), \
+       struct ab8500_btemp, btemp_psy);
+
+/**
+ * struct ab8500_btemp_interrupts - ab8500 interrupts
+ * @name:      name of the interrupt
+ * @isr                function pointer to the isr
+ */
+struct ab8500_btemp_interrupts {
+       char *name;
+       irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct ab8500_btemp_events {
+       bool batt_rem;
+       bool btemp_high;
+       bool btemp_medhigh;
+       bool btemp_lowmed;
+       bool btemp_low;
+       bool ac_conn;
+       bool usb_conn;
+};
+
+struct ab8500_btemp_ranges {
+       int btemp_high_limit;
+       int btemp_med_limit;
+       int btemp_low_limit;
+};
+
+/**
+ * struct ab8500_btemp - ab8500 BTEMP device information
+ * @dev:               Pointer to the structure device
+ * @node:              List of AB8500 BTEMPs, hence prepared for reentrance
+ * @curr_source:       What current source we use, in uA
+ * @bat_temp:          Battery temperature in degree Celcius
+ * @prev_bat_temp      Last dispatched battery temperature
+ * @parent:            Pointer to the struct ab8500
+ * @gpadc:             Pointer to the struct gpadc
+ * @fg:                        Pointer to the struct fg
+ * @pdata:             Pointer to the abx500_btemp platform data
+ * @bat:               Pointer to the abx500_bm platform data
+ * @btemp_psy:         Structure for BTEMP specific battery properties
+ * @events:            Structure for information about events triggered
+ * @btemp_ranges:      Battery temperature range structure
+ * @btemp_wq:          Work queue for measuring the temperature periodically
+ * @btemp_periodic_work:       Work for measuring the temperature periodically
+ */
+struct ab8500_btemp {
+       struct device *dev;
+       struct list_head node;
+       int curr_source;
+       int bat_temp;
+       int prev_bat_temp;
+       struct ab8500 *parent;
+       struct ab8500_gpadc *gpadc;
+       struct ab8500_fg *fg;
+       struct abx500_btemp_platform_data *pdata;
+       struct abx500_bm_data *bat;
+       struct power_supply btemp_psy;
+       struct ab8500_btemp_events events;
+       struct ab8500_btemp_ranges btemp_ranges;
+       struct workqueue_struct *btemp_wq;
+       struct delayed_work btemp_periodic_work;
+};
+
+/* BTEMP power supply properties */
+static enum power_supply_property ab8500_btemp_props[] = {
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_TECHNOLOGY,
+       POWER_SUPPLY_PROP_TEMP,
+};
+
+static LIST_HEAD(ab8500_btemp_list);
+
+/**
+ * ab8500_btemp_get() - returns a reference to the primary AB8500 BTEMP
+ * (i.e. the first BTEMP in the instance list)
+ */
+struct ab8500_btemp *ab8500_btemp_get(void)
+{
+       struct ab8500_btemp *btemp;
+       btemp = list_first_entry(&ab8500_btemp_list, struct ab8500_btemp, node);
+
+       return btemp;
+}
+
+/**
+ * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance
+ * @di:                pointer to the ab8500_btemp structure
+ * @v_batctrl: measured batctrl voltage
+ * @inst_curr: measured instant current
+ *
+ * This function returns the battery resistance that is
+ * derived from the BATCTRL voltage.
+ * Returns value in Ohms.
+ */
+static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di,
+       int v_batctrl, int inst_curr)
+{
+       int rbs;
+
+       if (is_ab8500_1p1_or_earlier(di->parent)) {
+               /*
+                * For ABB cut1.0 and 1.1 BAT_CTRL is internally
+                * connected to 1.8V through a 450k resistor
+                */
+               return (450000 * (v_batctrl)) / (1800 - v_batctrl);
+       }
+
+       if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL) {
+               /*
+                * If the battery has internal NTC, we use the current
+                * source to calculate the resistance, 7uA or 20uA
+                */
+               rbs = (v_batctrl * 1000
+                      - di->bat->gnd_lift_resistance * inst_curr)
+                     / di->curr_source;
+       } else {
+               /*
+                * BAT_CTRL is internally
+                * connected to 1.8V through a 80k resistor
+                */
+               rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl);
+       }
+
+       return rbs;
+}
+
+/**
+ * ab8500_btemp_read_batctrl_voltage() - measure batctrl voltage
+ * @di:                pointer to the ab8500_btemp structure
+ *
+ * This function returns the voltage on BATCTRL. Returns value in mV.
+ */
+static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di)
+{
+       int vbtemp;
+       static int prev;
+
+       vbtemp = ab8500_gpadc_convert(di->gpadc, BAT_CTRL);
+       if (vbtemp < 0) {
+               dev_err(di->dev,
+                       "%s gpadc conversion failed, using previous value",
+                       __func__);
+               return prev;
+       }
+       prev = vbtemp;
+       return vbtemp;
+}
+
+/**
+ * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source
+ * @di:                pointer to the ab8500_btemp structure
+ * @enable:    enable or disable the current source
+ *
+ * Enable or disable the current sources for the BatCtrl AD channel
+ */
+static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
+       bool enable)
+{
+       int curr;
+       int ret = 0;
+
+       /*
+        * BATCTRL current sources are included on AB8500 cut2.0
+        * and future versions
+        */
+       if (is_ab8500_1p1_or_earlier(di->parent))
+               return 0;
+
+       /* Only do this for batteries with internal NTC */
+       if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) {
+               if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA)
+                       curr = BAT_CTRL_7U_ENA;
+               else
+                       curr = BAT_CTRL_20U_ENA;
+
+               dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source);
+
+               ret = abx500_mask_and_set_register_interruptible(di->dev,
+                       AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+                       FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH);
+               if (ret) {
+                       dev_err(di->dev, "%s failed setting cmp_force\n",
+                               __func__);
+                       return ret;
+               }
+
+               /*
+                * We have to wait one 32kHz cycle before enabling
+                * the current source, since ForceBatCtrlCmpHigh needs
+                * to be written in a separate cycle
+                */
+               udelay(32);
+
+               ret = abx500_set_register_interruptible(di->dev,
+                       AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+                       FORCE_BAT_CTRL_CMP_HIGH | curr);
+               if (ret) {
+                       dev_err(di->dev, "%s failed enabling current source\n",
+                               __func__);
+                       goto disable_curr_source;
+               }
+       } else if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) {
+               dev_dbg(di->dev, "Disable BATCTRL curr source\n");
+
+               /* Write 0 to the curr bits */
+               ret = abx500_mask_and_set_register_interruptible(di->dev,
+                       AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+                       BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
+                       ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+               if (ret) {
+                       dev_err(di->dev, "%s failed disabling current source\n",
+                               __func__);
+                       goto disable_curr_source;
+               }
+
+               /* Enable Pull-Up and comparator */
+               ret = abx500_mask_and_set_register_interruptible(di->dev,
+                       AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+                       BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA,
+                       BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA);
+               if (ret) {
+                       dev_err(di->dev, "%s failed enabling PU and comp\n",
+                               __func__);
+                       goto enable_pu_comp;
+               }
+
+               /*
+                * We have to wait one 32kHz cycle before disabling
+                * ForceBatCtrlCmpHigh since this needs to be written
+                * in a separate cycle
+                */
+               udelay(32);
+
+               /* Disable 'force comparator' */
+               ret = abx500_mask_and_set_register_interruptible(di->dev,
+                       AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+                       FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH);
+               if (ret) {
+                       dev_err(di->dev, "%s failed disabling force comp\n",
+                               __func__);
+                       goto disable_force_comp;
+               }
+       }
+       return ret;
+
+       /*
+        * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time
+        * if we got an error above
+        */
+disable_curr_source:
+       /* Write 0 to the curr bits */
+       ret = abx500_mask_and_set_register_interruptible(di->dev,
+                       AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+                       BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
+                       ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+       if (ret) {
+               dev_err(di->dev, "%s failed disabling current source\n",
+                       __func__);
+               return ret;
+       }
+enable_pu_comp:
+       /* Enable Pull-Up and comparator */
+       ret = abx500_mask_and_set_register_interruptible(di->dev,
+               AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+               BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA,
+               BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA);
+       if (ret) {
+               dev_err(di->dev, "%s failed enabling PU and comp\n",
+                       __func__);
+               return ret;
+       }
+
+disable_force_comp:
+       /*
+        * We have to wait one 32kHz cycle before disabling
+        * ForceBatCtrlCmpHigh since this needs to be written
+        * in a separate cycle
+        */
+       udelay(32);
+
+       /* Disable 'force comparator' */
+       ret = abx500_mask_and_set_register_interruptible(di->dev,
+               AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+               FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH);
+       if (ret) {
+               dev_err(di->dev, "%s failed disabling force comp\n",
+                       __func__);
+               return ret;
+       }
+
+       return ret;
+}
+
+/**
+ * ab8500_btemp_get_batctrl_res() - get battery resistance
+ * @di:                pointer to the ab8500_btemp structure
+ *
+ * This function returns the battery pack identification resistance.
+ * Returns value in Ohms.
+ */
+static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di)
+{
+       int ret;
+       int batctrl = 0;
+       int res;
+       int inst_curr;
+       int i;
+
+       /*
+        * BATCTRL current sources are included on AB8500 cut2.0
+        * and future versions
+        */
+       ret = ab8500_btemp_curr_source_enable(di, true);
+       if (ret) {
+               dev_err(di->dev, "%s curr source enabled failed\n", __func__);
+               return ret;
+       }
+
+       if (!di->fg)
+               di->fg = ab8500_fg_get();
+       if (!di->fg) {
+               dev_err(di->dev, "No fg found\n");
+               return -EINVAL;
+       }
+
+       ret = ab8500_fg_inst_curr_start(di->fg);
+
+       if (ret) {
+               dev_err(di->dev, "Failed to start current measurement\n");
+               return ret;
+       }
+
+       /*
+        * Since there is no interrupt when current measurement is done,
+        * loop for over 250ms (250ms is one sample conversion time
+        * with 32.768 Khz RTC clock). Note that a stop time must be set
+        * since the ab8500_btemp_read_batctrl_voltage call can block and
+        * take an unknown amount of time to complete.
+        */
+       i = 0;
+
+       do {
+               batctrl += ab8500_btemp_read_batctrl_voltage(di);
+               i++;
+               msleep(20);
+       } while (!ab8500_fg_inst_curr_done(di->fg));
+       batctrl /= i;
+
+       ret = ab8500_fg_inst_curr_finalize(di->fg, &inst_curr);
+       if (ret) {
+               dev_err(di->dev, "Failed to finalize current measurement\n");
+               return ret;
+       }
+
+       res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr);
+
+       ret = ab8500_btemp_curr_source_enable(di, false);
+       if (ret) {
+               dev_err(di->dev, "%s curr source disable failed\n", __func__);
+               return ret;
+       }
+
+       dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d samples: %d\n",
+               __func__, batctrl, res, inst_curr, i);
+
+       return res;
+}
+
+/**
+ * ab8500_btemp_res_to_temp() - resistance to temperature
+ * @di:                pointer to the ab8500_btemp structure
+ * @tbl:       pointer to the resiatance to temperature table
+ * @tbl_size:  size of the resistance to temperature table
+ * @res:       resistance to calculate the temperature from
+ *
+ * This function returns the battery temperature in degrees Celcius
+ * based on the NTC resistance.
+ */
+static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di,
+       const struct abx500_res_to_temp *tbl, int tbl_size, int res)
+{
+       int i, temp;
+       /*
+        * Calculate the formula for the straight line
+        * Simple interpolation if we are within
+        * the resistance table limits, extrapolate
+        * if resistance is outside the limits.
+        */
+       if (res > tbl[0].resist)
+               i = 0;
+       else if (res <= tbl[tbl_size - 1].resist)
+               i = tbl_size - 2;
+       else {
+               i = 0;
+               while (!(res <= tbl[i].resist &&
+                       res > tbl[i + 1].resist))
+                       i++;
+       }
+
+       temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) *
+               (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);
+       return temp;
+}
+
+/**
+ * ab8500_btemp_measure_temp() - measure battery temperature
+ * @di:                pointer to the ab8500_btemp structure
+ *
+ * Returns battery temperature (on success) else the previous temperature
+ */
+static int ab8500_btemp_measure_temp(struct ab8500_btemp *di)
+{
+       int temp;
+       static int prev;
+       int rbat, rntc, vntc;
+       u8 id;
+
+       id = di->bat->batt_id;
+
+       if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL &&
+                       id != BATTERY_UNKNOWN) {
+
+               rbat = ab8500_btemp_get_batctrl_res(di);
+               if (rbat < 0) {
+                       dev_err(di->dev, "%s get batctrl res failed\n",
+                               __func__);
+                       /*
+                        * Return out-of-range temperature so that
+                        * charging is stopped
+                        */
+                       return BTEMP_THERMAL_LOW_LIMIT;
+               }
+
+               temp = ab8500_btemp_res_to_temp(di,
+                       di->bat->bat_type[id].r_to_t_tbl,
+                       di->bat->bat_type[id].n_temp_tbl_elements, rbat);
+       } else {
+               vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL);
+               if (vntc < 0) {
+                       dev_err(di->dev,
+                               "%s gpadc conversion failed,"
+                               " using previous value\n", __func__);
+                       return prev;
+               }
+               /*
+                * The PCB NTC is sourced from VTVOUT via a 230kOhm
+                * resistor.
+                */
+               rntc = 230000 * vntc / (VTVOUT_V - vntc);
+
+               temp = ab8500_btemp_res_to_temp(di,
+                       di->bat->bat_type[id].r_to_t_tbl,
+                       di->bat->bat_type[id].n_temp_tbl_elements, rntc);
+               prev = temp;
+       }
+       dev_dbg(di->dev, "Battery temperature is %d\n", temp);
+       return temp;
+}
+
+/**
+ * ab8500_btemp_id() - Identify the connected battery
+ * @di:                pointer to the ab8500_btemp structure
+ *
+ * This function will try to identify the battery by reading the ID
+ * resistor. Some brands use a combined ID resistor with a NTC resistor to
+ * both be able to identify and to read the temperature of it.
+ */
+static int ab8500_btemp_id(struct ab8500_btemp *di)
+{
+       int res;
+       u8 i;
+
+       di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
+       di->bat->batt_id = BATTERY_UNKNOWN;
+
+       res =  ab8500_btemp_get_batctrl_res(di);
+       if (res < 0) {
+               dev_err(di->dev, "%s get batctrl res failed\n", __func__);
+               return -ENXIO;
+       }
+
+       /* BATTERY_UNKNOWN is defined on position 0, skip it! */
+       for (i = BATTERY_UNKNOWN + 1; i < di->bat->n_btypes; i++) {
+               if ((res <= di->bat->bat_type[i].resis_high) &&
+                       (res >= di->bat->bat_type[i].resis_low)) {
+                       dev_dbg(di->dev, "Battery detected on %s"
+                               " low %d < res %d < high: %d"
+                               " index: %d\n",
+                               di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL ?
+                               "BATCTRL" : "BATTEMP",
+                               di->bat->bat_type[i].resis_low, res,
+                               di->bat->bat_type[i].resis_high, i);
+
+                       di->bat->batt_id = i;
+                       break;
+               }
+       }
+
+       if (di->bat->batt_id == BATTERY_UNKNOWN) {
+               dev_warn(di->dev, "Battery identified as unknown"
+                       ", resistance %d Ohm\n", res);
+               return -ENXIO;
+       }
+
+       /*
+        * We only have to change current source if the
+        * detected type is Type 1, else we use the 7uA source
+        */
+       if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL &&
+                       di->bat->batt_id == 1) {
+               dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
+               di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA;
+       }
+
+       return di->bat->batt_id;
+}
+
+/**
+ * ab8500_btemp_periodic_work() - Measuring the temperature periodically
+ * @work:      pointer to the work_struct structure
+ *
+ * Work function for measuring the temperature periodically
+ */
+static void ab8500_btemp_periodic_work(struct work_struct *work)
+{
+       int interval;
+       struct ab8500_btemp *di = container_of(work,
+               struct ab8500_btemp, btemp_periodic_work.work);
+
+       di->bat_temp = ab8500_btemp_measure_temp(di);
+
+       if (di->bat_temp != di->prev_bat_temp) {
+               di->prev_bat_temp = di->bat_temp;
+               power_supply_changed(&di->btemp_psy);
+       }
+
+       if (di->events.ac_conn || di->events.usb_conn)
+               interval = di->bat->temp_interval_chg;
+       else
+               interval = di->bat->temp_interval_nochg;
+
+       /* Schedule a new measurement */
+       queue_delayed_work(di->btemp_wq,
+               &di->btemp_periodic_work,
+               round_jiffies(interval * HZ));
+}
+
+/**
+ * ab8500_btemp_batctrlindb_handler() - battery removal detected
+ * @irq:       interrupt number
+ * @_di:       void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_batctrlindb_handler(int irq, void *_di)
+{
+       struct ab8500_btemp *di = _di;
+       dev_err(di->dev, "Battery removal detected!\n");
+
+       di->events.batt_rem = true;
+       power_supply_changed(&di->btemp_psy);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_templow_handler() - battery temp lower than 10 degrees
+ * @irq:       interrupt number
+ * @_di:       void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di)
+{
+       struct ab8500_btemp *di = _di;
+
+       if (is_ab8500_2p0_or_earlier(di->parent)) {
+               dev_dbg(di->dev, "Ignore false btemp low irq"
+                       " for ABB cut 1.0, 1.1 and 2.0\n");
+       } else {
+               dev_crit(di->dev, "Battery temperature lower than -10deg c\n");
+
+               di->events.btemp_low = true;
+               di->events.btemp_high = false;
+               di->events.btemp_medhigh = false;
+               di->events.btemp_lowmed = false;
+               power_supply_changed(&di->btemp_psy);
+       }
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_temphigh_handler() - battery temp higher than max temp
+ * @irq:       interrupt number
+ * @_di:       void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_temphigh_handler(int irq, void *_di)
+{
+       struct ab8500_btemp *di = _di;
+
+       dev_crit(di->dev, "Battery temperature is higher than MAX temp\n");
+
+       di->events.btemp_high = true;
+       di->events.btemp_medhigh = false;
+       di->events.btemp_lowmed = false;
+       di->events.btemp_low = false;
+       power_supply_changed(&di->btemp_psy);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_lowmed_handler() - battery temp between low and medium
+ * @irq:       interrupt number
+ * @_di:       void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_lowmed_handler(int irq, void *_di)
+{
+       struct ab8500_btemp *di = _di;
+
+       dev_dbg(di->dev, "Battery temperature is between low and medium\n");
+
+       di->events.btemp_lowmed = true;
+       di->events.btemp_medhigh = false;
+       di->events.btemp_high = false;
+       di->events.btemp_low = false;
+       power_supply_changed(&di->btemp_psy);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_medhigh_handler() - battery temp between medium and high
+ * @irq:       interrupt number
+ * @_di:       void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_medhigh_handler(int irq, void *_di)
+{
+       struct ab8500_btemp *di = _di;
+
+       dev_dbg(di->dev, "Battery temperature is between medium and high\n");
+
+       di->events.btemp_medhigh = true;
+       di->events.btemp_lowmed = false;
+       di->events.btemp_high = false;
+       di->events.btemp_low = false;
+       power_supply_changed(&di->btemp_psy);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_periodic() - Periodic temperature measurements
+ * @di:                pointer to the ab8500_btemp structure
+ * @enable:    enable or disable periodic temperature measurements
+ *
+ * Starts of stops periodic temperature measurements. Periodic measurements
+ * should only be done when a charger is connected.
+ */
+static void ab8500_btemp_periodic(struct ab8500_btemp *di,
+       bool enable)
+{
+       dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n",
+               enable);
+       /*
+        * Make sure a new measurement is done directly by cancelling
+        * any pending work
+        */
+       cancel_delayed_work_sync(&di->btemp_periodic_work);
+
+       if (enable)
+               queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0);
+}
+
+/**
+ * ab8500_btemp_get_temp() - get battery temperature
+ * @di:                pointer to the ab8500_btemp structure
+ *
+ * Returns battery temperature
+ */
+static int ab8500_btemp_get_temp(struct ab8500_btemp *di)
+{
+       int temp = 0;
+
+       /*
+        * The BTEMP events are not reliabe on AB8500 cut2.0
+        * and prior versions
+        */
+       if (is_ab8500_2p0_or_earlier(di->parent)) {
+               temp = di->bat_temp * 10;
+       } else {
+               if (di->events.btemp_low) {
+                       if (temp > di->btemp_ranges.btemp_low_limit)
+                               temp = di->btemp_ranges.btemp_low_limit;
+                       else
+                               temp = di->bat_temp * 10;
+               } else if (di->events.btemp_high) {
+                       if (temp < di->btemp_ranges.btemp_high_limit)
+                               temp = di->btemp_ranges.btemp_high_limit;
+                       else
+                               temp = di->bat_temp * 10;
+               } else if (di->events.btemp_lowmed) {
+                       if (temp > di->btemp_ranges.btemp_med_limit)
+                               temp = di->btemp_ranges.btemp_med_limit;
+                       else
+                               temp = di->bat_temp * 10;
+               } else if (di->events.btemp_medhigh) {
+                       if (temp < di->btemp_ranges.btemp_med_limit)
+                               temp = di->btemp_ranges.btemp_med_limit;
+                       else
+                               temp = di->bat_temp * 10;
+               } else
+                       temp = di->bat_temp * 10;
+       }
+       return temp;
+}
+
+/**
+ * ab8500_btemp_get_batctrl_temp() - get the temperature
+ * @btemp:      pointer to the btemp structure
+ *
+ * Returns the batctrl temperature in millidegrees
+ */
+int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp)
+{
+       return btemp->bat_temp * 1000;
+}
+
+/**
+ * ab8500_btemp_get_property() - get the btemp properties
+ * @psy:        pointer to the power_supply structure
+ * @psp:        pointer to the power_supply_property structure
+ * @val:        pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the btemp
+ * properties by reading the sysfs files.
+ * online:     presence of the battery
+ * present:    presence of the battery
+ * technology: battery technology
+ * temp:       battery temperature
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_btemp_get_property(struct power_supply *psy,
+       enum power_supply_property psp,
+       union power_supply_propval *val)
+{
+       struct ab8500_btemp *di;
+
+       di = to_ab8500_btemp_device_info(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_PRESENT:
+       case POWER_SUPPLY_PROP_ONLINE:
+               if (di->events.batt_rem)
+                       val->intval = 0;
+               else
+                       val->intval = 1;
+               break;
+       case POWER_SUPPLY_PROP_TECHNOLOGY:
+               val->intval = di->bat->bat_type[di->bat->batt_id].name;
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+               val->intval = ab8500_btemp_get_temp(di);
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data)
+{
+       struct power_supply *psy;
+       struct power_supply *ext;
+       struct ab8500_btemp *di;
+       union power_supply_propval ret;
+       int i, j;
+       bool psy_found = false;
+
+       psy = (struct power_supply *)data;
+       ext = dev_get_drvdata(dev);
+       di = to_ab8500_btemp_device_info(psy);
+
+       /*
+        * For all psy where the name of your driver
+        * appears in any supplied_to
+        */
+       for (i = 0; i < ext->num_supplicants; i++) {
+               if (!strcmp(ext->supplied_to[i], psy->name))
+                       psy_found = true;
+       }
+
+       if (!psy_found)
+               return 0;
+
+       /* Go through all properties for the psy */
+       for (j = 0; j < ext->num_properties; j++) {
+               enum power_supply_property prop;
+               prop = ext->properties[j];
+
+               if (ext->get_property(ext, prop, &ret))
+                       continue;
+
+               switch (prop) {
+               case POWER_SUPPLY_PROP_PRESENT:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_MAINS:
+                               /* AC disconnected */
+                               if (!ret.intval && di->events.ac_conn) {
+                                       di->events.ac_conn = false;
+                               }
+                               /* AC connected */
+                               else if (ret.intval && !di->events.ac_conn) {
+                                       di->events.ac_conn = true;
+                                       if (!di->events.usb_conn)
+                                               ab8500_btemp_periodic(di, true);
+                               }
+                               break;
+                       case POWER_SUPPLY_TYPE_USB:
+                               /* USB disconnected */
+                               if (!ret.intval && di->events.usb_conn) {
+                                       di->events.usb_conn = false;
+                               }
+                               /* USB connected */
+                               else if (ret.intval && !di->events.usb_conn) {
+                                       di->events.usb_conn = true;
+                                       if (!di->events.ac_conn)
+                                               ab8500_btemp_periodic(di, true);
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+       return 0;
+}
+
+/**
+ * ab8500_btemp_external_power_changed() - callback for power supply changes
+ * @psy:       pointer to the structure power_supply
+ *
+ * This function is pointing to the function pointer external_power_changed
+ * of the structure power_supply.
+ * This function gets executed when there is a change in the external power
+ * supply to the btemp.
+ */
+static void ab8500_btemp_external_power_changed(struct power_supply *psy)
+{
+       struct ab8500_btemp *di = to_ab8500_btemp_device_info(psy);
+
+       class_for_each_device(power_supply_class, NULL,
+               &di->btemp_psy, ab8500_btemp_get_ext_psy_data);
+}
+
+/* ab8500 btemp driver interrupts and their respective isr */
+static struct ab8500_btemp_interrupts ab8500_btemp_irq[] = {
+       {"BAT_CTRL_INDB", ab8500_btemp_batctrlindb_handler},
+       {"BTEMP_LOW", ab8500_btemp_templow_handler},
+       {"BTEMP_HIGH", ab8500_btemp_temphigh_handler},
+       {"BTEMP_LOW_MEDIUM", ab8500_btemp_lowmed_handler},
+       {"BTEMP_MEDIUM_HIGH", ab8500_btemp_medhigh_handler},
+};
+
+#if defined(CONFIG_PM)
+static int ab8500_btemp_resume(struct platform_device *pdev)
+{
+       struct ab8500_btemp *di = platform_get_drvdata(pdev);
+
+       ab8500_btemp_periodic(di, true);
+
+       return 0;
+}
+
+static int ab8500_btemp_suspend(struct platform_device *pdev,
+       pm_message_t state)
+{
+       struct ab8500_btemp *di = platform_get_drvdata(pdev);
+
+       ab8500_btemp_periodic(di, false);
+
+       return 0;
+}
+#else
+#define ab8500_btemp_suspend      NULL
+#define ab8500_btemp_resume       NULL
+#endif
+
+static int __devexit ab8500_btemp_remove(struct platform_device *pdev)
+{
+       struct ab8500_btemp *di = platform_get_drvdata(pdev);
+       int i, irq;
+
+       /* Disable interrupts */
+       for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) {
+               irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
+               free_irq(irq, di);
+       }
+
+       /* Delete the work queue */
+       destroy_workqueue(di->btemp_wq);
+
+       flush_scheduled_work();
+       power_supply_unregister(&di->btemp_psy);
+       platform_set_drvdata(pdev, NULL);
+       kfree(di);
+
+       return 0;
+}
+
+static int __devinit ab8500_btemp_probe(struct platform_device *pdev)
+{
+       int irq, i, ret = 0;
+       u8 val;
+       struct abx500_bm_plat_data *plat_data;
+
+       struct ab8500_btemp *di =
+               kzalloc(sizeof(struct ab8500_btemp), GFP_KERNEL);
+       if (!di)
+               return -ENOMEM;
+
+       /* get parent data */
+       di->dev = &pdev->dev;
+       di->parent = dev_get_drvdata(pdev->dev.parent);
+       di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+       /* get btemp specific platform data */
+       plat_data = pdev->dev.platform_data;
+       di->pdata = plat_data->btemp;
+       if (!di->pdata) {
+               dev_err(di->dev, "no btemp platform data supplied\n");
+               ret = -EINVAL;
+               goto free_device_info;
+       }
+
+       /* get battery specific platform data */
+       di->bat = plat_data->battery;
+       if (!di->bat) {
+               dev_err(di->dev, "no battery platform data supplied\n");
+               ret = -EINVAL;
+               goto free_device_info;
+       }
+
+       /* BTEMP supply */
+       di->btemp_psy.name = "ab8500_btemp";
+       di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY;
+       di->btemp_psy.properties = ab8500_btemp_props;
+       di->btemp_psy.num_properties = ARRAY_SIZE(ab8500_btemp_props);
+       di->btemp_psy.get_property = ab8500_btemp_get_property;
+       di->btemp_psy.supplied_to = di->pdata->supplied_to;
+       di->btemp_psy.num_supplicants = di->pdata->num_supplicants;
+       di->btemp_psy.external_power_changed =
+               ab8500_btemp_external_power_changed;
+
+
+       /* Create a work queue for the btemp */
+       di->btemp_wq =
+               create_singlethread_workqueue("ab8500_btemp_wq");
+       if (di->btemp_wq == NULL) {
+               dev_err(di->dev, "failed to create work queue\n");
+               goto free_device_info;
+       }
+
+       /* Init work for measuring temperature periodically */
+       INIT_DELAYED_WORK_DEFERRABLE(&di->btemp_periodic_work,
+               ab8500_btemp_periodic_work);
+
+       /* Identify the battery */
+       if (ab8500_btemp_id(di) < 0)
+               dev_warn(di->dev, "failed to identify the battery\n");
+
+       /* Set BTEMP thermal limits. Low and Med are fixed */
+       di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT;
+       di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT;
+
+       ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+               AB8500_BTEMP_HIGH_TH, &val);
+       if (ret < 0) {
+               dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+               goto free_btemp_wq;
+       }
+       switch (val) {
+       case BTEMP_HIGH_TH_57_0:
+       case BTEMP_HIGH_TH_57_1:
+               di->btemp_ranges.btemp_high_limit =
+                       BTEMP_THERMAL_HIGH_LIMIT_57;
+               break;
+       case BTEMP_HIGH_TH_52:
+               di->btemp_ranges.btemp_high_limit =
+                       BTEMP_THERMAL_HIGH_LIMIT_52;
+               break;
+       case BTEMP_HIGH_TH_62:
+               di->btemp_ranges.btemp_high_limit =
+                       BTEMP_THERMAL_HIGH_LIMIT_62;
+               break;
+       }
+
+       /* Register BTEMP power supply class */
+       ret = power_supply_register(di->dev, &di->btemp_psy);
+       if (ret) {
+               dev_err(di->dev, "failed to register BTEMP psy\n");
+               goto free_btemp_wq;
+       }
+
+       /* Register interrupts */
+       for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) {
+               irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
+               ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr,
+                       IRQF_SHARED | IRQF_NO_SUSPEND,
+                       ab8500_btemp_irq[i].name, di);
+
+               if (ret) {
+                       dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+                               , ab8500_btemp_irq[i].name, irq, ret);
+                       goto free_irq;
+               }
+               dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+                       ab8500_btemp_irq[i].name, irq, ret);
+       }
+
+       platform_set_drvdata(pdev, di);
+
+       /* Kick off periodic temperature measurements */
+       ab8500_btemp_periodic(di, true);
+       list_add_tail(&di->node, &ab8500_btemp_list);
+
+       return ret;
+
+free_irq:
+       power_supply_unregister(&di->btemp_psy);
+
+       /* We also have to free all successfully registered irqs */
+       for (i = i - 1; i >= 0; i--) {
+               irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
+               free_irq(irq, di);
+       }
+free_btemp_wq:
+       destroy_workqueue(di->btemp_wq);
+free_device_info:
+       kfree(di);
+
+       return ret;
+}
+
+static struct platform_driver ab8500_btemp_driver = {
+       .probe = ab8500_btemp_probe,
+       .remove = __devexit_p(ab8500_btemp_remove),
+       .suspend = ab8500_btemp_suspend,
+       .resume = ab8500_btemp_resume,
+       .driver = {
+               .name = "ab8500-btemp",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init ab8500_btemp_init(void)
+{
+       return platform_driver_register(&ab8500_btemp_driver);
+}
+
+static void __exit ab8500_btemp_exit(void)
+{
+       platform_driver_unregister(&ab8500_btemp_driver);
+}
+
+subsys_initcall_sync(ab8500_btemp_init);
+module_exit(ab8500_btemp_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
+MODULE_ALIAS("platform:ab8500-btemp");
+MODULE_DESCRIPTION("AB8500 battery temperature driver");
diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c
new file mode 100644 (file)
index 0000000..e2b4acc
--- /dev/null
@@ -0,0 +1,2789 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Charger driver for AB8500
+ *
+ * License Terms: GNU General Public License v2
+ * Author:
+ *     Johan Palsson <johan.palsson@stericsson.com>
+ *     Karl Komierowski <karl.komierowski@stericsson.com>
+ *     Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+#include <linux/kobject.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/usb/otg.h>
+
+/* Charger constants */
+#define NO_PW_CONN                     0
+#define AC_PW_CONN                     1
+#define USB_PW_CONN                    2
+
+#define MAIN_WDOG_ENA                  0x01
+#define MAIN_WDOG_KICK                 0x02
+#define MAIN_WDOG_DIS                  0x00
+#define CHARG_WD_KICK                  0x01
+#define MAIN_CH_ENA                    0x01
+#define MAIN_CH_NO_OVERSHOOT_ENA_N     0x02
+#define USB_CH_ENA                     0x01
+#define USB_CHG_NO_OVERSHOOT_ENA_N     0x02
+#define MAIN_CH_DET                    0x01
+#define MAIN_CH_CV_ON                  0x04
+#define USB_CH_CV_ON                   0x08
+#define VBUS_DET_DBNC100               0x02
+#define VBUS_DET_DBNC1                 0x01
+#define OTP_ENABLE_WD                  0x01
+
+#define MAIN_CH_INPUT_CURR_SHIFT       4
+#define VBUS_IN_CURR_LIM_SHIFT         4
+
+#define LED_INDICATOR_PWM_ENA          0x01
+#define LED_INDICATOR_PWM_DIS          0x00
+#define LED_IND_CUR_5MA                        0x04
+#define LED_INDICATOR_PWM_DUTY_252_256 0xBF
+
+/* HW failure constants */
+#define MAIN_CH_TH_PROT                        0x02
+#define VBUS_CH_NOK                    0x08
+#define USB_CH_TH_PROT                 0x02
+#define VBUS_OVV_TH                    0x01
+#define MAIN_CH_NOK                    0x01
+#define VBUS_DET                       0x80
+
+/* UsbLineStatus register bit masks */
+#define AB8500_USB_LINK_STATUS         0x78
+#define AB8500_STD_HOST_SUSP           0x18
+
+/* Watchdog timeout constant */
+#define WD_TIMER                       0x30 /* 4min */
+#define WD_KICK_INTERVAL               (60 * HZ)
+
+/* Lowest charger voltage is 3.39V -> 0x4E */
+#define LOW_VOLT_REG                   0x4E
+
+/* UsbLineStatus register - usb types */
+enum ab8500_charger_link_status {
+       USB_STAT_NOT_CONFIGURED,
+       USB_STAT_STD_HOST_NC,
+       USB_STAT_STD_HOST_C_NS,
+       USB_STAT_STD_HOST_C_S,
+       USB_STAT_HOST_CHG_NM,
+       USB_STAT_HOST_CHG_HS,
+       USB_STAT_HOST_CHG_HS_CHIRP,
+       USB_STAT_DEDICATED_CHG,
+       USB_STAT_ACA_RID_A,
+       USB_STAT_ACA_RID_B,
+       USB_STAT_ACA_RID_C_NM,
+       USB_STAT_ACA_RID_C_HS,
+       USB_STAT_ACA_RID_C_HS_CHIRP,
+       USB_STAT_HM_IDGND,
+       USB_STAT_RESERVED,
+       USB_STAT_NOT_VALID_LINK,
+};
+
+enum ab8500_usb_state {
+       AB8500_BM_USB_STATE_RESET_HS,   /* HighSpeed Reset */
+       AB8500_BM_USB_STATE_RESET_FS,   /* FullSpeed/LowSpeed Reset */
+       AB8500_BM_USB_STATE_CONFIGURED,
+       AB8500_BM_USB_STATE_SUSPEND,
+       AB8500_BM_USB_STATE_RESUME,
+       AB8500_BM_USB_STATE_MAX,
+};
+
+/* VBUS input current limits supported in AB8500 in mA */
+#define USB_CH_IP_CUR_LVL_0P05         50
+#define USB_CH_IP_CUR_LVL_0P09         98
+#define USB_CH_IP_CUR_LVL_0P19         193
+#define USB_CH_IP_CUR_LVL_0P29         290
+#define USB_CH_IP_CUR_LVL_0P38         380
+#define USB_CH_IP_CUR_LVL_0P45         450
+#define USB_CH_IP_CUR_LVL_0P5          500
+#define USB_CH_IP_CUR_LVL_0P6          600
+#define USB_CH_IP_CUR_LVL_0P7          700
+#define USB_CH_IP_CUR_LVL_0P8          800
+#define USB_CH_IP_CUR_LVL_0P9          900
+#define USB_CH_IP_CUR_LVL_1P0          1000
+#define USB_CH_IP_CUR_LVL_1P1          1100
+#define USB_CH_IP_CUR_LVL_1P3          1300
+#define USB_CH_IP_CUR_LVL_1P4          1400
+#define USB_CH_IP_CUR_LVL_1P5          1500
+
+#define VBAT_TRESH_IP_CUR_RED          3800
+
+#define to_ab8500_charger_usb_device_info(x) container_of((x), \
+       struct ab8500_charger, usb_chg)
+#define to_ab8500_charger_ac_device_info(x) container_of((x), \
+       struct ab8500_charger, ac_chg)
+
+/**
+ * struct ab8500_charger_interrupts - ab8500 interupts
+ * @name:      name of the interrupt
+ * @isr                function pointer to the isr
+ */
+struct ab8500_charger_interrupts {
+       char *name;
+       irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct ab8500_charger_info {
+       int charger_connected;
+       int charger_online;
+       int charger_voltage;
+       int cv_active;
+       bool wd_expired;
+};
+
+struct ab8500_charger_event_flags {
+       bool mainextchnotok;
+       bool main_thermal_prot;
+       bool usb_thermal_prot;
+       bool vbus_ovv;
+       bool usbchargernotok;
+       bool chgwdexp;
+       bool vbus_collapse;
+};
+
+struct ab8500_charger_usb_state {
+       bool usb_changed;
+       int usb_current;
+       enum ab8500_usb_state state;
+       spinlock_t usb_lock;
+};
+
+/**
+ * struct ab8500_charger - ab8500 Charger device information
+ * @dev:               Pointer to the structure device
+ * @max_usb_in_curr:   Max USB charger input current
+ * @vbus_detected:     VBUS detected
+ * @vbus_detected_start:
+ *                     VBUS detected during startup
+ * @ac_conn:           This will be true when the AC charger has been plugged
+ * @vddadc_en_ac:      Indicate if VDD ADC supply is enabled because AC
+ *                     charger is enabled
+ * @vddadc_en_usb:     Indicate if VDD ADC supply is enabled because USB
+ *                     charger is enabled
+ * @vbat               Battery voltage
+ * @old_vbat           Previously measured battery voltage
+ * @autopower          Indicate if we should have automatic pwron after pwrloss
+ * @parent:            Pointer to the struct ab8500
+ * @gpadc:             Pointer to the struct gpadc
+ * @pdata:             Pointer to the abx500_charger platform data
+ * @bat:               Pointer to the abx500_bm platform data
+ * @flags:             Structure for information about events triggered
+ * @usb_state:         Structure for usb stack information
+ * @ac_chg:            AC charger power supply
+ * @usb_chg:           USB charger power supply
+ * @ac:                        Structure that holds the AC charger properties
+ * @usb:               Structure that holds the USB charger properties
+ * @regu:              Pointer to the struct regulator
+ * @charger_wq:                Work queue for the IRQs and checking HW state
+ * @check_vbat_work    Work for checking vbat threshold to adjust vbus current
+ * @check_hw_failure_work:     Work for checking HW state
+ * @check_usbchgnotok_work:    Work for checking USB charger not ok status
+ * @kick_wd_work:              Work for kicking the charger watchdog in case
+ *                             of ABB rev 1.* due to the watchog logic bug
+ * @ac_work:                   Work for checking AC charger connection
+ * @detect_usb_type_work:      Work for detecting the USB type connected
+ * @usb_link_status_work:      Work for checking the new USB link status
+ * @usb_state_changed_work:    Work for checking USB state
+ * @check_main_thermal_prot_work:
+ *                             Work for checking Main thermal status
+ * @check_usb_thermal_prot_work:
+ *                             Work for checking USB thermal status
+ */
+struct ab8500_charger {
+       struct device *dev;
+       int max_usb_in_curr;
+       bool vbus_detected;
+       bool vbus_detected_start;
+       bool ac_conn;
+       bool vddadc_en_ac;
+       bool vddadc_en_usb;
+       int vbat;
+       int old_vbat;
+       bool autopower;
+       struct ab8500 *parent;
+       struct ab8500_gpadc *gpadc;
+       struct abx500_charger_platform_data *pdata;
+       struct abx500_bm_data *bat;
+       struct ab8500_charger_event_flags flags;
+       struct ab8500_charger_usb_state usb_state;
+       struct ux500_charger ac_chg;
+       struct ux500_charger usb_chg;
+       struct ab8500_charger_info ac;
+       struct ab8500_charger_info usb;
+       struct regulator *regu;
+       struct workqueue_struct *charger_wq;
+       struct delayed_work check_vbat_work;
+       struct delayed_work check_hw_failure_work;
+       struct delayed_work check_usbchgnotok_work;
+       struct delayed_work kick_wd_work;
+       struct work_struct ac_work;
+       struct work_struct detect_usb_type_work;
+       struct work_struct usb_link_status_work;
+       struct work_struct usb_state_changed_work;
+       struct work_struct check_main_thermal_prot_work;
+       struct work_struct check_usb_thermal_prot_work;
+       struct usb_phy *usb_phy;
+       struct notifier_block nb;
+};
+
+/* AC properties */
+static enum power_supply_property ab8500_charger_ac_props[] = {
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_VOLTAGE_AVG,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+/* USB properties */
+static enum power_supply_property ab8500_charger_usb_props[] = {
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_CURRENT_AVG,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_VOLTAGE_AVG,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+/**
+ * ab8500_power_loss_handling - set how we handle powerloss.
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Magic nummbers are from STE HW department.
+ */
+static void ab8500_power_loss_handling(struct ab8500_charger *di)
+{
+       u8 reg;
+       int ret;
+
+       dev_dbg(di->dev, "Autopower : %d\n", di->autopower);
+
+       /* read the autopower register */
+       ret = abx500_get_register_interruptible(di->dev, 0x15, 0x00, &reg);
+       if (ret) {
+               dev_err(di->dev, "%d write failed\n", __LINE__);
+               return;
+       }
+
+       /* enable the OPT emulation registers */
+       ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2);
+       if (ret) {
+               dev_err(di->dev, "%d write failed\n", __LINE__);
+               return;
+       }
+
+       if (di->autopower)
+               reg |= 0x8;
+       else
+               reg &= ~0x8;
+
+       /* write back the changed value to autopower reg */
+       ret = abx500_set_register_interruptible(di->dev, 0x15, 0x00, reg);
+       if (ret) {
+               dev_err(di->dev, "%d write failed\n", __LINE__);
+               return;
+       }
+
+       /* disable the set OTP registers again */
+       ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0);
+       if (ret) {
+               dev_err(di->dev, "%d write failed\n", __LINE__);
+               return;
+       }
+}
+
+/**
+ * ab8500_power_supply_changed - a wrapper with local extentions for
+ * power_supply_changed
+ * @di:          pointer to the ab8500_charger structure
+ * @psy:  pointer to power_supply_that have changed.
+ *
+ */
+static void ab8500_power_supply_changed(struct ab8500_charger *di,
+                                       struct power_supply *psy)
+{
+       if (di->pdata->autopower_cfg) {
+               if (!di->usb.charger_connected &&
+                   !di->ac.charger_connected &&
+                   di->autopower) {
+                       di->autopower = false;
+                       ab8500_power_loss_handling(di);
+               } else if (!di->autopower &&
+                          (di->ac.charger_connected ||
+                           di->usb.charger_connected)) {
+                       di->autopower = true;
+                       ab8500_power_loss_handling(di);
+               }
+       }
+       power_supply_changed(psy);
+}
+
+static void ab8500_charger_set_usb_connected(struct ab8500_charger *di,
+       bool connected)
+{
+       if (connected != di->usb.charger_connected) {
+               dev_dbg(di->dev, "USB connected:%i\n", connected);
+               di->usb.charger_connected = connected;
+               sysfs_notify(&di->usb_chg.psy.dev->kobj, NULL, "present");
+       }
+}
+
+/**
+ * ab8500_charger_get_ac_voltage() - get ac charger voltage
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Returns ac charger voltage (on success)
+ */
+static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di)
+{
+       int vch;
+
+       /* Only measure voltage if the charger is connected */
+       if (di->ac.charger_connected) {
+               vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V);
+               if (vch < 0)
+                       dev_err(di->dev, "%s gpadc conv failed,\n", __func__);
+       } else {
+               vch = 0;
+       }
+       return vch;
+}
+
+/**
+ * ab8500_charger_ac_cv() - check if the main charger is in CV mode
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Returns ac charger CV mode (on success) else error code
+ */
+static int ab8500_charger_ac_cv(struct ab8500_charger *di)
+{
+       u8 val;
+       int ret = 0;
+
+       /* Only check CV mode if the charger is online */
+       if (di->ac.charger_online) {
+               ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_CH_STATUS1_REG, &val);
+               if (ret < 0) {
+                       dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+                       return 0;
+               }
+
+               if (val & MAIN_CH_CV_ON)
+                       ret = 1;
+               else
+                       ret = 0;
+       }
+
+       return ret;
+}
+
+/**
+ * ab8500_charger_get_vbus_voltage() - get vbus voltage
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * This function returns the vbus voltage.
+ * Returns vbus voltage (on success)
+ */
+static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di)
+{
+       int vch;
+
+       /* Only measure voltage if the charger is connected */
+       if (di->usb.charger_connected) {
+               vch = ab8500_gpadc_convert(di->gpadc, VBUS_V);
+               if (vch < 0)
+                       dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+       } else {
+               vch = 0;
+       }
+       return vch;
+}
+
+/**
+ * ab8500_charger_get_usb_current() - get usb charger current
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * This function returns the usb charger current.
+ * Returns usb current (on success) and error code on failure
+ */
+static int ab8500_charger_get_usb_current(struct ab8500_charger *di)
+{
+       int ich;
+
+       /* Only measure current if the charger is online */
+       if (di->usb.charger_online) {
+               ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C);
+               if (ich < 0)
+                       dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+       } else {
+               ich = 0;
+       }
+       return ich;
+}
+
+/**
+ * ab8500_charger_get_ac_current() - get ac charger current
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * This function returns the ac charger current.
+ * Returns ac current (on success) and error code on failure.
+ */
+static int ab8500_charger_get_ac_current(struct ab8500_charger *di)
+{
+       int ich;
+
+       /* Only measure current if the charger is online */
+       if (di->ac.charger_online) {
+               ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C);
+               if (ich < 0)
+                       dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+       } else {
+               ich = 0;
+       }
+       return ich;
+}
+
+/**
+ * ab8500_charger_usb_cv() - check if the usb charger is in CV mode
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Returns ac charger CV mode (on success) else error code
+ */
+static int ab8500_charger_usb_cv(struct ab8500_charger *di)
+{
+       int ret;
+       u8 val;
+
+       /* Only check CV mode if the charger is online */
+       if (di->usb.charger_online) {
+               ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_CH_USBCH_STAT1_REG, &val);
+               if (ret < 0) {
+                       dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+                       return 0;
+               }
+
+               if (val & USB_CH_CV_ON)
+                       ret = 1;
+               else
+                       ret = 0;
+       } else {
+               ret = 0;
+       }
+
+       return ret;
+}
+
+/**
+ * ab8500_charger_detect_chargers() - Detect the connected chargers
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Returns the type of charger connected.
+ * For USB it will not mean we can actually charge from it
+ * but that there is a USB cable connected that we have to
+ * identify. This is used during startup when we don't get
+ * interrupts of the charger detection
+ *
+ * Returns an integer value, that means,
+ * NO_PW_CONN  no power supply is connected
+ * AC_PW_CONN  if the AC power supply is connected
+ * USB_PW_CONN  if the USB power supply is connected
+ * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected
+ */
+static int ab8500_charger_detect_chargers(struct ab8500_charger *di)
+{
+       int result = NO_PW_CONN;
+       int ret;
+       u8 val;
+
+       /* Check for AC charger */
+       ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+               AB8500_CH_STATUS1_REG, &val);
+       if (ret < 0) {
+               dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+               return ret;
+       }
+
+       if (val & MAIN_CH_DET)
+               result = AC_PW_CONN;
+
+       /* Check for USB charger */
+       ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+               AB8500_CH_USBCH_STAT1_REG, &val);
+       if (ret < 0) {
+               dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+               return ret;
+       }
+
+       if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100))
+               result |= USB_PW_CONN;
+
+       return result;
+}
+
+/**
+ * ab8500_charger_max_usb_curr() - get the max curr for the USB type
+ * @di:                        pointer to the ab8500_charger structure
+ * @link_status:       the identified USB type
+ *
+ * Get the maximum current that is allowed to be drawn from the host
+ * based on the USB type.
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
+       enum ab8500_charger_link_status link_status)
+{
+       int ret = 0;
+
+       switch (link_status) {
+       case USB_STAT_STD_HOST_NC:
+       case USB_STAT_STD_HOST_C_NS:
+       case USB_STAT_STD_HOST_C_S:
+               dev_dbg(di->dev, "USB Type - Standard host is "
+                       "detected through USB driver\n");
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09;
+               break;
+       case USB_STAT_HOST_CHG_HS_CHIRP:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+               break;
+       case USB_STAT_HOST_CHG_HS:
+       case USB_STAT_ACA_RID_C_HS:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9;
+               break;
+       case USB_STAT_ACA_RID_A:
+               /*
+                * Dedicated charger level minus maximum current accessory
+                * can consume (300mA). Closest level is 1100mA
+                */
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1;
+               break;
+       case USB_STAT_ACA_RID_B:
+               /*
+                * Dedicated charger level minus 120mA (20mA for ACA and
+                * 100mA for potential accessory). Closest level is 1300mA
+                */
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3;
+               break;
+       case USB_STAT_DEDICATED_CHG:
+       case USB_STAT_HOST_CHG_NM:
+       case USB_STAT_ACA_RID_C_HS_CHIRP:
+       case USB_STAT_ACA_RID_C_NM:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5;
+               break;
+       case USB_STAT_RESERVED:
+               /*
+                * This state is used to indicate that VBUS has dropped below
+                * the detection level 4 times in a row. This is due to the
+                * charger output current is set to high making the charger
+                * voltage collapse. This have to be propagated through to
+                * chargalg. This is done using the property
+                * POWER_SUPPLY_PROP_CURRENT_AVG = 1
+                */
+               di->flags.vbus_collapse = true;
+               dev_dbg(di->dev, "USB Type - USB_STAT_RESERVED "
+                       "VBUS has collapsed\n");
+               ret = -1;
+               break;
+       case USB_STAT_HM_IDGND:
+       case USB_STAT_NOT_CONFIGURED:
+       case USB_STAT_NOT_VALID_LINK:
+               dev_err(di->dev, "USB Type - Charging not allowed\n");
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+               ret = -ENXIO;
+               break;
+       default:
+               dev_err(di->dev, "USB Type - Unknown\n");
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+               ret = -ENXIO;
+               break;
+       };
+
+       dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
+               link_status, di->max_usb_in_curr);
+
+       return ret;
+}
+
+/**
+ * ab8500_charger_read_usb_type() - read the type of usb connected
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Detect the type of the plugged USB
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_charger_read_usb_type(struct ab8500_charger *di)
+{
+       int ret;
+       u8 val;
+
+       ret = abx500_get_register_interruptible(di->dev,
+               AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val);
+       if (ret < 0) {
+               dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+               return ret;
+       }
+       ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+               AB8500_USB_LINE_STAT_REG, &val);
+       if (ret < 0) {
+               dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+               return ret;
+       }
+
+       /* get the USB type */
+       val = (val & AB8500_USB_LINK_STATUS) >> 3;
+       ret = ab8500_charger_max_usb_curr(di,
+               (enum ab8500_charger_link_status) val);
+
+       return ret;
+}
+
+/**
+ * ab8500_charger_detect_usb_type() - get the type of usb connected
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Detect the type of the plugged USB
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
+{
+       int i, ret;
+       u8 val;
+
+       /*
+        * On getting the VBUS rising edge detect interrupt there
+        * is a 250ms delay after which the register UsbLineStatus
+        * is filled with valid data.
+        */
+       for (i = 0; i < 10; i++) {
+               msleep(250);
+               ret = abx500_get_register_interruptible(di->dev,
+                       AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG,
+                       &val);
+               if (ret < 0) {
+                       dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+                       return ret;
+               }
+               ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+                       AB8500_USB_LINE_STAT_REG, &val);
+               if (ret < 0) {
+                       dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+                       return ret;
+               }
+               /*
+                * Until the IT source register is read the UsbLineStatus
+                * register is not updated, hence doing the same
+                * Revisit this:
+                */
+
+               /* get the USB type */
+               val = (val & AB8500_USB_LINK_STATUS) >> 3;
+               if (val)
+                       break;
+       }
+       ret = ab8500_charger_max_usb_curr(di,
+               (enum ab8500_charger_link_status) val);
+
+       return ret;
+}
+
+/*
+ * This array maps the raw hex value to charger voltage used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_charger_voltage_map[] = {
+       3500 ,
+       3525 ,
+       3550 ,
+       3575 ,
+       3600 ,
+       3625 ,
+       3650 ,
+       3675 ,
+       3700 ,
+       3725 ,
+       3750 ,
+       3775 ,
+       3800 ,
+       3825 ,
+       3850 ,
+       3875 ,
+       3900 ,
+       3925 ,
+       3950 ,
+       3975 ,
+       4000 ,
+       4025 ,
+       4050 ,
+       4060 ,
+       4070 ,
+       4080 ,
+       4090 ,
+       4100 ,
+       4110 ,
+       4120 ,
+       4130 ,
+       4140 ,
+       4150 ,
+       4160 ,
+       4170 ,
+       4180 ,
+       4190 ,
+       4200 ,
+       4210 ,
+       4220 ,
+       4230 ,
+       4240 ,
+       4250 ,
+       4260 ,
+       4270 ,
+       4280 ,
+       4290 ,
+       4300 ,
+       4310 ,
+       4320 ,
+       4330 ,
+       4340 ,
+       4350 ,
+       4360 ,
+       4370 ,
+       4380 ,
+       4390 ,
+       4400 ,
+       4410 ,
+       4420 ,
+       4430 ,
+       4440 ,
+       4450 ,
+       4460 ,
+       4470 ,
+       4480 ,
+       4490 ,
+       4500 ,
+       4510 ,
+       4520 ,
+       4530 ,
+       4540 ,
+       4550 ,
+       4560 ,
+       4570 ,
+       4580 ,
+       4590 ,
+       4600 ,
+};
+
+/*
+ * This array maps the raw hex value to charger current used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_charger_current_map[] = {
+       100 ,
+       200 ,
+       300 ,
+       400 ,
+       500 ,
+       600 ,
+       700 ,
+       800 ,
+       900 ,
+       1000 ,
+       1100 ,
+       1200 ,
+       1300 ,
+       1400 ,
+       1500 ,
+};
+
+/*
+ * This array maps the raw hex value to VBUS input current used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_charger_vbus_in_curr_map[] = {
+       USB_CH_IP_CUR_LVL_0P05,
+       USB_CH_IP_CUR_LVL_0P09,
+       USB_CH_IP_CUR_LVL_0P19,
+       USB_CH_IP_CUR_LVL_0P29,
+       USB_CH_IP_CUR_LVL_0P38,
+       USB_CH_IP_CUR_LVL_0P45,
+       USB_CH_IP_CUR_LVL_0P5,
+       USB_CH_IP_CUR_LVL_0P6,
+       USB_CH_IP_CUR_LVL_0P7,
+       USB_CH_IP_CUR_LVL_0P8,
+       USB_CH_IP_CUR_LVL_0P9,
+       USB_CH_IP_CUR_LVL_1P0,
+       USB_CH_IP_CUR_LVL_1P1,
+       USB_CH_IP_CUR_LVL_1P3,
+       USB_CH_IP_CUR_LVL_1P4,
+       USB_CH_IP_CUR_LVL_1P5,
+};
+
+static int ab8500_voltage_to_regval(int voltage)
+{
+       int i;
+
+       /* Special case for voltage below 3.5V */
+       if (voltage < ab8500_charger_voltage_map[0])
+               return LOW_VOLT_REG;
+
+       for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) {
+               if (voltage < ab8500_charger_voltage_map[i])
+                       return i - 1;
+       }
+
+       /* If not last element, return error */
+       i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1;
+       if (voltage == ab8500_charger_voltage_map[i])
+               return i;
+       else
+               return -1;
+}
+
+static int ab8500_current_to_regval(int curr)
+{
+       int i;
+
+       if (curr < ab8500_charger_current_map[0])
+               return 0;
+
+       for (i = 0; i < ARRAY_SIZE(ab8500_charger_current_map); i++) {
+               if (curr < ab8500_charger_current_map[i])
+                       return i - 1;
+       }
+
+       /* If not last element, return error */
+       i = ARRAY_SIZE(ab8500_charger_current_map) - 1;
+       if (curr == ab8500_charger_current_map[i])
+               return i;
+       else
+               return -1;
+}
+
+static int ab8500_vbus_in_curr_to_regval(int curr)
+{
+       int i;
+
+       if (curr < ab8500_charger_vbus_in_curr_map[0])
+               return 0;
+
+       for (i = 0; i < ARRAY_SIZE(ab8500_charger_vbus_in_curr_map); i++) {
+               if (curr < ab8500_charger_vbus_in_curr_map[i])
+                       return i - 1;
+       }
+
+       /* If not last element, return error */
+       i = ARRAY_SIZE(ab8500_charger_vbus_in_curr_map) - 1;
+       if (curr == ab8500_charger_vbus_in_curr_map[i])
+               return i;
+       else
+               return -1;
+}
+
+/**
+ * ab8500_charger_get_usb_cur() - get usb current
+ * @di:                pointer to the ab8500_charger structre
+ *
+ * The usb stack provides the maximum current that can be drawn from
+ * the standard usb host. This will be in mA.
+ * This function converts current in mA to a value that can be written
+ * to the register. Returns -1 if charging is not allowed
+ */
+static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
+{
+       switch (di->usb_state.usb_current) {
+       case 100:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09;
+               break;
+       case 200:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19;
+               break;
+       case 300:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29;
+               break;
+       case 400:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38;
+               break;
+       case 500:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+               break;
+       default:
+               di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+               return -1;
+               break;
+       };
+       return 0;
+}
+
+/**
+ * ab8500_charger_set_vbus_in_curr() - set VBUS input current limit
+ * @di:                pointer to the ab8500_charger structure
+ * @ich_in:    charger input current limit
+ *
+ * Sets the current that can be drawn from the USB host
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
+               int ich_in)
+{
+       int ret;
+       int input_curr_index;
+       int min_value;
+
+       /* We should always use to lowest current limit */
+       min_value = min(di->bat->chg_params->usb_curr_max, ich_in);
+
+       switch (min_value) {
+       case 100:
+               if (di->vbat < VBAT_TRESH_IP_CUR_RED)
+                       min_value = USB_CH_IP_CUR_LVL_0P05;
+               break;
+       case 500:
+               if (di->vbat < VBAT_TRESH_IP_CUR_RED)
+                       min_value = USB_CH_IP_CUR_LVL_0P45;
+               break;
+       default:
+               break;
+       }
+
+       input_curr_index = ab8500_vbus_in_curr_to_regval(min_value);
+       if (input_curr_index < 0) {
+               dev_err(di->dev, "VBUS input current limit too high\n");
+               return -ENXIO;
+       }
+
+       ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+               AB8500_USBCH_IPT_CRNTLVL_REG,
+               input_curr_index << VBUS_IN_CURR_LIM_SHIFT);
+       if (ret)
+               dev_err(di->dev, "%s write failed\n", __func__);
+
+       return ret;
+}
+
+/**
+ * ab8500_charger_led_en() - turn on/off chargign led
+ * @di:                pointer to the ab8500_charger structure
+ * @on:                flag to turn on/off the chargign led
+ *
+ * Power ON/OFF charging LED indication
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_led_en(struct ab8500_charger *di, int on)
+{
+       int ret;
+
+       if (on) {
+               /* Power ON charging LED indicator, set LED current to 5mA */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_LED_INDICATOR_PWM_CTRL,
+                       (LED_IND_CUR_5MA | LED_INDICATOR_PWM_ENA));
+               if (ret) {
+                       dev_err(di->dev, "Power ON LED failed\n");
+                       return ret;
+               }
+               /* LED indicator PWM duty cycle 252/256 */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_LED_INDICATOR_PWM_DUTY,
+                       LED_INDICATOR_PWM_DUTY_252_256);
+               if (ret) {
+                       dev_err(di->dev, "Set LED PWM duty cycle failed\n");
+                       return ret;
+               }
+       } else {
+               /* Power off charging LED indicator */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_LED_INDICATOR_PWM_CTRL,
+                       LED_INDICATOR_PWM_DIS);
+               if (ret) {
+                       dev_err(di->dev, "Power-off LED failed\n");
+                       return ret;
+               }
+       }
+
+       return ret;
+}
+
+/**
+ * ab8500_charger_ac_en() - enable or disable ac charging
+ * @di:                pointer to the ab8500_charger structure
+ * @enable:    enable/disable flag
+ * @vset:      charging voltage
+ * @iset:      charging current
+ *
+ * Enable/Disable AC/Mains charging and turns on/off the charging led
+ * respectively.
+ **/
+static int ab8500_charger_ac_en(struct ux500_charger *charger,
+       int enable, int vset, int iset)
+{
+       int ret;
+       int volt_index;
+       int curr_index;
+       int input_curr_index;
+       u8 overshoot = 0;
+
+       struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger);
+
+       if (enable) {
+               /* Check if AC is connected */
+               if (!di->ac.charger_connected) {
+                       dev_err(di->dev, "AC charger not connected\n");
+                       return -ENXIO;
+               }
+
+               /* Enable AC charging */
+               dev_dbg(di->dev, "Enable AC: %dmV %dmA\n", vset, iset);
+
+               /*
+                * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts
+                * will be triggered everytime we enable the VDD ADC supply.
+                * This will turn off charging for a short while.
+                * It can be avoided by having the supply on when
+                * there is a charger enabled. Normally the VDD ADC supply
+                * is enabled everytime a GPADC conversion is triggered. We will
+                * force it to be enabled from this driver to have
+                * the GPADC module independant of the AB8500 chargers
+                */
+               if (!di->vddadc_en_ac) {
+                       regulator_enable(di->regu);
+                       di->vddadc_en_ac = true;
+               }
+
+               /* Check if the requested voltage or current is valid */
+               volt_index = ab8500_voltage_to_regval(vset);
+               curr_index = ab8500_current_to_regval(iset);
+               input_curr_index = ab8500_current_to_regval(
+                       di->bat->chg_params->ac_curr_max);
+               if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) {
+                       dev_err(di->dev,
+                               "Charger voltage or current too high, "
+                               "charging not started\n");
+                       return -ENXIO;
+               }
+
+               /* ChVoltLevel: maximum battery charging voltage */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_CH_VOLT_LVL_REG, (u8) volt_index);
+               if (ret) {
+                       dev_err(di->dev, "%s write failed\n", __func__);
+                       return ret;
+               }
+               /* MainChInputCurr: current that can be drawn from the charger*/
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_MCH_IPT_CURLVL_REG,
+                       input_curr_index << MAIN_CH_INPUT_CURR_SHIFT);
+               if (ret) {
+                       dev_err(di->dev, "%s write failed\n", __func__);
+                       return ret;
+               }
+               /* ChOutputCurentLevel: protected output current */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+               if (ret) {
+                       dev_err(di->dev, "%s write failed\n", __func__);
+                       return ret;
+               }
+
+               /* Check if VBAT overshoot control should be enabled */
+               if (!di->bat->enable_overshoot)
+                       overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N;
+
+               /* Enable Main Charger */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_MCH_CTRL1, MAIN_CH_ENA | overshoot);
+               if (ret) {
+                       dev_err(di->dev, "%s write failed\n", __func__);
+                       return ret;
+               }
+
+               /* Power on charging LED indication */
+               ret = ab8500_charger_led_en(di, true);
+               if (ret < 0)
+                       dev_err(di->dev, "failed to enable LED\n");
+
+               di->ac.charger_online = 1;
+       } else {
+               /* Disable AC charging */
+               if (is_ab8500_1p1_or_earlier(di->parent)) {
+                       /*
+                        * For ABB revision 1.0 and 1.1 there is a bug in the
+                        * watchdog logic. That means we have to continously
+                        * kick the charger watchdog even when no charger is
+                        * connected. This is only valid once the AC charger
+                        * has been enabled. This is a bug that is not handled
+                        * by the algorithm and the watchdog have to be kicked
+                        * by the charger driver when the AC charger
+                        * is disabled
+                        */
+                       if (di->ac_conn) {
+                               queue_delayed_work(di->charger_wq,
+                                       &di->kick_wd_work,
+                                       round_jiffies(WD_KICK_INTERVAL));
+                       }
+
+                       /*
+                        * We can't turn off charging completely
+                        * due to a bug in AB8500 cut1.
+                        * If we do, charging will not start again.
+                        * That is why we set the lowest voltage
+                        * and current possible
+                        */
+                       ret = abx500_set_register_interruptible(di->dev,
+                               AB8500_CHARGER,
+                               AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5);
+                       if (ret) {
+                               dev_err(di->dev,
+                                       "%s write failed\n", __func__);
+                               return ret;
+                       }
+
+                       ret = abx500_set_register_interruptible(di->dev,
+                               AB8500_CHARGER,
+                               AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1);
+                       if (ret) {
+                               dev_err(di->dev,
+                                       "%s write failed\n", __func__);
+                               return ret;
+                       }
+               } else {
+                       ret = abx500_set_register_interruptible(di->dev,
+                               AB8500_CHARGER,
+                               AB8500_MCH_CTRL1, 0);
+                       if (ret) {
+                               dev_err(di->dev,
+                                       "%s write failed\n", __func__);
+                               return ret;
+                       }
+               }
+
+               ret = ab8500_charger_led_en(di, false);
+               if (ret < 0)
+                       dev_err(di->dev, "failed to disable LED\n");
+
+               di->ac.charger_online = 0;
+               di->ac.wd_expired = false;
+
+               /* Disable regulator if enabled */
+               if (di->vddadc_en_ac) {
+                       regulator_disable(di->regu);
+                       di->vddadc_en_ac = false;
+               }
+
+               dev_dbg(di->dev, "%s Disabled AC charging\n", __func__);
+       }
+       ab8500_power_supply_changed(di, &di->ac_chg.psy);
+
+       return ret;
+}
+
+/**
+ * ab8500_charger_usb_en() - enable usb charging
+ * @di:                pointer to the ab8500_charger structure
+ * @enable:    enable/disable flag
+ * @vset:      charging voltage
+ * @ich_out:   charger output current
+ *
+ * Enable/Disable USB charging and turns on/off the charging led respectively.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_usb_en(struct ux500_charger *charger,
+       int enable, int vset, int ich_out)
+{
+       int ret;
+       int volt_index;
+       int curr_index;
+       u8 overshoot = 0;
+
+       struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger);
+
+       if (enable) {
+               /* Check if USB is connected */
+               if (!di->usb.charger_connected) {
+                       dev_err(di->dev, "USB charger not connected\n");
+                       return -ENXIO;
+               }
+
+               /*
+                * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts
+                * will be triggered everytime we enable the VDD ADC supply.
+                * This will turn off charging for a short while.
+                * It can be avoided by having the supply on when
+                * there is a charger enabled. Normally the VDD ADC supply
+                * is enabled everytime a GPADC conversion is triggered. We will
+                * force it to be enabled from this driver to have
+                * the GPADC module independant of the AB8500 chargers
+                */
+               if (!di->vddadc_en_usb) {
+                       regulator_enable(di->regu);
+                       di->vddadc_en_usb = true;
+               }
+
+               /* Enable USB charging */
+               dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out);
+
+               /* Check if the requested voltage or current is valid */
+               volt_index = ab8500_voltage_to_regval(vset);
+               curr_index = ab8500_current_to_regval(ich_out);
+               if (volt_index < 0 || curr_index < 0) {
+                       dev_err(di->dev,
+                               "Charger voltage or current too high, "
+                               "charging not started\n");
+                       return -ENXIO;
+               }
+
+               /* ChVoltLevel: max voltage upto which battery can be charged */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_CH_VOLT_LVL_REG, (u8) volt_index);
+               if (ret) {
+                       dev_err(di->dev, "%s write failed\n", __func__);
+                       return ret;
+               }
+               /* USBChInputCurr: current that can be drawn from the usb */
+               ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
+               if (ret) {
+                       dev_err(di->dev, "setting USBChInputCurr failed\n");
+                       return ret;
+               }
+               /* ChOutputCurentLevel: protected output current */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+               if (ret) {
+                       dev_err(di->dev, "%s write failed\n", __func__);
+                       return ret;
+               }
+               /* Check if VBAT overshoot control should be enabled */
+               if (!di->bat->enable_overshoot)
+                       overshoot = USB_CHG_NO_OVERSHOOT_ENA_N;
+
+               /* Enable USB Charger */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot);
+               if (ret) {
+                       dev_err(di->dev, "%s write failed\n", __func__);
+                       return ret;
+               }
+
+               /* If success power on charging LED indication */
+               ret = ab8500_charger_led_en(di, true);
+               if (ret < 0)
+                       dev_err(di->dev, "failed to enable LED\n");
+
+               queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ);
+
+               di->usb.charger_online = 1;
+       } else {
+               /* Disable USB charging */
+               ret = abx500_set_register_interruptible(di->dev,
+                       AB8500_CHARGER,
+                       AB8500_USBCH_CTRL1_REG, 0);
+               if (ret) {
+                       dev_err(di->dev,
+                               "%s write failed\n", __func__);
+                       return ret;
+               }
+
+               ret = ab8500_charger_led_en(di, false);
+               if (ret < 0)
+                       dev_err(di->dev, "failed to disable LED\n");
+
+               di->usb.charger_online = 0;
+               di->usb.wd_expired = false;
+
+               /* Disable regulator if enabled */
+               if (di->vddadc_en_usb) {
+                       regulator_disable(di->regu);
+                       di->vddadc_en_usb = false;
+               }
+
+               dev_dbg(di->dev, "%s Disabled USB charging\n", __func__);
+
+               /* Cancel any pending Vbat check work */
+               if (delayed_work_pending(&di->check_vbat_work))
+                       cancel_delayed_work(&di->check_vbat_work);
+
+       }
+       ab8500_power_supply_changed(di, &di->usb_chg.psy);
+
+       return ret;
+}
+
+/**
+ * ab8500_charger_watchdog_kick() - kick charger watchdog
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Kick charger watchdog
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_watchdog_kick(struct ux500_charger *charger)
+{
+       int ret;
+       struct ab8500_charger *di;
+
+       if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
+               di = to_ab8500_charger_ac_device_info(charger);
+       else if (charger->psy.type == POWER_SUPPLY_TYPE_USB)
+               di = to_ab8500_charger_usb_device_info(charger);
+       else
+               return -ENXIO;
+
+       ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+               AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
+       if (ret)
+               dev_err(di->dev, "Failed to kick WD!\n");
+
+       return ret;
+}
+
+/**
+ * ab8500_charger_update_charger_current() - update charger current
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Update the charger output current for the specified charger
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_update_charger_current(struct ux500_charger *charger,
+               int ich_out)
+{
+       int ret;
+       int curr_index;
+       struct ab8500_charger *di;
+
+       if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
+               di = to_ab8500_charger_ac_device_info(charger);
+       else if (charger->psy.type == POWER_SUPPLY_TYPE_USB)
+               di = to_ab8500_charger_usb_device_info(charger);
+       else
+               return -ENXIO;
+
+       curr_index = ab8500_current_to_regval(ich_out);
+       if (curr_index < 0) {
+               dev_err(di->dev,
+                       "Charger current too high, "
+                       "charging not started\n");
+               return -ENXIO;
+       }
+
+       ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+               AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+       if (ret) {
+               dev_err(di->dev, "%s write failed\n", __func__);
+               return ret;
+       }
+
+       /* Reset the main and usb drop input current measurement counter */
+       ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                               AB8500_CHARGER_CTRL,
+                               0x1);
+       if (ret) {
+               dev_err(di->dev, "%s write failed\n", __func__);
+               return ret;
+       }
+
+       return ret;
+}
+
+static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data)
+{
+       struct power_supply *psy;
+       struct power_supply *ext;
+       struct ab8500_charger *di;
+       union power_supply_propval ret;
+       int i, j;
+       bool psy_found = false;
+       struct ux500_charger *usb_chg;
+
+       usb_chg = (struct ux500_charger *)data;
+       psy = &usb_chg->psy;
+
+       di = to_ab8500_charger_usb_device_info(usb_chg);
+
+       ext = dev_get_drvdata(dev);
+
+       /* For all psy where the driver name appears in any supplied_to */
+       for (i = 0; i < ext->num_supplicants; i++) {
+               if (!strcmp(ext->supplied_to[i], psy->name))
+                       psy_found = true;
+       }
+
+       if (!psy_found)
+               return 0;
+
+       /* Go through all properties for the psy */
+       for (j = 0; j < ext->num_properties; j++) {
+               enum power_supply_property prop;
+               prop = ext->properties[j];
+
+               if (ext->get_property(ext, prop, &ret))
+                       continue;
+
+               switch (prop) {
+               case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               di->vbat = ret.intval / 1000;
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+       return 0;
+}
+
+/**
+ * ab8500_charger_check_vbat_work() - keep vbus current within spec
+ * @work       pointer to the work_struct structure
+ *
+ * Due to a asic bug it is necessary to lower the input current to the vbus
+ * charger when charging with at some specific levels. This issue is only valid
+ * for below a certain battery voltage. This function makes sure that the
+ * the allowed current limit isn't exceeded.
+ */
+static void ab8500_charger_check_vbat_work(struct work_struct *work)
+{
+       int t = 10;
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, check_vbat_work.work);
+
+       class_for_each_device(power_supply_class, NULL,
+               &di->usb_chg.psy, ab8500_charger_get_ext_psy_data);
+
+       /* First run old_vbat is 0. */
+       if (di->old_vbat == 0)
+               di->old_vbat = di->vbat;
+
+       if (!((di->old_vbat <= VBAT_TRESH_IP_CUR_RED &&
+               di->vbat <= VBAT_TRESH_IP_CUR_RED) ||
+               (di->old_vbat > VBAT_TRESH_IP_CUR_RED &&
+               di->vbat > VBAT_TRESH_IP_CUR_RED))) {
+
+               dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d,"
+                       " old: %d\n", di->max_usb_in_curr, di->vbat,
+                       di->old_vbat);
+               ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
+               power_supply_changed(&di->usb_chg.psy);
+       }
+
+       di->old_vbat = di->vbat;
+
+       /*
+        * No need to check the battery voltage every second when not close to
+        * the threshold.
+        */
+       if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100) &&
+               (di->vbat > (VBAT_TRESH_IP_CUR_RED - 100)))
+                       t = 1;
+
+       queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ);
+}
+
+/**
+ * ab8500_charger_check_hw_failure_work() - check main charger failure
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for checking the main charger status
+ */
+static void ab8500_charger_check_hw_failure_work(struct work_struct *work)
+{
+       int ret;
+       u8 reg_value;
+
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, check_hw_failure_work.work);
+
+       /* Check if the status bits for HW failure is still active */
+       if (di->flags.mainextchnotok) {
+               ret = abx500_get_register_interruptible(di->dev,
+                       AB8500_CHARGER, AB8500_CH_STATUS2_REG, &reg_value);
+               if (ret < 0) {
+                       dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+                       return;
+               }
+               if (!(reg_value & MAIN_CH_NOK)) {
+                       di->flags.mainextchnotok = false;
+                       ab8500_power_supply_changed(di, &di->ac_chg.psy);
+               }
+       }
+       if (di->flags.vbus_ovv) {
+               ret = abx500_get_register_interruptible(di->dev,
+                       AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG,
+                       &reg_value);
+               if (ret < 0) {
+                       dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+                       return;
+               }
+               if (!(reg_value & VBUS_OVV_TH)) {
+                       di->flags.vbus_ovv = false;
+                       ab8500_power_supply_changed(di, &di->usb_chg.psy);
+               }
+       }
+       /* If we still have a failure, schedule a new check */
+       if (di->flags.mainextchnotok || di->flags.vbus_ovv) {
+               queue_delayed_work(di->charger_wq,
+                       &di->check_hw_failure_work, round_jiffies(HZ));
+       }
+}
+
+/**
+ * ab8500_charger_kick_watchdog_work() - kick the watchdog
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for kicking the charger watchdog.
+ *
+ * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+ * logic. That means we have to continously kick the charger
+ * watchdog even when no charger is connected. This is only
+ * valid once the AC charger has been enabled. This is
+ * a bug that is not handled by the algorithm and the
+ * watchdog have to be kicked by the charger driver
+ * when the AC charger is disabled
+ */
+static void ab8500_charger_kick_watchdog_work(struct work_struct *work)
+{
+       int ret;
+
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, kick_wd_work.work);
+
+       ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+               AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
+       if (ret)
+               dev_err(di->dev, "Failed to kick WD!\n");
+
+       /* Schedule a new watchdog kick */
+       queue_delayed_work(di->charger_wq,
+               &di->kick_wd_work, round_jiffies(WD_KICK_INTERVAL));
+}
+
+/**
+ * ab8500_charger_ac_work() - work to get and set main charger status
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for checking the main charger status
+ */
+static void ab8500_charger_ac_work(struct work_struct *work)
+{
+       int ret;
+
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, ac_work);
+
+       /*
+        * Since we can't be sure that the events are received
+        * synchronously, we have the check if the main charger is
+        * connected by reading the status register
+        */
+       ret = ab8500_charger_detect_chargers(di);
+       if (ret < 0)
+               return;
+
+       if (ret & AC_PW_CONN) {
+               di->ac.charger_connected = 1;
+               di->ac_conn = true;
+       } else {
+               di->ac.charger_connected = 0;
+       }
+
+       ab8500_power_supply_changed(di, &di->ac_chg.psy);
+       sysfs_notify(&di->ac_chg.psy.dev->kobj, NULL, "present");
+}
+
+/**
+ * ab8500_charger_detect_usb_type_work() - work to detect USB type
+ * @work:      Pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
+{
+       int ret;
+
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, detect_usb_type_work);
+
+       /*
+        * Since we can't be sure that the events are received
+        * synchronously, we have the check if is
+        * connected by reading the status register
+        */
+       ret = ab8500_charger_detect_chargers(di);
+       if (ret < 0)
+               return;
+
+       if (!(ret & USB_PW_CONN)) {
+               di->vbus_detected = 0;
+               ab8500_charger_set_usb_connected(di, false);
+               ab8500_power_supply_changed(di, &di->usb_chg.psy);
+       } else {
+               di->vbus_detected = 1;
+
+               if (is_ab8500_1p1_or_earlier(di->parent)) {
+                       ret = ab8500_charger_detect_usb_type(di);
+                       if (!ret) {
+                               ab8500_charger_set_usb_connected(di, true);
+                               ab8500_power_supply_changed(di,
+                                                           &di->usb_chg.psy);
+                       }
+               } else {
+                       /* For ABB cut2.0 and onwards we have an IRQ,
+                        * USB_LINK_STATUS that will be triggered when the USB
+                        * link status changes. The exception is USB connected
+                        * during startup. Then we don't get a
+                        * USB_LINK_STATUS IRQ
+                        */
+                       if (di->vbus_detected_start) {
+                               di->vbus_detected_start = false;
+                               ret = ab8500_charger_detect_usb_type(di);
+                               if (!ret) {
+                                       ab8500_charger_set_usb_connected(di,
+                                               true);
+                                       ab8500_power_supply_changed(di,
+                                               &di->usb_chg.psy);
+                               }
+                       }
+               }
+       }
+}
+
+/**
+ * ab8500_charger_usb_link_status_work() - work to detect USB type
+ * @work:      pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+static void ab8500_charger_usb_link_status_work(struct work_struct *work)
+{
+       int ret;
+
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, usb_link_status_work);
+
+       /*
+        * Since we can't be sure that the events are received
+        * synchronously, we have the check if  is
+        * connected by reading the status register
+        */
+       ret = ab8500_charger_detect_chargers(di);
+       if (ret < 0)
+               return;
+
+       if (!(ret & USB_PW_CONN)) {
+               di->vbus_detected = 0;
+               ab8500_charger_set_usb_connected(di, false);
+               ab8500_power_supply_changed(di, &di->usb_chg.psy);
+       } else {
+               di->vbus_detected = 1;
+               ret = ab8500_charger_read_usb_type(di);
+               if (!ret) {
+                       /* Update maximum input current */
+                       ret = ab8500_charger_set_vbus_in_curr(di,
+                                       di->max_usb_in_curr);
+                       if (ret)
+                               return;
+
+                       ab8500_charger_set_usb_connected(di, true);
+                       ab8500_power_supply_changed(di, &di->usb_chg.psy);
+               } else if (ret == -ENXIO) {
+                       /* No valid charger type detected */
+                       ab8500_charger_set_usb_connected(di, false);
+                       ab8500_power_supply_changed(di, &di->usb_chg.psy);
+               }
+       }
+}
+
+static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
+{
+       int ret;
+       unsigned long flags;
+
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, usb_state_changed_work);
+
+       if (!di->vbus_detected)
+               return;
+
+       spin_lock_irqsave(&di->usb_state.usb_lock, flags);
+       di->usb_state.usb_changed = false;
+       spin_unlock_irqrestore(&di->usb_state.usb_lock, flags);
+
+       /*
+        * wait for some time until you get updates from the usb stack
+        * and negotiations are completed
+        */
+       msleep(250);
+
+       if (di->usb_state.usb_changed)
+               return;
+
+       dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n",
+               __func__, di->usb_state.state, di->usb_state.usb_current);
+
+       switch (di->usb_state.state) {
+       case AB8500_BM_USB_STATE_RESET_HS:
+       case AB8500_BM_USB_STATE_RESET_FS:
+       case AB8500_BM_USB_STATE_SUSPEND:
+       case AB8500_BM_USB_STATE_MAX:
+               ab8500_charger_set_usb_connected(di, false);
+               ab8500_power_supply_changed(di, &di->usb_chg.psy);
+               break;
+
+       case AB8500_BM_USB_STATE_RESUME:
+               /*
+                * when suspend->resume there should be delay
+                * of 1sec for enabling charging
+                */
+               msleep(1000);
+               /* Intentional fall through */
+       case AB8500_BM_USB_STATE_CONFIGURED:
+               /*
+                * USB is configured, enable charging with the charging
+                * input current obtained from USB driver
+                */
+               if (!ab8500_charger_get_usb_cur(di)) {
+                       /* Update maximum input current */
+                       ret = ab8500_charger_set_vbus_in_curr(di,
+                                       di->max_usb_in_curr);
+                       if (ret)
+                               return;
+
+                       ab8500_charger_set_usb_connected(di, true);
+                       ab8500_power_supply_changed(di, &di->usb_chg.psy);
+               }
+               break;
+
+       default:
+               break;
+       };
+}
+
+/**
+ * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for checking the USB charger Not OK status
+ */
+static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work)
+{
+       int ret;
+       u8 reg_value;
+       bool prev_status;
+
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, check_usbchgnotok_work.work);
+
+       /* Check if the status bit for usbchargernotok is still active */
+       ret = abx500_get_register_interruptible(di->dev,
+               AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, &reg_value);
+       if (ret < 0) {
+               dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+               return;
+       }
+       prev_status = di->flags.usbchargernotok;
+
+       if (reg_value & VBUS_CH_NOK) {
+               di->flags.usbchargernotok = true;
+               /* Check again in 1sec */
+               queue_delayed_work(di->charger_wq,
+                       &di->check_usbchgnotok_work, HZ);
+       } else {
+               di->flags.usbchargernotok = false;
+               di->flags.vbus_collapse = false;
+       }
+
+       if (prev_status != di->flags.usbchargernotok)
+               ab8500_power_supply_changed(di, &di->usb_chg.psy);
+}
+
+/**
+ * ab8500_charger_check_main_thermal_prot_work() - check main thermal status
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for checking the Main thermal prot status
+ */
+static void ab8500_charger_check_main_thermal_prot_work(
+       struct work_struct *work)
+{
+       int ret;
+       u8 reg_value;
+
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, check_main_thermal_prot_work);
+
+       /* Check if the status bit for main_thermal_prot is still active */
+       ret = abx500_get_register_interruptible(di->dev,
+               AB8500_CHARGER, AB8500_CH_STATUS2_REG, &reg_value);
+       if (ret < 0) {
+               dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+               return;
+       }
+       if (reg_value & MAIN_CH_TH_PROT)
+               di->flags.main_thermal_prot = true;
+       else
+               di->flags.main_thermal_prot = false;
+
+       ab8500_power_supply_changed(di, &di->ac_chg.psy);
+}
+
+/**
+ * ab8500_charger_check_usb_thermal_prot_work() - check usb thermal status
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for checking the USB thermal prot status
+ */
+static void ab8500_charger_check_usb_thermal_prot_work(
+       struct work_struct *work)
+{
+       int ret;
+       u8 reg_value;
+
+       struct ab8500_charger *di = container_of(work,
+               struct ab8500_charger, check_usb_thermal_prot_work);
+
+       /* Check if the status bit for usb_thermal_prot is still active */
+       ret = abx500_get_register_interruptible(di->dev,
+               AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, &reg_value);
+       if (ret < 0) {
+               dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+               return;
+       }
+       if (reg_value & USB_CH_TH_PROT)
+               di->flags.usb_thermal_prot = true;
+       else
+               di->flags.usb_thermal_prot = false;
+
+       ab8500_power_supply_changed(di, &di->usb_chg.psy);
+}
+
+/**
+ * ab8500_charger_mainchunplugdet_handler() - main charger unplugged
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev, "Main charger unplugged\n");
+       queue_work(di->charger_wq, &di->ac_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainchplugdet_handler() - main charger plugged
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev, "Main charger plugged\n");
+       queue_work(di->charger_wq, &di->ac_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainextchnotok_handler() - main charger not ok
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainextchnotok_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev, "Main charger not ok\n");
+       di->flags.mainextchnotok = true;
+       ab8500_power_supply_changed(di, &di->ac_chg.psy);
+
+       /* Schedule a new HW failure check */
+       queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainchthprotr_handler() - Die temp is above main charger
+ * thermal protection threshold
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchthprotr_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev,
+               "Die temp above Main charger thermal protection threshold\n");
+       queue_work(di->charger_wq, &di->check_main_thermal_prot_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainchthprotf_handler() - Die temp is below main charger
+ * thermal protection threshold
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev,
+               "Die temp ok for Main charger thermal protection threshold\n");
+       queue_work(di->charger_wq, &di->check_main_thermal_prot_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_vbusdetf_handler() - VBUS falling detected
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev, "VBUS falling detected\n");
+       queue_work(di->charger_wq, &di->detect_usb_type_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_vbusdetr_handler() - VBUS rising detected
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       di->vbus_detected = true;
+       dev_dbg(di->dev, "VBUS rising detected\n");
+       queue_work(di->charger_wq, &di->detect_usb_type_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usblinkstatus_handler() - USB link status has changed
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usblinkstatus_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev, "USB link status changed\n");
+
+       queue_work(di->charger_wq, &di->usb_link_status_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usbchthprotr_handler() - Die temp is above usb charger
+ * thermal protection threshold
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usbchthprotr_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev,
+               "Die temp above USB charger thermal protection threshold\n");
+       queue_work(di->charger_wq, &di->check_usb_thermal_prot_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usbchthprotf_handler() - Die temp is below usb charger
+ * thermal protection threshold
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev,
+               "Die temp ok for USB charger thermal protection threshold\n");
+       queue_work(di->charger_wq, &di->check_usb_thermal_prot_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev, "Not allowed USB charger detected\n");
+       queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_chwdexp_handler() - Charger watchdog expired
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev, "Charger watchdog expired\n");
+
+       /*
+        * The charger that was online when the watchdog expired
+        * needs to be restarted for charging to start again
+        */
+       if (di->ac.charger_online) {
+               di->ac.wd_expired = true;
+               ab8500_power_supply_changed(di, &di->ac_chg.psy);
+       }
+       if (di->usb.charger_online) {
+               di->usb.wd_expired = true;
+               ab8500_power_supply_changed(di, &di->usb_chg.psy);
+       }
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_vbusovv_handler() - VBUS overvoltage detected
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbusovv_handler(int irq, void *_di)
+{
+       struct ab8500_charger *di = _di;
+
+       dev_dbg(di->dev, "VBUS overvoltage detected\n");
+       di->flags.vbus_ovv = true;
+       ab8500_power_supply_changed(di, &di->usb_chg.psy);
+
+       /* Schedule a new HW failure check */
+       queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_ac_get_property() - get the ac/mains properties
+ * @psy:       pointer to the power_supply structure
+ * @psp:       pointer to the power_supply_property structure
+ * @val:       pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the ac/mains
+ * properties by reading the sysfs files.
+ * AC/Mains properties are online, present and voltage.
+ * online:     ac/mains charging is in progress or not
+ * present:    presence of the ac/mains
+ * voltage:    AC/Mains voltage
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_ac_get_property(struct power_supply *psy,
+       enum power_supply_property psp,
+       union power_supply_propval *val)
+{
+       struct ab8500_charger *di;
+
+       di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy));
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_HEALTH:
+               if (di->flags.mainextchnotok)
+                       val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+               else if (di->ac.wd_expired || di->usb.wd_expired)
+                       val->intval = POWER_SUPPLY_HEALTH_DEAD;
+               else if (di->flags.main_thermal_prot)
+                       val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+               else
+                       val->intval = POWER_SUPPLY_HEALTH_GOOD;
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = di->ac.charger_online;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = di->ac.charger_connected;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               di->ac.charger_voltage = ab8500_charger_get_ac_voltage(di);
+               val->intval = di->ac.charger_voltage * 1000;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+               /*
+                * This property is used to indicate when CV mode is entered
+                * for the AC charger
+                */
+               di->ac.cv_active = ab8500_charger_ac_cv(di);
+               val->intval = di->ac.cv_active;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               val->intval = ab8500_charger_get_ac_current(di) * 1000;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+/**
+ * ab8500_charger_usb_get_property() - get the usb properties
+ * @psy:        pointer to the power_supply structure
+ * @psp:        pointer to the power_supply_property structure
+ * @val:        pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the usb
+ * properties by reading the sysfs files.
+ * USB properties are online, present and voltage.
+ * online:     usb charging is in progress or not
+ * present:    presence of the usb
+ * voltage:    vbus voltage
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_usb_get_property(struct power_supply *psy,
+       enum power_supply_property psp,
+       union power_supply_propval *val)
+{
+       struct ab8500_charger *di;
+
+       di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy));
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_HEALTH:
+               if (di->flags.usbchargernotok)
+                       val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+               else if (di->ac.wd_expired || di->usb.wd_expired)
+                       val->intval = POWER_SUPPLY_HEALTH_DEAD;
+               else if (di->flags.usb_thermal_prot)
+                       val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+               else if (di->flags.vbus_ovv)
+                       val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+               else
+                       val->intval = POWER_SUPPLY_HEALTH_GOOD;
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               val->intval = di->usb.charger_online;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = di->usb.charger_connected;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               di->usb.charger_voltage = ab8500_charger_get_vbus_voltage(di);
+               val->intval = di->usb.charger_voltage * 1000;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+               /*
+                * This property is used to indicate when CV mode is entered
+                * for the USB charger
+                */
+               di->usb.cv_active = ab8500_charger_usb_cv(di);
+               val->intval = di->usb.cv_active;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               val->intval = ab8500_charger_get_usb_current(di) * 1000;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+               /*
+                * This property is used to indicate when VBUS has collapsed
+                * due to too high output current from the USB charger
+                */
+               if (di->flags.vbus_collapse)
+                       val->intval = 1;
+               else
+                       val->intval = 0;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+/**
+ * ab8500_charger_init_hw_registers() - Set up charger related registers
+ * @di:                pointer to the ab8500_charger structure
+ *
+ * Set up charger OVV, watchdog and maximum voltage registers as well as
+ * charging of the backup battery
+ */
+static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
+{
+       int ret = 0;
+
+       /* Setup maximum charger current and voltage for ABB cut2.0 */
+       if (!is_ab8500_1p1_or_earlier(di->parent)) {
+               ret = abx500_set_register_interruptible(di->dev,
+                       AB8500_CHARGER,
+                       AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6);
+               if (ret) {
+                       dev_err(di->dev,
+                               "failed to set CH_VOLT_LVL_MAX_REG\n");
+                       goto out;
+               }
+
+               ret = abx500_set_register_interruptible(di->dev,
+                       AB8500_CHARGER,
+                       AB8500_CH_OPT_CRNTLVL_MAX_REG, CH_OP_CUR_LVL_1P6);
+               if (ret) {
+                       dev_err(di->dev,
+                               "failed to set CH_OPT_CRNTLVL_MAX_REG\n");
+                       goto out;
+               }
+       }
+
+       /* VBUS OVV set to 6.3V and enable automatic current limitiation */
+       ret = abx500_set_register_interruptible(di->dev,
+               AB8500_CHARGER,
+               AB8500_USBCH_CTRL2_REG,
+               VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA);
+       if (ret) {
+               dev_err(di->dev, "failed to set VBUS OVV\n");
+               goto out;
+       }
+
+       /* Enable main watchdog in OTP */
+       ret = abx500_set_register_interruptible(di->dev,
+               AB8500_OTP_EMUL, AB8500_OTP_CONF_15, OTP_ENABLE_WD);
+       if (ret) {
+               dev_err(di->dev, "failed to enable main WD in OTP\n");
+               goto out;
+       }
+
+       /* Enable main watchdog */
+       ret = abx500_set_register_interruptible(di->dev,
+               AB8500_SYS_CTRL2_BLOCK,
+               AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA);
+       if (ret) {
+               dev_err(di->dev, "faile to enable main watchdog\n");
+               goto out;
+       }
+
+       /*
+        * Due to internal synchronisation, Enable and Kick watchdog bits
+        * cannot be enabled in a single write.
+        * A minimum delay of 2*32 kHz period (62.5µs) must be inserted
+        * between writing Enable then Kick bits.
+        */
+       udelay(63);
+
+       /* Kick main watchdog */
+       ret = abx500_set_register_interruptible(di->dev,
+               AB8500_SYS_CTRL2_BLOCK,
+               AB8500_MAIN_WDOG_CTRL_REG,
+               (MAIN_WDOG_ENA | MAIN_WDOG_KICK));
+       if (ret) {
+               dev_err(di->dev, "failed to kick main watchdog\n");
+               goto out;
+       }
+
+       /* Disable main watchdog */
+       ret = abx500_set_register_interruptible(di->dev,
+               AB8500_SYS_CTRL2_BLOCK,
+               AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS);
+       if (ret) {
+               dev_err(di->dev, "failed to disable main watchdog\n");
+               goto out;
+       }
+
+       /* Set watchdog timeout */
+       ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+               AB8500_CH_WD_TIMER_REG, WD_TIMER);
+       if (ret) {
+               dev_err(di->dev, "failed to set charger watchdog timeout\n");
+               goto out;
+       }
+
+       /* Backup battery voltage and current */
+       ret = abx500_set_register_interruptible(di->dev,
+               AB8500_RTC,
+               AB8500_RTC_BACKUP_CHG_REG,
+               di->bat->bkup_bat_v |
+               di->bat->bkup_bat_i);
+       if (ret) {
+               dev_err(di->dev, "failed to setup backup battery charging\n");
+               goto out;
+       }
+
+       /* Enable backup battery charging */
+       abx500_mask_and_set_register_interruptible(di->dev,
+               AB8500_RTC, AB8500_RTC_CTRL_REG,
+               RTC_BUP_CH_ENA, RTC_BUP_CH_ENA);
+       if (ret < 0)
+               dev_err(di->dev, "%s mask and set failed\n", __func__);
+
+out:
+       return ret;
+}
+
+/*
+ * ab8500 charger driver interrupts and their respective isr
+ */
+static struct ab8500_charger_interrupts ab8500_charger_irq[] = {
+       {"MAIN_CH_UNPLUG_DET", ab8500_charger_mainchunplugdet_handler},
+       {"MAIN_CHARGE_PLUG_DET", ab8500_charger_mainchplugdet_handler},
+       {"MAIN_EXT_CH_NOT_OK", ab8500_charger_mainextchnotok_handler},
+       {"MAIN_CH_TH_PROT_R", ab8500_charger_mainchthprotr_handler},
+       {"MAIN_CH_TH_PROT_F", ab8500_charger_mainchthprotf_handler},
+       {"VBUS_DET_F", ab8500_charger_vbusdetf_handler},
+       {"VBUS_DET_R", ab8500_charger_vbusdetr_handler},
+       {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler},
+       {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler},
+       {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler},
+       {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler},
+       {"VBUS_OVV", ab8500_charger_vbusovv_handler},
+       {"CH_WD_EXP", ab8500_charger_chwdexp_handler},
+};
+
+static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
+               unsigned long event, void *power)
+{
+       struct ab8500_charger *di =
+               container_of(nb, struct ab8500_charger, nb);
+       enum ab8500_usb_state bm_usb_state;
+       unsigned mA = *((unsigned *)power);
+
+       if (event != USB_EVENT_VBUS) {
+               dev_dbg(di->dev, "not a standard host, returning\n");
+               return NOTIFY_DONE;
+       }
+
+       /* TODO: State is fabricate  here. See if charger really needs USB
+        * state or if mA is enough
+        */
+       if ((di->usb_state.usb_current == 2) && (mA > 2))
+               bm_usb_state = AB8500_BM_USB_STATE_RESUME;
+       else if (mA == 0)
+               bm_usb_state = AB8500_BM_USB_STATE_RESET_HS;
+       else if (mA == 2)
+               bm_usb_state = AB8500_BM_USB_STATE_SUSPEND;
+       else if (mA >= 8) /* 8, 100, 500 */
+               bm_usb_state = AB8500_BM_USB_STATE_CONFIGURED;
+       else /* Should never occur */
+               bm_usb_state = AB8500_BM_USB_STATE_RESET_FS;
+
+       dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n",
+               __func__, bm_usb_state, mA);
+
+       spin_lock(&di->usb_state.usb_lock);
+       di->usb_state.usb_changed = true;
+       spin_unlock(&di->usb_state.usb_lock);
+
+       di->usb_state.state = bm_usb_state;
+       di->usb_state.usb_current = mA;
+
+       queue_work(di->charger_wq, &di->usb_state_changed_work);
+
+       return NOTIFY_OK;
+}
+
+#if defined(CONFIG_PM)
+static int ab8500_charger_resume(struct platform_device *pdev)
+{
+       int ret;
+       struct ab8500_charger *di = platform_get_drvdata(pdev);
+
+       /*
+        * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+        * logic. That means we have to continously kick the charger
+        * watchdog even when no charger is connected. This is only
+        * valid once the AC charger has been enabled. This is
+        * a bug that is not handled by the algorithm and the
+        * watchdog have to be kicked by the charger driver
+        * when the AC charger is disabled
+        */
+       if (di->ac_conn && is_ab8500_1p1_or_earlier(di->parent)) {
+               ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+                       AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
+               if (ret)
+                       dev_err(di->dev, "Failed to kick WD!\n");
+
+               /* If not already pending start a new timer */
+               if (!delayed_work_pending(
+                       &di->kick_wd_work)) {
+                       queue_delayed_work(di->charger_wq, &di->kick_wd_work,
+                               round_jiffies(WD_KICK_INTERVAL));
+               }
+       }
+
+       /* If we still have a HW failure, schedule a new check */
+       if (di->flags.mainextchnotok || di->flags.vbus_ovv) {
+               queue_delayed_work(di->charger_wq,
+                       &di->check_hw_failure_work, 0);
+       }
+
+       return 0;
+}
+
+static int ab8500_charger_suspend(struct platform_device *pdev,
+       pm_message_t state)
+{
+       struct ab8500_charger *di = platform_get_drvdata(pdev);
+
+       /* Cancel any pending HW failure check */
+       if (delayed_work_pending(&di->check_hw_failure_work))
+               cancel_delayed_work(&di->check_hw_failure_work);
+
+       return 0;
+}
+#else
+#define ab8500_charger_suspend      NULL
+#define ab8500_charger_resume       NULL
+#endif
+
+static int __devexit ab8500_charger_remove(struct platform_device *pdev)
+{
+       struct ab8500_charger *di = platform_get_drvdata(pdev);
+       int i, irq, ret;
+
+       /* Disable AC charging */
+       ab8500_charger_ac_en(&di->ac_chg, false, 0, 0);
+
+       /* Disable USB charging */
+       ab8500_charger_usb_en(&di->usb_chg, false, 0, 0);
+
+       /* Disable interrupts */
+       for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
+               irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+               free_irq(irq, di);
+       }
+
+       /* disable the regulator */
+       regulator_put(di->regu);
+
+       /* Backup battery voltage and current disable */
+       ret = abx500_mask_and_set_register_interruptible(di->dev,
+               AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0);
+       if (ret < 0)
+               dev_err(di->dev, "%s mask and set failed\n", __func__);
+
+       usb_unregister_notifier(di->usb_phy, &di->nb);
+       usb_put_transceiver(di->usb_phy);
+
+       /* Delete the work queue */
+       destroy_workqueue(di->charger_wq);
+
+       flush_scheduled_work();
+       power_supply_unregister(&di->usb_chg.psy);
+       power_supply_unregister(&di->ac_chg.psy);
+       platform_set_drvdata(pdev, NULL);
+       kfree(di);
+
+       return 0;
+}
+
+static int __devinit ab8500_charger_probe(struct platform_device *pdev)
+{
+       int irq, i, charger_status, ret = 0;
+       struct abx500_bm_plat_data *plat_data;
+
+       struct ab8500_charger *di =
+               kzalloc(sizeof(struct ab8500_charger), GFP_KERNEL);
+       if (!di)
+               return -ENOMEM;
+
+       /* get parent data */
+       di->dev = &pdev->dev;
+       di->parent = dev_get_drvdata(pdev->dev.parent);
+       di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+       /* initialize lock */
+       spin_lock_init(&di->usb_state.usb_lock);
+
+       /* get charger specific platform data */
+       plat_data = pdev->dev.platform_data;
+       di->pdata = plat_data->charger;
+
+       if (!di->pdata) {
+               dev_err(di->dev, "no charger platform data supplied\n");
+               ret = -EINVAL;
+               goto free_device_info;
+       }
+
+       /* get battery specific platform data */
+       di->bat = plat_data->battery;
+       if (!di->bat) {
+               dev_err(di->dev, "no battery platform data supplied\n");
+               ret = -EINVAL;
+               goto free_device_info;
+       }
+
+       di->autopower = false;
+
+       /* AC supply */
+       /* power_supply base class */
+       di->ac_chg.psy.name = "ab8500_ac";
+       di->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS;
+       di->ac_chg.psy.properties = ab8500_charger_ac_props;
+       di->ac_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_ac_props);
+       di->ac_chg.psy.get_property = ab8500_charger_ac_get_property;
+       di->ac_chg.psy.supplied_to = di->pdata->supplied_to;
+       di->ac_chg.psy.num_supplicants = di->pdata->num_supplicants;
+       /* ux500_charger sub-class */
+       di->ac_chg.ops.enable = &ab8500_charger_ac_en;
+       di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
+       di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current;
+       di->ac_chg.max_out_volt = ab8500_charger_voltage_map[
+               ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
+       di->ac_chg.max_out_curr = ab8500_charger_current_map[
+               ARRAY_SIZE(ab8500_charger_current_map) - 1];
+
+       /* USB supply */
+       /* power_supply base class */
+       di->usb_chg.psy.name = "ab8500_usb";
+       di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB;
+       di->usb_chg.psy.properties = ab8500_charger_usb_props;
+       di->usb_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_usb_props);
+       di->usb_chg.psy.get_property = ab8500_charger_usb_get_property;
+       di->usb_chg.psy.supplied_to = di->pdata->supplied_to;
+       di->usb_chg.psy.num_supplicants = di->pdata->num_supplicants;
+       /* ux500_charger sub-class */
+       di->usb_chg.ops.enable = &ab8500_charger_usb_en;
+       di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
+       di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current;
+       di->usb_chg.max_out_volt = ab8500_charger_voltage_map[
+               ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
+       di->usb_chg.max_out_curr = ab8500_charger_current_map[
+               ARRAY_SIZE(ab8500_charger_current_map) - 1];
+
+
+       /* Create a work queue for the charger */
+       di->charger_wq =
+               create_singlethread_workqueue("ab8500_charger_wq");
+       if (di->charger_wq == NULL) {
+               dev_err(di->dev, "failed to create work queue\n");
+               goto free_device_info;
+       }
+
+       /* Init work for HW failure check */
+       INIT_DELAYED_WORK_DEFERRABLE(&di->check_hw_failure_work,
+               ab8500_charger_check_hw_failure_work);
+       INIT_DELAYED_WORK_DEFERRABLE(&di->check_usbchgnotok_work,
+               ab8500_charger_check_usbchargernotok_work);
+
+       /*
+        * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+        * logic. That means we have to continously kick the charger
+        * watchdog even when no charger is connected. This is only
+        * valid once the AC charger has been enabled. This is
+        * a bug that is not handled by the algorithm and the
+        * watchdog have to be kicked by the charger driver
+        * when the AC charger is disabled
+        */
+       INIT_DELAYED_WORK_DEFERRABLE(&di->kick_wd_work,
+               ab8500_charger_kick_watchdog_work);
+
+       INIT_DELAYED_WORK_DEFERRABLE(&di->check_vbat_work,
+               ab8500_charger_check_vbat_work);
+
+       /* Init work for charger detection */
+       INIT_WORK(&di->usb_link_status_work,
+               ab8500_charger_usb_link_status_work);
+       INIT_WORK(&di->ac_work, ab8500_charger_ac_work);
+       INIT_WORK(&di->detect_usb_type_work,
+               ab8500_charger_detect_usb_type_work);
+
+       INIT_WORK(&di->usb_state_changed_work,
+               ab8500_charger_usb_state_changed_work);
+
+       /* Init work for checking HW status */
+       INIT_WORK(&di->check_main_thermal_prot_work,
+               ab8500_charger_check_main_thermal_prot_work);
+       INIT_WORK(&di->check_usb_thermal_prot_work,
+               ab8500_charger_check_usb_thermal_prot_work);
+
+       /*
+        * VDD ADC supply needs to be enabled from this driver when there
+        * is a charger connected to avoid erroneous BTEMP_HIGH/LOW
+        * interrupts during charging
+        */
+       di->regu = regulator_get(di->dev, "vddadc");
+       if (IS_ERR(di->regu)) {
+               ret = PTR_ERR(di->regu);
+               dev_err(di->dev, "failed to get vddadc regulator\n");
+               goto free_charger_wq;
+       }
+
+
+       /* Initialize OVV, and other registers */
+       ret = ab8500_charger_init_hw_registers(di);
+       if (ret) {
+               dev_err(di->dev, "failed to initialize ABB registers\n");
+               goto free_regulator;
+       }
+
+       /* Register AC charger class */
+       ret = power_supply_register(di->dev, &di->ac_chg.psy);
+       if (ret) {
+               dev_err(di->dev, "failed to register AC charger\n");
+               goto free_regulator;
+       }
+
+       /* Register USB charger class */
+       ret = power_supply_register(di->dev, &di->usb_chg.psy);
+       if (ret) {
+               dev_err(di->dev, "failed to register USB charger\n");
+               goto free_ac;
+       }
+
+       di->usb_phy = usb_get_transceiver();
+       if (!di->usb_phy) {
+               dev_err(di->dev, "failed to get usb transceiver\n");
+               ret = -EINVAL;
+               goto free_usb;
+       }
+       di->nb.notifier_call = ab8500_charger_usb_notifier_call;
+       ret = usb_register_notifier(di->usb_phy, &di->nb);
+       if (ret) {
+               dev_err(di->dev, "failed to register usb notifier\n");
+               goto put_usb_phy;
+       }
+
+       /* Identify the connected charger types during startup */
+       charger_status = ab8500_charger_detect_chargers(di);
+       if (charger_status & AC_PW_CONN) {
+               di->ac.charger_connected = 1;
+               di->ac_conn = true;
+               ab8500_power_supply_changed(di, &di->ac_chg.psy);
+               sysfs_notify(&di->ac_chg.psy.dev->kobj, NULL, "present");
+       }
+
+       if (charger_status & USB_PW_CONN) {
+               dev_dbg(di->dev, "VBUS Detect during startup\n");
+               di->vbus_detected = true;
+               di->vbus_detected_start = true;
+               queue_work(di->charger_wq,
+                       &di->detect_usb_type_work);
+       }
+
+       /* Register interrupts */
+       for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
+               irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+               ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr,
+                       IRQF_SHARED | IRQF_NO_SUSPEND,
+                       ab8500_charger_irq[i].name, di);
+
+               if (ret != 0) {
+                       dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+                               , ab8500_charger_irq[i].name, irq, ret);
+                       goto free_irq;
+               }
+               dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+                       ab8500_charger_irq[i].name, irq, ret);
+       }
+
+       platform_set_drvdata(pdev, di);
+
+       return ret;
+
+free_irq:
+       usb_unregister_notifier(di->usb_phy, &di->nb);
+
+       /* We also have to free all successfully registered irqs */
+       for (i = i - 1; i >= 0; i--) {
+               irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+               free_irq(irq, di);
+       }
+put_usb_phy:
+       usb_put_transceiver(di->usb_phy);
+free_usb:
+       power_supply_unregister(&di->usb_chg.psy);
+free_ac:
+       power_supply_unregister(&di->ac_chg.psy);
+free_regulator:
+       regulator_put(di->regu);
+free_charger_wq:
+       destroy_workqueue(di->charger_wq);
+free_device_info:
+       kfree(di);
+
+       return ret;
+}
+
+static struct platform_driver ab8500_charger_driver = {
+       .probe = ab8500_charger_probe,
+       .remove = __devexit_p(ab8500_charger_remove),
+       .suspend = ab8500_charger_suspend,
+       .resume = ab8500_charger_resume,
+       .driver = {
+               .name = "ab8500-charger",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init ab8500_charger_init(void)
+{
+       return platform_driver_register(&ab8500_charger_driver);
+}
+
+static void __exit ab8500_charger_exit(void)
+{
+       platform_driver_unregister(&ab8500_charger_driver);
+}
+
+subsys_initcall_sync(ab8500_charger_init);
+module_exit(ab8500_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
+MODULE_ALIAS("platform:ab8500-charger");
+MODULE_DESCRIPTION("AB8500 charger management driver");
diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c
new file mode 100644 (file)
index 0000000..c22f2f0
--- /dev/null
@@ -0,0 +1,2637 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2012
+ *
+ * Main and Back-up battery management driver.
+ *
+ * Note: Backup battery management is required in case of Li-Ion battery and not
+ * for capacitive battery. HREF boards have capacitive battery and hence backup
+ * battery management is not used and the supported code is available in this
+ * driver.
+ *
+ * License Terms: GNU General Public License v2
+ * Author:
+ *     Johan Palsson <johan.palsson@stericsson.com>
+ *     Karl Komierowski <karl.komierowski@stericsson.com>
+ *     Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/kobject.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500.h>
+#include <linux/slab.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/delay.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/mfd/abx500.h>
+#include <linux/time.h>
+#include <linux/completion.h>
+
+#define MILLI_TO_MICRO                 1000
+#define FG_LSB_IN_MA                   1627
+#define QLSB_NANO_AMP_HOURS_X10                1129
+#define INS_CURR_TIMEOUT               (3 * HZ)
+
+#define SEC_TO_SAMPLE(S)               (S * 4)
+
+#define NBR_AVG_SAMPLES                        20
+
+#define LOW_BAT_CHECK_INTERVAL         (2 * HZ)
+
+#define VALID_CAPACITY_SEC             (45 * 60) /* 45 minutes */
+#define BATT_OK_MIN                    2360 /* mV */
+#define BATT_OK_INCREMENT              50 /* mV */
+#define BATT_OK_MAX_NR_INCREMENTS      0xE
+
+/* FG constants */
+#define BATT_OVV                       0x01
+
+#define interpolate(x, x1, y1, x2, y2) \
+       ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1))));
+
+#define to_ab8500_fg_device_info(x) container_of((x), \
+       struct ab8500_fg, fg_psy);
+
+/**
+ * struct ab8500_fg_interrupts - ab8500 fg interupts
+ * @name:      name of the interrupt
+ * @isr                function pointer to the isr
+ */
+struct ab8500_fg_interrupts {
+       char *name;
+       irqreturn_t (*isr)(int irq, void *data);
+};
+
+enum ab8500_fg_discharge_state {
+       AB8500_FG_DISCHARGE_INIT,
+       AB8500_FG_DISCHARGE_INITMEASURING,
+       AB8500_FG_DISCHARGE_INIT_RECOVERY,
+       AB8500_FG_DISCHARGE_RECOVERY,
+       AB8500_FG_DISCHARGE_READOUT_INIT,
+       AB8500_FG_DISCHARGE_READOUT,
+       AB8500_FG_DISCHARGE_WAKEUP,
+};
+
+static char *discharge_state[] = {
+       "DISCHARGE_INIT",
+       "DISCHARGE_INITMEASURING",
+       "DISCHARGE_INIT_RECOVERY",
+       "DISCHARGE_RECOVERY",
+       "DISCHARGE_READOUT_INIT",
+       "DISCHARGE_READOUT",
+       "DISCHARGE_WAKEUP",
+};
+
+enum ab8500_fg_charge_state {
+       AB8500_FG_CHARGE_INIT,
+       AB8500_FG_CHARGE_READOUT,
+};
+
+static char *charge_state[] = {
+       "CHARGE_INIT",
+       "CHARGE_READOUT",
+};
+
+enum ab8500_fg_calibration_state {
+       AB8500_FG_CALIB_INIT,
+       AB8500_FG_CALIB_WAIT,
+       AB8500_FG_CALIB_END,
+};
+
+struct ab8500_fg_avg_cap {
+       int avg;
+       int samples[NBR_AVG_SAMPLES];
+       __kernel_time_t time_stamps[NBR_AVG_SAMPLES];
+       int pos;
+       int nbr_samples;
+       int sum;
+};
+
+struct ab8500_fg_battery_capacity {
+       int max_mah_design;
+       int max_mah;
+       int mah;
+       int permille;
+       int level;
+       int prev_mah;
+       int prev_percent;
+       int prev_level;
+       int user_mah;
+};
+
+struct ab8500_fg_flags {
+       bool fg_enabled;
+       bool conv_done;
+       bool charging;
+       bool fully_charged;
+       bool force_full;
+       bool low_bat_delay;
+       bool low_bat;
+       bool bat_ovv;
+       bool batt_unknown;
+       bool calibrate;
+       bool user_cap;
+       bool batt_id_received;
+};
+
+struct inst_curr_result_list {
+       struct list_head list;
+       int *result;
+};
+
+/**
+ * struct ab8500_fg - ab8500 FG device information
+ * @dev:               Pointer to the structure device
+ * @node:              a list of AB8500 FGs, hence prepared for reentrance
+ * @irq                        holds the CCEOC interrupt number
+ * @vbat:              Battery voltage in mV
+ * @vbat_nom:          Nominal battery voltage in mV
+ * @inst_curr:         Instantenous battery current in mA
+ * @avg_curr:          Average battery current in mA
+ * @bat_temp           battery temperature
+ * @fg_samples:                Number of samples used in the FG accumulation
+ * @accu_charge:       Accumulated charge from the last conversion
+ * @recovery_cnt:      Counter for recovery mode
+ * @high_curr_cnt:     Counter for high current mode
+ * @init_cnt:          Counter for init mode
+ * @recovery_needed:   Indicate if recovery is needed
+ * @high_curr_mode:    Indicate if we're in high current mode
+ * @init_capacity:     Indicate if initial capacity measuring should be done
+ * @turn_off_fg:       True if fg was off before current measurement
+ * @calib_state                State during offset calibration
+ * @discharge_state:   Current discharge state
+ * @charge_state:      Current charge state
+ * @ab8500_fg_complete Completion struct used for the instant current reading
+ * @flags:             Structure for information about events triggered
+ * @bat_cap:           Structure for battery capacity specific parameters
+ * @avg_cap:           Average capacity filter
+ * @parent:            Pointer to the struct ab8500
+ * @gpadc:             Pointer to the struct gpadc
+ * @pdata:             Pointer to the abx500_fg platform data
+ * @bat:               Pointer to the abx500_bm platform data
+ * @fg_psy:            Structure that holds the FG specific battery properties
+ * @fg_wq:             Work queue for running the FG algorithm
+ * @fg_periodic_work:  Work to run the FG algorithm periodically
+ * @fg_low_bat_work:   Work to check low bat condition
+ * @fg_reinit_work     Work used to reset and reinitialise the FG algorithm
+ * @fg_work:           Work to run the FG algorithm instantly
+ * @fg_acc_cur_work:   Work to read the FG accumulator
+ * @fg_check_hw_failure_work:  Work for checking HW state
+ * @cc_lock:           Mutex for locking the CC
+ * @fg_kobject:                Structure of type kobject
+ */
+struct ab8500_fg {
+       struct device *dev;
+       struct list_head node;
+       int irq;
+       int vbat;
+       int vbat_nom;
+       int inst_curr;
+       int avg_curr;
+       int bat_temp;
+       int fg_samples;
+       int accu_charge;
+       int recovery_cnt;
+       int high_curr_cnt;
+       int init_cnt;
+       bool recovery_needed;
+       bool high_curr_mode;
+       bool init_capacity;
+       bool turn_off_fg;
+       enum ab8500_fg_calibration_state calib_state;
+       enum ab8500_fg_discharge_state discharge_state;
+       enum ab8500_fg_charge_state charge_state;
+       struct completion ab8500_fg_complete;
+       struct ab8500_fg_flags flags;
+       struct ab8500_fg_battery_capacity bat_cap;
+       struct ab8500_fg_avg_cap avg_cap;
+       struct ab8500 *parent;
+       struct ab8500_gpadc *gpadc;
+       struct abx500_fg_platform_data *pdata;
+       struct abx500_bm_data *bat;
+       struct power_supply fg_psy;
+       struct workqueue_struct *fg_wq;
+       struct delayed_work fg_periodic_work;
+       struct delayed_work fg_low_bat_work;
+       struct delayed_work fg_reinit_work;
+       struct work_struct fg_work;
+       struct work_struct fg_acc_cur_work;
+       struct delayed_work fg_check_hw_failure_work;
+       struct mutex cc_lock;
+       struct kobject fg_kobject;
+};
+static LIST_HEAD(ab8500_fg_list);
+
+/**
+ * ab8500_fg_get() - returns a reference to the primary AB8500 fuel gauge
+ * (i.e. the first fuel gauge in the instance list)
+ */
+struct ab8500_fg *ab8500_fg_get(void)
+{
+       struct ab8500_fg *fg;
+
+       if (list_empty(&ab8500_fg_list))
+               return NULL;
+
+       fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node);
+       return fg;
+}
+
+/* Main battery properties */
+static enum power_supply_property ab8500_fg_props[] = {
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+       POWER_SUPPLY_PROP_CURRENT_AVG,
+       POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+       POWER_SUPPLY_PROP_ENERGY_FULL,
+       POWER_SUPPLY_PROP_ENERGY_NOW,
+       POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+       POWER_SUPPLY_PROP_CHARGE_FULL,
+       POWER_SUPPLY_PROP_CHARGE_NOW,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+};
+
+/*
+ * This array maps the raw hex value to lowbat voltage used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_fg_lowbat_voltage_map[] = {
+       2300 ,
+       2325 ,
+       2350 ,
+       2375 ,
+       2400 ,
+       2425 ,
+       2450 ,
+       2475 ,
+       2500 ,
+       2525 ,
+       2550 ,
+       2575 ,
+       2600 ,
+       2625 ,
+       2650 ,
+       2675 ,
+       2700 ,
+       2725 ,
+       2750 ,
+       2775 ,
+       2800 ,
+       2825 ,
+       2850 ,
+       2875 ,
+       2900 ,
+       2925 ,
+       2950 ,
+       2975 ,
+       3000 ,
+       3025 ,
+       3050 ,
+       3075 ,
+       3100 ,
+       3125 ,
+       3150 ,
+       3175 ,
+       3200 ,
+       3225 ,
+       3250 ,
+       3275 ,
+       3300 ,
+       3325 ,
+       3350 ,
+       3375 ,
+       3400 ,
+       3425 ,
+       3450 ,
+       3475 ,
+       3500 ,
+       3525 ,
+       3550 ,
+       3575 ,
+       3600 ,
+       3625 ,
+       3650 ,
+       3675 ,
+       3700 ,
+       3725 ,
+       3750 ,
+       3775 ,
+       3800 ,
+       3825 ,
+       3850 ,
+       3850 ,
+};
+
+static u8 ab8500_volt_to_regval(int voltage)
+{
+       int i;
+
+       if (voltage < ab8500_fg_lowbat_voltage_map[0])
+               return 0;
+
+       for (i = 0; i < ARRAY_SIZE(ab8500_fg_lowbat_voltage_map); i++) {
+               if (voltage < ab8500_fg_lowbat_voltage_map[i])
+                       return (u8) i - 1;
+       }
+
+       /* If not captured above, return index of last element */
+       return (u8) ARRAY_SIZE(ab8500_fg_lowbat_voltage_map) - 1;
+}
+
+/**
+ * ab8500_fg_is_low_curr() - Low or high current mode
+ * @di:                pointer to the ab8500_fg structure
+ * @curr:      the current to base or our decision on
+ *
+ * Low current mode if the current consumption is below a certain threshold
+ */
+static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr)
+{
+       /*
+        * We want to know if we're in low current mode
+        */
+       if (curr > -di->bat->fg_params->high_curr_threshold)
+               return true;
+       else
+               return false;
+}
+
+/**
+ * ab8500_fg_add_cap_sample() - Add capacity to average filter
+ * @di:                pointer to the ab8500_fg structure
+ * @sample:    the capacity in mAh to add to the filter
+ *
+ * A capacity is added to the filter and a new mean capacity is calculated and
+ * returned
+ */
+static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample)
+{
+       struct timespec ts;
+       struct ab8500_fg_avg_cap *avg = &di->avg_cap;
+
+       getnstimeofday(&ts);
+
+       do {
+               avg->sum += sample - avg->samples[avg->pos];
+               avg->samples[avg->pos] = sample;
+               avg->time_stamps[avg->pos] = ts.tv_sec;
+               avg->pos++;
+
+               if (avg->pos == NBR_AVG_SAMPLES)
+                       avg->pos = 0;
+
+               if (avg->nbr_samples < NBR_AVG_SAMPLES)
+                       avg->nbr_samples++;
+
+               /*
+                * Check the time stamp for each sample. If too old,
+                * replace with latest sample
+                */
+       } while (ts.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]);
+
+       avg->avg = avg->sum / avg->nbr_samples;
+
+       return avg->avg;
+}
+
+/**
+ * ab8500_fg_clear_cap_samples() - Clear average filter
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * The capacity filter is is reset to zero.
+ */
+static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di)
+{
+       int i;
+       struct ab8500_fg_avg_cap *avg = &di->avg_cap;
+
+       avg->pos = 0;
+       avg->nbr_samples = 0;
+       avg->sum = 0;
+       avg->avg = 0;
+
+       for (i = 0; i < NBR_AVG_SAMPLES; i++) {
+               avg->samples[i] = 0;
+               avg->time_stamps[i] = 0;
+       }
+}
+
+/**
+ * ab8500_fg_fill_cap_sample() - Fill average filter
+ * @di:                pointer to the ab8500_fg structure
+ * @sample:    the capacity in mAh to fill the filter with
+ *
+ * The capacity filter is filled with a capacity in mAh
+ */
+static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample)
+{
+       int i;
+       struct timespec ts;
+       struct ab8500_fg_avg_cap *avg = &di->avg_cap;
+
+       getnstimeofday(&ts);
+
+       for (i = 0; i < NBR_AVG_SAMPLES; i++) {
+               avg->samples[i] = sample;
+               avg->time_stamps[i] = ts.tv_sec;
+       }
+
+       avg->pos = 0;
+       avg->nbr_samples = NBR_AVG_SAMPLES;
+       avg->sum = sample * NBR_AVG_SAMPLES;
+       avg->avg = sample;
+}
+
+/**
+ * ab8500_fg_coulomb_counter() - enable coulomb counter
+ * @di:                pointer to the ab8500_fg structure
+ * @enable:    enable/disable
+ *
+ * Enable/Disable coulomb counter.
+ * On failure returns negative value.
+ */
+static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable)
+{
+       int ret = 0;
+       mutex_lock(&di->cc_lock);
+       if (enable) {
+               /* To be able to reprogram the number of samples, we have to
+                * first stop the CC and then enable it again */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+                       AB8500_RTC_CC_CONF_REG, 0x00);
+               if (ret)
+                       goto cc_err;
+
+               /* Program the samples */
+               ret = abx500_set_register_interruptible(di->dev,
+                       AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU,
+                       di->fg_samples);
+               if (ret)
+                       goto cc_err;
+
+               /* Start the CC */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+                       AB8500_RTC_CC_CONF_REG,
+                       (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+               if (ret)
+                       goto cc_err;
+
+               di->flags.fg_enabled = true;
+       } else {
+               /* Clear any pending read requests */
+               ret = abx500_set_register_interruptible(di->dev,
+                       AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0);
+               if (ret)
+                       goto cc_err;
+
+               ret = abx500_set_register_interruptible(di->dev,
+                       AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU_CTRL, 0);
+               if (ret)
+                       goto cc_err;
+
+               /* Stop the CC */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+                       AB8500_RTC_CC_CONF_REG, 0);
+               if (ret)
+                       goto cc_err;
+
+               di->flags.fg_enabled = false;
+
+       }
+       dev_dbg(di->dev, " CC enabled: %d Samples: %d\n",
+               enable, di->fg_samples);
+
+       mutex_unlock(&di->cc_lock);
+
+       return ret;
+cc_err:
+       dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__);
+       mutex_unlock(&di->cc_lock);
+       return ret;
+}
+
+/**
+ * ab8500_fg_inst_curr_start() - start battery instantaneous current
+ * @di:         pointer to the ab8500_fg structure
+ *
+ * Returns 0 or error code
+ * Note: This is part "one" and has to be called before
+ * ab8500_fg_inst_curr_finalize()
+ */
+ int ab8500_fg_inst_curr_start(struct ab8500_fg *di)
+{
+       u8 reg_val;
+       int ret;
+
+       mutex_lock(&di->cc_lock);
+
+       ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+               AB8500_RTC_CC_CONF_REG, &reg_val);
+       if (ret < 0)
+               goto fail;
+
+       if (!(reg_val & CC_PWR_UP_ENA)) {
+               dev_dbg(di->dev, "%s Enable FG\n", __func__);
+               di->turn_off_fg = true;
+
+               /* Program the samples */
+               ret = abx500_set_register_interruptible(di->dev,
+                       AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU,
+                       SEC_TO_SAMPLE(10));
+               if (ret)
+                       goto fail;
+
+               /* Start the CC */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+                       AB8500_RTC_CC_CONF_REG,
+                       (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+               if (ret)
+                       goto fail;
+       } else {
+               di->turn_off_fg = false;
+       }
+
+       /* Return and WFI */
+       INIT_COMPLETION(di->ab8500_fg_complete);
+       enable_irq(di->irq);
+
+       /* Note: cc_lock is still locked */
+       return 0;
+fail:
+       mutex_unlock(&di->cc_lock);
+       return ret;
+}
+
+/**
+ * ab8500_fg_inst_curr_done() - check if fg conversion is done
+ * @di:         pointer to the ab8500_fg structure
+ *
+ * Returns 1 if conversion done, 0 if still waiting
+ */
+int ab8500_fg_inst_curr_done(struct ab8500_fg *di)
+{
+       return completion_done(&di->ab8500_fg_complete);
+}
+
+/**
+ * ab8500_fg_inst_curr_finalize() - battery instantaneous current
+ * @di:         pointer to the ab8500_fg structure
+ * @res:       battery instantenous current(on success)
+ *
+ * Returns 0 or an error code
+ * Note: This is part "two" and has to be called at earliest 250 ms
+ * after ab8500_fg_inst_curr_start()
+ */
+int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res)
+{
+       u8 low, high;
+       int val;
+       int ret;
+       int timeout;
+
+       if (!completion_done(&di->ab8500_fg_complete)) {
+               timeout = wait_for_completion_timeout(&di->ab8500_fg_complete,
+                       INS_CURR_TIMEOUT);
+               dev_dbg(di->dev, "Finalize time: %d ms\n",
+                       ((INS_CURR_TIMEOUT - timeout) * 1000) / HZ);
+               if (!timeout) {
+                       ret = -ETIME;
+                       disable_irq(di->irq);
+                       dev_err(di->dev, "completion timed out [%d]\n",
+                               __LINE__);
+                       goto fail;
+               }
+       }
+
+       disable_irq(di->irq);
+
+       ret = abx500_mask_and_set_register_interruptible(di->dev,
+                       AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+                       READ_REQ, READ_REQ);
+
+       /* 100uS between read request and read is needed */
+       usleep_range(100, 100);
+
+       /* Read CC Sample conversion value Low and high */
+       ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+               AB8500_GASG_CC_SMPL_CNVL_REG,  &low);
+       if (ret < 0)
+               goto fail;
+
+       ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+               AB8500_GASG_CC_SMPL_CNVH_REG,  &high);
+       if (ret < 0)
+               goto fail;
+
+       /*
+        * negative value for Discharging
+        * convert 2's compliment into decimal
+        */
+       if (high & 0x10)
+               val = (low | (high << 8) | 0xFFFFE000);
+       else
+               val = (low | (high << 8));
+
+       /*
+        * Convert to unit value in mA
+        * Full scale input voltage is
+        * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA
+        * Given a 250ms conversion cycle time the LSB corresponds
+        * to 112.9 nAh. Convert to current by dividing by the conversion
+        * time in hours (250ms = 1 / (3600 * 4)h)
+        * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+        */
+       val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) /
+               (1000 * di->bat->fg_res);
+
+       if (di->turn_off_fg) {
+               dev_dbg(di->dev, "%s Disable FG\n", __func__);
+
+               /* Clear any pending read requests */
+               ret = abx500_set_register_interruptible(di->dev,
+                       AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0);
+               if (ret)
+                       goto fail;
+
+               /* Stop the CC */
+               ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+                       AB8500_RTC_CC_CONF_REG, 0);
+               if (ret)
+                       goto fail;
+       }
+       mutex_unlock(&di->cc_lock);
+       (*res) = val;
+
+       return 0;
+fail:
+       mutex_unlock(&di->cc_lock);
+       return ret;
+}
+
+/**
+ * ab8500_fg_inst_curr_blocking() - battery instantaneous current
+ * @di:         pointer to the ab8500_fg structure
+ * @res:       battery instantenous current(on success)
+ *
+ * Returns 0 else error code
+ */
+int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di)
+{
+       int ret;
+       int res = 0;
+
+       ret = ab8500_fg_inst_curr_start(di);
+       if (ret) {
+               dev_err(di->dev, "Failed to initialize fg_inst\n");
+               return 0;
+       }
+
+       ret = ab8500_fg_inst_curr_finalize(di, &res);
+       if (ret) {
+               dev_err(di->dev, "Failed to finalize fg_inst\n");
+               return 0;
+       }
+
+       return res;
+}
+
+/**
+ * ab8500_fg_acc_cur_work() - average battery current
+ * @work:      pointer to the work_struct structure
+ *
+ * Updated the average battery current obtained from the
+ * coulomb counter.
+ */
+static void ab8500_fg_acc_cur_work(struct work_struct *work)
+{
+       int val;
+       int ret;
+       u8 low, med, high;
+
+       struct ab8500_fg *di = container_of(work,
+               struct ab8500_fg, fg_acc_cur_work);
+
+       mutex_lock(&di->cc_lock);
+       ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+               AB8500_GASG_CC_NCOV_ACCU_CTRL, RD_NCONV_ACCU_REQ);
+       if (ret)
+               goto exit;
+
+       ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+               AB8500_GASG_CC_NCOV_ACCU_LOW,  &low);
+       if (ret < 0)
+               goto exit;
+
+       ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+               AB8500_GASG_CC_NCOV_ACCU_MED,  &med);
+       if (ret < 0)
+               goto exit;
+
+       ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+               AB8500_GASG_CC_NCOV_ACCU_HIGH, &high);
+       if (ret < 0)
+               goto exit;
+
+       /* Check for sign bit in case of negative value, 2's compliment */
+       if (high & 0x10)
+               val = (low | (med << 8) | (high << 16) | 0xFFE00000);
+       else
+               val = (low | (med << 8) | (high << 16));
+
+       /*
+        * Convert to uAh
+        * Given a 250ms conversion cycle time the LSB corresponds
+        * to 112.9 nAh.
+        * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+        */
+       di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10) /
+               (100 * di->bat->fg_res);
+
+       /*
+        * Convert to unit value in mA
+        * Full scale input voltage is
+        * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA
+        * Given a 250ms conversion cycle time the LSB corresponds
+        * to 112.9 nAh. Convert to current by dividing by the conversion
+        * time in hours (= samples / (3600 * 4)h)
+        * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+        */
+       di->avg_curr = (val * QLSB_NANO_AMP_HOURS_X10 * 36) /
+               (1000 * di->bat->fg_res * (di->fg_samples / 4));
+
+       di->flags.conv_done = true;
+
+       mutex_unlock(&di->cc_lock);
+
+       queue_work(di->fg_wq, &di->fg_work);
+
+       return;
+exit:
+       dev_err(di->dev,
+               "Failed to read or write gas gauge registers\n");
+       mutex_unlock(&di->cc_lock);
+       queue_work(di->fg_wq, &di->fg_work);
+}
+
+/**
+ * ab8500_fg_bat_voltage() - get battery voltage
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Returns battery voltage(on success) else error code
+ */
+static int ab8500_fg_bat_voltage(struct ab8500_fg *di)
+{
+       int vbat;
+       static int prev;
+
+       vbat = ab8500_gpadc_convert(di->gpadc, MAIN_BAT_V);
+       if (vbat < 0) {
+               dev_err(di->dev,
+                       "%s gpadc conversion failed, using previous value\n",
+                       __func__);
+               return prev;
+       }
+
+       prev = vbat;
+       return vbat;
+}
+
+/**
+ * ab8500_fg_volt_to_capacity() - Voltage based capacity
+ * @di:                pointer to the ab8500_fg structure
+ * @voltage:   The voltage to convert to a capacity
+ *
+ * Returns battery capacity in per mille based on voltage
+ */
+static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage)
+{
+       int i, tbl_size;
+       struct abx500_v_to_cap *tbl;
+       int cap = 0;
+
+       tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl,
+       tbl_size = di->bat->bat_type[di->bat->batt_id].n_v_cap_tbl_elements;
+
+       for (i = 0; i < tbl_size; ++i) {
+               if (voltage > tbl[i].voltage)
+                       break;
+       }
+
+       if ((i > 0) && (i < tbl_size)) {
+               cap = interpolate(voltage,
+                       tbl[i].voltage,
+                       tbl[i].capacity * 10,
+                       tbl[i-1].voltage,
+                       tbl[i-1].capacity * 10);
+       } else if (i == 0) {
+               cap = 1000;
+       } else {
+               cap = 0;
+       }
+
+       dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille",
+               __func__, voltage, cap);
+
+       return cap;
+}
+
+/**
+ * ab8500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Returns battery capacity based on battery voltage that is not compensated
+ * for the voltage drop due to the load
+ */
+static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di)
+{
+       di->vbat = ab8500_fg_bat_voltage(di);
+       return ab8500_fg_volt_to_capacity(di, di->vbat);
+}
+
+/**
+ * ab8500_fg_battery_resistance() - Returns the battery inner resistance
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Returns battery inner resistance added with the fuel gauge resistor value
+ * to get the total resistance in the whole link from gnd to bat+ node.
+ */
+static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
+{
+       int i, tbl_size;
+       struct batres_vs_temp *tbl;
+       int resist = 0;
+
+       tbl = di->bat->bat_type[di->bat->batt_id].batres_tbl;
+       tbl_size = di->bat->bat_type[di->bat->batt_id].n_batres_tbl_elements;
+
+       for (i = 0; i < tbl_size; ++i) {
+               if (di->bat_temp / 10 > tbl[i].temp)
+                       break;
+       }
+
+       if ((i > 0) && (i < tbl_size)) {
+               resist = interpolate(di->bat_temp / 10,
+                       tbl[i].temp,
+                       tbl[i].resist,
+                       tbl[i-1].temp,
+                       tbl[i-1].resist);
+       } else if (i == 0) {
+               resist = tbl[0].resist;
+       } else {
+               resist = tbl[tbl_size - 1].resist;
+       }
+
+       dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d"
+           " fg resistance %d, total: %d (mOhm)\n",
+               __func__, di->bat_temp, resist, di->bat->fg_res / 10,
+               (di->bat->fg_res / 10) + resist);
+
+       /* fg_res variable is in 0.1mOhm */
+       resist += di->bat->fg_res / 10;
+
+       return resist;
+}
+
+/**
+ * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Returns battery capacity based on battery voltage that is load compensated
+ * for the voltage drop
+ */
+static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di)
+{
+       int vbat_comp, res;
+       int i = 0;
+       int vbat = 0;
+
+       ab8500_fg_inst_curr_start(di);
+
+       do {
+               vbat += ab8500_fg_bat_voltage(di);
+               i++;
+               msleep(5);
+       } while (!ab8500_fg_inst_curr_done(di));
+
+       ab8500_fg_inst_curr_finalize(di, &di->inst_curr);
+
+       di->vbat = vbat / i;
+       res = ab8500_fg_battery_resistance(di);
+
+       /* Use Ohms law to get the load compensated voltage */
+       vbat_comp = di->vbat - (di->inst_curr * res) / 1000;
+
+       dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, "
+               "R: %dmOhm, Current: %dmA Vbat Samples: %d\n",
+               __func__, di->vbat, vbat_comp, res, di->inst_curr, i);
+
+       return ab8500_fg_volt_to_capacity(di, vbat_comp);
+}
+
+/**
+ * ab8500_fg_convert_mah_to_permille() - Capacity in mAh to permille
+ * @di:                pointer to the ab8500_fg structure
+ * @cap_mah:   capacity in mAh
+ *
+ * Converts capacity in mAh to capacity in permille
+ */
+static int ab8500_fg_convert_mah_to_permille(struct ab8500_fg *di, int cap_mah)
+{
+       return (cap_mah * 1000) / di->bat_cap.max_mah_design;
+}
+
+/**
+ * ab8500_fg_convert_permille_to_mah() - Capacity in permille to mAh
+ * @di:                pointer to the ab8500_fg structure
+ * @cap_pm:    capacity in permille
+ *
+ * Converts capacity in permille to capacity in mAh
+ */
+static int ab8500_fg_convert_permille_to_mah(struct ab8500_fg *di, int cap_pm)
+{
+       return cap_pm * di->bat_cap.max_mah_design / 1000;
+}
+
+/**
+ * ab8500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh
+ * @di:                pointer to the ab8500_fg structure
+ * @cap_mah:   capacity in mAh
+ *
+ * Converts capacity in mAh to capacity in uWh
+ */
+static int ab8500_fg_convert_mah_to_uwh(struct ab8500_fg *di, int cap_mah)
+{
+       u64 div_res;
+       u32 div_rem;
+
+       div_res = ((u64) cap_mah) * ((u64) di->vbat_nom);
+       div_rem = do_div(div_res, 1000);
+
+       /* Make sure to round upwards if necessary */
+       if (div_rem >= 1000 / 2)
+               div_res++;
+
+       return (int) div_res;
+}
+
+/**
+ * ab8500_fg_calc_cap_charging() - Calculate remaining capacity while charging
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Return the capacity in mAh based on previous calculated capcity and the FG
+ * accumulator register value. The filter is filled with this capacity
+ */
+static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di)
+{
+       dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n",
+               __func__,
+               di->bat_cap.mah,
+               di->accu_charge);
+
+       /* Capacity should not be less than 0 */
+       if (di->bat_cap.mah + di->accu_charge > 0)
+               di->bat_cap.mah += di->accu_charge;
+       else
+               di->bat_cap.mah = 0;
+       /*
+        * We force capacity to 100% once when the algorithm
+        * reports that it's full.
+        */
+       if (di->bat_cap.mah >= di->bat_cap.max_mah_design ||
+               di->flags.force_full) {
+               di->bat_cap.mah = di->bat_cap.max_mah_design;
+       }
+
+       ab8500_fg_fill_cap_sample(di, di->bat_cap.mah);
+       di->bat_cap.permille =
+               ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+
+       /* We need to update battery voltage and inst current when charging */
+       di->vbat = ab8500_fg_bat_voltage(di);
+       di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+       return di->bat_cap.mah;
+}
+
+/**
+ * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage
+ * @di:                pointer to the ab8500_fg structure
+ * @comp:      if voltage should be load compensated before capacity calc
+ *
+ * Return the capacity in mAh based on the battery voltage. The voltage can
+ * either be load compensated or not. This value is added to the filter and a
+ * new mean value is calculated and returned.
+ */
+static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp)
+{
+       int permille, mah;
+
+       if (comp)
+               permille = ab8500_fg_load_comp_volt_to_capacity(di);
+       else
+               permille = ab8500_fg_uncomp_volt_to_capacity(di);
+
+       mah = ab8500_fg_convert_permille_to_mah(di, permille);
+
+       di->bat_cap.mah = ab8500_fg_add_cap_sample(di, mah);
+       di->bat_cap.permille =
+               ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+
+       return di->bat_cap.mah;
+}
+
+/**
+ * ab8500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Return the capacity in mAh based on previous calculated capcity and the FG
+ * accumulator register value. This value is added to the filter and a
+ * new mean value is calculated and returned.
+ */
+static int ab8500_fg_calc_cap_discharge_fg(struct ab8500_fg *di)
+{
+       int permille_volt, permille;
+
+       dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n",
+               __func__,
+               di->bat_cap.mah,
+               di->accu_charge);
+
+       /* Capacity should not be less than 0 */
+       if (di->bat_cap.mah + di->accu_charge > 0)
+               di->bat_cap.mah += di->accu_charge;
+       else
+               di->bat_cap.mah = 0;
+
+       if (di->bat_cap.mah >= di->bat_cap.max_mah_design)
+               di->bat_cap.mah = di->bat_cap.max_mah_design;
+
+       /*
+        * Check against voltage based capacity. It can not be lower
+        * than what the uncompensated voltage says
+        */
+       permille = ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+       permille_volt = ab8500_fg_uncomp_volt_to_capacity(di);
+
+       if (permille < permille_volt) {
+               di->bat_cap.permille = permille_volt;
+               di->bat_cap.mah = ab8500_fg_convert_permille_to_mah(di,
+                       di->bat_cap.permille);
+
+               dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n",
+                       __func__,
+                       permille,
+                       permille_volt);
+
+               ab8500_fg_fill_cap_sample(di, di->bat_cap.mah);
+       } else {
+               ab8500_fg_fill_cap_sample(di, di->bat_cap.mah);
+               di->bat_cap.permille =
+                       ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+       }
+
+       return di->bat_cap.mah;
+}
+
+/**
+ * ab8500_fg_capacity_level() - Get the battery capacity level
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Get the battery capacity level based on the capacity in percent
+ */
+static int ab8500_fg_capacity_level(struct ab8500_fg *di)
+{
+       int ret, percent;
+
+       percent = di->bat_cap.permille / 10;
+
+       if (percent <= di->bat->cap_levels->critical ||
+               di->flags.low_bat)
+               ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+       else if (percent <= di->bat->cap_levels->low)
+               ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+       else if (percent <= di->bat->cap_levels->normal)
+               ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+       else if (percent <= di->bat->cap_levels->high)
+               ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+       else
+               ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+
+       return ret;
+}
+
+/**
+ * ab8500_fg_check_capacity_limits() - Check if capacity has changed
+ * @di:                pointer to the ab8500_fg structure
+ * @init:      capacity is allowed to go up in init mode
+ *
+ * Check if capacity or capacity limit has changed and notify the system
+ * about it using the power_supply framework
+ */
+static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
+{
+       bool changed = false;
+
+       di->bat_cap.level = ab8500_fg_capacity_level(di);
+
+       if (di->bat_cap.level != di->bat_cap.prev_level) {
+               /*
+                * We do not allow reported capacity level to go up
+                * unless we're charging or if we're in init
+                */
+               if (!(!di->flags.charging && di->bat_cap.level >
+                       di->bat_cap.prev_level) || init) {
+                       dev_dbg(di->dev, "level changed from %d to %d\n",
+                               di->bat_cap.prev_level,
+                               di->bat_cap.level);
+                       di->bat_cap.prev_level = di->bat_cap.level;
+                       changed = true;
+               } else {
+                       dev_dbg(di->dev, "level not allowed to go up "
+                               "since no charger is connected: %d to %d\n",
+                               di->bat_cap.prev_level,
+                               di->bat_cap.level);
+               }
+       }
+
+       /*
+        * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate
+        * shutdown
+        */
+       if (di->flags.low_bat) {
+               dev_dbg(di->dev, "Battery low, set capacity to 0\n");
+               di->bat_cap.prev_percent = 0;
+               di->bat_cap.permille = 0;
+               di->bat_cap.prev_mah = 0;
+               di->bat_cap.mah = 0;
+               changed = true;
+       } else if (di->flags.fully_charged) {
+               /*
+                * We report 100% if algorithm reported fully charged
+                * unless capacity drops too much
+                */
+               if (di->flags.force_full) {
+                       di->bat_cap.prev_percent = di->bat_cap.permille / 10;
+                       di->bat_cap.prev_mah = di->bat_cap.mah;
+               } else if (!di->flags.force_full &&
+                       di->bat_cap.prev_percent !=
+                       (di->bat_cap.permille) / 10 &&
+                       (di->bat_cap.permille / 10) <
+                       di->bat->fg_params->maint_thres) {
+                       dev_dbg(di->dev,
+                               "battery reported full "
+                               "but capacity dropping: %d\n",
+                               di->bat_cap.permille / 10);
+                       di->bat_cap.prev_percent = di->bat_cap.permille / 10;
+                       di->bat_cap.prev_mah = di->bat_cap.mah;
+
+                       changed = true;
+               }
+       } else if (di->bat_cap.prev_percent != di->bat_cap.permille / 10) {
+               if (di->bat_cap.permille / 10 == 0) {
+                       /*
+                        * We will not report 0% unless we've got
+                        * the LOW_BAT IRQ, no matter what the FG
+                        * algorithm says.
+                        */
+                       di->bat_cap.prev_percent = 1;
+                       di->bat_cap.permille = 1;
+                       di->bat_cap.prev_mah = 1;
+                       di->bat_cap.mah = 1;
+
+                       changed = true;
+               } else if (!(!di->flags.charging &&
+                       (di->bat_cap.permille / 10) >
+                       di->bat_cap.prev_percent) || init) {
+                       /*
+                        * We do not allow reported capacity to go up
+                        * unless we're charging or if we're in init
+                        */
+                       dev_dbg(di->dev,
+                               "capacity changed from %d to %d (%d)\n",
+                               di->bat_cap.prev_percent,
+                               di->bat_cap.permille / 10,
+                               di->bat_cap.permille);
+                       di->bat_cap.prev_percent = di->bat_cap.permille / 10;
+                       di->bat_cap.prev_mah = di->bat_cap.mah;
+
+                       changed = true;
+               } else {
+                       dev_dbg(di->dev, "capacity not allowed to go up since "
+                               "no charger is connected: %d to %d (%d)\n",
+                               di->bat_cap.prev_percent,
+                               di->bat_cap.permille / 10,
+                               di->bat_cap.permille);
+               }
+       }
+
+       if (changed) {
+               power_supply_changed(&di->fg_psy);
+               if (di->flags.fully_charged && di->flags.force_full) {
+                       dev_dbg(di->dev, "Battery full, notifying.\n");
+                       di->flags.force_full = false;
+                       sysfs_notify(&di->fg_kobject, NULL, "charge_full");
+               }
+               sysfs_notify(&di->fg_kobject, NULL, "charge_now");
+       }
+}
+
+static void ab8500_fg_charge_state_to(struct ab8500_fg *di,
+       enum ab8500_fg_charge_state new_state)
+{
+       dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n",
+               di->charge_state,
+               charge_state[di->charge_state],
+               new_state,
+               charge_state[new_state]);
+
+       di->charge_state = new_state;
+}
+
+static void ab8500_fg_discharge_state_to(struct ab8500_fg *di,
+       enum ab8500_fg_discharge_state new_state)
+{
+       dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n",
+               di->discharge_state,
+               discharge_state[di->discharge_state],
+               new_state,
+               discharge_state[new_state]);
+
+       di->discharge_state = new_state;
+}
+
+/**
+ * ab8500_fg_algorithm_charging() - FG algorithm for when charging
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Battery capacity calculation state machine for when we're charging
+ */
+static void ab8500_fg_algorithm_charging(struct ab8500_fg *di)
+{
+       /*
+        * If we change to discharge mode
+        * we should start with recovery
+        */
+       if (di->discharge_state != AB8500_FG_DISCHARGE_INIT_RECOVERY)
+               ab8500_fg_discharge_state_to(di,
+                       AB8500_FG_DISCHARGE_INIT_RECOVERY);
+
+       switch (di->charge_state) {
+       case AB8500_FG_CHARGE_INIT:
+               di->fg_samples = SEC_TO_SAMPLE(
+                       di->bat->fg_params->accu_charging);
+
+               ab8500_fg_coulomb_counter(di, true);
+               ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT);
+
+               break;
+
+       case AB8500_FG_CHARGE_READOUT:
+               /*
+                * Read the FG and calculate the new capacity
+                */
+               mutex_lock(&di->cc_lock);
+               if (!di->flags.conv_done) {
+                       /* Wasn't the CC IRQ that got us here */
+                       mutex_unlock(&di->cc_lock);
+                       dev_dbg(di->dev, "%s CC conv not done\n",
+                               __func__);
+
+                       break;
+               }
+               di->flags.conv_done = false;
+               mutex_unlock(&di->cc_lock);
+
+               ab8500_fg_calc_cap_charging(di);
+
+               break;
+
+       default:
+               break;
+       }
+
+       /* Check capacity limits */
+       ab8500_fg_check_capacity_limits(di, false);
+}
+
+static void force_capacity(struct ab8500_fg *di)
+{
+       int cap;
+
+       ab8500_fg_clear_cap_samples(di);
+       cap = di->bat_cap.user_mah;
+       if (cap > di->bat_cap.max_mah_design) {
+               dev_dbg(di->dev, "Remaining cap %d can't be bigger than total"
+                       " %d\n", cap, di->bat_cap.max_mah_design);
+               cap = di->bat_cap.max_mah_design;
+       }
+       ab8500_fg_fill_cap_sample(di, di->bat_cap.user_mah);
+       di->bat_cap.permille = ab8500_fg_convert_mah_to_permille(di, cap);
+       di->bat_cap.mah = cap;
+       ab8500_fg_check_capacity_limits(di, true);
+}
+
+static bool check_sysfs_capacity(struct ab8500_fg *di)
+{
+       int cap, lower, upper;
+       int cap_permille;
+
+       cap = di->bat_cap.user_mah;
+
+       cap_permille = ab8500_fg_convert_mah_to_permille(di,
+               di->bat_cap.user_mah);
+
+       lower = di->bat_cap.permille - di->bat->fg_params->user_cap_limit * 10;
+       upper = di->bat_cap.permille + di->bat->fg_params->user_cap_limit * 10;
+
+       if (lower < 0)
+               lower = 0;
+       /* 1000 is permille, -> 100 percent */
+       if (upper > 1000)
+               upper = 1000;
+
+       dev_dbg(di->dev, "Capacity limits:"
+               " (Lower: %d User: %d Upper: %d) [user: %d, was: %d]\n",
+               lower, cap_permille, upper, cap, di->bat_cap.mah);
+
+       /* If within limits, use the saved capacity and exit estimation...*/
+       if (cap_permille > lower && cap_permille < upper) {
+               dev_dbg(di->dev, "OK! Using users cap %d uAh now\n", cap);
+               force_capacity(di);
+               return true;
+       }
+       dev_dbg(di->dev, "Capacity from user out of limits, ignoring");
+       return false;
+}
+
+/**
+ * ab8500_fg_algorithm_discharging() - FG algorithm for when discharging
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Battery capacity calculation state machine for when we're discharging
+ */
+static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
+{
+       int sleep_time;
+
+       /* If we change to charge mode we should start with init */
+       if (di->charge_state != AB8500_FG_CHARGE_INIT)
+               ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
+
+       switch (di->discharge_state) {
+       case AB8500_FG_DISCHARGE_INIT:
+               /* We use the FG IRQ to work on */
+               di->init_cnt = 0;
+               di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer);
+               ab8500_fg_coulomb_counter(di, true);
+               ab8500_fg_discharge_state_to(di,
+                       AB8500_FG_DISCHARGE_INITMEASURING);
+
+               /* Intentional fallthrough */
+       case AB8500_FG_DISCHARGE_INITMEASURING:
+               /*
+                * Discard a number of samples during startup.
+                * After that, use compensated voltage for a few
+                * samples to get an initial capacity.
+                * Then go to READOUT
+                */
+               sleep_time = di->bat->fg_params->init_timer;
+
+               /* Discard the first [x] seconds */
+               if (di->init_cnt >
+                       di->bat->fg_params->init_discard_time) {
+                       ab8500_fg_calc_cap_discharge_voltage(di, true);
+
+                       ab8500_fg_check_capacity_limits(di, true);
+               }
+
+               di->init_cnt += sleep_time;
+               if (di->init_cnt > di->bat->fg_params->init_total_time)
+                       ab8500_fg_discharge_state_to(di,
+                               AB8500_FG_DISCHARGE_READOUT_INIT);
+
+               break;
+
+       case AB8500_FG_DISCHARGE_INIT_RECOVERY:
+               di->recovery_cnt = 0;
+               di->recovery_needed = true;
+               ab8500_fg_discharge_state_to(di,
+                       AB8500_FG_DISCHARGE_RECOVERY);
+
+               /* Intentional fallthrough */
+
+       case AB8500_FG_DISCHARGE_RECOVERY:
+               sleep_time = di->bat->fg_params->recovery_sleep_timer;
+
+               /*
+                * We should check the power consumption
+                * If low, go to READOUT (after x min) or
+                * RECOVERY_SLEEP if time left.
+                * If high, go to READOUT
+                */
+               di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+               if (ab8500_fg_is_low_curr(di, di->inst_curr)) {
+                       if (di->recovery_cnt >
+                               di->bat->fg_params->recovery_total_time) {
+                               di->fg_samples = SEC_TO_SAMPLE(
+                                       di->bat->fg_params->accu_high_curr);
+                               ab8500_fg_coulomb_counter(di, true);
+                               ab8500_fg_discharge_state_to(di,
+                                       AB8500_FG_DISCHARGE_READOUT);
+                               di->recovery_needed = false;
+                       } else {
+                               queue_delayed_work(di->fg_wq,
+                                       &di->fg_periodic_work,
+                                       sleep_time * HZ);
+                       }
+                       di->recovery_cnt += sleep_time;
+               } else {
+                       di->fg_samples = SEC_TO_SAMPLE(
+                               di->bat->fg_params->accu_high_curr);
+                       ab8500_fg_coulomb_counter(di, true);
+                       ab8500_fg_discharge_state_to(di,
+                               AB8500_FG_DISCHARGE_READOUT);
+               }
+               break;
+
+       case AB8500_FG_DISCHARGE_READOUT_INIT:
+               di->fg_samples = SEC_TO_SAMPLE(
+                       di->bat->fg_params->accu_high_curr);
+               ab8500_fg_coulomb_counter(di, true);
+               ab8500_fg_discharge_state_to(di,
+                               AB8500_FG_DISCHARGE_READOUT);
+               break;
+
+       case AB8500_FG_DISCHARGE_READOUT:
+               di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+               if (ab8500_fg_is_low_curr(di, di->inst_curr)) {
+                       /* Detect mode change */
+                       if (di->high_curr_mode) {
+                               di->high_curr_mode = false;
+                               di->high_curr_cnt = 0;
+                       }
+
+                       if (di->recovery_needed) {
+                               ab8500_fg_discharge_state_to(di,
+                                       AB8500_FG_DISCHARGE_RECOVERY);
+
+                               queue_delayed_work(di->fg_wq,
+                                       &di->fg_periodic_work, 0);
+
+                               break;
+                       }
+
+                       ab8500_fg_calc_cap_discharge_voltage(di, true);
+               } else {
+                       mutex_lock(&di->cc_lock);
+                       if (!di->flags.conv_done) {
+                               /* Wasn't the CC IRQ that got us here */
+                               mutex_unlock(&di->cc_lock);
+                               dev_dbg(di->dev, "%s CC conv not done\n",
+                                       __func__);
+
+                               break;
+                       }
+                       di->flags.conv_done = false;
+                       mutex_unlock(&di->cc_lock);
+
+                       /* Detect mode change */
+                       if (!di->high_curr_mode) {
+                               di->high_curr_mode = true;
+                               di->high_curr_cnt = 0;
+                       }
+
+                       di->high_curr_cnt +=
+                               di->bat->fg_params->accu_high_curr;
+                       if (di->high_curr_cnt >
+                               di->bat->fg_params->high_curr_time)
+                               di->recovery_needed = true;
+
+                       ab8500_fg_calc_cap_discharge_fg(di);
+               }
+
+               ab8500_fg_check_capacity_limits(di, false);
+
+               break;
+
+       case AB8500_FG_DISCHARGE_WAKEUP:
+               ab8500_fg_coulomb_counter(di, true);
+               di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+               ab8500_fg_calc_cap_discharge_voltage(di, true);
+
+               di->fg_samples = SEC_TO_SAMPLE(
+                       di->bat->fg_params->accu_high_curr);
+               ab8500_fg_coulomb_counter(di, true);
+               ab8500_fg_discharge_state_to(di,
+                               AB8500_FG_DISCHARGE_READOUT);
+
+               ab8500_fg_check_capacity_limits(di, false);
+
+               break;
+
+       default:
+               break;
+       }
+}
+
+/**
+ * ab8500_fg_algorithm_calibrate() - Internal columb counter offset calibration
+ * @di:                pointer to the ab8500_fg structure
+ *
+ */
+static void ab8500_fg_algorithm_calibrate(struct ab8500_fg *di)
+{
+       int ret;
+
+       switch (di->calib_state) {
+       case AB8500_FG_CALIB_INIT:
+               dev_dbg(di->dev, "Calibration ongoing...\n");
+
+               ret = abx500_mask_and_set_register_interruptible(di->dev,
+                       AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+                       CC_INT_CAL_N_AVG_MASK, CC_INT_CAL_SAMPLES_8);
+               if (ret < 0)
+                       goto err;
+
+               ret = abx500_mask_and_set_register_interruptible(di->dev,
+                       AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+                       CC_INTAVGOFFSET_ENA, CC_INTAVGOFFSET_ENA);
+               if (ret < 0)
+                       goto err;
+               di->calib_state = AB8500_FG_CALIB_WAIT;
+               break;
+       case AB8500_FG_CALIB_END:
+               ret = abx500_mask_and_set_register_interruptible(di->dev,
+                       AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+                       CC_MUXOFFSET, CC_MUXOFFSET);
+               if (ret < 0)
+                       goto err;
+               di->flags.calibrate = false;
+               dev_dbg(di->dev, "Calibration done...\n");
+               queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+               break;
+       case AB8500_FG_CALIB_WAIT:
+               dev_dbg(di->dev, "Calibration WFI\n");
+       default:
+               break;
+       }
+       return;
+err:
+       /* Something went wrong, don't calibrate then */
+       dev_err(di->dev, "failed to calibrate the CC\n");
+       di->flags.calibrate = false;
+       di->calib_state = AB8500_FG_CALIB_INIT;
+       queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+}
+
+/**
+ * ab8500_fg_algorithm() - Entry point for the FG algorithm
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Entry point for the battery capacity calculation state machine
+ */
+static void ab8500_fg_algorithm(struct ab8500_fg *di)
+{
+       if (di->flags.calibrate)
+               ab8500_fg_algorithm_calibrate(di);
+       else {
+               if (di->flags.charging)
+                       ab8500_fg_algorithm_charging(di);
+               else
+                       ab8500_fg_algorithm_discharging(di);
+       }
+
+       dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d "
+               "%d %d %d %d %d %d %d\n",
+               di->bat_cap.max_mah_design,
+               di->bat_cap.mah,
+               di->bat_cap.permille,
+               di->bat_cap.level,
+               di->bat_cap.prev_mah,
+               di->bat_cap.prev_percent,
+               di->bat_cap.prev_level,
+               di->vbat,
+               di->inst_curr,
+               di->avg_curr,
+               di->accu_charge,
+               di->flags.charging,
+               di->charge_state,
+               di->discharge_state,
+               di->high_curr_mode,
+               di->recovery_needed);
+}
+
+/**
+ * ab8500_fg_periodic_work() - Run the FG state machine periodically
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for periodic work
+ */
+static void ab8500_fg_periodic_work(struct work_struct *work)
+{
+       struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+               fg_periodic_work.work);
+
+       if (di->init_capacity) {
+               /* A dummy read that will return 0 */
+               di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+               /* Get an initial capacity calculation */
+               ab8500_fg_calc_cap_discharge_voltage(di, true);
+               ab8500_fg_check_capacity_limits(di, true);
+               di->init_capacity = false;
+
+               queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+       } else if (di->flags.user_cap) {
+               if (check_sysfs_capacity(di)) {
+                       ab8500_fg_check_capacity_limits(di, true);
+                       if (di->flags.charging)
+                               ab8500_fg_charge_state_to(di,
+                                       AB8500_FG_CHARGE_INIT);
+                       else
+                               ab8500_fg_discharge_state_to(di,
+                                       AB8500_FG_DISCHARGE_READOUT_INIT);
+               }
+               di->flags.user_cap = false;
+               queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+       } else
+               ab8500_fg_algorithm(di);
+
+}
+
+/**
+ * ab8500_fg_check_hw_failure_work() - Check OVV_BAT condition
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for checking the OVV_BAT condition
+ */
+static void ab8500_fg_check_hw_failure_work(struct work_struct *work)
+{
+       int ret;
+       u8 reg_value;
+
+       struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+               fg_check_hw_failure_work.work);
+
+       /*
+        * If we have had a battery over-voltage situation,
+        * check ovv-bit to see if it should be reset.
+        */
+       if (di->flags.bat_ovv) {
+               ret = abx500_get_register_interruptible(di->dev,
+                       AB8500_CHARGER, AB8500_CH_STAT_REG,
+                       &reg_value);
+               if (ret < 0) {
+                       dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+                       return;
+               }
+               if ((reg_value & BATT_OVV) != BATT_OVV) {
+                       dev_dbg(di->dev, "Battery recovered from OVV\n");
+                       di->flags.bat_ovv = false;
+                       power_supply_changed(&di->fg_psy);
+                       return;
+               }
+
+               /* Not yet recovered from ovv, reschedule this test */
+               queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work,
+                                  round_jiffies(HZ));
+       }
+}
+
+/**
+ * ab8500_fg_low_bat_work() - Check LOW_BAT condition
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for checking the LOW_BAT condition
+ */
+static void ab8500_fg_low_bat_work(struct work_struct *work)
+{
+       int vbat;
+
+       struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+               fg_low_bat_work.work);
+
+       vbat = ab8500_fg_bat_voltage(di);
+
+       /* Check if LOW_BAT still fulfilled */
+       if (vbat < di->bat->fg_params->lowbat_threshold) {
+               di->flags.low_bat = true;
+               dev_warn(di->dev, "Battery voltage still LOW\n");
+
+               /*
+                * We need to re-schedule this check to be able to detect
+                * if the voltage increases again during charging
+                */
+               queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
+                       round_jiffies(LOW_BAT_CHECK_INTERVAL));
+       } else {
+               di->flags.low_bat = false;
+               dev_warn(di->dev, "Battery voltage OK again\n");
+       }
+
+       /* This is needed to dispatch LOW_BAT */
+       ab8500_fg_check_capacity_limits(di, false);
+
+       /* Set this flag to check if LOW_BAT IRQ still occurs */
+       di->flags.low_bat_delay = false;
+}
+
+/**
+ * ab8500_fg_battok_calc - calculate the bit pattern corresponding
+ * to the target voltage.
+ * @di:       pointer to the ab8500_fg structure
+ * @target    target voltage
+ *
+ * Returns bit pattern closest to the target voltage
+ * valid return values are 0-14. (0-BATT_OK_MAX_NR_INCREMENTS)
+ */
+
+static int ab8500_fg_battok_calc(struct ab8500_fg *di, int target)
+{
+       if (target > BATT_OK_MIN +
+               (BATT_OK_INCREMENT * BATT_OK_MAX_NR_INCREMENTS))
+               return BATT_OK_MAX_NR_INCREMENTS;
+       if (target < BATT_OK_MIN)
+               return 0;
+       return (target - BATT_OK_MIN) / BATT_OK_INCREMENT;
+}
+
+/**
+ * ab8500_fg_battok_init_hw_register - init battok levels
+ * @di:       pointer to the ab8500_fg structure
+ *
+ */
+
+static int ab8500_fg_battok_init_hw_register(struct ab8500_fg *di)
+{
+       int selected;
+       int sel0;
+       int sel1;
+       int cbp_sel0;
+       int cbp_sel1;
+       int ret;
+       int new_val;
+
+       sel0 = di->bat->fg_params->battok_falling_th_sel0;
+       sel1 = di->bat->fg_params->battok_raising_th_sel1;
+
+       cbp_sel0 = ab8500_fg_battok_calc(di, sel0);
+       cbp_sel1 = ab8500_fg_battok_calc(di, sel1);
+
+       selected = BATT_OK_MIN + cbp_sel0 * BATT_OK_INCREMENT;
+
+       if (selected != sel0)
+               dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n",
+                       sel0, selected, cbp_sel0);
+
+       selected = BATT_OK_MIN + cbp_sel1 * BATT_OK_INCREMENT;
+
+       if (selected != sel1)
+               dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n",
+                       sel1, selected, cbp_sel1);
+
+       new_val = cbp_sel0 | (cbp_sel1 << 4);
+
+       dev_dbg(di->dev, "using: %x %d %d\n", new_val, cbp_sel0, cbp_sel1);
+       ret = abx500_set_register_interruptible(di->dev, AB8500_SYS_CTRL2_BLOCK,
+               AB8500_BATT_OK_REG, new_val);
+       return ret;
+}
+
+/**
+ * ab8500_fg_instant_work() - Run the FG state machine instantly
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for instant work
+ */
+static void ab8500_fg_instant_work(struct work_struct *work)
+{
+       struct ab8500_fg *di = container_of(work, struct ab8500_fg, fg_work);
+
+       ab8500_fg_algorithm(di);
+}
+
+/**
+ * ab8500_fg_cc_data_end_handler() - isr to get battery avg current.
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_cc_data_end_handler(int irq, void *_di)
+{
+       struct ab8500_fg *di = _di;
+       complete(&di->ab8500_fg_complete);
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_cc_convend_handler() - isr to get battery avg current.
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_cc_int_calib_handler(int irq, void *_di)
+{
+       struct ab8500_fg *di = _di;
+       di->calib_state = AB8500_FG_CALIB_END;
+       queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_cc_convend_handler() - isr to get battery avg current.
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_cc_convend_handler(int irq, void *_di)
+{
+       struct ab8500_fg *di = _di;
+
+       queue_work(di->fg_wq, &di->fg_acc_cur_work);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_batt_ovv_handler() - Battery OVV occured
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di)
+{
+       struct ab8500_fg *di = _di;
+
+       dev_dbg(di->dev, "Battery OVV\n");
+       di->flags.bat_ovv = true;
+       power_supply_changed(&di->fg_psy);
+
+       /* Schedule a new HW failure check */
+       queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, 0);
+
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_lowbatf_handler() - Battery voltage is below LOW threshold
+ * @irq:       interrupt number
+ * @_di:       pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di)
+{
+       struct ab8500_fg *di = _di;
+
+       if (!di->flags.low_bat_delay) {
+               dev_warn(di->dev, "Battery voltage is below LOW threshold\n");
+               di->flags.low_bat_delay = true;
+               /*
+                * Start a timer to check LOW_BAT again after some time
+                * This is done to avoid shutdown on single voltage dips
+                */
+               queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
+                       round_jiffies(LOW_BAT_CHECK_INTERVAL));
+       }
+       return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_get_property() - get the fg properties
+ * @psy:       pointer to the power_supply structure
+ * @psp:       pointer to the power_supply_property structure
+ * @val:       pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the
+ * fg properties by reading the sysfs files.
+ * voltage_now:                battery voltage
+ * current_now:                battery instant current
+ * current_avg:                battery average current
+ * charge_full_design: capacity where battery is considered full
+ * charge_now:         battery capacity in nAh
+ * capacity:           capacity in percent
+ * capacity_level:     capacity level
+ *
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_fg_get_property(struct power_supply *psy,
+       enum power_supply_property psp,
+       union power_supply_propval *val)
+{
+       struct ab8500_fg *di;
+
+       di = to_ab8500_fg_device_info(psy);
+
+       /*
+        * If battery is identified as unknown and charging of unknown
+        * batteries is disabled, we always report 100% capacity and
+        * capacity level UNKNOWN, since we can't calculate
+        * remaining capacity
+        */
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               if (di->flags.bat_ovv)
+                       val->intval = BATT_OVV_VALUE * 1000;
+               else
+                       val->intval = di->vbat * 1000;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               val->intval = di->inst_curr * 1000;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+               val->intval = di->avg_curr * 1000;
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+               val->intval = ab8500_fg_convert_mah_to_uwh(di,
+                               di->bat_cap.max_mah_design);
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_FULL:
+               val->intval = ab8500_fg_convert_mah_to_uwh(di,
+                               di->bat_cap.max_mah);
+               break;
+       case POWER_SUPPLY_PROP_ENERGY_NOW:
+               if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+                               di->flags.batt_id_received)
+                       val->intval = ab8500_fg_convert_mah_to_uwh(di,
+                                       di->bat_cap.max_mah);
+               else
+                       val->intval = ab8500_fg_convert_mah_to_uwh(di,
+                                       di->bat_cap.prev_mah);
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+               val->intval = di->bat_cap.max_mah_design;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               val->intval = di->bat_cap.max_mah;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_NOW:
+               if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+                               di->flags.batt_id_received)
+                       val->intval = di->bat_cap.max_mah;
+               else
+                       val->intval = di->bat_cap.prev_mah;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+                               di->flags.batt_id_received)
+                       val->intval = 100;
+               else
+                       val->intval = di->bat_cap.prev_percent;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+               if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+                               di->flags.batt_id_received)
+                       val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+               else
+                       val->intval = di->bat_cap.prev_level;
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
+{
+       struct power_supply *psy;
+       struct power_supply *ext;
+       struct ab8500_fg *di;
+       union power_supply_propval ret;
+       int i, j;
+       bool psy_found = false;
+
+       psy = (struct power_supply *)data;
+       ext = dev_get_drvdata(dev);
+       di = to_ab8500_fg_device_info(psy);
+
+       /*
+        * For all psy where the name of your driver
+        * appears in any supplied_to
+        */
+       for (i = 0; i < ext->num_supplicants; i++) {
+               if (!strcmp(ext->supplied_to[i], psy->name))
+                       psy_found = true;
+       }
+
+       if (!psy_found)
+               return 0;
+
+       /* Go through all properties for the psy */
+       for (j = 0; j < ext->num_properties; j++) {
+               enum power_supply_property prop;
+               prop = ext->properties[j];
+
+               if (ext->get_property(ext, prop, &ret))
+                       continue;
+
+               switch (prop) {
+               case POWER_SUPPLY_PROP_STATUS:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               switch (ret.intval) {
+                               case POWER_SUPPLY_STATUS_UNKNOWN:
+                               case POWER_SUPPLY_STATUS_DISCHARGING:
+                               case POWER_SUPPLY_STATUS_NOT_CHARGING:
+                                       if (!di->flags.charging)
+                                               break;
+                                       di->flags.charging = false;
+                                       di->flags.fully_charged = false;
+                                       queue_work(di->fg_wq, &di->fg_work);
+                                       break;
+                               case POWER_SUPPLY_STATUS_FULL:
+                                       if (di->flags.fully_charged)
+                                               break;
+                                       di->flags.fully_charged = true;
+                                       di->flags.force_full = true;
+                                       /* Save current capacity as maximum */
+                                       di->bat_cap.max_mah = di->bat_cap.mah;
+                                       queue_work(di->fg_wq, &di->fg_work);
+                                       break;
+                               case POWER_SUPPLY_STATUS_CHARGING:
+                                       if (di->flags.charging)
+                                               break;
+                                       di->flags.charging = true;
+                                       di->flags.fully_charged = false;
+                                       queue_work(di->fg_wq, &di->fg_work);
+                                       break;
+                               };
+                       default:
+                               break;
+                       };
+                       break;
+               case POWER_SUPPLY_PROP_TECHNOLOGY:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               if (!di->flags.batt_id_received) {
+                                       const struct abx500_battery_type *b;
+
+                                       b = &(di->bat->bat_type[di->bat->batt_id]);
+
+                                       di->flags.batt_id_received = true;
+
+                                       di->bat_cap.max_mah_design =
+                                               MILLI_TO_MICRO *
+                                               b->charge_full_design;
+
+                                       di->bat_cap.max_mah =
+                                               di->bat_cap.max_mah_design;
+
+                                       di->vbat_nom = b->nominal_voltage;
+                               }
+
+                               if (ret.intval)
+                                       di->flags.batt_unknown = false;
+                               else
+                                       di->flags.batt_unknown = true;
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               case POWER_SUPPLY_PROP_TEMP:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                           if (di->flags.batt_id_received)
+                               di->bat_temp = ret.intval;
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               default:
+                       break;
+               }
+       }
+       return 0;
+}
+
+/**
+ * ab8500_fg_init_hw_registers() - Set up FG related registers
+ * @di:                pointer to the ab8500_fg structure
+ *
+ * Set up battery OVV, low battery voltage registers
+ */
+static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
+{
+       int ret;
+
+       /* Set VBAT OVV threshold */
+       ret = abx500_mask_and_set_register_interruptible(di->dev,
+               AB8500_CHARGER,
+               AB8500_BATT_OVV,
+               BATT_OVV_TH_4P75,
+               BATT_OVV_TH_4P75);
+       if (ret) {
+               dev_err(di->dev, "failed to set BATT_OVV\n");
+               goto out;
+       }
+
+       /* Enable VBAT OVV detection */
+       ret = abx500_mask_and_set_register_interruptible(di->dev,
+               AB8500_CHARGER,
+               AB8500_BATT_OVV,
+               BATT_OVV_ENA,
+               BATT_OVV_ENA);
+       if (ret) {
+               dev_err(di->dev, "failed to enable BATT_OVV\n");
+               goto out;
+       }
+
+       /* Low Battery Voltage */
+       ret = abx500_set_register_interruptible(di->dev,
+               AB8500_SYS_CTRL2_BLOCK,
+               AB8500_LOW_BAT_REG,
+               ab8500_volt_to_regval(
+                       di->bat->fg_params->lowbat_threshold) << 1 |
+               LOW_BAT_ENABLE);
+       if (ret) {
+               dev_err(di->dev, "%s write failed\n", __func__);
+               goto out;
+       }
+
+       /* Battery OK threshold */
+       ret = ab8500_fg_battok_init_hw_register(di);
+       if (ret) {
+               dev_err(di->dev, "BattOk init write failed.\n");
+               goto out;
+       }
+out:
+       return ret;
+}
+
+/**
+ * ab8500_fg_external_power_changed() - callback for power supply changes
+ * @psy:       pointer to the structure power_supply
+ *
+ * This function is the entry point of the pointer external_power_changed
+ * of the structure power_supply.
+ * This function gets executed when there is a change in any external power
+ * supply that this driver needs to be notified of.
+ */
+static void ab8500_fg_external_power_changed(struct power_supply *psy)
+{
+       struct ab8500_fg *di = to_ab8500_fg_device_info(psy);
+
+       class_for_each_device(power_supply_class, NULL,
+               &di->fg_psy, ab8500_fg_get_ext_psy_data);
+}
+
+/**
+ * abab8500_fg_reinit_work() - work to reset the FG algorithm
+ * @work:      pointer to the work_struct structure
+ *
+ * Used to reset the current battery capacity to be able to
+ * retrigger a new voltage base capacity calculation. For
+ * test and verification purpose.
+ */
+static void ab8500_fg_reinit_work(struct work_struct *work)
+{
+       struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+               fg_reinit_work.work);
+
+       if (di->flags.calibrate == false) {
+               dev_dbg(di->dev, "Resetting FG state machine to init.\n");
+               ab8500_fg_clear_cap_samples(di);
+               ab8500_fg_calc_cap_discharge_voltage(di, true);
+               ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
+               ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT);
+               queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+
+       } else {
+               dev_err(di->dev, "Residual offset calibration ongoing "
+                       "retrying..\n");
+               /* Wait one second until next try*/
+               queue_delayed_work(di->fg_wq, &di->fg_reinit_work,
+                       round_jiffies(1));
+       }
+}
+
+/**
+ * ab8500_fg_reinit() - forces FG algorithm to reinitialize with current values
+ *
+ * This function can be used to force the FG algorithm to recalculate a new
+ * voltage based battery capacity.
+ */
+void ab8500_fg_reinit(void)
+{
+       struct ab8500_fg *di = ab8500_fg_get();
+       /* User won't be notified if a null pointer returned. */
+       if (di != NULL)
+               queue_delayed_work(di->fg_wq, &di->fg_reinit_work, 0);
+}
+
+/* Exposure to the sysfs interface */
+
+struct ab8500_fg_sysfs_entry {
+       struct attribute attr;
+       ssize_t (*show)(struct ab8500_fg *, char *);
+       ssize_t (*store)(struct ab8500_fg *, const char *, size_t);
+};
+
+static ssize_t charge_full_show(struct ab8500_fg *di, char *buf)
+{
+       return sprintf(buf, "%d\n", di->bat_cap.max_mah);
+}
+
+static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf,
+                                size_t count)
+{
+       unsigned long charge_full;
+       ssize_t ret = -EINVAL;
+
+       ret = strict_strtoul(buf, 10, &charge_full);
+
+       dev_dbg(di->dev, "Ret %zd charge_full %lu", ret, charge_full);
+
+       if (!ret) {
+               di->bat_cap.max_mah = (int) charge_full;
+               ret = count;
+       }
+       return ret;
+}
+
+static ssize_t charge_now_show(struct ab8500_fg *di, char *buf)
+{
+       return sprintf(buf, "%d\n", di->bat_cap.prev_mah);
+}
+
+static ssize_t charge_now_store(struct ab8500_fg *di, const char *buf,
+                                size_t count)
+{
+       unsigned long charge_now;
+       ssize_t ret;
+
+       ret = strict_strtoul(buf, 10, &charge_now);
+
+       dev_dbg(di->dev, "Ret %zd charge_now %lu was %d",
+               ret, charge_now, di->bat_cap.prev_mah);
+
+       if (!ret) {
+               di->bat_cap.user_mah = (int) charge_now;
+               di->flags.user_cap = true;
+               ret = count;
+               queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+       }
+       return ret;
+}
+
+static struct ab8500_fg_sysfs_entry charge_full_attr =
+       __ATTR(charge_full, 0644, charge_full_show, charge_full_store);
+
+static struct ab8500_fg_sysfs_entry charge_now_attr =
+       __ATTR(charge_now, 0644, charge_now_show, charge_now_store);
+
+static ssize_t
+ab8500_fg_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+       struct ab8500_fg_sysfs_entry *entry;
+       struct ab8500_fg *di;
+
+       entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr);
+       di = container_of(kobj, struct ab8500_fg, fg_kobject);
+
+       if (!entry->show)
+               return -EIO;
+
+       return entry->show(di, buf);
+}
+static ssize_t
+ab8500_fg_store(struct kobject *kobj, struct attribute *attr, const char *buf,
+               size_t count)
+{
+       struct ab8500_fg_sysfs_entry *entry;
+       struct ab8500_fg *di;
+
+       entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr);
+       di = container_of(kobj, struct ab8500_fg, fg_kobject);
+
+       if (!entry->store)
+               return -EIO;
+
+       return entry->store(di, buf, count);
+}
+
+static const struct sysfs_ops ab8500_fg_sysfs_ops = {
+       .show = ab8500_fg_show,
+       .store = ab8500_fg_store,
+};
+
+static struct attribute *ab8500_fg_attrs[] = {
+       &charge_full_attr.attr,
+       &charge_now_attr.attr,
+       NULL,
+};
+
+static struct kobj_type ab8500_fg_ktype = {
+       .sysfs_ops = &ab8500_fg_sysfs_ops,
+       .default_attrs = ab8500_fg_attrs,
+};
+
+/**
+ * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry
+ * @di:                pointer to the struct ab8500_chargalg
+ *
+ * This function removes the entry in sysfs.
+ */
+static void ab8500_fg_sysfs_exit(struct ab8500_fg *di)
+{
+       kobject_del(&di->fg_kobject);
+}
+
+/**
+ * ab8500_chargalg_sysfs_init() - init of sysfs entry
+ * @di:                pointer to the struct ab8500_chargalg
+ *
+ * This function adds an entry in sysfs.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_fg_sysfs_init(struct ab8500_fg *di)
+{
+       int ret = 0;
+
+       ret = kobject_init_and_add(&di->fg_kobject,
+               &ab8500_fg_ktype,
+               NULL, "battery");
+       if (ret < 0)
+               dev_err(di->dev, "failed to create sysfs entry\n");
+
+       return ret;
+}
+/* Exposure to the sysfs interface <<END>> */
+
+#if defined(CONFIG_PM)
+static int ab8500_fg_resume(struct platform_device *pdev)
+{
+       struct ab8500_fg *di = platform_get_drvdata(pdev);
+
+       /*
+        * Change state if we're not charging. If we're charging we will wake
+        * up on the FG IRQ
+        */
+       if (!di->flags.charging) {
+               ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP);
+               queue_work(di->fg_wq, &di->fg_work);
+       }
+
+       return 0;
+}
+
+static int ab8500_fg_suspend(struct platform_device *pdev,
+       pm_message_t state)
+{
+       struct ab8500_fg *di = platform_get_drvdata(pdev);
+
+       flush_delayed_work(&di->fg_periodic_work);
+
+       /*
+        * If the FG is enabled we will disable it before going to suspend
+        * only if we're not charging
+        */
+       if (di->flags.fg_enabled && !di->flags.charging)
+               ab8500_fg_coulomb_counter(di, false);
+
+       return 0;
+}
+#else
+#define ab8500_fg_suspend      NULL
+#define ab8500_fg_resume       NULL
+#endif
+
+static int __devexit ab8500_fg_remove(struct platform_device *pdev)
+{
+       int ret = 0;
+       struct ab8500_fg *di = platform_get_drvdata(pdev);
+
+       list_del(&di->node);
+
+       /* Disable coulomb counter */
+       ret = ab8500_fg_coulomb_counter(di, false);
+       if (ret)
+               dev_err(di->dev, "failed to disable coulomb counter\n");
+
+       destroy_workqueue(di->fg_wq);
+       ab8500_fg_sysfs_exit(di);
+
+       flush_scheduled_work();
+       power_supply_unregister(&di->fg_psy);
+       platform_set_drvdata(pdev, NULL);
+       kfree(di);
+       return ret;
+}
+
+/* ab8500 fg driver interrupts and their respective isr */
+static struct ab8500_fg_interrupts ab8500_fg_irq[] = {
+       {"NCONV_ACCU", ab8500_fg_cc_convend_handler},
+       {"BATT_OVV", ab8500_fg_batt_ovv_handler},
+       {"LOW_BAT_F", ab8500_fg_lowbatf_handler},
+       {"CC_INT_CALIB", ab8500_fg_cc_int_calib_handler},
+       {"CCEOC", ab8500_fg_cc_data_end_handler},
+};
+
+static int __devinit ab8500_fg_probe(struct platform_device *pdev)
+{
+       int i, irq;
+       int ret = 0;
+       struct abx500_bm_plat_data *plat_data;
+
+       struct ab8500_fg *di =
+               kzalloc(sizeof(struct ab8500_fg), GFP_KERNEL);
+       if (!di)
+               return -ENOMEM;
+
+       mutex_init(&di->cc_lock);
+
+       /* get parent data */
+       di->dev = &pdev->dev;
+       di->parent = dev_get_drvdata(pdev->dev.parent);
+       di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+       /* get fg specific platform data */
+       plat_data = pdev->dev.platform_data;
+       di->pdata = plat_data->fg;
+       if (!di->pdata) {
+               dev_err(di->dev, "no fg platform data supplied\n");
+               ret = -EINVAL;
+               goto free_device_info;
+       }
+
+       /* get battery specific platform data */
+       di->bat = plat_data->battery;
+       if (!di->bat) {
+               dev_err(di->dev, "no battery platform data supplied\n");
+               ret = -EINVAL;
+               goto free_device_info;
+       }
+
+       di->fg_psy.name = "ab8500_fg";
+       di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY;
+       di->fg_psy.properties = ab8500_fg_props;
+       di->fg_psy.num_properties = ARRAY_SIZE(ab8500_fg_props);
+       di->fg_psy.get_property = ab8500_fg_get_property;
+       di->fg_psy.supplied_to = di->pdata->supplied_to;
+       di->fg_psy.num_supplicants = di->pdata->num_supplicants;
+       di->fg_psy.external_power_changed = ab8500_fg_external_power_changed;
+
+       di->bat_cap.max_mah_design = MILLI_TO_MICRO *
+               di->bat->bat_type[di->bat->batt_id].charge_full_design;
+
+       di->bat_cap.max_mah = di->bat_cap.max_mah_design;
+
+       di->vbat_nom = di->bat->bat_type[di->bat->batt_id].nominal_voltage;
+
+       di->init_capacity = true;
+
+       ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
+       ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT);
+
+       /* Create a work queue for running the FG algorithm */
+       di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq");
+       if (di->fg_wq == NULL) {
+               dev_err(di->dev, "failed to create work queue\n");
+               goto free_device_info;
+       }
+
+       /* Init work for running the fg algorithm instantly */
+       INIT_WORK(&di->fg_work, ab8500_fg_instant_work);
+
+       /* Init work for getting the battery accumulated current */
+       INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work);
+
+       /* Init work for reinitialising the fg algorithm */
+       INIT_DELAYED_WORK_DEFERRABLE(&di->fg_reinit_work,
+               ab8500_fg_reinit_work);
+
+       /* Work delayed Queue to run the state machine */
+       INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work,
+               ab8500_fg_periodic_work);
+
+       /* Work to check low battery condition */
+       INIT_DELAYED_WORK_DEFERRABLE(&di->fg_low_bat_work,
+               ab8500_fg_low_bat_work);
+
+       /* Init work for HW failure check */
+       INIT_DELAYED_WORK_DEFERRABLE(&di->fg_check_hw_failure_work,
+               ab8500_fg_check_hw_failure_work);
+
+       /* Initialize OVV, and other registers */
+       ret = ab8500_fg_init_hw_registers(di);
+       if (ret) {
+               dev_err(di->dev, "failed to initialize registers\n");
+               goto free_inst_curr_wq;
+       }
+
+       /* Consider battery unknown until we're informed otherwise */
+       di->flags.batt_unknown = true;
+       di->flags.batt_id_received = false;
+
+       /* Register FG power supply class */
+       ret = power_supply_register(di->dev, &di->fg_psy);
+       if (ret) {
+               dev_err(di->dev, "failed to register FG psy\n");
+               goto free_inst_curr_wq;
+       }
+
+       di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer);
+       ab8500_fg_coulomb_counter(di, true);
+
+       /* Initialize completion used to notify completion of inst current */
+       init_completion(&di->ab8500_fg_complete);
+
+       /* Register interrupts */
+       for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) {
+               irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name);
+               ret = request_threaded_irq(irq, NULL, ab8500_fg_irq[i].isr,
+                       IRQF_SHARED | IRQF_NO_SUSPEND,
+                       ab8500_fg_irq[i].name, di);
+
+               if (ret != 0) {
+                       dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+                               , ab8500_fg_irq[i].name, irq, ret);
+                       goto free_irq;
+               }
+               dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+                       ab8500_fg_irq[i].name, irq, ret);
+       }
+       di->irq = platform_get_irq_byname(pdev, "CCEOC");
+       disable_irq(di->irq);
+
+       platform_set_drvdata(pdev, di);
+
+       ret = ab8500_fg_sysfs_init(di);
+       if (ret) {
+               dev_err(di->dev, "failed to create sysfs entry\n");
+               goto free_irq;
+       }
+
+       /* Calibrate the fg first time */
+       di->flags.calibrate = true;
+       di->calib_state = AB8500_FG_CALIB_INIT;
+
+       /* Use room temp as default value until we get an update from driver. */
+       di->bat_temp = 210;
+
+       /* Run the FG algorithm */
+       queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+
+       list_add_tail(&di->node, &ab8500_fg_list);
+
+       return ret;
+
+free_irq:
+       power_supply_unregister(&di->fg_psy);
+
+       /* We also have to free all successfully registered irqs */
+       for (i = i - 1; i >= 0; i--) {
+               irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name);
+               free_irq(irq, di);
+       }
+free_inst_curr_wq:
+       destroy_workqueue(di->fg_wq);
+free_device_info:
+       kfree(di);
+
+       return ret;
+}
+
+static struct platform_driver ab8500_fg_driver = {
+       .probe = ab8500_fg_probe,
+       .remove = __devexit_p(ab8500_fg_remove),
+       .suspend = ab8500_fg_suspend,
+       .resume = ab8500_fg_resume,
+       .driver = {
+               .name = "ab8500-fg",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init ab8500_fg_init(void)
+{
+       return platform_driver_register(&ab8500_fg_driver);
+}
+
+static void __exit ab8500_fg_exit(void)
+{
+       platform_driver_unregister(&ab8500_fg_driver);
+}
+
+subsys_initcall_sync(ab8500_fg_init);
+module_exit(ab8500_fg_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
+MODULE_ALIAS("platform:ab8500-fg");
+MODULE_DESCRIPTION("AB8500 Fuel Gauge driver");
diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c
new file mode 100644 (file)
index 0000000..804b88c
--- /dev/null
@@ -0,0 +1,1921 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Charging algorithm driver for abx500 variants
+ *
+ * License Terms: GNU General Public License v2
+ * Authors:
+ *     Johan Palsson <johan.palsson@stericsson.com>
+ *     Karl Komierowski <karl.komierowski@stericsson.com>
+ *     Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/kobject.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+
+/* Watchdog kick interval */
+#define CHG_WD_INTERVAL                        (6 * HZ)
+
+/* End-of-charge criteria counter */
+#define EOC_COND_CNT                   10
+
+/* Recharge criteria counter */
+#define RCH_COND_CNT                   3
+
+#define to_abx500_chargalg_device_info(x) container_of((x), \
+       struct abx500_chargalg, chargalg_psy);
+
+enum abx500_chargers {
+       NO_CHG,
+       AC_CHG,
+       USB_CHG,
+};
+
+struct abx500_chargalg_charger_info {
+       enum abx500_chargers conn_chg;
+       enum abx500_chargers prev_conn_chg;
+       enum abx500_chargers online_chg;
+       enum abx500_chargers prev_online_chg;
+       enum abx500_chargers charger_type;
+       bool usb_chg_ok;
+       bool ac_chg_ok;
+       int usb_volt;
+       int usb_curr;
+       int ac_volt;
+       int ac_curr;
+       int usb_vset;
+       int usb_iset;
+       int ac_vset;
+       int ac_iset;
+};
+
+struct abx500_chargalg_suspension_status {
+       bool suspended_change;
+       bool ac_suspended;
+       bool usb_suspended;
+};
+
+struct abx500_chargalg_battery_data {
+       int temp;
+       int volt;
+       int avg_curr;
+       int inst_curr;
+       int percent;
+};
+
+enum abx500_chargalg_states {
+       STATE_HANDHELD_INIT,
+       STATE_HANDHELD,
+       STATE_CHG_NOT_OK_INIT,
+       STATE_CHG_NOT_OK,
+       STATE_HW_TEMP_PROTECT_INIT,
+       STATE_HW_TEMP_PROTECT,
+       STATE_NORMAL_INIT,
+       STATE_NORMAL,
+       STATE_WAIT_FOR_RECHARGE_INIT,
+       STATE_WAIT_FOR_RECHARGE,
+       STATE_MAINTENANCE_A_INIT,
+       STATE_MAINTENANCE_A,
+       STATE_MAINTENANCE_B_INIT,
+       STATE_MAINTENANCE_B,
+       STATE_TEMP_UNDEROVER_INIT,
+       STATE_TEMP_UNDEROVER,
+       STATE_TEMP_LOWHIGH_INIT,
+       STATE_TEMP_LOWHIGH,
+       STATE_SUSPENDED_INIT,
+       STATE_SUSPENDED,
+       STATE_OVV_PROTECT_INIT,
+       STATE_OVV_PROTECT,
+       STATE_SAFETY_TIMER_EXPIRED_INIT,
+       STATE_SAFETY_TIMER_EXPIRED,
+       STATE_BATT_REMOVED_INIT,
+       STATE_BATT_REMOVED,
+       STATE_WD_EXPIRED_INIT,
+       STATE_WD_EXPIRED,
+};
+
+static const char *states[] = {
+       "HANDHELD_INIT",
+       "HANDHELD",
+       "CHG_NOT_OK_INIT",
+       "CHG_NOT_OK",
+       "HW_TEMP_PROTECT_INIT",
+       "HW_TEMP_PROTECT",
+       "NORMAL_INIT",
+       "NORMAL",
+       "WAIT_FOR_RECHARGE_INIT",
+       "WAIT_FOR_RECHARGE",
+       "MAINTENANCE_A_INIT",
+       "MAINTENANCE_A",
+       "MAINTENANCE_B_INIT",
+       "MAINTENANCE_B",
+       "TEMP_UNDEROVER_INIT",
+       "TEMP_UNDEROVER",
+       "TEMP_LOWHIGH_INIT",
+       "TEMP_LOWHIGH",
+       "SUSPENDED_INIT",
+       "SUSPENDED",
+       "OVV_PROTECT_INIT",
+       "OVV_PROTECT",
+       "SAFETY_TIMER_EXPIRED_INIT",
+       "SAFETY_TIMER_EXPIRED",
+       "BATT_REMOVED_INIT",
+       "BATT_REMOVED",
+       "WD_EXPIRED_INIT",
+       "WD_EXPIRED",
+};
+
+struct abx500_chargalg_events {
+       bool batt_unknown;
+       bool mainextchnotok;
+       bool batt_ovv;
+       bool batt_rem;
+       bool btemp_underover;
+       bool btemp_lowhigh;
+       bool main_thermal_prot;
+       bool usb_thermal_prot;
+       bool main_ovv;
+       bool vbus_ovv;
+       bool usbchargernotok;
+       bool safety_timer_expired;
+       bool maintenance_timer_expired;
+       bool ac_wd_expired;
+       bool usb_wd_expired;
+       bool ac_cv_active;
+       bool usb_cv_active;
+       bool vbus_collapsed;
+};
+
+/**
+ * struct abx500_charge_curr_maximization - Charger maximization parameters
+ * @original_iset:     the non optimized/maximised charger current
+ * @current_iset:      the charging current used at this moment
+ * @test_delta_i:      the delta between the current we want to charge and the
+                       current that is really going into the battery
+ * @condition_cnt:     number of iterations needed before a new charger current
+                       is set
+ * @max_current:       maximum charger current
+ * @wait_cnt:          to avoid too fast current step down in case of charger
+ *                     voltage collapse, we insert this delay between step
+ *                     down
+ * @level:             tells in how many steps the charging current has been
+                       increased
+ */
+struct abx500_charge_curr_maximization {
+       int original_iset;
+       int current_iset;
+       int test_delta_i;
+       int condition_cnt;
+       int max_current;
+       int wait_cnt;
+       u8 level;
+};
+
+enum maxim_ret {
+       MAXIM_RET_NOACTION,
+       MAXIM_RET_CHANGE,
+       MAXIM_RET_IBAT_TOO_HIGH,
+};
+
+/**
+ * struct abx500_chargalg - abx500 Charging algorithm device information
+ * @dev:               pointer to the structure device
+ * @charge_status:     battery operating status
+ * @eoc_cnt:           counter used to determine end-of_charge
+ * @rch_cnt:           counter used to determine start of recharge
+ * @maintenance_chg:   indicate if maintenance charge is active
+ * @t_hyst_norm                temperature hysteresis when the temperature has been
+ *                     over or under normal limits
+ * @t_hyst_lowhigh     temperature hysteresis when the temperature has been
+ *                     over or under the high or low limits
+ * @charge_state:      current state of the charging algorithm
+ * @ccm                        charging current maximization parameters
+ * @chg_info:          information about connected charger types
+ * @batt_data:         data of the battery
+ * @susp_status:       current charger suspension status
+ * @pdata:             pointer to the abx500_chargalg platform data
+ * @bat:               pointer to the abx500_bm platform data
+ * @chargalg_psy:      structure that holds the battery properties exposed by
+ *                     the charging algorithm
+ * @events:            structure for information about events triggered
+ * @chargalg_wq:               work queue for running the charging algorithm
+ * @chargalg_periodic_work:    work to run the charging algorithm periodically
+ * @chargalg_wd_work:          work to kick the charger watchdog periodically
+ * @chargalg_work:             work to run the charging algorithm instantly
+ * @safety_timer:              charging safety timer
+ * @maintenance_timer:         maintenance charging timer
+ * @chargalg_kobject:          structure of type kobject
+ */
+struct abx500_chargalg {
+       struct device *dev;
+       int charge_status;
+       int eoc_cnt;
+       int rch_cnt;
+       bool maintenance_chg;
+       int t_hyst_norm;
+       int t_hyst_lowhigh;
+       enum abx500_chargalg_states charge_state;
+       struct abx500_charge_curr_maximization ccm;
+       struct abx500_chargalg_charger_info chg_info;
+       struct abx500_chargalg_battery_data batt_data;
+       struct abx500_chargalg_suspension_status susp_status;
+       struct abx500_chargalg_platform_data *pdata;
+       struct abx500_bm_data *bat;
+       struct power_supply chargalg_psy;
+       struct ux500_charger *ac_chg;
+       struct ux500_charger *usb_chg;
+       struct abx500_chargalg_events events;
+       struct workqueue_struct *chargalg_wq;
+       struct delayed_work chargalg_periodic_work;
+       struct delayed_work chargalg_wd_work;
+       struct work_struct chargalg_work;
+       struct timer_list safety_timer;
+       struct timer_list maintenance_timer;
+       struct kobject chargalg_kobject;
+};
+
+/* Main battery properties */
+static enum power_supply_property abx500_chargalg_props[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_HEALTH,
+};
+
+/**
+ * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer
+ * @data:      pointer to the abx500_chargalg structure
+ *
+ * This function gets called when the safety timer for the charger
+ * expires
+ */
+static void abx500_chargalg_safety_timer_expired(unsigned long data)
+{
+       struct abx500_chargalg *di = (struct abx500_chargalg *) data;
+       dev_err(di->dev, "Safety timer expired\n");
+       di->events.safety_timer_expired = true;
+
+       /* Trigger execution of the algorithm instantly */
+       queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_maintenance_timer_expired() - Expiration of
+ * the maintenance timer
+ * @i:         pointer to the abx500_chargalg structure
+ *
+ * This function gets called when the maintenence timer
+ * expires
+ */
+static void abx500_chargalg_maintenance_timer_expired(unsigned long data)
+{
+
+       struct abx500_chargalg *di = (struct abx500_chargalg *) data;
+       dev_dbg(di->dev, "Maintenance timer expired\n");
+       di->events.maintenance_timer_expired = true;
+
+       /* Trigger execution of the algorithm instantly */
+       queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_state_to() - Change charge state
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * This function gets called when a charge state change should occur
+ */
+static void abx500_chargalg_state_to(struct abx500_chargalg *di,
+       enum abx500_chargalg_states state)
+{
+       dev_dbg(di->dev,
+               "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n",
+               di->charge_state == state ? "NO" : "YES",
+               di->charge_state,
+               states[di->charge_state],
+               state,
+               states[state]);
+
+       di->charge_state = state;
+}
+
+/**
+ * abx500_chargalg_check_charger_connection() - Check charger connection change
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * This function will check if there is a change in the charger connection
+ * and change charge state accordingly. AC has precedence over USB.
+ */
+static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di)
+{
+       if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg ||
+               di->susp_status.suspended_change) {
+               /*
+                * Charger state changed or suspension
+                * has changed since last update
+                */
+               if ((di->chg_info.conn_chg & AC_CHG) &&
+                       !di->susp_status.ac_suspended) {
+                       dev_dbg(di->dev, "Charging source is AC\n");
+                       if (di->chg_info.charger_type != AC_CHG) {
+                               di->chg_info.charger_type = AC_CHG;
+                               abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+                       }
+               } else if ((di->chg_info.conn_chg & USB_CHG) &&
+                       !di->susp_status.usb_suspended) {
+                       dev_dbg(di->dev, "Charging source is USB\n");
+                       di->chg_info.charger_type = USB_CHG;
+                       abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               } else if (di->chg_info.conn_chg &&
+                       (di->susp_status.ac_suspended ||
+                       di->susp_status.usb_suspended)) {
+                       dev_dbg(di->dev, "Charging is suspended\n");
+                       di->chg_info.charger_type = NO_CHG;
+                       abx500_chargalg_state_to(di, STATE_SUSPENDED_INIT);
+               } else {
+                       dev_dbg(di->dev, "Charging source is OFF\n");
+                       di->chg_info.charger_type = NO_CHG;
+                       abx500_chargalg_state_to(di, STATE_HANDHELD_INIT);
+               }
+               di->chg_info.prev_conn_chg = di->chg_info.conn_chg;
+               di->susp_status.suspended_change = false;
+       }
+       return di->chg_info.conn_chg;
+}
+
+/**
+ * abx500_chargalg_start_safety_timer() - Start charging safety timer
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * The safety timer is used to avoid overcharging of old or bad batteries.
+ * There are different timers for AC and USB
+ */
+static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)
+{
+       unsigned long timer_expiration = 0;
+
+       switch (di->chg_info.charger_type) {
+       case AC_CHG:
+               timer_expiration =
+               round_jiffies(jiffies +
+                       (di->bat->main_safety_tmr_h * 3600 * HZ));
+               break;
+
+       case USB_CHG:
+               timer_expiration =
+               round_jiffies(jiffies +
+                       (di->bat->usb_safety_tmr_h * 3600 * HZ));
+               break;
+
+       default:
+               dev_err(di->dev, "Unknown charger to charge from\n");
+               break;
+       }
+
+       di->events.safety_timer_expired = false;
+       di->safety_timer.expires = timer_expiration;
+       if (!timer_pending(&di->safety_timer))
+               add_timer(&di->safety_timer);
+       else
+               mod_timer(&di->safety_timer, timer_expiration);
+}
+
+/**
+ * abx500_chargalg_stop_safety_timer() - Stop charging safety timer
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * The safety timer is stopped whenever the NORMAL state is exited
+ */
+static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di)
+{
+       di->events.safety_timer_expired = false;
+       del_timer(&di->safety_timer);
+}
+
+/**
+ * abx500_chargalg_start_maintenance_timer() - Start charging maintenance timer
+ * @di:                pointer to the abx500_chargalg structure
+ * @duration:  duration of ther maintenance timer in hours
+ *
+ * The maintenance timer is used to maintain the charge in the battery once
+ * the battery is considered full. These timers are chosen to match the
+ * discharge curve of the battery
+ */
+static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di,
+       int duration)
+{
+       unsigned long timer_expiration;
+
+       /* Convert from hours to jiffies */
+       timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ));
+
+       di->events.maintenance_timer_expired = false;
+       di->maintenance_timer.expires = timer_expiration;
+       if (!timer_pending(&di->maintenance_timer))
+               add_timer(&di->maintenance_timer);
+       else
+               mod_timer(&di->maintenance_timer, timer_expiration);
+}
+
+/**
+ * abx500_chargalg_stop_maintenance_timer() - Stop maintenance timer
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * The maintenance timer is stopped whenever maintenance ends or when another
+ * state is entered
+ */
+static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di)
+{
+       di->events.maintenance_timer_expired = false;
+       del_timer(&di->maintenance_timer);
+}
+
+/**
+ * abx500_chargalg_kick_watchdog() - Kick charger watchdog
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * The charger watchdog have to be kicked periodically whenever the charger is
+ * on, else the ABB will reset the system
+ */
+static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di)
+{
+       /* Check if charger exists and kick watchdog if charging */
+       if (di->ac_chg && di->ac_chg->ops.kick_wd &&
+                       di->chg_info.online_chg & AC_CHG)
+               return di->ac_chg->ops.kick_wd(di->ac_chg);
+       else if (di->usb_chg && di->usb_chg->ops.kick_wd &&
+                       di->chg_info.online_chg & USB_CHG)
+               return di->usb_chg->ops.kick_wd(di->usb_chg);
+
+       return -ENXIO;
+}
+
+/**
+ * abx500_chargalg_ac_en() - Turn on/off the AC charger
+ * @di:                pointer to the abx500_chargalg structure
+ * @enable:    charger on/off
+ * @vset:      requested charger output voltage
+ * @iset:      requested charger output current
+ *
+ * The AC charger will be turned on/off with the requested charge voltage and
+ * current
+ */
+static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable,
+       int vset, int iset)
+{
+       if (!di->ac_chg || !di->ac_chg->ops.enable)
+               return -ENXIO;
+
+       /* Select maximum of what both the charger and the battery supports */
+       if (di->ac_chg->max_out_volt)
+               vset = min(vset, di->ac_chg->max_out_volt);
+       if (di->ac_chg->max_out_curr)
+               iset = min(iset, di->ac_chg->max_out_curr);
+
+       di->chg_info.ac_iset = iset;
+       di->chg_info.ac_vset = vset;
+
+       return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset);
+}
+
+/**
+ * abx500_chargalg_usb_en() - Turn on/off the USB charger
+ * @di:                pointer to the abx500_chargalg structure
+ * @enable:    charger on/off
+ * @vset:      requested charger output voltage
+ * @iset:      requested charger output current
+ *
+ * The USB charger will be turned on/off with the requested charge voltage and
+ * current
+ */
+static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable,
+       int vset, int iset)
+{
+       if (!di->usb_chg || !di->usb_chg->ops.enable)
+               return -ENXIO;
+
+       /* Select maximum of what both the charger and the battery supports */
+       if (di->usb_chg->max_out_volt)
+               vset = min(vset, di->usb_chg->max_out_volt);
+       if (di->usb_chg->max_out_curr)
+               iset = min(iset, di->usb_chg->max_out_curr);
+
+       di->chg_info.usb_iset = iset;
+       di->chg_info.usb_vset = vset;
+
+       return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset);
+}
+
+/**
+ * abx500_chargalg_update_chg_curr() - Update charger current
+ * @di:                pointer to the abx500_chargalg structure
+ * @iset:      requested charger output current
+ *
+ * The charger output current will be updated for the charger
+ * that is currently in use
+ */
+static int abx500_chargalg_update_chg_curr(struct abx500_chargalg *di,
+               int iset)
+{
+       /* Check if charger exists and update current if charging */
+       if (di->ac_chg && di->ac_chg->ops.update_curr &&
+                       di->chg_info.charger_type & AC_CHG) {
+               /*
+                * Select maximum of what both the charger
+                * and the battery supports
+                */
+               if (di->ac_chg->max_out_curr)
+                       iset = min(iset, di->ac_chg->max_out_curr);
+
+               di->chg_info.ac_iset = iset;
+
+               return di->ac_chg->ops.update_curr(di->ac_chg, iset);
+       } else if (di->usb_chg && di->usb_chg->ops.update_curr &&
+                       di->chg_info.charger_type & USB_CHG) {
+               /*
+                * Select maximum of what both the charger
+                * and the battery supports
+                */
+               if (di->usb_chg->max_out_curr)
+                       iset = min(iset, di->usb_chg->max_out_curr);
+
+               di->chg_info.usb_iset = iset;
+
+               return di->usb_chg->ops.update_curr(di->usb_chg, iset);
+       }
+
+       return -ENXIO;
+}
+
+/**
+ * abx500_chargalg_stop_charging() - Stop charging
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * This function is called from any state where charging should be stopped.
+ * All charging is disabled and all status parameters and timers are changed
+ * accordingly
+ */
+static void abx500_chargalg_stop_charging(struct abx500_chargalg *di)
+{
+       abx500_chargalg_ac_en(di, false, 0, 0);
+       abx500_chargalg_usb_en(di, false, 0, 0);
+       abx500_chargalg_stop_safety_timer(di);
+       abx500_chargalg_stop_maintenance_timer(di);
+       di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+       di->maintenance_chg = false;
+       cancel_delayed_work(&di->chargalg_wd_work);
+       power_supply_changed(&di->chargalg_psy);
+}
+
+/**
+ * abx500_chargalg_hold_charging() - Pauses charging
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * This function is called in the case where maintenance charging has been
+ * disabled and instead a battery voltage mode is entered to check when the
+ * battery voltage has reached a certain recharge voltage
+ */
+static void abx500_chargalg_hold_charging(struct abx500_chargalg *di)
+{
+       abx500_chargalg_ac_en(di, false, 0, 0);
+       abx500_chargalg_usb_en(di, false, 0, 0);
+       abx500_chargalg_stop_safety_timer(di);
+       abx500_chargalg_stop_maintenance_timer(di);
+       di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+       di->maintenance_chg = false;
+       cancel_delayed_work(&di->chargalg_wd_work);
+       power_supply_changed(&di->chargalg_psy);
+}
+
+/**
+ * abx500_chargalg_start_charging() - Start the charger
+ * @di:                pointer to the abx500_chargalg structure
+ * @vset:      requested charger output voltage
+ * @iset:      requested charger output current
+ *
+ * A charger will be enabled depending on the requested charger type that was
+ * detected previously.
+ */
+static void abx500_chargalg_start_charging(struct abx500_chargalg *di,
+       int vset, int iset)
+{
+       switch (di->chg_info.charger_type) {
+       case AC_CHG:
+               dev_dbg(di->dev,
+                       "AC parameters: Vset %d, Ich %d\n", vset, iset);
+               abx500_chargalg_usb_en(di, false, 0, 0);
+               abx500_chargalg_ac_en(di, true, vset, iset);
+               break;
+
+       case USB_CHG:
+               dev_dbg(di->dev,
+                       "USB parameters: Vset %d, Ich %d\n", vset, iset);
+               abx500_chargalg_ac_en(di, false, 0, 0);
+               abx500_chargalg_usb_en(di, true, vset, iset);
+               break;
+
+       default:
+               dev_err(di->dev, "Unknown charger to charge from\n");
+               break;
+       }
+}
+
+/**
+ * abx500_chargalg_check_temp() - Check battery temperature ranges
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * The battery temperature is checked against the predefined limits and the
+ * charge state is changed accordingly
+ */
+static void abx500_chargalg_check_temp(struct abx500_chargalg *di)
+{
+       if (di->batt_data.temp > (di->bat->temp_low + di->t_hyst_norm) &&
+               di->batt_data.temp < (di->bat->temp_high - di->t_hyst_norm)) {
+               /* Temp OK! */
+               di->events.btemp_underover = false;
+               di->events.btemp_lowhigh = false;
+               di->t_hyst_norm = 0;
+               di->t_hyst_lowhigh = 0;
+       } else {
+               if (((di->batt_data.temp >= di->bat->temp_high) &&
+                       (di->batt_data.temp <
+                               (di->bat->temp_over - di->t_hyst_lowhigh))) ||
+                       ((di->batt_data.temp >
+                               (di->bat->temp_under + di->t_hyst_lowhigh)) &&
+                       (di->batt_data.temp <= di->bat->temp_low))) {
+                       /* TEMP minor!!!!! */
+                       di->events.btemp_underover = false;
+                       di->events.btemp_lowhigh = true;
+                       di->t_hyst_norm = di->bat->temp_hysteresis;
+                       di->t_hyst_lowhigh = 0;
+               } else if (di->batt_data.temp <= di->bat->temp_under ||
+                       di->batt_data.temp >= di->bat->temp_over) {
+                       /* TEMP major!!!!! */
+                       di->events.btemp_underover = true;
+                       di->events.btemp_lowhigh = false;
+                       di->t_hyst_norm = 0;
+                       di->t_hyst_lowhigh = di->bat->temp_hysteresis;
+               } else {
+               /* Within hysteresis */
+               dev_dbg(di->dev, "Within hysteresis limit temp: %d "
+                               "hyst_lowhigh %d, hyst normal %d\n",
+                               di->batt_data.temp, di->t_hyst_lowhigh,
+                               di->t_hyst_norm);
+               }
+       }
+}
+
+/**
+ * abx500_chargalg_check_charger_voltage() - Check charger voltage
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * Charger voltage is checked against maximum limit
+ */
+static void abx500_chargalg_check_charger_voltage(struct abx500_chargalg *di)
+{
+       if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max)
+               di->chg_info.usb_chg_ok = false;
+       else
+               di->chg_info.usb_chg_ok = true;
+
+       if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max)
+               di->chg_info.ac_chg_ok = false;
+       else
+               di->chg_info.ac_chg_ok = true;
+
+}
+
+/**
+ * abx500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * End-of-charge criteria is fulfilled when the battery voltage is above a
+ * certain limit and the battery current is below a certain limit for a
+ * predefined number of consecutive seconds. If true, the battery is full
+ */
+static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di)
+{
+       if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING &&
+               di->charge_state == STATE_NORMAL &&
+               !di->maintenance_chg && (di->batt_data.volt >=
+               di->bat->bat_type[di->bat->batt_id].termination_vol ||
+               di->events.usb_cv_active || di->events.ac_cv_active) &&
+               di->batt_data.avg_curr <
+               di->bat->bat_type[di->bat->batt_id].termination_curr &&
+               di->batt_data.avg_curr > 0) {
+               if (++di->eoc_cnt >= EOC_COND_CNT) {
+                       di->eoc_cnt = 0;
+                       di->charge_status = POWER_SUPPLY_STATUS_FULL;
+                       di->maintenance_chg = true;
+                       dev_dbg(di->dev, "EOC reached!\n");
+                       power_supply_changed(&di->chargalg_psy);
+               } else {
+                       dev_dbg(di->dev,
+                               " EOC limit reached for the %d"
+                               " time, out of %d before EOC\n",
+                               di->eoc_cnt,
+                               EOC_COND_CNT);
+               }
+       } else {
+               di->eoc_cnt = 0;
+       }
+}
+
+static void init_maxim_chg_curr(struct abx500_chargalg *di)
+{
+       di->ccm.original_iset =
+               di->bat->bat_type[di->bat->batt_id].normal_cur_lvl;
+       di->ccm.current_iset =
+               di->bat->bat_type[di->bat->batt_id].normal_cur_lvl;
+       di->ccm.test_delta_i = di->bat->maxi->charger_curr_step;
+       di->ccm.max_current = di->bat->maxi->chg_curr;
+       di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+       di->ccm.level = 0;
+}
+
+/**
+ * abx500_chargalg_chg_curr_maxim - increases the charger current to
+ *                     compensate for the system load
+ * @di         pointer to the abx500_chargalg structure
+ *
+ * This maximization function is used to raise the charger current to get the
+ * battery current as close to the optimal value as possible. The battery
+ * current during charging is affected by the system load
+ */
+static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di)
+{
+       int delta_i;
+
+       if (!di->bat->maxi->ena_maxi)
+               return MAXIM_RET_NOACTION;
+
+       delta_i = di->ccm.original_iset - di->batt_data.inst_curr;
+
+       if (di->events.vbus_collapsed) {
+               dev_dbg(di->dev, "Charger voltage has collapsed %d\n",
+                               di->ccm.wait_cnt);
+               if (di->ccm.wait_cnt == 0) {
+                       dev_dbg(di->dev, "lowering current\n");
+                       di->ccm.wait_cnt++;
+                       di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+                       di->ccm.max_current =
+                               di->ccm.current_iset - di->ccm.test_delta_i;
+                       di->ccm.current_iset = di->ccm.max_current;
+                       di->ccm.level--;
+                       return MAXIM_RET_CHANGE;
+               } else {
+                       dev_dbg(di->dev, "waiting\n");
+                       /* Let's go in here twice before lowering curr again */
+                       di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3;
+                       return MAXIM_RET_NOACTION;
+               }
+       }
+
+       di->ccm.wait_cnt = 0;
+
+       if ((di->batt_data.inst_curr > di->ccm.original_iset)) {
+               dev_dbg(di->dev, " Maximization Ibat (%dmA) too high"
+                       " (limit %dmA) (current iset: %dmA)!\n",
+                       di->batt_data.inst_curr, di->ccm.original_iset,
+                       di->ccm.current_iset);
+
+               if (di->ccm.current_iset == di->ccm.original_iset)
+                       return MAXIM_RET_NOACTION;
+
+               di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+               di->ccm.current_iset = di->ccm.original_iset;
+               di->ccm.level = 0;
+
+               return MAXIM_RET_IBAT_TOO_HIGH;
+       }
+
+       if (delta_i > di->ccm.test_delta_i &&
+               (di->ccm.current_iset + di->ccm.test_delta_i) <
+               di->ccm.max_current) {
+               if (di->ccm.condition_cnt-- == 0) {
+                       /* Increse the iset with cco.test_delta_i */
+                       di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+                       di->ccm.current_iset += di->ccm.test_delta_i;
+                       di->ccm.level++;
+                       dev_dbg(di->dev, " Maximization needed, increase"
+                               " with %d mA to %dmA (Optimal ibat: %d)"
+                               " Level %d\n",
+                               di->ccm.test_delta_i,
+                               di->ccm.current_iset,
+                               di->ccm.original_iset,
+                               di->ccm.level);
+                       return MAXIM_RET_CHANGE;
+               } else {
+                       return MAXIM_RET_NOACTION;
+               }
+       }  else {
+               di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+               return MAXIM_RET_NOACTION;
+       }
+}
+
+static void handle_maxim_chg_curr(struct abx500_chargalg *di)
+{
+       enum maxim_ret ret;
+       int result;
+
+       ret = abx500_chargalg_chg_curr_maxim(di);
+       switch (ret) {
+       case MAXIM_RET_CHANGE:
+               result = abx500_chargalg_update_chg_curr(di,
+                       di->ccm.current_iset);
+               if (result)
+                       dev_err(di->dev, "failed to set chg curr\n");
+               break;
+       case MAXIM_RET_IBAT_TOO_HIGH:
+               result = abx500_chargalg_update_chg_curr(di,
+                       di->bat->bat_type[di->bat->batt_id].normal_cur_lvl);
+               if (result)
+                       dev_err(di->dev, "failed to set chg curr\n");
+               break;
+
+       case MAXIM_RET_NOACTION:
+       default:
+               /* Do nothing..*/
+               break;
+       }
+}
+
+static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data)
+{
+       struct power_supply *psy;
+       struct power_supply *ext;
+       struct abx500_chargalg *di;
+       union power_supply_propval ret;
+       int i, j;
+       bool psy_found = false;
+
+       psy = (struct power_supply *)data;
+       ext = dev_get_drvdata(dev);
+       di = to_abx500_chargalg_device_info(psy);
+       /* For all psy where the driver name appears in any supplied_to */
+       for (i = 0; i < ext->num_supplicants; i++) {
+               if (!strcmp(ext->supplied_to[i], psy->name))
+                       psy_found = true;
+       }
+       if (!psy_found)
+               return 0;
+
+       /* Go through all properties for the psy */
+       for (j = 0; j < ext->num_properties; j++) {
+               enum power_supply_property prop;
+               prop = ext->properties[j];
+
+               /* Initialize chargers if not already done */
+               if (!di->ac_chg &&
+                       ext->type == POWER_SUPPLY_TYPE_MAINS)
+                       di->ac_chg = psy_to_ux500_charger(ext);
+               else if (!di->usb_chg &&
+                       ext->type == POWER_SUPPLY_TYPE_USB)
+                       di->usb_chg = psy_to_ux500_charger(ext);
+
+               if (ext->get_property(ext, prop, &ret))
+                       continue;
+               switch (prop) {
+               case POWER_SUPPLY_PROP_PRESENT:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               /* Battery present */
+                               if (ret.intval)
+                                       di->events.batt_rem = false;
+                               /* Battery removed */
+                               else
+                                       di->events.batt_rem = true;
+                               break;
+                       case POWER_SUPPLY_TYPE_MAINS:
+                               /* AC disconnected */
+                               if (!ret.intval &&
+                                       (di->chg_info.conn_chg & AC_CHG)) {
+                                       di->chg_info.prev_conn_chg =
+                                               di->chg_info.conn_chg;
+                                       di->chg_info.conn_chg &= ~AC_CHG;
+                               }
+                               /* AC connected */
+                               else if (ret.intval &&
+                                       !(di->chg_info.conn_chg & AC_CHG)) {
+                                       di->chg_info.prev_conn_chg =
+                                               di->chg_info.conn_chg;
+                                       di->chg_info.conn_chg |= AC_CHG;
+                               }
+                               break;
+                       case POWER_SUPPLY_TYPE_USB:
+                               /* USB disconnected */
+                               if (!ret.intval &&
+                                       (di->chg_info.conn_chg & USB_CHG)) {
+                                       di->chg_info.prev_conn_chg =
+                                               di->chg_info.conn_chg;
+                                       di->chg_info.conn_chg &= ~USB_CHG;
+                               }
+                               /* USB connected */
+                               else if (ret.intval &&
+                                       !(di->chg_info.conn_chg & USB_CHG)) {
+                                       di->chg_info.prev_conn_chg =
+                                               di->chg_info.conn_chg;
+                                       di->chg_info.conn_chg |= USB_CHG;
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+
+               case POWER_SUPPLY_PROP_ONLINE:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               break;
+                       case POWER_SUPPLY_TYPE_MAINS:
+                               /* AC offline */
+                               if (!ret.intval &&
+                                       (di->chg_info.online_chg & AC_CHG)) {
+                                       di->chg_info.prev_online_chg =
+                                               di->chg_info.online_chg;
+                                       di->chg_info.online_chg &= ~AC_CHG;
+                               }
+                               /* AC online */
+                               else if (ret.intval &&
+                                       !(di->chg_info.online_chg & AC_CHG)) {
+                                       di->chg_info.prev_online_chg =
+                                               di->chg_info.online_chg;
+                                       di->chg_info.online_chg |= AC_CHG;
+                                       queue_delayed_work(di->chargalg_wq,
+                                               &di->chargalg_wd_work, 0);
+                               }
+                               break;
+                       case POWER_SUPPLY_TYPE_USB:
+                               /* USB offline */
+                               if (!ret.intval &&
+                                       (di->chg_info.online_chg & USB_CHG)) {
+                                       di->chg_info.prev_online_chg =
+                                               di->chg_info.online_chg;
+                                       di->chg_info.online_chg &= ~USB_CHG;
+                               }
+                               /* USB online */
+                               else if (ret.intval &&
+                                       !(di->chg_info.online_chg & USB_CHG)) {
+                                       di->chg_info.prev_online_chg =
+                                               di->chg_info.online_chg;
+                                       di->chg_info.online_chg |= USB_CHG;
+                                       queue_delayed_work(di->chargalg_wq,
+                                               &di->chargalg_wd_work, 0);
+                               }
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+
+               case POWER_SUPPLY_PROP_HEALTH:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               break;
+                       case POWER_SUPPLY_TYPE_MAINS:
+                               switch (ret.intval) {
+                               case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
+                                       di->events.mainextchnotok = true;
+                                       di->events.main_thermal_prot = false;
+                                       di->events.main_ovv = false;
+                                       di->events.ac_wd_expired = false;
+                                       break;
+                               case POWER_SUPPLY_HEALTH_DEAD:
+                                       di->events.ac_wd_expired = true;
+                                       di->events.mainextchnotok = false;
+                                       di->events.main_ovv = false;
+                                       di->events.main_thermal_prot = false;
+                                       break;
+                               case POWER_SUPPLY_HEALTH_COLD:
+                               case POWER_SUPPLY_HEALTH_OVERHEAT:
+                                       di->events.main_thermal_prot = true;
+                                       di->events.mainextchnotok = false;
+                                       di->events.main_ovv = false;
+                                       di->events.ac_wd_expired = false;
+                                       break;
+                               case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
+                                       di->events.main_ovv = true;
+                                       di->events.mainextchnotok = false;
+                                       di->events.main_thermal_prot = false;
+                                       di->events.ac_wd_expired = false;
+                                       break;
+                               case POWER_SUPPLY_HEALTH_GOOD:
+                                       di->events.main_thermal_prot = false;
+                                       di->events.mainextchnotok = false;
+                                       di->events.main_ovv = false;
+                                       di->events.ac_wd_expired = false;
+                                       break;
+                               default:
+                                       break;
+                               }
+                               break;
+
+                       case POWER_SUPPLY_TYPE_USB:
+                               switch (ret.intval) {
+                               case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
+                                       di->events.usbchargernotok = true;
+                                       di->events.usb_thermal_prot = false;
+                                       di->events.vbus_ovv = false;
+                                       di->events.usb_wd_expired = false;
+                                       break;
+                               case POWER_SUPPLY_HEALTH_DEAD:
+                                       di->events.usb_wd_expired = true;
+                                       di->events.usbchargernotok = false;
+                                       di->events.usb_thermal_prot = false;
+                                       di->events.vbus_ovv = false;
+                                       break;
+                               case POWER_SUPPLY_HEALTH_COLD:
+                               case POWER_SUPPLY_HEALTH_OVERHEAT:
+                                       di->events.usb_thermal_prot = true;
+                                       di->events.usbchargernotok = false;
+                                       di->events.vbus_ovv = false;
+                                       di->events.usb_wd_expired = false;
+                                       break;
+                               case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
+                                       di->events.vbus_ovv = true;
+                                       di->events.usbchargernotok = false;
+                                       di->events.usb_thermal_prot = false;
+                                       di->events.usb_wd_expired = false;
+                                       break;
+                               case POWER_SUPPLY_HEALTH_GOOD:
+                                       di->events.usbchargernotok = false;
+                                       di->events.usb_thermal_prot = false;
+                                       di->events.vbus_ovv = false;
+                                       di->events.usb_wd_expired = false;
+                                       break;
+                               default:
+                                       break;
+                               }
+                       default:
+                               break;
+                       }
+                       break;
+
+               case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               di->batt_data.volt = ret.intval / 1000;
+                               break;
+                       case POWER_SUPPLY_TYPE_MAINS:
+                               di->chg_info.ac_volt = ret.intval / 1000;
+                               break;
+                       case POWER_SUPPLY_TYPE_USB:
+                               di->chg_info.usb_volt = ret.intval / 1000;
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+
+               case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_MAINS:
+                               /* AVG is used to indicate when we are
+                                * in CV mode */
+                               if (ret.intval)
+                                       di->events.ac_cv_active = true;
+                               else
+                                       di->events.ac_cv_active = false;
+
+                               break;
+                       case POWER_SUPPLY_TYPE_USB:
+                               /* AVG is used to indicate when we are
+                                * in CV mode */
+                               if (ret.intval)
+                                       di->events.usb_cv_active = true;
+                               else
+                                       di->events.usb_cv_active = false;
+
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+
+               case POWER_SUPPLY_PROP_TECHNOLOGY:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               if (ret.intval)
+                                       di->events.batt_unknown = false;
+                               else
+                                       di->events.batt_unknown = true;
+
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+
+               case POWER_SUPPLY_PROP_TEMP:
+                       di->batt_data.temp = ret.intval / 10;
+                       break;
+
+               case POWER_SUPPLY_PROP_CURRENT_NOW:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_MAINS:
+                                       di->chg_info.ac_curr =
+                                               ret.intval / 1000;
+                                       break;
+                       case POWER_SUPPLY_TYPE_USB:
+                                       di->chg_info.usb_curr =
+                                               ret.intval / 1000;
+                               break;
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               di->batt_data.inst_curr = ret.intval / 1000;
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+
+               case POWER_SUPPLY_PROP_CURRENT_AVG:
+                       switch (ext->type) {
+                       case POWER_SUPPLY_TYPE_BATTERY:
+                               di->batt_data.avg_curr = ret.intval / 1000;
+                               break;
+                       case POWER_SUPPLY_TYPE_USB:
+                               if (ret.intval)
+                                       di->events.vbus_collapsed = true;
+                               else
+                                       di->events.vbus_collapsed = false;
+                               break;
+                       default:
+                               break;
+                       }
+                       break;
+               case POWER_SUPPLY_PROP_CAPACITY:
+                       di->batt_data.percent = ret.intval;
+                       break;
+               default:
+                       break;
+               }
+       }
+       return 0;
+}
+
+/**
+ * abx500_chargalg_external_power_changed() - callback for power supply changes
+ * @psy:       pointer to the structure power_supply
+ *
+ * This function is the entry point of the pointer external_power_changed
+ * of the structure power_supply.
+ * This function gets executed when there is a change in any external power
+ * supply that this driver needs to be notified of.
+ */
+static void abx500_chargalg_external_power_changed(struct power_supply *psy)
+{
+       struct abx500_chargalg *di = to_abx500_chargalg_device_info(psy);
+
+       /*
+        * Trigger execution of the algorithm instantly and read
+        * all power_supply properties there instead
+        */
+       queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_algorithm() - Main function for the algorithm
+ * @di:                pointer to the abx500_chargalg structure
+ *
+ * This is the main control function for the charging algorithm.
+ * It is called periodically or when something happens that will
+ * trigger a state change
+ */
+static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
+{
+       int charger_status;
+
+       /* Collect data from all power_supply class devices */
+       class_for_each_device(power_supply_class, NULL,
+               &di->chargalg_psy, abx500_chargalg_get_ext_psy_data);
+
+       abx500_chargalg_end_of_charge(di);
+       abx500_chargalg_check_temp(di);
+       abx500_chargalg_check_charger_voltage(di);
+
+       charger_status = abx500_chargalg_check_charger_connection(di);
+       /*
+        * First check if we have a charger connected.
+        * Also we don't allow charging of unknown batteries if configured
+        * this way
+        */
+       if (!charger_status ||
+               (di->events.batt_unknown && !di->bat->chg_unknown_bat)) {
+               if (di->charge_state != STATE_HANDHELD) {
+                       di->events.safety_timer_expired = false;
+                       abx500_chargalg_state_to(di, STATE_HANDHELD_INIT);
+               }
+       }
+
+       /* If suspended, we should not continue checking the flags */
+       else if (di->charge_state == STATE_SUSPENDED_INIT ||
+               di->charge_state == STATE_SUSPENDED) {
+               /* We don't do anything here, just don,t continue */
+       }
+
+       /* Safety timer expiration */
+       else if (di->events.safety_timer_expired) {
+               if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED)
+                       abx500_chargalg_state_to(di,
+                               STATE_SAFETY_TIMER_EXPIRED_INIT);
+       }
+       /*
+        * Check if any interrupts has occured
+        * that will prevent us from charging
+        */
+
+       /* Battery removed */
+       else if (di->events.batt_rem) {
+               if (di->charge_state != STATE_BATT_REMOVED)
+                       abx500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT);
+       }
+       /* Main or USB charger not ok. */
+       else if (di->events.mainextchnotok || di->events.usbchargernotok) {
+               /*
+                * If vbus_collapsed is set, we have to lower the charger
+                * current, which is done in the normal state below
+                */
+               if (di->charge_state != STATE_CHG_NOT_OK &&
+                               !di->events.vbus_collapsed)
+                       abx500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT);
+       }
+       /* VBUS, Main or VBAT OVV. */
+       else if (di->events.vbus_ovv ||
+                       di->events.main_ovv ||
+                       di->events.batt_ovv ||
+                       !di->chg_info.usb_chg_ok ||
+                       !di->chg_info.ac_chg_ok) {
+               if (di->charge_state != STATE_OVV_PROTECT)
+                       abx500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT);
+       }
+       /* USB Thermal, stop charging */
+       else if (di->events.main_thermal_prot ||
+               di->events.usb_thermal_prot) {
+               if (di->charge_state != STATE_HW_TEMP_PROTECT)
+                       abx500_chargalg_state_to(di,
+                               STATE_HW_TEMP_PROTECT_INIT);
+       }
+       /* Battery temp over/under */
+       else if (di->events.btemp_underover) {
+               if (di->charge_state != STATE_TEMP_UNDEROVER)
+                       abx500_chargalg_state_to(di,
+                               STATE_TEMP_UNDEROVER_INIT);
+       }
+       /* Watchdog expired */
+       else if (di->events.ac_wd_expired ||
+               di->events.usb_wd_expired) {
+               if (di->charge_state != STATE_WD_EXPIRED)
+                       abx500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT);
+       }
+       /* Battery temp high/low */
+       else if (di->events.btemp_lowhigh) {
+               if (di->charge_state != STATE_TEMP_LOWHIGH)
+                       abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT);
+       }
+
+       dev_dbg(di->dev,
+               "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d "
+               "State %s Active_chg %d Chg_status %d AC %d USB %d "
+               "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d "
+               "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n",
+               di->batt_data.volt,
+               di->batt_data.avg_curr,
+               di->batt_data.inst_curr,
+               di->batt_data.temp,
+               di->batt_data.percent,
+               di->maintenance_chg,
+               states[di->charge_state],
+               di->chg_info.charger_type,
+               di->charge_status,
+               di->chg_info.conn_chg & AC_CHG,
+               di->chg_info.conn_chg & USB_CHG,
+               di->chg_info.online_chg & AC_CHG,
+               di->chg_info.online_chg & USB_CHG,
+               di->events.ac_cv_active,
+               di->events.usb_cv_active,
+               di->chg_info.ac_curr,
+               di->chg_info.usb_curr,
+               di->chg_info.ac_vset,
+               di->chg_info.ac_iset,
+               di->chg_info.usb_vset,
+               di->chg_info.usb_iset);
+
+       switch (di->charge_state) {
+       case STATE_HANDHELD_INIT:
+               abx500_chargalg_stop_charging(di);
+               di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
+               abx500_chargalg_state_to(di, STATE_HANDHELD);
+               /* Intentional fallthrough */
+
+       case STATE_HANDHELD:
+               break;
+
+       case STATE_SUSPENDED_INIT:
+               if (di->susp_status.ac_suspended)
+                       abx500_chargalg_ac_en(di, false, 0, 0);
+               if (di->susp_status.usb_suspended)
+                       abx500_chargalg_usb_en(di, false, 0, 0);
+               abx500_chargalg_stop_safety_timer(di);
+               abx500_chargalg_stop_maintenance_timer(di);
+               di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               di->maintenance_chg = false;
+               abx500_chargalg_state_to(di, STATE_SUSPENDED);
+               power_supply_changed(&di->chargalg_psy);
+               /* Intentional fallthrough */
+
+       case STATE_SUSPENDED:
+               /* CHARGING is suspended */
+               break;
+
+       case STATE_BATT_REMOVED_INIT:
+               abx500_chargalg_stop_charging(di);
+               abx500_chargalg_state_to(di, STATE_BATT_REMOVED);
+               /* Intentional fallthrough */
+
+       case STATE_BATT_REMOVED:
+               if (!di->events.batt_rem)
+                       abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               break;
+
+       case STATE_HW_TEMP_PROTECT_INIT:
+               abx500_chargalg_stop_charging(di);
+               abx500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT);
+               /* Intentional fallthrough */
+
+       case STATE_HW_TEMP_PROTECT:
+               if (!di->events.main_thermal_prot &&
+                               !di->events.usb_thermal_prot)
+                       abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               break;
+
+       case STATE_OVV_PROTECT_INIT:
+               abx500_chargalg_stop_charging(di);
+               abx500_chargalg_state_to(di, STATE_OVV_PROTECT);
+               /* Intentional fallthrough */
+
+       case STATE_OVV_PROTECT:
+               if (!di->events.vbus_ovv &&
+                               !di->events.main_ovv &&
+                               !di->events.batt_ovv &&
+                               di->chg_info.usb_chg_ok &&
+                               di->chg_info.ac_chg_ok)
+                       abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               break;
+
+       case STATE_CHG_NOT_OK_INIT:
+               abx500_chargalg_stop_charging(di);
+               abx500_chargalg_state_to(di, STATE_CHG_NOT_OK);
+               /* Intentional fallthrough */
+
+       case STATE_CHG_NOT_OK:
+               if (!di->events.mainextchnotok &&
+                               !di->events.usbchargernotok)
+                       abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               break;
+
+       case STATE_SAFETY_TIMER_EXPIRED_INIT:
+               abx500_chargalg_stop_charging(di);
+               abx500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED);
+               /* Intentional fallthrough */
+
+       case STATE_SAFETY_TIMER_EXPIRED:
+               /* We exit this state when charger is removed */
+               break;
+
+       case STATE_NORMAL_INIT:
+               abx500_chargalg_start_charging(di,
+                       di->bat->bat_type[di->bat->batt_id].normal_vol_lvl,
+                       di->bat->bat_type[di->bat->batt_id].normal_cur_lvl);
+               abx500_chargalg_state_to(di, STATE_NORMAL);
+               abx500_chargalg_start_safety_timer(di);
+               abx500_chargalg_stop_maintenance_timer(di);
+               init_maxim_chg_curr(di);
+               di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+               di->eoc_cnt = 0;
+               di->maintenance_chg = false;
+               power_supply_changed(&di->chargalg_psy);
+
+               break;
+
+       case STATE_NORMAL:
+               handle_maxim_chg_curr(di);
+               if (di->charge_status == POWER_SUPPLY_STATUS_FULL &&
+                       di->maintenance_chg) {
+                       if (di->bat->no_maintenance)
+                               abx500_chargalg_state_to(di,
+                                       STATE_WAIT_FOR_RECHARGE_INIT);
+                       else
+                               abx500_chargalg_state_to(di,
+                                       STATE_MAINTENANCE_A_INIT);
+               }
+               break;
+
+       /* This state will be used when the maintenance state is disabled */
+       case STATE_WAIT_FOR_RECHARGE_INIT:
+               abx500_chargalg_hold_charging(di);
+               abx500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE);
+               di->rch_cnt = RCH_COND_CNT;
+               /* Intentional fallthrough */
+
+       case STATE_WAIT_FOR_RECHARGE:
+               if (di->batt_data.volt <=
+                       di->bat->bat_type[di->bat->batt_id].recharge_vol) {
+                       if (di->rch_cnt-- == 0)
+                               abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               } else
+                       di->rch_cnt = RCH_COND_CNT;
+               break;
+
+       case STATE_MAINTENANCE_A_INIT:
+               abx500_chargalg_stop_safety_timer(di);
+               abx500_chargalg_start_maintenance_timer(di,
+                       di->bat->bat_type[
+                               di->bat->batt_id].maint_a_chg_timer_h);
+               abx500_chargalg_start_charging(di,
+                       di->bat->bat_type[
+                               di->bat->batt_id].maint_a_vol_lvl,
+                       di->bat->bat_type[
+                               di->bat->batt_id].maint_a_cur_lvl);
+               abx500_chargalg_state_to(di, STATE_MAINTENANCE_A);
+               power_supply_changed(&di->chargalg_psy);
+               /* Intentional fallthrough*/
+
+       case STATE_MAINTENANCE_A:
+               if (di->events.maintenance_timer_expired) {
+                       abx500_chargalg_stop_maintenance_timer(di);
+                       abx500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT);
+               }
+               break;
+
+       case STATE_MAINTENANCE_B_INIT:
+               abx500_chargalg_start_maintenance_timer(di,
+                       di->bat->bat_type[
+                               di->bat->batt_id].maint_b_chg_timer_h);
+               abx500_chargalg_start_charging(di,
+                       di->bat->bat_type[
+                               di->bat->batt_id].maint_b_vol_lvl,
+                       di->bat->bat_type[
+                               di->bat->batt_id].maint_b_cur_lvl);
+               abx500_chargalg_state_to(di, STATE_MAINTENANCE_B);
+               power_supply_changed(&di->chargalg_psy);
+               /* Intentional fallthrough*/
+
+       case STATE_MAINTENANCE_B:
+               if (di->events.maintenance_timer_expired) {
+                       abx500_chargalg_stop_maintenance_timer(di);
+                       abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               }
+               break;
+
+       case STATE_TEMP_LOWHIGH_INIT:
+               abx500_chargalg_start_charging(di,
+                       di->bat->bat_type[
+                               di->bat->batt_id].low_high_vol_lvl,
+                       di->bat->bat_type[
+                               di->bat->batt_id].low_high_cur_lvl);
+               abx500_chargalg_stop_maintenance_timer(di);
+               di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+               abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH);
+               power_supply_changed(&di->chargalg_psy);
+               /* Intentional fallthrough */
+
+       case STATE_TEMP_LOWHIGH:
+               if (!di->events.btemp_lowhigh)
+                       abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               break;
+
+       case STATE_WD_EXPIRED_INIT:
+               abx500_chargalg_stop_charging(di);
+               abx500_chargalg_state_to(di, STATE_WD_EXPIRED);
+               /* Intentional fallthrough */
+
+       case STATE_WD_EXPIRED:
+               if (!di->events.ac_wd_expired &&
+                               !di->events.usb_wd_expired)
+                       abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               break;
+
+       case STATE_TEMP_UNDEROVER_INIT:
+               abx500_chargalg_stop_charging(di);
+               abx500_chargalg_state_to(di, STATE_TEMP_UNDEROVER);
+               /* Intentional fallthrough */
+
+       case STATE_TEMP_UNDEROVER:
+               if (!di->events.btemp_underover)
+                       abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+               break;
+       }
+
+       /* Start charging directly if the new state is a charge state */
+       if (di->charge_state == STATE_NORMAL_INIT ||
+                       di->charge_state == STATE_MAINTENANCE_A_INIT ||
+                       di->charge_state == STATE_MAINTENANCE_B_INIT)
+               queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_periodic_work() - Periodic work for the algorithm
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for the charging algorithm
+ */
+static void abx500_chargalg_periodic_work(struct work_struct *work)
+{
+       struct abx500_chargalg *di = container_of(work,
+               struct abx500_chargalg, chargalg_periodic_work.work);
+
+       abx500_chargalg_algorithm(di);
+
+       /*
+        * If a charger is connected then the battery has to be monitored
+        * frequently, else the work can be delayed.
+        */
+       if (di->chg_info.conn_chg)
+               queue_delayed_work(di->chargalg_wq,
+                       &di->chargalg_periodic_work,
+                       di->bat->interval_charging * HZ);
+       else
+               queue_delayed_work(di->chargalg_wq,
+                       &di->chargalg_periodic_work,
+                       di->bat->interval_not_charging * HZ);
+}
+
+/**
+ * abx500_chargalg_wd_work() - periodic work to kick the charger watchdog
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for kicking the charger watchdog
+ */
+static void abx500_chargalg_wd_work(struct work_struct *work)
+{
+       int ret;
+       struct abx500_chargalg *di = container_of(work,
+               struct abx500_chargalg, chargalg_wd_work.work);
+
+       dev_dbg(di->dev, "abx500_chargalg_wd_work\n");
+
+       ret = abx500_chargalg_kick_watchdog(di);
+       if (ret < 0)
+               dev_err(di->dev, "failed to kick watchdog\n");
+
+       queue_delayed_work(di->chargalg_wq,
+               &di->chargalg_wd_work, CHG_WD_INTERVAL);
+}
+
+/**
+ * abx500_chargalg_work() - Work to run the charging algorithm instantly
+ * @work:      pointer to the work_struct structure
+ *
+ * Work queue function for calling the charging algorithm
+ */
+static void abx500_chargalg_work(struct work_struct *work)
+{
+       struct abx500_chargalg *di = container_of(work,
+               struct abx500_chargalg, chargalg_work);
+
+       abx500_chargalg_algorithm(di);
+}
+
+/**
+ * abx500_chargalg_get_property() - get the chargalg properties
+ * @psy:       pointer to the power_supply structure
+ * @psp:       pointer to the power_supply_property structure
+ * @val:       pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the
+ * chargalg properties by reading the sysfs files.
+ * status:     charging/discharging/full/unknown
+ * health:     health of the battery
+ * Returns error code in case of failure else 0 on success
+ */
+static int abx500_chargalg_get_property(struct power_supply *psy,
+       enum power_supply_property psp,
+       union power_supply_propval *val)
+{
+       struct abx500_chargalg *di;
+
+       di = to_abx500_chargalg_device_info(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               val->intval = di->charge_status;
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               if (di->events.batt_ovv) {
+                       val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+               } else if (di->events.btemp_underover) {
+                       if (di->batt_data.temp <= di->bat->temp_under)
+                               val->intval = POWER_SUPPLY_HEALTH_COLD;
+                       else
+                               val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+               } else {
+                       val->intval = POWER_SUPPLY_HEALTH_GOOD;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+       return 0;
+}
+
+/* Exposure to the sysfs interface */
+
+/**
+ * abx500_chargalg_sysfs_charger() - sysfs store operations
+ * @kobj:      pointer to the struct kobject
+ * @attr:      pointer to the struct attribute
+ * @buf:       buffer that holds the parameter passed from userspace
+ * @length:    length of the parameter passed
+ *
+ * Returns length of the buffer(input taken from user space) on success
+ * else error code on failure
+ * The operation to be performed on passing the parameters from the user space.
+ */
+static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj,
+       struct attribute *attr, const char *buf, size_t length)
+{
+       struct abx500_chargalg *di = container_of(kobj,
+               struct abx500_chargalg, chargalg_kobject);
+       long int param;
+       int ac_usb;
+       int ret;
+       char entry = *attr->name;
+
+       switch (entry) {
+       case 'c':
+               ret = strict_strtol(buf, 10, &param);
+               if (ret < 0)
+                       return ret;
+
+               ac_usb = param;
+               switch (ac_usb) {
+               case 0:
+                       /* Disable charging */
+                       di->susp_status.ac_suspended = true;
+                       di->susp_status.usb_suspended = true;
+                       di->susp_status.suspended_change = true;
+                       /* Trigger a state change */
+                       queue_work(di->chargalg_wq,
+                               &di->chargalg_work);
+                       break;
+               case 1:
+                       /* Enable AC Charging */
+                       di->susp_status.ac_suspended = false;
+                       di->susp_status.suspended_change = true;
+                       /* Trigger a state change */
+                       queue_work(di->chargalg_wq,
+                               &di->chargalg_work);
+                       break;
+               case 2:
+                       /* Enable USB charging */
+                       di->susp_status.usb_suspended = false;
+                       di->susp_status.suspended_change = true;
+                       /* Trigger a state change */
+                       queue_work(di->chargalg_wq,
+                               &di->chargalg_work);
+                       break;
+               default:
+                       dev_info(di->dev, "Wrong input\n"
+                               "Enter 0. Disable AC/USB Charging\n"
+                               "1. Enable AC charging\n"
+                               "2. Enable USB Charging\n");
+               };
+               break;
+       };
+       return strlen(buf);
+}
+
+static struct attribute abx500_chargalg_en_charger = \
+{
+       .name = "chargalg",
+       .mode = S_IWUGO,
+};
+
+static struct attribute *abx500_chargalg_chg[] = {
+       &abx500_chargalg_en_charger,
+       NULL
+};
+
+static const struct sysfs_ops abx500_chargalg_sysfs_ops = {
+       .store = abx500_chargalg_sysfs_charger,
+};
+
+static struct kobj_type abx500_chargalg_ktype = {
+       .sysfs_ops = &abx500_chargalg_sysfs_ops,
+       .default_attrs = abx500_chargalg_chg,
+};
+
+/**
+ * abx500_chargalg_sysfs_exit() - de-init of sysfs entry
+ * @di:                pointer to the struct abx500_chargalg
+ *
+ * This function removes the entry in sysfs.
+ */
+static void abx500_chargalg_sysfs_exit(struct abx500_chargalg *di)
+{
+       kobject_del(&di->chargalg_kobject);
+}
+
+/**
+ * abx500_chargalg_sysfs_init() - init of sysfs entry
+ * @di:                pointer to the struct abx500_chargalg
+ *
+ * This function adds an entry in sysfs.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int abx500_chargalg_sysfs_init(struct abx500_chargalg *di)
+{
+       int ret = 0;
+
+       ret = kobject_init_and_add(&di->chargalg_kobject,
+               &abx500_chargalg_ktype,
+               NULL, "abx500_chargalg");
+       if (ret < 0)
+               dev_err(di->dev, "failed to create sysfs entry\n");
+
+       return ret;
+}
+/* Exposure to the sysfs interface <<END>> */
+
+#if defined(CONFIG_PM)
+static int abx500_chargalg_resume(struct platform_device *pdev)
+{
+       struct abx500_chargalg *di = platform_get_drvdata(pdev);
+
+       /* Kick charger watchdog if charging (any charger online) */
+       if (di->chg_info.online_chg)
+               queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0);
+
+       /*
+        * Run the charging algorithm directly to be sure we don't
+        * do it too seldom
+        */
+       queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
+
+       return 0;
+}
+
+static int abx500_chargalg_suspend(struct platform_device *pdev,
+       pm_message_t state)
+{
+       struct abx500_chargalg *di = platform_get_drvdata(pdev);
+
+       if (di->chg_info.online_chg)
+               cancel_delayed_work_sync(&di->chargalg_wd_work);
+
+       cancel_delayed_work_sync(&di->chargalg_periodic_work);
+
+       return 0;
+}
+#else
+#define abx500_chargalg_suspend      NULL
+#define abx500_chargalg_resume       NULL
+#endif
+
+static int __devexit abx500_chargalg_remove(struct platform_device *pdev)
+{
+       struct abx500_chargalg *di = platform_get_drvdata(pdev);
+
+       /* sysfs interface to enable/disbale charging from user space */
+       abx500_chargalg_sysfs_exit(di);
+
+       /* Delete the work queue */
+       destroy_workqueue(di->chargalg_wq);
+
+       flush_scheduled_work();
+       power_supply_unregister(&di->chargalg_psy);
+       platform_set_drvdata(pdev, NULL);
+       kfree(di);
+
+       return 0;
+}
+
+static int __devinit abx500_chargalg_probe(struct platform_device *pdev)
+{
+       struct abx500_bm_plat_data *plat_data;
+       int ret = 0;
+
+       struct abx500_chargalg *di =
+               kzalloc(sizeof(struct abx500_chargalg), GFP_KERNEL);
+       if (!di)
+               return -ENOMEM;
+
+       /* get device struct */
+       di->dev = &pdev->dev;
+
+       plat_data = pdev->dev.platform_data;
+       di->pdata = plat_data->chargalg;
+       di->bat = plat_data->battery;
+
+       /* chargalg supply */
+       di->chargalg_psy.name = "abx500_chargalg";
+       di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY;
+       di->chargalg_psy.properties = abx500_chargalg_props;
+       di->chargalg_psy.num_properties = ARRAY_SIZE(abx500_chargalg_props);
+       di->chargalg_psy.get_property = abx500_chargalg_get_property;
+       di->chargalg_psy.supplied_to = di->pdata->supplied_to;
+       di->chargalg_psy.num_supplicants = di->pdata->num_supplicants;
+       di->chargalg_psy.external_power_changed =
+               abx500_chargalg_external_power_changed;
+
+       /* Initilialize safety timer */
+       init_timer(&di->safety_timer);
+       di->safety_timer.function = abx500_chargalg_safety_timer_expired;
+       di->safety_timer.data = (unsigned long) di;
+
+       /* Initilialize maintenance timer */
+       init_timer(&di->maintenance_timer);
+       di->maintenance_timer.function =
+               abx500_chargalg_maintenance_timer_expired;
+       di->maintenance_timer.data = (unsigned long) di;
+
+       /* Create a work queue for the chargalg */
+       di->chargalg_wq =
+               create_singlethread_workqueue("abx500_chargalg_wq");
+       if (di->chargalg_wq == NULL) {
+               dev_err(di->dev, "failed to create work queue\n");
+               goto free_device_info;
+       }
+
+       /* Init work for chargalg */
+       INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_periodic_work,
+               abx500_chargalg_periodic_work);
+       INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_wd_work,
+               abx500_chargalg_wd_work);
+
+       /* Init work for chargalg */
+       INIT_WORK(&di->chargalg_work, abx500_chargalg_work);
+
+       /* To detect charger at startup */
+       di->chg_info.prev_conn_chg = -1;
+
+       /* Register chargalg power supply class */
+       ret = power_supply_register(di->dev, &di->chargalg_psy);
+       if (ret) {
+               dev_err(di->dev, "failed to register chargalg psy\n");
+               goto free_chargalg_wq;
+       }
+
+       platform_set_drvdata(pdev, di);
+
+       /* sysfs interface to enable/disable charging from user space */
+       ret = abx500_chargalg_sysfs_init(di);
+       if (ret) {
+               dev_err(di->dev, "failed to create sysfs entry\n");
+               goto free_psy;
+       }
+
+       /* Run the charging algorithm */
+       queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
+
+       dev_info(di->dev, "probe success\n");
+       return ret;
+
+free_psy:
+       power_supply_unregister(&di->chargalg_psy);
+free_chargalg_wq:
+       destroy_workqueue(di->chargalg_wq);
+free_device_info:
+       kfree(di);
+
+       return ret;
+}
+
+static struct platform_driver abx500_chargalg_driver = {
+       .probe = abx500_chargalg_probe,
+       .remove = __devexit_p(abx500_chargalg_remove),
+       .suspend = abx500_chargalg_suspend,
+       .resume = abx500_chargalg_resume,
+       .driver = {
+               .name = "abx500-chargalg",
+               .owner = THIS_MODULE,
+       },
+};
+
+static int __init abx500_chargalg_init(void)
+{
+       return platform_driver_register(&abx500_chargalg_driver);
+}
+
+static void __exit abx500_chargalg_exit(void)
+{
+       platform_driver_unregister(&abx500_chargalg_driver);
+}
+
+module_init(abx500_chargalg_init);
+module_exit(abx500_chargalg_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
+MODULE_ALIAS("platform:abx500-chargalg");
+MODULE_DESCRIPTION("abx500 battery charging algorithm");
index 88fd9710bda212c22b59da04f970977e408ea6f2..9eca9f1ff0eae2e5b381e503f035ceb446340e66 100644 (file)
@@ -134,12 +134,11 @@ static int get_batt_uV(struct charger_manager *cm, int *uV)
        union power_supply_propval val;
        int ret;
 
-       if (cm->fuel_gauge)
-               ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
-                               POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
-       else
+       if (!cm->fuel_gauge)
                return -ENODEV;
 
+       ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+                               POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
        if (ret)
                return ret;
 
@@ -245,9 +244,7 @@ static int try_charger_enable(struct charger_manager *cm, bool enable)
        struct charger_desc *desc = cm->desc;
 
        /* Ignore if it's redundent command */
-       if (enable && cm->charger_enabled)
-               return 0;
-       if (!enable && !cm->charger_enabled)
+       if (enable == cm->charger_enabled)
                return 0;
 
        if (enable) {
@@ -309,9 +306,7 @@ static void uevent_notify(struct charger_manager *cm, const char *event)
 
                if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE))
                        return; /* Duplicated. */
-               else
-                       strncpy(env_str_save, event, UEVENT_BUF_SIZE);
-
+               strncpy(env_str_save, event, UEVENT_BUF_SIZE);
                return;
        }
 
@@ -387,8 +382,10 @@ static bool cm_monitor(void)
 
        mutex_lock(&cm_list_mtx);
 
-       list_for_each_entry(cm, &cm_list, entry)
-               stop = stop || _cm_monitor(cm);
+       list_for_each_entry(cm, &cm_list, entry) {
+               if (_cm_monitor(cm))
+                       stop = true;
+       }
 
        mutex_unlock(&cm_list_mtx);
 
@@ -402,7 +399,8 @@ static int charger_get_property(struct power_supply *psy,
        struct charger_manager *cm = container_of(psy,
                        struct charger_manager, charger_psy);
        struct charger_desc *desc = cm->desc;
-       int i, ret = 0, uV;
+       int ret = 0;
+       int uV;
 
        switch (psp) {
        case POWER_SUPPLY_PROP_STATUS:
@@ -428,8 +426,7 @@ static int charger_get_property(struct power_supply *psy,
                        val->intval = 0;
                break;
        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
-               ret = get_batt_uV(cm, &i);
-               val->intval = i;
+               ret = get_batt_uV(cm, &val->intval);
                break;
        case POWER_SUPPLY_PROP_CURRENT_NOW:
                ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
@@ -697,8 +694,10 @@ bool cm_suspend_again(void)
        mutex_lock(&cm_list_mtx);
        list_for_each_entry(cm, &cm_list, entry) {
                if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) ||
-                   cm->status_save_batt != is_batt_present(cm))
+                   cm->status_save_batt != is_batt_present(cm)) {
                        ret = false;
+                       break;
+               }
        }
        mutex_unlock(&cm_list_mtx);
 
@@ -855,11 +854,10 @@ static int charger_manager_probe(struct platform_device *pdev)
 
        platform_set_drvdata(pdev, cm);
 
-       memcpy(&cm->charger_psy, &psy_default,
-                               sizeof(psy_default));
+       memcpy(&cm->charger_psy, &psy_default, sizeof(psy_default));
+
        if (!desc->psy_name) {
-               strncpy(cm->psy_name_buf, psy_default.name,
-                               PSY_NAME_MAX);
+               strncpy(cm->psy_name_buf, psy_default.name, PSY_NAME_MAX);
        } else {
                strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX);
        }
@@ -894,15 +892,15 @@ static int charger_manager_probe(struct platform_device *pdev)
                                POWER_SUPPLY_PROP_CURRENT_NOW;
                cm->charger_psy.num_properties++;
        }
-       if (!desc->measure_battery_temp) {
-               cm->charger_psy.properties[cm->charger_psy.num_properties] =
-                               POWER_SUPPLY_PROP_TEMP_AMBIENT;
-               cm->charger_psy.num_properties++;
-       }
+
        if (desc->measure_battery_temp) {
                cm->charger_psy.properties[cm->charger_psy.num_properties] =
                                POWER_SUPPLY_PROP_TEMP;
                cm->charger_psy.num_properties++;
+       } else {
+               cm->charger_psy.properties[cm->charger_psy.num_properties] =
+                               POWER_SUPPLY_PROP_TEMP_AMBIENT;
+               cm->charger_psy.num_properties++;
        }
 
        ret = power_supply_register(NULL, &cm->charger_psy);
@@ -933,9 +931,8 @@ static int charger_manager_probe(struct platform_device *pdev)
        return 0;
 
 err_chg_enable:
-       if (desc->charger_regulators)
-               regulator_bulk_free(desc->num_charger_regulators,
-                                       desc->charger_regulators);
+       regulator_bulk_free(desc->num_charger_regulators,
+                           desc->charger_regulators);
 err_bulk_get:
        power_supply_unregister(&cm->charger_psy);
 err_register:
@@ -961,10 +958,8 @@ static int __devexit charger_manager_remove(struct platform_device *pdev)
        list_del(&cm->entry);
        mutex_unlock(&cm_list_mtx);
 
-       if (desc->charger_regulators)
-               regulator_bulk_free(desc->num_charger_regulators,
-                                       desc->charger_regulators);
-
+       regulator_bulk_free(desc->num_charger_regulators,
+                           desc->charger_regulators);
        power_supply_unregister(&cm->charger_psy);
        kfree(cm->charger_psy.properties);
        kfree(cm->charger_stat);
@@ -982,9 +977,7 @@ MODULE_DEVICE_TABLE(platform, charger_manager_id);
 
 static int cm_suspend_prepare(struct device *dev)
 {
-       struct platform_device *pdev = container_of(dev, struct platform_device,
-                                                   dev);
-       struct charger_manager *cm = platform_get_drvdata(pdev);
+       struct charger_manager *cm = dev_get_drvdata(dev);
 
        if (!cm_suspended) {
                if (rtc_dev) {
@@ -1020,9 +1013,7 @@ static int cm_suspend_prepare(struct device *dev)
 
 static void cm_suspend_complete(struct device *dev)
 {
-       struct platform_device *pdev = container_of(dev, struct platform_device,
-                                                   dev);
-       struct charger_manager *cm = platform_get_drvdata(pdev);
+       struct charger_manager *cm = dev_get_drvdata(dev);
 
        if (cm_suspended) {
                if (rtc_dev) {
index e8ea47a53dee53e2c29314038cdbf9ff9f0afc3f..a5f6a0ec15726b8e93180ea51b1a4facde4474a5 100644 (file)
@@ -612,6 +612,7 @@ static s32 __devinit da9052_bat_probe(struct platform_device *pdev)
         if (ret)
                goto err;
 
+       platform_set_drvdata(pdev, bat);
        return 0;
 
 err:
@@ -633,6 +634,7 @@ static int __devexit da9052_bat_remove(struct platform_device *pdev)
                free_irq(bat->da9052->irq_base + irq, bat);
        }
        power_supply_unregister(&bat->psy);
+       kfree(bat);
 
        return 0;
 }
@@ -645,18 +647,7 @@ static struct platform_driver da9052_bat_driver = {
                .owner = THIS_MODULE,
        },
 };
-
-static int __init da9052_bat_init(void)
-{
-       return platform_driver_register(&da9052_bat_driver);
-}
-module_init(da9052_bat_init);
-
-static void __exit da9052_bat_exit(void)
-{
-       platform_driver_unregister(&da9052_bat_driver);
-}
-module_exit(da9052_bat_exit);
+module_platform_driver(da9052_bat_driver);
 
 MODULE_DESCRIPTION("DA9052 BAT Device Driver");
 MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
index bfbce5de49daeeccfc3b8cf9daa585dbbd1076e4..6bb6e2f5ea814e408c52f23ca78d35f8a22e7444 100644 (file)
@@ -403,18 +403,7 @@ static struct i2c_driver ds278x_battery_driver = {
        .remove         = ds278x_battery_remove,
        .id_table       = ds278x_id,
 };
-
-static int __init ds278x_init(void)
-{
-       return i2c_add_driver(&ds278x_battery_driver);
-}
-module_init(ds278x_init);
-
-static void __exit ds278x_exit(void)
-{
-       i2c_del_driver(&ds278x_battery_driver);
-}
-module_exit(ds278x_exit);
+module_i2c_driver(ds278x_battery_driver);
 
 MODULE_AUTHOR("Ryan Mallon");
 MODULE_DESCRIPTION("Maxim/Dallas DS2782 Stand-Alone Fuel Gauage IC driver");
index 1289a5f790a12143a837c1ddcefd6a713610bcd6..39eb50f35f09fd777445a53202fb30823d9f9438 100644 (file)
@@ -480,6 +480,7 @@ fail0:
 
        dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
 
+       isp1704_charger_set_power(isp, 0);
        return ret;
 }
 
index c53dd1292f818bc595a54c863c63f362e28ad5aa..d8b75780bfef15718e9f578618381eeb4bc69560 100644 (file)
@@ -1,6 +1,7 @@
 /*
- * Driver for LP8727 Micro/Mini USB IC with intergrated charger
+ * Driver for LP8727 Micro/Mini USB IC with integrated charger
  *
+ *                     Copyright (C) 2011 Texas Instruments
  *                     Copyright (C) 2011 National Semiconductor
  *
  * This program is free software; you can redistribute it and/or modify
@@ -25,7 +26,7 @@
 #define INT1           0x4
 #define INT2           0x5
 #define STATUS1                0x6
-#define STATUS2        0x7
+#define STATUS2                0x7
 #define CHGCTRL2       0x9
 
 /* CTRL1 register */
@@ -91,7 +92,7 @@ struct lp8727_chg {
        enum lp8727_dev_id devid;
 };
 
-static int lp8727_i2c_read(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len)
+static int lp8727_read_bytes(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len)
 {
        s32 ret;
 
@@ -102,29 +103,22 @@ static int lp8727_i2c_read(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len)
        return (ret != len) ? -EIO : 0;
 }
 
-static int lp8727_i2c_write(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len)
+static inline int lp8727_read_byte(struct lp8727_chg *pchg, u8 reg, u8 *data)
 {
-       s32 ret;
+       return lp8727_read_bytes(pchg, reg, data, 1);
+}
+
+static int lp8727_write_byte(struct lp8727_chg *pchg, u8 reg, u8 data)
+{
+       int ret;
 
        mutex_lock(&pchg->xfer_lock);
-       ret = i2c_smbus_write_i2c_block_data(pchg->client, reg, len, data);
+       ret = i2c_smbus_write_byte_data(pchg->client, reg, data);
        mutex_unlock(&pchg->xfer_lock);
 
        return ret;
 }
 
-static inline int lp8727_i2c_read_byte(struct lp8727_chg *pchg, u8 reg,
-                                      u8 *data)
-{
-       return lp8727_i2c_read(pchg, reg, data, 1);
-}
-
-static inline int lp8727_i2c_write_byte(struct lp8727_chg *pchg, u8 reg,
-                                       u8 *data)
-{
-       return lp8727_i2c_write(pchg, reg, data, 1);
-}
-
 static int lp8727_is_charger_attached(const char *name, int id)
 {
        if (name) {
@@ -137,37 +131,41 @@ static int lp8727_is_charger_attached(const char *name, int id)
        return (id >= ID_TA && id <= ID_USB_CHG) ? 1 : 0;
 }
 
-static void lp8727_init_device(struct lp8727_chg *pchg)
+static int lp8727_init_device(struct lp8727_chg *pchg)
 {
        u8 val;
+       int ret;
 
        val = ID200_EN | ADC_EN | CP_EN;
-       if (lp8727_i2c_write_byte(pchg, CTRL1, &val))
-               dev_err(pchg->dev, "i2c write err : addr=0x%.2x\n", CTRL1);
+       ret = lp8727_write_byte(pchg, CTRL1, val);
+       if (ret)
+               return ret;
 
        val = INT_EN | CHGDET_EN;
-       if (lp8727_i2c_write_byte(pchg, CTRL2, &val))
-               dev_err(pchg->dev, "i2c write err : addr=0x%.2x\n", CTRL2);
+       ret = lp8727_write_byte(pchg, CTRL2, val);
+       if (ret)
+               return ret;
+
+       return 0;
 }
 
 static int lp8727_is_dedicated_charger(struct lp8727_chg *pchg)
 {
        u8 val;
-       lp8727_i2c_read_byte(pchg, STATUS1, &val);
-       return (val & DCPORT);
+       lp8727_read_byte(pchg, STATUS1, &val);
+       return val & DCPORT;
 }
 
 static int lp8727_is_usb_charger(struct lp8727_chg *pchg)
 {
        u8 val;
-       lp8727_i2c_read_byte(pchg, STATUS1, &val);
-       return (val & CHPORT);
+       lp8727_read_byte(pchg, STATUS1, &val);
+       return val & CHPORT;
 }
 
 static void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw)
 {
-       u8 val = sw;
-       lp8727_i2c_write_byte(pchg, SWCTRL, &val);
+       lp8727_write_byte(pchg, SWCTRL, sw);
 }
 
 static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin)
@@ -207,9 +205,9 @@ static void lp8727_enable_chgdet(struct lp8727_chg *pchg)
 {
        u8 val;
 
-       lp8727_i2c_read_byte(pchg, CTRL2, &val);
+       lp8727_read_byte(pchg, CTRL2, &val);
        val |= CHGDET_EN;
-       lp8727_i2c_write_byte(pchg, CTRL2, &val);
+       lp8727_write_byte(pchg, CTRL2, val);
 }
 
 static void lp8727_delayed_func(struct work_struct *_work)
@@ -218,7 +216,7 @@ static void lp8727_delayed_func(struct work_struct *_work)
        struct lp8727_chg *pchg =
            container_of(_work, struct lp8727_chg, work.work);
 
-       if (lp8727_i2c_read(pchg, INT1, intstat, 2)) {
+       if (lp8727_read_bytes(pchg, INT1, intstat, 2)) {
                dev_err(pchg->dev, "can not read INT registers\n");
                return;
        }
@@ -244,20 +242,22 @@ static irqreturn_t lp8727_isr_func(int irq, void *ptr)
        return IRQ_HANDLED;
 }
 
-static void lp8727_intr_config(struct lp8727_chg *pchg)
+static int lp8727_intr_config(struct lp8727_chg *pchg)
 {
        INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func);
 
        pchg->irqthread = create_singlethread_workqueue("lp8727-irqthd");
-       if (!pchg->irqthread)
+       if (!pchg->irqthread) {
                dev_err(pchg->dev, "can not create thread for lp8727\n");
-
-       if (request_threaded_irq(pchg->client->irq,
-                                NULL,
-                                lp8727_isr_func,
-                                IRQF_TRIGGER_FALLING, "lp8727_irq", pchg)) {
-               dev_err(pchg->dev, "lp8727 irq can not be registered\n");
+               return -ENOMEM;
        }
+
+       return request_threaded_irq(pchg->client->irq,
+                               NULL,
+                               lp8727_isr_func,
+                               IRQF_TRIGGER_FALLING,
+                               "lp8727_irq",
+                               pchg);
 }
 
 static enum power_supply_property lp8727_charger_prop[] = {
@@ -300,7 +300,7 @@ static int lp8727_battery_get_property(struct power_supply *psy,
        switch (psp) {
        case POWER_SUPPLY_PROP_STATUS:
                if (lp8727_is_charger_attached(psy->name, pchg->devid)) {
-                       lp8727_i2c_read_byte(pchg, STATUS1, &read);
+                       lp8727_read_byte(pchg, STATUS1, &read);
                        if (((read & CHGSTAT) >> 4) == EOC)
                                val->intval = POWER_SUPPLY_STATUS_FULL;
                        else
@@ -310,7 +310,7 @@ static int lp8727_battery_get_property(struct power_supply *psy,
                }
                break;
        case POWER_SUPPLY_PROP_HEALTH:
-               lp8727_i2c_read_byte(pchg, STATUS2, &read);
+               lp8727_read_byte(pchg, STATUS2, &read);
                read = (read & TEMP_STAT) >> 5;
                if (read >= 0x1 && read <= 0x3)
                        val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
@@ -351,7 +351,7 @@ static void lp8727_charger_changed(struct power_supply *psy)
                        eoc_level = pchg->chg_parm->eoc_level;
                        ichg = pchg->chg_parm->ichg;
                        val = (ichg << 4) | eoc_level;
-                       lp8727_i2c_write_byte(pchg, CHGCTRL2, &val);
+                       lp8727_write_byte(pchg, CHGCTRL2, val);
                }
        }
 }
@@ -439,15 +439,29 @@ static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id)
 
        mutex_init(&pchg->xfer_lock);
 
-       lp8727_init_device(pchg);
-       lp8727_intr_config(pchg);
+       ret = lp8727_init_device(pchg);
+       if (ret) {
+               dev_err(pchg->dev, "i2c communication err: %d", ret);
+               goto error;
+       }
+
+       ret = lp8727_intr_config(pchg);
+       if (ret) {
+               dev_err(pchg->dev, "irq handler err: %d", ret);
+               goto error;
+       }
 
        ret = lp8727_register_psy(pchg);
-       if (ret)
-               dev_err(pchg->dev,
-                       "can not register power supplies. err=%d", ret);
+       if (ret) {
+               dev_err(pchg->dev, "power supplies register err: %d", ret);
+               goto error;
+       }
 
        return 0;
+
+error:
+       kfree(pchg);
+       return ret;
 }
 
 static int __devexit lp8727_remove(struct i2c_client *cl)
@@ -466,6 +480,7 @@ static const struct i2c_device_id lp8727_ids[] = {
        {"lp8727", 0},
        { }
 };
+MODULE_DEVICE_TABLE(i2c, lp8727_ids);
 
 static struct i2c_driver lp8727_driver = {
        .driver = {
@@ -475,21 +490,9 @@ static struct i2c_driver lp8727_driver = {
        .remove = __devexit_p(lp8727_remove),
        .id_table = lp8727_ids,
 };
+module_i2c_driver(lp8727_driver);
 
-static int __init lp8727_init(void)
-{
-       return i2c_add_driver(&lp8727_driver);
-}
-
-static void __exit lp8727_exit(void)
-{
-       i2c_del_driver(&lp8727_driver);
-}
-
-module_init(lp8727_init);
-module_exit(lp8727_exit);
-
-MODULE_DESCRIPTION("National Semiconductor LP8727 charger driver");
-MODULE_AUTHOR
-    ("Woogyom Kim <milo.kim@ti.com>, Daniel Jeong <daniel.jeong@ti.com>");
+MODULE_DESCRIPTION("TI/National Semiconductor LP8727 charger driver");
+MODULE_AUTHOR("Woogyom Kim <milo.kim@ti.com>, "
+             "Daniel Jeong <daniel.jeong@ti.com>");
 MODULE_LICENSE("GPL");
index 2f2f9a6f54fad6d75dc692fbee4d128b7ba642a4..c284143cfcd76ba7c3ccd3245fb379f953e0f40f 100644 (file)
@@ -290,18 +290,7 @@ static struct i2c_driver max17040_i2c_driver = {
        .resume         = max17040_resume,
        .id_table       = max17040_id,
 };
-
-static int __init max17040_init(void)
-{
-       return i2c_add_driver(&max17040_i2c_driver);
-}
-module_init(max17040_init);
-
-static void __exit max17040_exit(void)
-{
-       i2c_del_driver(&max17040_i2c_driver);
-}
-module_exit(max17040_exit);
+module_i2c_driver(max17040_i2c_driver);
 
 MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>");
 MODULE_DESCRIPTION("MAX17040 Fuel Gauge");
index 86acee2f988917912919e710d4b62f1592b98397..04620c2cb388f3f5f2411f6a2b8191c5b36b459f 100644 (file)
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
 #include <linux/mod_devicetable.h>
 #include <linux/power_supply.h>
 #include <linux/power/max17042_battery.h>
+#include <linux/of.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)
+
+/* Interrupt mask bits */
+#define CONFIG_ALRT_BIT_ENBL   (1 << 2)
+#define STATUS_INTR_SOCMIN_BIT (1 << 10)
+#define STATUS_INTR_SOCMAX_BIT (1 << 14)
+
+#define VFSOC0_LOCK            0x0000
+#define VFSOC0_UNLOCK          0x0080
+#define MODEL_UNLOCK1  0X0059
+#define MODEL_UNLOCK2  0X00C4
+#define MODEL_LOCK1            0X0000
+#define MODEL_LOCK2            0X0000
+
+#define dQ_ACC_DIV     0x4
+#define dP_ACC_100     0x1900
+#define dP_ACC_200     0x3200
 
 struct max17042_chip {
        struct i2c_client *client;
        struct power_supply battery;
        struct max17042_platform_data *pdata;
+       struct work_struct work;
+       int    init_complete;
 };
 
 static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value)
@@ -87,6 +120,9 @@ static int max17042_get_property(struct power_supply *psy,
                                struct max17042_chip, battery);
        int ret;
 
+       if (!chip->init_complete)
+               return -EAGAIN;
+
        switch (psp) {
        case POWER_SUPPLY_PROP_PRESENT:
                ret = max17042_read_reg(chip->client, MAX17042_STATUS);
@@ -136,21 +172,18 @@ static int max17042_get_property(struct power_supply *psy,
                val->intval = ret * 625 / 8;
                break;
        case POWER_SUPPLY_PROP_CAPACITY:
-               ret = max17042_read_reg(chip->client, MAX17042_SOC);
+               ret = max17042_read_reg(chip->client, MAX17042_RepSOC);
                if (ret < 0)
                        return ret;
 
                val->intval = ret >> 8;
                break;
        case POWER_SUPPLY_PROP_CHARGE_FULL:
-               ret = max17042_read_reg(chip->client, MAX17042_RepSOC);
+               ret = max17042_read_reg(chip->client, MAX17042_FullCAP);
                if (ret < 0)
                        return ret;
 
-               if ((ret >> 8) >= MAX17042_BATTERY_FULL)
-                       val->intval = 1;
-               else if (ret >= 0)
-                       val->intval = 0;
+               val->intval = ret * 1000 / 2;
                break;
        case POWER_SUPPLY_PROP_TEMP:
                ret = max17042_read_reg(chip->client, MAX17042_TEMP);
@@ -210,22 +243,419 @@ static int max17042_get_property(struct power_supply *psy,
        return 0;
 }
 
+static int max17042_write_verify_reg(struct i2c_client *client,
+                               u8 reg, u16 value)
+{
+       int retries = 8;
+       int ret;
+       u16 read_value;
+
+       do {
+               ret = i2c_smbus_write_word_data(client, reg, value);
+               read_value =  max17042_read_reg(client, reg);
+               if (read_value != value) {
+                       ret = -EIO;
+                       retries--;
+               }
+       } while (retries && read_value != value);
+
+       if (ret < 0)
+               dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+       return ret;
+}
+
+static inline void max17042_override_por(
+       struct i2c_client *client, u8 reg, u16 value)
+{
+       if (value)
+               max17042_write_reg(client, reg, value);
+}
+
+static inline void max10742_unlock_model(struct max17042_chip *chip)
+{
+       struct i2c_client *client = chip->client;
+       max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_UNLOCK1);
+       max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_UNLOCK2);
+}
+
+static inline void max10742_lock_model(struct max17042_chip *chip)
+{
+       struct i2c_client *client = chip->client;
+       max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_LOCK1);
+       max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_LOCK2);
+}
+
+static inline void max17042_write_model_data(struct max17042_chip *chip,
+                                       u8 addr, int size)
+{
+       struct i2c_client *client = chip->client;
+       int i;
+       for (i = 0; i < size; i++)
+               max17042_write_reg(client, addr + i,
+                               chip->pdata->config_data->cell_char_tbl[i]);
+}
+
+static inline void max17042_read_model_data(struct max17042_chip *chip,
+                                       u8 addr, u16 *data, int size)
+{
+       struct i2c_client *client = chip->client;
+       int i;
+
+       for (i = 0; i < size; i++)
+               data[i] = max17042_read_reg(client, addr + i);
+}
+
+static inline int max17042_model_data_compare(struct max17042_chip *chip,
+                                       u16 *data1, u16 *data2, int size)
+{
+       int i;
+
+       if (memcmp(data1, data2, size)) {
+               dev_err(&chip->client->dev, "%s compare failed\n", __func__);
+               for (i = 0; i < size; i++)
+                       dev_info(&chip->client->dev, "0x%x, 0x%x",
+                               data1[i], data2[i]);
+               dev_info(&chip->client->dev, "\n");
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static int max17042_init_model(struct max17042_chip *chip)
+{
+       int ret;
+       int table_size =
+               sizeof(chip->pdata->config_data->cell_char_tbl)/sizeof(u16);
+       u16 *temp_data;
+
+       temp_data = kzalloc(table_size, GFP_KERNEL);
+       if (!temp_data)
+               return -ENOMEM;
+
+       max10742_unlock_model(chip);
+       max17042_write_model_data(chip, MAX17042_MODELChrTbl,
+                               table_size);
+       max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data,
+                               table_size);
+
+       ret = max17042_model_data_compare(
+               chip,
+               chip->pdata->config_data->cell_char_tbl,
+               temp_data,
+               table_size);
+
+       max10742_lock_model(chip);
+       kfree(temp_data);
+
+       return ret;
+}
+
+static int max17042_verify_model_lock(struct max17042_chip *chip)
+{
+       int i;
+       int table_size =
+               sizeof(chip->pdata->config_data->cell_char_tbl);
+       u16 *temp_data;
+       int ret = 0;
+
+       temp_data = kzalloc(table_size, GFP_KERNEL);
+       if (!temp_data)
+               return -ENOMEM;
+
+       max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data,
+                               table_size);
+       for (i = 0; i < table_size; i++)
+               if (temp_data[i])
+                       ret = -EINVAL;
+
+       kfree(temp_data);
+       return ret;
+}
+
+static void max17042_write_config_regs(struct max17042_chip *chip)
+{
+       struct max17042_config_data *config = chip->pdata->config_data;
+
+       max17042_write_reg(chip->client, MAX17042_CONFIG, config->config);
+       max17042_write_reg(chip->client, MAX17042_LearnCFG, config->learn_cfg);
+       max17042_write_reg(chip->client, MAX17042_FilterCFG,
+                       config->filter_cfg);
+       max17042_write_reg(chip->client, MAX17042_RelaxCFG, config->relax_cfg);
+}
+
+static void  max17042_write_custom_regs(struct max17042_chip *chip)
+{
+       struct max17042_config_data *config = chip->pdata->config_data;
+
+       max17042_write_verify_reg(chip->client, MAX17042_RCOMP0,
+                               config->rcomp0);
+       max17042_write_verify_reg(chip->client, MAX17042_TempCo,
+                               config->tcompc0);
+       max17042_write_reg(chip->client, MAX17042_EmptyTempCo,
+                       config->empty_tempco);
+       max17042_write_verify_reg(chip->client, MAX17042_K_empty0,
+                               config->kempty0);
+       max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm,
+                               config->ichgt_term);
+}
+
+static void max17042_update_capacity_regs(struct max17042_chip *chip)
+{
+       struct max17042_config_data *config = chip->pdata->config_data;
+
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+                               config->fullcap);
+       max17042_write_reg(chip->client, MAX17042_DesignCap,
+                       config->design_cap);
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+                               config->fullcapnom);
+}
+
+static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip)
+{
+       u16 vfSoc;
+
+       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, vfSoc);
+       max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK);
+}
+
+static void max17042_load_new_capacity_params(struct max17042_chip *chip)
+{
+       u16 full_cap0, rep_cap, dq_acc, vfSoc;
+       u32 rem_cap;
+
+       struct max17042_config_data *config = chip->pdata->config_data;
+
+       full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0);
+       vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC);
+
+       /* 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 = ((vfSoc >> 8) * full_cap0) / 100;
+       max17042_write_verify_reg(chip->client, MAX17042_RemCap, (u16)rem_cap);
+
+       rep_cap = (u16)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 = config->fullcap / 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,
+                       config->fullcap);
+       max17042_write_reg(chip->client, MAX17042_DesignCap,
+                       config->design_cap);
+       max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+                       config->fullcapnom);
+}
+
+/*
+ * Block write all the override values coming from platform data.
+ * This function MUST be called before the POR initialization proceedure
+ * specified by maxim.
+ */
+static inline void max17042_override_por_values(struct max17042_chip *chip)
+{
+       struct i2c_client *client = chip->client;
+       struct max17042_config_data *config = chip->pdata->config_data;
+
+       max17042_override_por(client, MAX17042_TGAIN, config->tgain);
+       max17042_override_por(client, MAx17042_TOFF, config->toff);
+       max17042_override_por(client, MAX17042_CGAIN, config->cgain);
+       max17042_override_por(client, MAX17042_COFF, config->coff);
+
+       max17042_override_por(client, MAX17042_VALRT_Th, config->valrt_thresh);
+       max17042_override_por(client, MAX17042_TALRT_Th, config->talrt_thresh);
+       max17042_override_por(client, MAX17042_SALRT_Th,
+                       config->soc_alrt_thresh);
+       max17042_override_por(client, MAX17042_CONFIG, config->config);
+       max17042_override_por(client, MAX17042_SHDNTIMER, config->shdntimer);
+
+       max17042_override_por(client, MAX17042_DesignCap, config->design_cap);
+       max17042_override_por(client, MAX17042_ICHGTerm, config->ichgt_term);
+
+       max17042_override_por(client, MAX17042_AtRate, config->at_rate);
+       max17042_override_por(client, MAX17042_LearnCFG, config->learn_cfg);
+       max17042_override_por(client, MAX17042_FilterCFG, config->filter_cfg);
+       max17042_override_por(client, MAX17042_RelaxCFG, config->relax_cfg);
+       max17042_override_por(client, MAX17042_MiscCFG, config->misc_cfg);
+       max17042_override_por(client, MAX17042_MaskSOC, config->masksoc);
+
+       max17042_override_por(client, MAX17042_FullCAP, config->fullcap);
+       max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom);
+       max17042_override_por(client, MAX17042_SOC_empty, config->socempty);
+       max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty);
+       max17042_override_por(client, MAX17042_dQacc, config->dqacc);
+       max17042_override_por(client, MAX17042_dPacc, config->dpacc);
+
+       max17042_override_por(client, MAX17042_V_empty, config->vempty);
+       max17042_override_por(client, MAX17042_TempNom, config->temp_nom);
+       max17042_override_por(client, MAX17042_TempLim, config->temp_lim);
+       max17042_override_por(client, MAX17042_FCTC, config->fctc);
+       max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0);
+       max17042_override_por(client, MAX17042_TempCo, config->tcompc0);
+       max17042_override_por(client, MAX17042_EmptyTempCo,
+                       config->empty_tempco);
+       max17042_override_por(client, MAX17042_K_empty0, config->kempty0);
+}
+
+static int max17042_init_chip(struct max17042_chip *chip)
+{
+       int ret;
+       int val;
+
+       max17042_override_por_values(chip);
+       /* After Power up, the MAX17042 requires 500mS in order
+        * to perform signal debouncing and initial SOC reporting
+        */
+       msleep(500);
+
+       /* Initialize configaration */
+       max17042_write_config_regs(chip);
+
+       /* write cell characterization data */
+       ret = max17042_init_model(chip);
+       if (ret) {
+               dev_err(&chip->client->dev, "%s init failed\n",
+                       __func__);
+               return -EIO;
+       }
+       max17042_verify_model_lock(chip);
+       if (ret) {
+               dev_err(&chip->client->dev, "%s lock verify failed\n",
+                       __func__);
+               return -EIO;
+       }
+       /* write custom parameters */
+       max17042_write_custom_regs(chip);
+
+       /* update capacity params */
+       max17042_update_capacity_regs(chip);
+
+       /* delay must be atleast 350mS to allow VFSOC
+        * to be calculated from the new configuration
+        */
+       msleep(350);
+
+       /* reset vfsoc0 reg */
+       max17042_reset_vfsoc0_reg(chip);
+
+       /* load new capacity params */
+       max17042_load_new_capacity_params(chip);
+
+       /* 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));
+       return 0;
+}
+
+static void max17042_set_soc_threshold(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 irqreturn_t max17042_thread_handler(int id, void *dev)
+{
+       struct max17042_chip *chip = dev;
+       u16 val;
+
+       val = max17042_read_reg(chip->client, MAX17042_STATUS);
+       if ((val & STATUS_INTR_SOCMIN_BIT) ||
+               (val & STATUS_INTR_SOCMAX_BIT)) {
+               dev_info(&chip->client->dev, "SOC threshold INTR\n");
+               max17042_set_soc_threshold(chip, 1);
+       }
+
+       power_supply_changed(&chip->battery);
+       return IRQ_HANDLED;
+}
+
+static void max17042_init_worker(struct work_struct *work)
+{
+       struct max17042_chip *chip = container_of(work,
+                               struct max17042_chip, work);
+       int ret;
+
+       /* Initialize registers according to values from the platform data */
+       if (chip->pdata->enable_por_init && chip->pdata->config_data) {
+               ret = max17042_init_chip(chip);
+               if (ret)
+                       return;
+       }
+
+       chip->init_complete = 1;
+}
+
+#ifdef CONFIG_OF
+static struct max17042_platform_data *
+max17042_get_pdata(struct device *dev)
+{
+       struct device_node *np = dev->of_node;
+       u32 prop;
+       struct max17042_platform_data *pdata;
+
+       if (!np)
+               return dev->platform_data;
+
+       pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+       if (!pdata)
+               return NULL;
+
+       /*
+        * Require current sense resistor value to be specified for
+        * current-sense functionality to be enabled at all.
+        */
+       if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) {
+               pdata->r_sns = prop;
+               pdata->enable_current_sense = true;
+       }
+
+       return pdata;
+}
+#else
+static struct max17042_platform_data *
+max17042_get_pdata(struct device *dev)
+{
+       return dev->platform_data;
+}
+#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;
+       int reg;
 
        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
                return -EIO;
 
-       chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+       chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
        if (!chip)
                return -ENOMEM;
 
        chip->client = client;
-       chip->pdata = client->dev.platform_data;
+       chip->pdata = max17042_get_pdata(&client->dev);
+       if (!chip->pdata) {
+               dev_err(&client->dev, "no platform data provided\n");
+               return -EINVAL;
+       }
 
        i2c_set_clientdata(client, chip);
 
@@ -243,17 +673,9 @@ static int __devinit max17042_probe(struct i2c_client *client,
        if (chip->pdata->r_sns == 0)
                chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
 
-       ret = power_supply_register(&client->dev, &chip->battery);
-       if (ret) {
-               dev_err(&client->dev, "failed: power supply register\n");
-               kfree(chip);
-               return ret;
-       }
-
-       /* Initialize registers according to values from the platform data */
        if (chip->pdata->init_data)
                max17042_set_reg(client, chip->pdata->init_data,
-                                chip->pdata->num_init_data);
+                               chip->pdata->num_init_data);
 
        if (!chip->pdata->enable_current_sense) {
                max17042_write_reg(client, MAX17042_CGAIN, 0x0000);
@@ -261,7 +683,34 @@ static int __devinit max17042_probe(struct i2c_client *client,
                max17042_write_reg(client, MAX17042_LearnCFG, 0x0007);
        }
 
-       return 0;
+       if (client->irq) {
+               ret = request_threaded_irq(client->irq, NULL,
+                                               max17042_thread_handler,
+                                               IRQF_TRIGGER_FALLING,
+                                               chip->battery.name, chip);
+               if (!ret) {
+                       reg =  max17042_read_reg(client, MAX17042_CONFIG);
+                       reg |= CONFIG_ALRT_BIT_ENBL;
+                       max17042_write_reg(client, MAX17042_CONFIG, reg);
+                       max17042_set_soc_threshold(chip, 1);
+               } else
+                       dev_err(&client->dev, "%s(): cannot get IRQ\n",
+                               __func__);
+       }
+
+       reg = max17042_read_reg(chip->client, MAX17042_STATUS);
+
+       if (reg & STATUS_POR_BIT) {
+               INIT_WORK(&chip->work, max17042_init_worker);
+               schedule_work(&chip->work);
+       } else {
+               chip->init_complete = 1;
+       }
+
+       ret = power_supply_register(&client->dev, &chip->battery);
+       if (ret)
+               dev_err(&client->dev, "failed: power supply register\n");
+       return ret;
 }
 
 static int __devexit max17042_remove(struct i2c_client *client)
@@ -269,10 +718,17 @@ static int __devexit max17042_remove(struct i2c_client *client)
        struct max17042_chip *chip = i2c_get_clientdata(client);
 
        power_supply_unregister(&chip->battery);
-       kfree(chip);
        return 0;
 }
 
+#ifdef CONFIG_OF
+static const struct of_device_id max17042_dt_match[] = {
+       { .compatible = "maxim,max17042" },
+       { },
+};
+MODULE_DEVICE_TABLE(of, max17042_dt_match);
+#endif
+
 static const struct i2c_device_id max17042_id[] = {
        { "max17042", 0 },
        { }
@@ -282,23 +738,13 @@ MODULE_DEVICE_TABLE(i2c, max17042_id);
 static struct i2c_driver max17042_i2c_driver = {
        .driver = {
                .name   = "max17042",
+               .of_match_table = of_match_ptr(max17042_dt_match),
        },
        .probe          = max17042_probe,
        .remove         = __devexit_p(max17042_remove),
        .id_table       = max17042_id,
 };
-
-static int __init max17042_init(void)
-{
-       return i2c_add_driver(&max17042_i2c_driver);
-}
-module_init(max17042_init);
-
-static void __exit max17042_exit(void)
-{
-       i2c_del_driver(&max17042_i2c_driver);
-}
-module_exit(max17042_exit);
+module_i2c_driver(max17042_i2c_driver);
 
 MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
 MODULE_DESCRIPTION("MAX17042 Fuel Gauge");
index 9ff8af069da6f681830ccd7207e86d9163c62e3c..06b659d9179009e032bd7aaf2953aa8bf82c84fb 100644 (file)
@@ -852,18 +852,7 @@ static struct i2c_driver sbs_battery_driver = {
                .of_match_table = sbs_dt_ids,
        },
 };
-
-static int __init sbs_battery_init(void)
-{
-       return i2c_add_driver(&sbs_battery_driver);
-}
-module_init(sbs_battery_init);
-
-static void __exit sbs_battery_exit(void)
-{
-       i2c_del_driver(&sbs_battery_driver);
-}
-module_exit(sbs_battery_exit);
+module_i2c_driver(sbs_battery_driver);
 
 MODULE_DESCRIPTION("SBS battery monitor driver");
 MODULE_LICENSE("GPL");
diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c
new file mode 100644 (file)
index 0000000..ce1694d
--- /dev/null
@@ -0,0 +1,1294 @@
+/*
+ * Summit Microelectronics SMB347 Battery Charger Driver
+ *
+ * Copyright (C) 2011, Intel Corporation
+ *
+ * Authors: Bruce E. Robertson <bruce.e.robertson@intel.com>
+ *          Mika Westerberg <mika.westerberg@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/power/smb347-charger.h>
+#include <linux/seq_file.h>
+
+/*
+ * Configuration registers. These are mirrored to volatile RAM and can be
+ * written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be
+ * reloaded from non-volatile registers after POR.
+ */
+#define CFG_CHARGE_CURRENT                     0x00
+#define CFG_CHARGE_CURRENT_FCC_MASK            0xe0
+#define CFG_CHARGE_CURRENT_FCC_SHIFT           5
+#define CFG_CHARGE_CURRENT_PCC_MASK            0x18
+#define CFG_CHARGE_CURRENT_PCC_SHIFT           3
+#define CFG_CHARGE_CURRENT_TC_MASK             0x07
+#define CFG_CURRENT_LIMIT                      0x01
+#define CFG_CURRENT_LIMIT_DC_MASK              0xf0
+#define CFG_CURRENT_LIMIT_DC_SHIFT             4
+#define CFG_CURRENT_LIMIT_USB_MASK             0x0f
+#define CFG_FLOAT_VOLTAGE                      0x03
+#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK       0xc0
+#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT      6
+#define CFG_STAT                               0x05
+#define CFG_STAT_DISABLED                      BIT(5)
+#define CFG_STAT_ACTIVE_HIGH                   BIT(7)
+#define CFG_PIN                                        0x06
+#define CFG_PIN_EN_CTRL_MASK                   0x60
+#define CFG_PIN_EN_CTRL_ACTIVE_HIGH            0x40
+#define CFG_PIN_EN_CTRL_ACTIVE_LOW             0x60
+#define CFG_PIN_EN_APSD_IRQ                    BIT(1)
+#define CFG_PIN_EN_CHARGER_ERROR               BIT(2)
+#define CFG_THERM                              0x07
+#define CFG_THERM_SOFT_HOT_COMPENSATION_MASK   0x03
+#define CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT  0
+#define CFG_THERM_SOFT_COLD_COMPENSATION_MASK  0x0c
+#define CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT 2
+#define CFG_THERM_MONITOR_DISABLED             BIT(4)
+#define CFG_SYSOK                              0x08
+#define CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED  BIT(2)
+#define CFG_OTHER                              0x09
+#define CFG_OTHER_RID_MASK                     0xc0
+#define CFG_OTHER_RID_ENABLED_AUTO_OTG         0xc0
+#define CFG_OTG                                        0x0a
+#define CFG_OTG_TEMP_THRESHOLD_MASK            0x30
+#define CFG_OTG_TEMP_THRESHOLD_SHIFT           4
+#define CFG_OTG_CC_COMPENSATION_MASK           0xc0
+#define CFG_OTG_CC_COMPENSATION_SHIFT          6
+#define CFG_TEMP_LIMIT                         0x0b
+#define CFG_TEMP_LIMIT_SOFT_HOT_MASK           0x03
+#define CFG_TEMP_LIMIT_SOFT_HOT_SHIFT          0
+#define CFG_TEMP_LIMIT_SOFT_COLD_MASK          0x0c
+#define CFG_TEMP_LIMIT_SOFT_COLD_SHIFT         2
+#define CFG_TEMP_LIMIT_HARD_HOT_MASK           0x30
+#define CFG_TEMP_LIMIT_HARD_HOT_SHIFT          4
+#define CFG_TEMP_LIMIT_HARD_COLD_MASK          0xc0
+#define CFG_TEMP_LIMIT_HARD_COLD_SHIFT         6
+#define CFG_FAULT_IRQ                          0x0c
+#define CFG_FAULT_IRQ_DCIN_UV                  BIT(2)
+#define CFG_STATUS_IRQ                         0x0d
+#define CFG_STATUS_IRQ_TERMINATION_OR_TAPER    BIT(4)
+#define CFG_ADDRESS                            0x0e
+
+/* Command registers */
+#define CMD_A                                  0x30
+#define CMD_A_CHG_ENABLED                      BIT(1)
+#define CMD_A_SUSPEND_ENABLED                  BIT(2)
+#define CMD_A_ALLOW_WRITE                      BIT(7)
+#define CMD_B                                  0x31
+#define CMD_C                                  0x33
+
+/* Interrupt Status registers */
+#define IRQSTAT_A                              0x35
+#define IRQSTAT_C                              0x37
+#define IRQSTAT_C_TERMINATION_STAT             BIT(0)
+#define IRQSTAT_C_TERMINATION_IRQ              BIT(1)
+#define IRQSTAT_C_TAPER_IRQ                    BIT(3)
+#define IRQSTAT_E                              0x39
+#define IRQSTAT_E_USBIN_UV_STAT                        BIT(0)
+#define IRQSTAT_E_USBIN_UV_IRQ                 BIT(1)
+#define IRQSTAT_E_DCIN_UV_STAT                 BIT(4)
+#define IRQSTAT_E_DCIN_UV_IRQ                  BIT(5)
+#define IRQSTAT_F                              0x3a
+
+/* Status registers */
+#define STAT_A                                 0x3b
+#define STAT_A_FLOAT_VOLTAGE_MASK              0x3f
+#define STAT_B                                 0x3c
+#define STAT_C                                 0x3d
+#define STAT_C_CHG_ENABLED                     BIT(0)
+#define STAT_C_CHG_MASK                                0x06
+#define STAT_C_CHG_SHIFT                       1
+#define STAT_C_CHARGER_ERROR                   BIT(6)
+#define STAT_E                                 0x3f
+
+/**
+ * struct smb347_charger - smb347 charger instance
+ * @lock: protects concurrent access to online variables
+ * @client: pointer to i2c client
+ * @mains: power_supply instance for AC/DC power
+ * @usb: power_supply instance for USB power
+ * @battery: power_supply instance for battery
+ * @mains_online: is AC/DC input connected
+ * @usb_online: is USB input connected
+ * @charging_enabled: is charging enabled
+ * @dentry: for debugfs
+ * @pdata: pointer to platform data
+ */
+struct smb347_charger {
+       struct mutex            lock;
+       struct i2c_client       *client;
+       struct power_supply     mains;
+       struct power_supply     usb;
+       struct power_supply     battery;
+       bool                    mains_online;
+       bool                    usb_online;
+       bool                    charging_enabled;
+       struct dentry           *dentry;
+       const struct smb347_charger_platform_data *pdata;
+};
+
+/* Fast charge current in uA */
+static const unsigned int fcc_tbl[] = {
+       700000,
+       900000,
+       1200000,
+       1500000,
+       1800000,
+       2000000,
+       2200000,
+       2500000,
+};
+
+/* Pre-charge current in uA */
+static const unsigned int pcc_tbl[] = {
+       100000,
+       150000,
+       200000,
+       250000,
+};
+
+/* Termination current in uA */
+static const unsigned int tc_tbl[] = {
+       37500,
+       50000,
+       100000,
+       150000,
+       200000,
+       250000,
+       500000,
+       600000,
+};
+
+/* Input current limit in uA */
+static const unsigned int icl_tbl[] = {
+       300000,
+       500000,
+       700000,
+       900000,
+       1200000,
+       1500000,
+       1800000,
+       2000000,
+       2200000,
+       2500000,
+};
+
+/* Charge current compensation in uA */
+static const unsigned int ccc_tbl[] = {
+       250000,
+       700000,
+       900000,
+       1200000,
+};
+
+/* Convert register value to current using lookup table */
+static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val)
+{
+       if (val >= size)
+               return -EINVAL;
+       return tbl[val];
+}
+
+/* Convert current to register value using lookup table */
+static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val)
+{
+       size_t i;
+
+       for (i = 0; i < size; i++)
+               if (val < tbl[i])
+                       break;
+       return i > 0 ? i - 1 : -EINVAL;
+}
+
+static int smb347_read(struct smb347_charger *smb, u8 reg)
+{
+       int ret;
+
+       ret = i2c_smbus_read_byte_data(smb->client, reg);
+       if (ret < 0)
+               dev_warn(&smb->client->dev, "failed to read reg 0x%x: %d\n",
+                        reg, ret);
+       return ret;
+}
+
+static int smb347_write(struct smb347_charger *smb, u8 reg, u8 val)
+{
+       int ret;
+
+       ret = i2c_smbus_write_byte_data(smb->client, reg, val);
+       if (ret < 0)
+               dev_warn(&smb->client->dev, "failed to write reg 0x%x: %d\n",
+                        reg, ret);
+       return ret;
+}
+
+/**
+ * smb347_update_status - updates the charging status
+ * @smb: pointer to smb347 charger instance
+ *
+ * Function checks status of the charging and updates internal state
+ * accordingly. Returns %0 if there is no change in status, %1 if the
+ * status has changed and negative errno in case of failure.
+ */
+static int smb347_update_status(struct smb347_charger *smb)
+{
+       bool usb = false;
+       bool dc = false;
+       int ret;
+
+       ret = smb347_read(smb, IRQSTAT_E);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * Dc and usb are set depending on whether they are enabled in
+        * platform data _and_ whether corresponding undervoltage is set.
+        */
+       if (smb->pdata->use_mains)
+               dc = !(ret & IRQSTAT_E_DCIN_UV_STAT);
+       if (smb->pdata->use_usb)
+               usb = !(ret & IRQSTAT_E_USBIN_UV_STAT);
+
+       mutex_lock(&smb->lock);
+       ret = smb->mains_online != dc || smb->usb_online != usb;
+       smb->mains_online = dc;
+       smb->usb_online = usb;
+       mutex_unlock(&smb->lock);
+
+       return ret;
+}
+
+/*
+ * smb347_is_online - returns whether input power source is connected
+ * @smb: pointer to smb347 charger instance
+ *
+ * Returns %true if input power source is connected. Note that this is
+ * dependent on what platform has configured for usable power sources. For
+ * example if USB is disabled, this will return %false even if the USB
+ * cable is connected.
+ */
+static bool smb347_is_online(struct smb347_charger *smb)
+{
+       bool ret;
+
+       mutex_lock(&smb->lock);
+       ret = smb->usb_online || smb->mains_online;
+       mutex_unlock(&smb->lock);
+
+       return ret;
+}
+
+/**
+ * smb347_charging_status - returns status of charging
+ * @smb: pointer to smb347 charger instance
+ *
+ * Function returns charging status. %0 means no charging is in progress,
+ * %1 means pre-charging, %2 fast-charging and %3 taper-charging.
+ */
+static int smb347_charging_status(struct smb347_charger *smb)
+{
+       int ret;
+
+       if (!smb347_is_online(smb))
+               return 0;
+
+       ret = smb347_read(smb, STAT_C);
+       if (ret < 0)
+               return 0;
+
+       return (ret & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT;
+}
+
+static int smb347_charging_set(struct smb347_charger *smb, bool enable)
+{
+       int ret = 0;
+
+       if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) {
+               dev_dbg(&smb->client->dev,
+                       "charging enable/disable in SW disabled\n");
+               return 0;
+       }
+
+       mutex_lock(&smb->lock);
+       if (smb->charging_enabled != enable) {
+               ret = smb347_read(smb, CMD_A);
+               if (ret < 0)
+                       goto out;
+
+               smb->charging_enabled = enable;
+
+               if (enable)
+                       ret |= CMD_A_CHG_ENABLED;
+               else
+                       ret &= ~CMD_A_CHG_ENABLED;
+
+               ret = smb347_write(smb, CMD_A, ret);
+       }
+out:
+       mutex_unlock(&smb->lock);
+       return ret;
+}
+
+static inline int smb347_charging_enable(struct smb347_charger *smb)
+{
+       return smb347_charging_set(smb, true);
+}
+
+static inline int smb347_charging_disable(struct smb347_charger *smb)
+{
+       return smb347_charging_set(smb, false);
+}
+
+static int smb347_update_online(struct smb347_charger *smb)
+{
+       int ret;
+
+       /*
+        * Depending on whether valid power source is connected or not, we
+        * disable or enable the charging. We do it manually because it
+        * depends on how the platform has configured the valid inputs.
+        */
+       if (smb347_is_online(smb)) {
+               ret = smb347_charging_enable(smb);
+               if (ret < 0)
+                       dev_err(&smb->client->dev,
+                               "failed to enable charging\n");
+       } else {
+               ret = smb347_charging_disable(smb);
+               if (ret < 0)
+                       dev_err(&smb->client->dev,
+                               "failed to disable charging\n");
+       }
+
+       return ret;
+}
+
+static int smb347_set_charge_current(struct smb347_charger *smb)
+{
+       int ret, val;
+
+       ret = smb347_read(smb, CFG_CHARGE_CURRENT);
+       if (ret < 0)
+               return ret;
+
+       if (smb->pdata->max_charge_current) {
+               val = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl),
+                                   smb->pdata->max_charge_current);
+               if (val < 0)
+                       return val;
+
+               ret &= ~CFG_CHARGE_CURRENT_FCC_MASK;
+               ret |= val << CFG_CHARGE_CURRENT_FCC_SHIFT;
+       }
+
+       if (smb->pdata->pre_charge_current) {
+               val = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl),
+                                   smb->pdata->pre_charge_current);
+               if (val < 0)
+                       return val;
+
+               ret &= ~CFG_CHARGE_CURRENT_PCC_MASK;
+               ret |= val << CFG_CHARGE_CURRENT_PCC_SHIFT;
+       }
+
+       if (smb->pdata->termination_current) {
+               val = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl),
+                                   smb->pdata->termination_current);
+               if (val < 0)
+                       return val;
+
+               ret &= ~CFG_CHARGE_CURRENT_TC_MASK;
+               ret |= val;
+       }
+
+       return smb347_write(smb, CFG_CHARGE_CURRENT, ret);
+}
+
+static int smb347_set_current_limits(struct smb347_charger *smb)
+{
+       int ret, val;
+
+       ret = smb347_read(smb, CFG_CURRENT_LIMIT);
+       if (ret < 0)
+               return ret;
+
+       if (smb->pdata->mains_current_limit) {
+               val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
+                                   smb->pdata->mains_current_limit);
+               if (val < 0)
+                       return val;
+
+               ret &= ~CFG_CURRENT_LIMIT_DC_MASK;
+               ret |= val << CFG_CURRENT_LIMIT_DC_SHIFT;
+       }
+
+       if (smb->pdata->usb_hc_current_limit) {
+               val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
+                                   smb->pdata->usb_hc_current_limit);
+               if (val < 0)
+                       return val;
+
+               ret &= ~CFG_CURRENT_LIMIT_USB_MASK;
+               ret |= val;
+       }
+
+       return smb347_write(smb, CFG_CURRENT_LIMIT, ret);
+}
+
+static int smb347_set_voltage_limits(struct smb347_charger *smb)
+{
+       int ret, val;
+
+       ret = smb347_read(smb, CFG_FLOAT_VOLTAGE);
+       if (ret < 0)
+               return ret;
+
+       if (smb->pdata->pre_to_fast_voltage) {
+               val = smb->pdata->pre_to_fast_voltage;
+
+               /* uV */
+               val = clamp_val(val, 2400000, 3000000) - 2400000;
+               val /= 200000;
+
+               ret &= ~CFG_FLOAT_VOLTAGE_THRESHOLD_MASK;
+               ret |= val << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT;
+       }
+
+       if (smb->pdata->max_charge_voltage) {
+               val = smb->pdata->max_charge_voltage;
+
+               /* uV */
+               val = clamp_val(val, 3500000, 4500000) - 3500000;
+               val /= 20000;
+
+               ret |= val;
+       }
+
+       return smb347_write(smb, CFG_FLOAT_VOLTAGE, ret);
+}
+
+static int smb347_set_temp_limits(struct smb347_charger *smb)
+{
+       bool enable_therm_monitor = false;
+       int ret, val;
+
+       if (smb->pdata->chip_temp_threshold) {
+               val = smb->pdata->chip_temp_threshold;
+
+               /* degree C */
+               val = clamp_val(val, 100, 130) - 100;
+               val /= 10;
+
+               ret = smb347_read(smb, CFG_OTG);
+               if (ret < 0)
+                       return ret;
+
+               ret &= ~CFG_OTG_TEMP_THRESHOLD_MASK;
+               ret |= val << CFG_OTG_TEMP_THRESHOLD_SHIFT;
+
+               ret = smb347_write(smb, CFG_OTG, ret);
+               if (ret < 0)
+                       return ret;
+       }
+
+       ret = smb347_read(smb, CFG_TEMP_LIMIT);
+       if (ret < 0)
+               return ret;
+
+       if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+               val = smb->pdata->soft_cold_temp_limit;
+
+               val = clamp_val(val, 0, 15);
+               val /= 5;
+               /* this goes from higher to lower so invert the value */
+               val = ~val & 0x3;
+
+               ret &= ~CFG_TEMP_LIMIT_SOFT_COLD_MASK;
+               ret |= val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT;
+
+               enable_therm_monitor = true;
+       }
+
+       if (smb->pdata->soft_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+               val = smb->pdata->soft_hot_temp_limit;
+
+               val = clamp_val(val, 40, 55) - 40;
+               val /= 5;
+
+               ret &= ~CFG_TEMP_LIMIT_SOFT_HOT_MASK;
+               ret |= val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT;
+
+               enable_therm_monitor = true;
+       }
+
+       if (smb->pdata->hard_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+               val = smb->pdata->hard_cold_temp_limit;
+
+               val = clamp_val(val, -5, 10) + 5;
+               val /= 5;
+               /* this goes from higher to lower so invert the value */
+               val = ~val & 0x3;
+
+               ret &= ~CFG_TEMP_LIMIT_HARD_COLD_MASK;
+               ret |= val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT;
+
+               enable_therm_monitor = true;
+       }
+
+       if (smb->pdata->hard_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+               val = smb->pdata->hard_hot_temp_limit;
+
+               val = clamp_val(val, 50, 65) - 50;
+               val /= 5;
+
+               ret &= ~CFG_TEMP_LIMIT_HARD_HOT_MASK;
+               ret |= val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT;
+
+               enable_therm_monitor = true;
+       }
+
+       ret = smb347_write(smb, CFG_TEMP_LIMIT, ret);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * If any of the temperature limits are set, we also enable the
+        * thermistor monitoring.
+        *
+        * When soft limits are hit, the device will start to compensate
+        * current and/or voltage depending on the configuration.
+        *
+        * When hard limit is hit, the device will suspend charging
+        * depending on the configuration.
+        */
+       if (enable_therm_monitor) {
+               ret = smb347_read(smb, CFG_THERM);
+               if (ret < 0)
+                       return ret;
+
+               ret &= ~CFG_THERM_MONITOR_DISABLED;
+
+               ret = smb347_write(smb, CFG_THERM, ret);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (smb->pdata->suspend_on_hard_temp_limit) {
+               ret = smb347_read(smb, CFG_SYSOK);
+               if (ret < 0)
+                       return ret;
+
+               ret &= ~CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED;
+
+               ret = smb347_write(smb, CFG_SYSOK, ret);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (smb->pdata->soft_temp_limit_compensation !=
+           SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) {
+               val = smb->pdata->soft_temp_limit_compensation & 0x3;
+
+               ret = smb347_read(smb, CFG_THERM);
+               if (ret < 0)
+                       return ret;
+
+               ret &= ~CFG_THERM_SOFT_HOT_COMPENSATION_MASK;
+               ret |= val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT;
+
+               ret &= ~CFG_THERM_SOFT_COLD_COMPENSATION_MASK;
+               ret |= val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT;
+
+               ret = smb347_write(smb, CFG_THERM, ret);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (smb->pdata->charge_current_compensation) {
+               val = current_to_hw(ccc_tbl, ARRAY_SIZE(ccc_tbl),
+                                   smb->pdata->charge_current_compensation);
+               if (val < 0)
+                       return val;
+
+               ret = smb347_read(smb, CFG_OTG);
+               if (ret < 0)
+                       return ret;
+
+               ret &= ~CFG_OTG_CC_COMPENSATION_MASK;
+               ret |= (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT;
+
+               ret = smb347_write(smb, CFG_OTG, ret);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return ret;
+}
+
+/*
+ * smb347_set_writable - enables/disables writing to non-volatile registers
+ * @smb: pointer to smb347 charger instance
+ *
+ * You can enable/disable writing to the non-volatile configuration
+ * registers by calling this function.
+ *
+ * Returns %0 on success and negative errno in case of failure.
+ */
+static int smb347_set_writable(struct smb347_charger *smb, bool writable)
+{
+       int ret;
+
+       ret = smb347_read(smb, CMD_A);
+       if (ret < 0)
+               return ret;
+
+       if (writable)
+               ret |= CMD_A_ALLOW_WRITE;
+       else
+               ret &= ~CMD_A_ALLOW_WRITE;
+
+       return smb347_write(smb, CMD_A, ret);
+}
+
+static int smb347_hw_init(struct smb347_charger *smb)
+{
+       int ret;
+
+       ret = smb347_set_writable(smb, true);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * Program the platform specific configuration values to the device
+        * first.
+        */
+       ret = smb347_set_charge_current(smb);
+       if (ret < 0)
+               goto fail;
+
+       ret = smb347_set_current_limits(smb);
+       if (ret < 0)
+               goto fail;
+
+       ret = smb347_set_voltage_limits(smb);
+       if (ret < 0)
+               goto fail;
+
+       ret = smb347_set_temp_limits(smb);
+       if (ret < 0)
+               goto fail;
+
+       /* If USB charging is disabled we put the USB in suspend mode */
+       if (!smb->pdata->use_usb) {
+               ret = smb347_read(smb, CMD_A);
+               if (ret < 0)
+                       goto fail;
+
+               ret |= CMD_A_SUSPEND_ENABLED;
+
+               ret = smb347_write(smb, CMD_A, ret);
+               if (ret < 0)
+                       goto fail;
+       }
+
+       ret = smb347_read(smb, CFG_OTHER);
+       if (ret < 0)
+               goto fail;
+
+       /*
+        * If configured by platform data, we enable hardware Auto-OTG
+        * support for driving VBUS. Otherwise we disable it.
+        */
+       ret &= ~CFG_OTHER_RID_MASK;
+       if (smb->pdata->use_usb_otg)
+               ret |= CFG_OTHER_RID_ENABLED_AUTO_OTG;
+
+       ret = smb347_write(smb, CFG_OTHER, ret);
+       if (ret < 0)
+               goto fail;
+
+       ret = smb347_read(smb, CFG_PIN);
+       if (ret < 0)
+               goto fail;
+
+       /*
+        * Make the charging functionality controllable by a write to the
+        * command register unless pin control is specified in the platform
+        * data.
+        */
+       ret &= ~CFG_PIN_EN_CTRL_MASK;
+
+       switch (smb->pdata->enable_control) {
+       case SMB347_CHG_ENABLE_SW:
+               /* Do nothing, 0 means i2c control */
+               break;
+       case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW:
+               ret |= CFG_PIN_EN_CTRL_ACTIVE_LOW;
+               break;
+       case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH:
+               ret |= CFG_PIN_EN_CTRL_ACTIVE_HIGH;
+               break;
+       }
+
+       /* Disable Automatic Power Source Detection (APSD) interrupt. */
+       ret &= ~CFG_PIN_EN_APSD_IRQ;
+
+       ret = smb347_write(smb, CFG_PIN, ret);
+       if (ret < 0)
+               goto fail;
+
+       ret = smb347_update_status(smb);
+       if (ret < 0)
+               goto fail;
+
+       ret = smb347_update_online(smb);
+
+fail:
+       smb347_set_writable(smb, false);
+       return ret;
+}
+
+static irqreturn_t smb347_interrupt(int irq, void *data)
+{
+       struct smb347_charger *smb = data;
+       int stat_c, irqstat_e, irqstat_c;
+       irqreturn_t ret = IRQ_NONE;
+
+       stat_c = smb347_read(smb, STAT_C);
+       if (stat_c < 0) {
+               dev_warn(&smb->client->dev, "reading STAT_C failed\n");
+               return IRQ_NONE;
+       }
+
+       irqstat_c = smb347_read(smb, IRQSTAT_C);
+       if (irqstat_c < 0) {
+               dev_warn(&smb->client->dev, "reading IRQSTAT_C failed\n");
+               return IRQ_NONE;
+       }
+
+       irqstat_e = smb347_read(smb, IRQSTAT_E);
+       if (irqstat_e < 0) {
+               dev_warn(&smb->client->dev, "reading IRQSTAT_E failed\n");
+               return IRQ_NONE;
+       }
+
+       /*
+        * If we get charger error we report the error back to user and
+        * disable charging.
+        */
+       if (stat_c & STAT_C_CHARGER_ERROR) {
+               dev_err(&smb->client->dev,
+                       "error in charger, disabling charging\n");
+
+               smb347_charging_disable(smb);
+               power_supply_changed(&smb->battery);
+
+               ret = IRQ_HANDLED;
+       }
+
+       /*
+        * If we reached the termination current the battery is charged and
+        * we can update the status now. Charging is automatically
+        * disabled by the hardware.
+        */
+       if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) {
+               if (irqstat_c & IRQSTAT_C_TERMINATION_STAT)
+                       power_supply_changed(&smb->battery);
+               ret = IRQ_HANDLED;
+       }
+
+       /*
+        * If we got an under voltage interrupt it means that AC/USB input
+        * was connected or disconnected.
+        */
+       if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) {
+               if (smb347_update_status(smb) > 0) {
+                       smb347_update_online(smb);
+                       power_supply_changed(&smb->mains);
+                       power_supply_changed(&smb->usb);
+               }
+               ret = IRQ_HANDLED;
+       }
+
+       return ret;
+}
+
+static int smb347_irq_set(struct smb347_charger *smb, bool enable)
+{
+       int ret;
+
+       ret = smb347_set_writable(smb, true);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * Enable/disable interrupts for:
+        *      - under voltage
+        *      - termination current reached
+        *      - charger error
+        */
+       if (enable) {
+               ret = smb347_write(smb, CFG_FAULT_IRQ, CFG_FAULT_IRQ_DCIN_UV);
+               if (ret < 0)
+                       goto fail;
+
+               ret = smb347_write(smb, CFG_STATUS_IRQ,
+                                  CFG_STATUS_IRQ_TERMINATION_OR_TAPER);
+               if (ret < 0)
+                       goto fail;
+
+               ret = smb347_read(smb, CFG_PIN);
+               if (ret < 0)
+                       goto fail;
+
+               ret |= CFG_PIN_EN_CHARGER_ERROR;
+
+               ret = smb347_write(smb, CFG_PIN, ret);
+       } else {
+               ret = smb347_write(smb, CFG_FAULT_IRQ, 0);
+               if (ret < 0)
+                       goto fail;
+
+               ret = smb347_write(smb, CFG_STATUS_IRQ, 0);
+               if (ret < 0)
+                       goto fail;
+
+               ret = smb347_read(smb, CFG_PIN);
+               if (ret < 0)
+                       goto fail;
+
+               ret &= ~CFG_PIN_EN_CHARGER_ERROR;
+
+               ret = smb347_write(smb, CFG_PIN, ret);
+       }
+
+fail:
+       smb347_set_writable(smb, false);
+       return ret;
+}
+
+static inline int smb347_irq_enable(struct smb347_charger *smb)
+{
+       return smb347_irq_set(smb, true);
+}
+
+static inline int smb347_irq_disable(struct smb347_charger *smb)
+{
+       return smb347_irq_set(smb, false);
+}
+
+static int smb347_irq_init(struct smb347_charger *smb)
+{
+       const struct smb347_charger_platform_data *pdata = smb->pdata;
+       int ret, irq = gpio_to_irq(pdata->irq_gpio);
+
+       ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, smb->client->name);
+       if (ret < 0)
+               goto fail;
+
+       ret = request_threaded_irq(irq, NULL, smb347_interrupt,
+                                  IRQF_TRIGGER_FALLING, smb->client->name,
+                                  smb);
+       if (ret < 0)
+               goto fail_gpio;
+
+       ret = smb347_set_writable(smb, true);
+       if (ret < 0)
+               goto fail_irq;
+
+       /*
+        * Configure the STAT output to be suitable for interrupts: disable
+        * all other output (except interrupts) and make it active low.
+        */
+       ret = smb347_read(smb, CFG_STAT);
+       if (ret < 0)
+               goto fail_readonly;
+
+       ret &= ~CFG_STAT_ACTIVE_HIGH;
+       ret |= CFG_STAT_DISABLED;
+
+       ret = smb347_write(smb, CFG_STAT, ret);
+       if (ret < 0)
+               goto fail_readonly;
+
+       ret = smb347_irq_enable(smb);
+       if (ret < 0)
+               goto fail_readonly;
+
+       smb347_set_writable(smb, false);
+       smb->client->irq = irq;
+       return 0;
+
+fail_readonly:
+       smb347_set_writable(smb, false);
+fail_irq:
+       free_irq(irq, smb);
+fail_gpio:
+       gpio_free(pdata->irq_gpio);
+fail:
+       smb->client->irq = 0;
+       return ret;
+}
+
+static int smb347_mains_get_property(struct power_supply *psy,
+                                    enum power_supply_property prop,
+                                    union power_supply_propval *val)
+{
+       struct smb347_charger *smb =
+               container_of(psy, struct smb347_charger, mains);
+
+       if (prop == POWER_SUPPLY_PROP_ONLINE) {
+               val->intval = smb->mains_online;
+               return 0;
+       }
+       return -EINVAL;
+}
+
+static enum power_supply_property smb347_mains_properties[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int smb347_usb_get_property(struct power_supply *psy,
+                                  enum power_supply_property prop,
+                                  union power_supply_propval *val)
+{
+       struct smb347_charger *smb =
+               container_of(psy, struct smb347_charger, usb);
+
+       if (prop == POWER_SUPPLY_PROP_ONLINE) {
+               val->intval = smb->usb_online;
+               return 0;
+       }
+       return -EINVAL;
+}
+
+static enum power_supply_property smb347_usb_properties[] = {
+       POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int smb347_battery_get_property(struct power_supply *psy,
+                                      enum power_supply_property prop,
+                                      union power_supply_propval *val)
+{
+       struct smb347_charger *smb =
+                       container_of(psy, struct smb347_charger, battery);
+       const struct smb347_charger_platform_data *pdata = smb->pdata;
+       int ret;
+
+       ret = smb347_update_status(smb);
+       if (ret < 0)
+               return ret;
+
+       switch (prop) {
+       case POWER_SUPPLY_PROP_STATUS:
+               if (!smb347_is_online(smb)) {
+                       val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+                       break;
+               }
+               if (smb347_charging_status(smb))
+                       val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               else
+                       val->intval = POWER_SUPPLY_STATUS_FULL;
+               break;
+
+       case POWER_SUPPLY_PROP_CHARGE_TYPE:
+               if (!smb347_is_online(smb))
+                       return -ENODATA;
+
+               /*
+                * We handle trickle and pre-charging the same, and taper
+                * and none the same.
+                */
+               switch (smb347_charging_status(smb)) {
+               case 1:
+                       val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+                       break;
+               case 2:
+                       val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+                       break;
+               default:
+                       val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+                       break;
+               }
+               break;
+
+       case POWER_SUPPLY_PROP_TECHNOLOGY:
+               val->intval = pdata->battery_info.technology;
+               break;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+               val->intval = pdata->battery_info.voltage_min_design;
+               break;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+               val->intval = pdata->battery_info.voltage_max_design;
+               break;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               if (!smb347_is_online(smb))
+                       return -ENODATA;
+               ret = smb347_read(smb, STAT_A);
+               if (ret < 0)
+                       return ret;
+
+               ret &= STAT_A_FLOAT_VOLTAGE_MASK;
+               if (ret > 0x3d)
+                       ret = 0x3d;
+
+               val->intval = 3500000 + ret * 20000;
+               break;
+
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               if (!smb347_is_online(smb))
+                       return -ENODATA;
+
+               ret = smb347_read(smb, STAT_B);
+               if (ret < 0)
+                       return ret;
+
+               /*
+                * The current value is composition of FCC and PCC values
+                * and we can detect which table to use from bit 5.
+                */
+               if (ret & 0x20) {
+                       val->intval = hw_to_current(fcc_tbl,
+                                                   ARRAY_SIZE(fcc_tbl),
+                                                   ret & 7);
+               } else {
+                       ret >>= 3;
+                       val->intval = hw_to_current(pcc_tbl,
+                                                   ARRAY_SIZE(pcc_tbl),
+                                                   ret & 7);
+               }
+               break;
+
+       case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+               val->intval = pdata->battery_info.charge_full_design;
+               break;
+
+       case POWER_SUPPLY_PROP_MODEL_NAME:
+               val->strval = pdata->battery_info.name;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static enum power_supply_property smb347_battery_properties[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_CHARGE_TYPE,
+       POWER_SUPPLY_PROP_TECHNOLOGY,
+       POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+       POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+       POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+       POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int smb347_debugfs_show(struct seq_file *s, void *data)
+{
+       struct smb347_charger *smb = s->private;
+       int ret;
+       u8 reg;
+
+       seq_printf(s, "Control registers:\n");
+       seq_printf(s, "==================\n");
+       for (reg = CFG_CHARGE_CURRENT; reg <= CFG_ADDRESS; reg++) {
+               ret = smb347_read(smb, reg);
+               seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
+       }
+       seq_printf(s, "\n");
+
+       seq_printf(s, "Command registers:\n");
+       seq_printf(s, "==================\n");
+       ret = smb347_read(smb, CMD_A);
+       seq_printf(s, "0x%02x:\t0x%02x\n", CMD_A, ret);
+       ret = smb347_read(smb, CMD_B);
+       seq_printf(s, "0x%02x:\t0x%02x\n", CMD_B, ret);
+       ret = smb347_read(smb, CMD_C);
+       seq_printf(s, "0x%02x:\t0x%02x\n", CMD_C, ret);
+       seq_printf(s, "\n");
+
+       seq_printf(s, "Interrupt status registers:\n");
+       seq_printf(s, "===========================\n");
+       for (reg = IRQSTAT_A; reg <= IRQSTAT_F; reg++) {
+               ret = smb347_read(smb, reg);
+               seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
+       }
+       seq_printf(s, "\n");
+
+       seq_printf(s, "Status registers:\n");
+       seq_printf(s, "=================\n");
+       for (reg = STAT_A; reg <= STAT_E; reg++) {
+               ret = smb347_read(smb, reg);
+               seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
+       }
+
+       return 0;
+}
+
+static int smb347_debugfs_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, smb347_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations smb347_debugfs_fops = {
+       .open           = smb347_debugfs_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = single_release,
+};
+
+static int smb347_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       static char *battery[] = { "smb347-battery" };
+       const struct smb347_charger_platform_data *pdata;
+       struct device *dev = &client->dev;
+       struct smb347_charger *smb;
+       int ret;
+
+       pdata = dev->platform_data;
+       if (!pdata)
+               return -EINVAL;
+
+       if (!pdata->use_mains && !pdata->use_usb)
+               return -EINVAL;
+
+       smb = devm_kzalloc(dev, sizeof(*smb), GFP_KERNEL);
+       if (!smb)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, smb);
+
+       mutex_init(&smb->lock);
+       smb->client = client;
+       smb->pdata = pdata;
+
+       ret = smb347_hw_init(smb);
+       if (ret < 0)
+               return ret;
+
+       smb->mains.name = "smb347-mains";
+       smb->mains.type = POWER_SUPPLY_TYPE_MAINS;
+       smb->mains.get_property = smb347_mains_get_property;
+       smb->mains.properties = smb347_mains_properties;
+       smb->mains.num_properties = ARRAY_SIZE(smb347_mains_properties);
+       smb->mains.supplied_to = battery;
+       smb->mains.num_supplicants = ARRAY_SIZE(battery);
+
+       smb->usb.name = "smb347-usb";
+       smb->usb.type = POWER_SUPPLY_TYPE_USB;
+       smb->usb.get_property = smb347_usb_get_property;
+       smb->usb.properties = smb347_usb_properties;
+       smb->usb.num_properties = ARRAY_SIZE(smb347_usb_properties);
+       smb->usb.supplied_to = battery;
+       smb->usb.num_supplicants = ARRAY_SIZE(battery);
+
+       smb->battery.name = "smb347-battery";
+       smb->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+       smb->battery.get_property = smb347_battery_get_property;
+       smb->battery.properties = smb347_battery_properties;
+       smb->battery.num_properties = ARRAY_SIZE(smb347_battery_properties);
+
+       ret = power_supply_register(dev, &smb->mains);
+       if (ret < 0)
+               return ret;
+
+       ret = power_supply_register(dev, &smb->usb);
+       if (ret < 0) {
+               power_supply_unregister(&smb->mains);
+               return ret;
+       }
+
+       ret = power_supply_register(dev, &smb->battery);
+       if (ret < 0) {
+               power_supply_unregister(&smb->usb);
+               power_supply_unregister(&smb->mains);
+               return ret;
+       }
+
+       /*
+        * Interrupt pin is optional. If it is connected, we setup the
+        * interrupt support here.
+        */
+       if (pdata->irq_gpio >= 0) {
+               ret = smb347_irq_init(smb);
+               if (ret < 0) {
+                       dev_warn(dev, "failed to initialize IRQ: %d\n", ret);
+                       dev_warn(dev, "disabling IRQ support\n");
+               }
+       }
+
+       smb->dentry = debugfs_create_file("smb347-regs", S_IRUSR, NULL, smb,
+                                         &smb347_debugfs_fops);
+       return 0;
+}
+
+static int smb347_remove(struct i2c_client *client)
+{
+       struct smb347_charger *smb = i2c_get_clientdata(client);
+
+       if (!IS_ERR_OR_NULL(smb->dentry))
+               debugfs_remove(smb->dentry);
+
+       if (client->irq) {
+               smb347_irq_disable(smb);
+               free_irq(client->irq, smb);
+               gpio_free(smb->pdata->irq_gpio);
+       }
+
+       power_supply_unregister(&smb->battery);
+       power_supply_unregister(&smb->usb);
+       power_supply_unregister(&smb->mains);
+       return 0;
+}
+
+static const struct i2c_device_id smb347_id[] = {
+       { "smb347", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, smb347_id);
+
+static struct i2c_driver smb347_driver = {
+       .driver = {
+               .name = "smb347",
+       },
+       .probe        = smb347_probe,
+       .remove       = __devexit_p(smb347_remove),
+       .id_table     = smb347_id,
+};
+
+static int __init smb347_init(void)
+{
+       return i2c_add_driver(&smb347_driver);
+}
+module_init(smb347_init);
+
+static void __exit smb347_exit(void)
+{
+       i2c_del_driver(&smb347_driver);
+}
+module_exit(smb347_exit);
+
+MODULE_AUTHOR("Bruce E. Robertson <bruce.e.robertson@intel.com>");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
+MODULE_DESCRIPTION("SMB347 battery charger driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("i2c:smb347");
index 636ebb2a0e807bd44e28f902200cabdf16e399b9..8c9a607ea77a9ef3a02f7df2a0e94075d7448a78 100644 (file)
@@ -316,19 +316,7 @@ static struct i2c_driver z2_batt_driver = {
        .remove         = __devexit_p(z2_batt_remove),
        .id_table       = z2_batt_id,
 };
-
-static int __init z2_batt_init(void)
-{
-       return i2c_add_driver(&z2_batt_driver);
-}
-
-static void __exit z2_batt_exit(void)
-{
-       i2c_del_driver(&z2_batt_driver);
-}
-
-module_init(z2_batt_init);
-module_exit(z2_batt_exit);
+module_i2c_driver(z2_batt_driver);
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Peter Edwards <sweetlilmre@gmail.com>");
index d21fa2865bf4da4aa2873adc081091b840b6cf08..ea98c6133d32c694b57641906a146163f50a2af0 100644 (file)
@@ -1,4 +1,7 @@
 /*
+ * LP8727 Micro/Mini USB IC with integrated charger
+ *
+ *                     Copyright (C) 2011 Texas Instruments
  *                     Copyright (C) 2011 National Semiconductor
  *
  * This program is free software; you can redistribute it and/or modify
@@ -32,13 +35,24 @@ enum lp8727_ichg {
        ICHG_1000mA,
 };
 
+/**
+ * struct lp8727_chg_param
+ * @eoc_level : end of charge level setting
+ * @ichg : charging current
+ */
 struct lp8727_chg_param {
-       /* end of charge level setting */
        enum lp8727_eoc_level eoc_level;
-       /* charging current */
        enum lp8727_ichg ichg;
 };
 
+/**
+ * struct lp8727_platform_data
+ * @get_batt_present : check battery status - exists or not
+ * @get_batt_level : get battery voltage (mV)
+ * @get_batt_capacity : get battery capacity (%)
+ * @get_batt_temp : get battery temperature
+ * @ac, @usb : charging parameters each charger type
+ */
 struct lp8727_platform_data {
        u8 (*get_batt_present)(void);
        u16 (*get_batt_level)(void);
index 5fa697477b71fa6d6fe14fecf3f679bb22851b9a..ee96cd51d8b23b54683950a50831cb5cda65d2ac 100644 (file)
@@ -146,6 +146,279 @@ struct abx500_init_settings {
        u8 setting;
 };
 
+/* Battery driver related data */
+/*
+ * ADC for the battery thermistor.
+ * When using the ABx500_ADC_THERM_BATCTRL the battery ID resistor is combined
+ * with a NTC resistor to both identify the battery and to measure its
+ * temperature. Different phone manufactures uses different techniques to both
+ * identify the battery and to read its temperature.
+ */
+enum abx500_adc_therm {
+       ABx500_ADC_THERM_BATCTRL,
+       ABx500_ADC_THERM_BATTEMP,
+};
+
+/**
+ * struct abx500_res_to_temp - defines one point in a temp to res curve. To
+ * be used in battery packs that combines the identification resistor with a
+ * NTC resistor.
+ * @temp:                      battery pack temperature in Celcius
+ * @resist:                    NTC resistor net total resistance
+ */
+struct abx500_res_to_temp {
+       int temp;
+       int resist;
+};
+
+/**
+ * struct abx500_v_to_cap - Table for translating voltage to capacity
+ * @voltage:           Voltage in mV
+ * @capacity:          Capacity in percent
+ */
+struct abx500_v_to_cap {
+       int voltage;
+       int capacity;
+};
+
+/* Forward declaration */
+struct abx500_fg;
+
+/**
+ * struct abx500_fg_parameters - Fuel gauge algorithm parameters, in seconds
+ * if not specified
+ * @recovery_sleep_timer:      Time between measurements while recovering
+ * @recovery_total_time:       Total recovery time
+ * @init_timer:                        Measurement interval during startup
+ * @init_discard_time:         Time we discard voltage measurement at startup
+ * @init_total_time:           Total init time during startup
+ * @high_curr_time:            Time current has to be high to go to recovery
+ * @accu_charging:             FG accumulation time while charging
+ * @accu_high_curr:            FG accumulation time in high current mode
+ * @high_curr_threshold:       High current threshold, in mA
+ * @lowbat_threshold:          Low battery threshold, in mV
+ * @overbat_threshold:         Over battery threshold, in mV
+ * @battok_falling_th_sel0     Threshold in mV for battOk signal sel0
+ *                             Resolution in 50 mV step.
+ * @battok_raising_th_sel1     Threshold in mV for battOk signal sel1
+ *                             Resolution in 50 mV step.
+ * @user_cap_limit             Capacity reported from user must be within this
+ *                             limit to be considered as sane, in percentage
+ *                             points.
+ * @maint_thres                        This is the threshold where we stop reporting
+ *                             battery full while in maintenance, in per cent
+ */
+struct abx500_fg_parameters {
+       int recovery_sleep_timer;
+       int recovery_total_time;
+       int init_timer;
+       int init_discard_time;
+       int init_total_time;
+       int high_curr_time;
+       int accu_charging;
+       int accu_high_curr;
+       int high_curr_threshold;
+       int lowbat_threshold;
+       int overbat_threshold;
+       int battok_falling_th_sel0;
+       int battok_raising_th_sel1;
+       int user_cap_limit;
+       int maint_thres;
+};
+
+/**
+ * struct abx500_charger_maximization - struct used by the board config.
+ * @use_maxi:          Enable maximization for this battery type
+ * @maxi_chg_curr:     Maximum charger current allowed
+ * @maxi_wait_cycles:  cycles to wait before setting charger current
+ * @charger_curr_step  delta between two charger current settings (mA)
+ */
+struct abx500_maxim_parameters {
+       bool ena_maxi;
+       int chg_curr;
+       int wait_cycles;
+       int charger_curr_step;
+};
+
+/**
+ * struct abx500_battery_type - different batteries supported
+ * @name:                      battery technology
+ * @resis_high:                        battery upper resistance limit
+ * @resis_low:                 battery lower resistance limit
+ * @charge_full_design:                Maximum battery capacity in mAh
+ * @nominal_voltage:           Nominal voltage of the battery in mV
+ * @termination_vol:           max voltage upto which battery can be charged
+ * @termination_curr           battery charging termination current in mA
+ * @recharge_vol               battery voltage limit that will trigger a new
+ *                             full charging cycle in the case where maintenan-
+ *                             -ce charging has been disabled
+ * @normal_cur_lvl:            charger current in normal state in mA
+ * @normal_vol_lvl:            charger voltage in normal state in mV
+ * @maint_a_cur_lvl:           charger current in maintenance A state in mA
+ * @maint_a_vol_lvl:           charger voltage in maintenance A state in mV
+ * @maint_a_chg_timer_h:       charge time in maintenance A state
+ * @maint_b_cur_lvl:           charger current in maintenance B state in mA
+ * @maint_b_vol_lvl:           charger voltage in maintenance B state in mV
+ * @maint_b_chg_timer_h:       charge time in maintenance B state
+ * @low_high_cur_lvl:          charger current in temp low/high state in mA
+ * @low_high_vol_lvl:          charger voltage in temp low/high state in mV'
+ * @battery_resistance:                battery inner resistance in mOhm.
+ * @n_r_t_tbl_elements:                number of elements in r_to_t_tbl
+ * @r_to_t_tbl:                        table containing resistance to temp points
+ * @n_v_cap_tbl_elements:      number of elements in v_to_cap_tbl
+ * @v_to_cap_tbl:              Voltage to capacity (in %) table
+ * @n_batres_tbl_elements      number of elements in the batres_tbl
+ * @batres_tbl                 battery internal resistance vs temperature table
+ */
+struct abx500_battery_type {
+       int name;
+       int resis_high;
+       int resis_low;
+       int charge_full_design;
+       int nominal_voltage;
+       int termination_vol;
+       int termination_curr;
+       int recharge_vol;
+       int normal_cur_lvl;
+       int normal_vol_lvl;
+       int maint_a_cur_lvl;
+       int maint_a_vol_lvl;
+       int maint_a_chg_timer_h;
+       int maint_b_cur_lvl;
+       int maint_b_vol_lvl;
+       int maint_b_chg_timer_h;
+       int low_high_cur_lvl;
+       int low_high_vol_lvl;
+       int battery_resistance;
+       int n_temp_tbl_elements;
+       struct abx500_res_to_temp *r_to_t_tbl;
+       int n_v_cap_tbl_elements;
+       struct abx500_v_to_cap *v_to_cap_tbl;
+       int n_batres_tbl_elements;
+       struct batres_vs_temp *batres_tbl;
+};
+
+/**
+ * struct abx500_bm_capacity_levels - abx500 capacity level data
+ * @critical:          critical capacity level in percent
+ * @low:               low capacity level in percent
+ * @normal:            normal capacity level in percent
+ * @high:              high capacity level in percent
+ * @full:              full capacity level in percent
+ */
+struct abx500_bm_capacity_levels {
+       int critical;
+       int low;
+       int normal;
+       int high;
+       int full;
+};
+
+/**
+ * struct abx500_bm_charger_parameters - Charger specific parameters
+ * @usb_volt_max:      maximum allowed USB charger voltage in mV
+ * @usb_curr_max:      maximum allowed USB charger current in mA
+ * @ac_volt_max:       maximum allowed AC charger voltage in mV
+ * @ac_curr_max:       maximum allowed AC charger current in mA
+ */
+struct abx500_bm_charger_parameters {
+       int usb_volt_max;
+       int usb_curr_max;
+       int ac_volt_max;
+       int ac_curr_max;
+};
+
+/**
+ * struct abx500_bm_data - abx500 battery management data
+ * @temp_under         under this temp, charging is stopped
+ * @temp_low           between this temp and temp_under charging is reduced
+ * @temp_high          between this temp and temp_over charging is reduced
+ * @temp_over          over this temp, charging is stopped
+ * @temp_now           present battery temperature
+ * @temp_interval_chg  temperature measurement interval in s when charging
+ * @temp_interval_nochg        temperature measurement interval in s when not charging
+ * @main_safety_tmr_h  safety timer for main charger
+ * @usb_safety_tmr_h   safety timer for usb charger
+ * @bkup_bat_v         voltage which we charge the backup battery with
+ * @bkup_bat_i         current which we charge the backup battery with
+ * @no_maintenance     indicates that maintenance charging is disabled
+ * @abx500_adc_therm   placement of thermistor, batctrl or battemp adc
+ * @chg_unknown_bat    flag to enable charging of unknown batteries
+ * @enable_overshoot   flag to enable VBAT overshoot control
+ * @auto_trig          flag to enable auto adc trigger
+ * @fg_res             resistance of FG resistor in 0.1mOhm
+ * @n_btypes           number of elements in array bat_type
+ * @batt_id            index of the identified battery in array bat_type
+ * @interval_charging  charge alg cycle period time when charging (sec)
+ * @interval_not_charging charge alg cycle period time when not charging (sec)
+ * @temp_hysteresis    temperature hysteresis
+ * @gnd_lift_resistance        Battery ground to phone ground resistance (mOhm)
+ * @maxi:              maximization parameters
+ * @cap_levels         capacity in percent for the different capacity levels
+ * @bat_type           table of supported battery types
+ * @chg_params         charger parameters
+ * @fg_params          fuel gauge parameters
+ */
+struct abx500_bm_data {
+       int temp_under;
+       int temp_low;
+       int temp_high;
+       int temp_over;
+       int temp_now;
+       int temp_interval_chg;
+       int temp_interval_nochg;
+       int main_safety_tmr_h;
+       int usb_safety_tmr_h;
+       int bkup_bat_v;
+       int bkup_bat_i;
+       bool no_maintenance;
+       bool chg_unknown_bat;
+       bool enable_overshoot;
+       bool auto_trig;
+       enum abx500_adc_therm adc_therm;
+       int fg_res;
+       int n_btypes;
+       int batt_id;
+       int interval_charging;
+       int interval_not_charging;
+       int temp_hysteresis;
+       int gnd_lift_resistance;
+       const struct abx500_maxim_parameters *maxi;
+       const struct abx500_bm_capacity_levels *cap_levels;
+       const struct abx500_battery_type *bat_type;
+       const struct abx500_bm_charger_parameters *chg_params;
+       const struct abx500_fg_parameters *fg_params;
+};
+
+struct abx500_chargalg_platform_data {
+       char **supplied_to;
+       size_t num_supplicants;
+};
+
+struct abx500_charger_platform_data {
+       char **supplied_to;
+       size_t num_supplicants;
+       bool autopower_cfg;
+};
+
+struct abx500_btemp_platform_data {
+       char **supplied_to;
+       size_t num_supplicants;
+};
+
+struct abx500_fg_platform_data {
+       char **supplied_to;
+       size_t num_supplicants;
+};
+
+struct abx500_bm_plat_data {
+       struct abx500_bm_data *battery;
+       struct abx500_charger_platform_data *charger;
+       struct abx500_btemp_platform_data *btemp;
+       struct abx500_fg_platform_data *fg;
+       struct abx500_chargalg_platform_data *chargalg;
+};
+
 int abx500_set_register_interruptible(struct device *dev, u8 bank, u8 reg,
        u8 value);
 int abx500_get_register_interruptible(struct device *dev, u8 bank, u8 reg,
diff --git a/include/linux/mfd/abx500/ab8500-bm.h b/include/linux/mfd/abx500/ab8500-bm.h
new file mode 100644 (file)
index 0000000..44310c9
--- /dev/null
@@ -0,0 +1,474 @@
+/*
+ * Copyright ST-Ericsson 2012.
+ *
+ * Author: Arun Murthy <arun.murthy@stericsson.com>
+ * Licensed under GPLv2.
+ */
+
+#ifndef _AB8500_BM_H
+#define _AB8500_BM_H
+
+#include <linux/kernel.h>
+#include <linux/mfd/abx500.h>
+
+/*
+ * System control 2 register offsets.
+ * bank = 0x02
+ */
+#define AB8500_MAIN_WDOG_CTRL_REG      0x01
+#define AB8500_LOW_BAT_REG             0x03
+#define AB8500_BATT_OK_REG             0x04
+/*
+ * USB/ULPI register offsets
+ * Bank : 0x5
+ */
+#define AB8500_USB_LINE_STAT_REG       0x80
+
+/*
+ * Charger / status register offfsets
+ * Bank : 0x0B
+ */
+#define AB8500_CH_STATUS1_REG          0x00
+#define AB8500_CH_STATUS2_REG          0x01
+#define AB8500_CH_USBCH_STAT1_REG      0x02
+#define AB8500_CH_USBCH_STAT2_REG      0x03
+#define AB8500_CH_FSM_STAT_REG         0x04
+#define AB8500_CH_STAT_REG             0x05
+
+/*
+ * Charger / control register offfsets
+ * Bank : 0x0B
+ */
+#define AB8500_CH_VOLT_LVL_REG         0x40
+#define AB8500_CH_VOLT_LVL_MAX_REG     0x41  /*Only in Cut2.0*/
+#define AB8500_CH_OPT_CRNTLVL_REG      0x42
+#define AB8500_CH_OPT_CRNTLVL_MAX_REG  0x43  /*Only in Cut2.0*/
+#define AB8500_CH_WD_TIMER_REG         0x50
+#define AB8500_CHARG_WD_CTRL           0x51
+#define AB8500_BTEMP_HIGH_TH           0x52
+#define AB8500_LED_INDICATOR_PWM_CTRL  0x53
+#define AB8500_LED_INDICATOR_PWM_DUTY  0x54
+#define AB8500_BATT_OVV                        0x55
+#define AB8500_CHARGER_CTRL            0x56
+#define AB8500_BAT_CTRL_CURRENT_SOURCE 0x60  /*Only in Cut2.0*/
+
+/*
+ * Charger / main control register offsets
+ * Bank : 0x0B
+ */
+#define AB8500_MCH_CTRL1               0x80
+#define AB8500_MCH_CTRL2               0x81
+#define AB8500_MCH_IPT_CURLVL_REG      0x82
+#define AB8500_CH_WD_REG               0x83
+
+/*
+ * Charger / USB control register offsets
+ * Bank : 0x0B
+ */
+#define AB8500_USBCH_CTRL1_REG         0xC0
+#define AB8500_USBCH_CTRL2_REG         0xC1
+#define AB8500_USBCH_IPT_CRNTLVL_REG   0xC2
+
+/*
+ * Gas Gauge register offsets
+ * Bank : 0x0C
+ */
+#define AB8500_GASG_CC_CTRL_REG                0x00
+#define AB8500_GASG_CC_ACCU1_REG       0x01
+#define AB8500_GASG_CC_ACCU2_REG       0x02
+#define AB8500_GASG_CC_ACCU3_REG       0x03
+#define AB8500_GASG_CC_ACCU4_REG       0x04
+#define AB8500_GASG_CC_SMPL_CNTRL_REG  0x05
+#define AB8500_GASG_CC_SMPL_CNTRH_REG  0x06
+#define AB8500_GASG_CC_SMPL_CNVL_REG   0x07
+#define AB8500_GASG_CC_SMPL_CNVH_REG   0x08
+#define AB8500_GASG_CC_CNTR_AVGOFF_REG 0x09
+#define AB8500_GASG_CC_OFFSET_REG      0x0A
+#define AB8500_GASG_CC_NCOV_ACCU       0x10
+#define AB8500_GASG_CC_NCOV_ACCU_CTRL  0x11
+#define AB8500_GASG_CC_NCOV_ACCU_LOW   0x12
+#define AB8500_GASG_CC_NCOV_ACCU_MED   0x13
+#define AB8500_GASG_CC_NCOV_ACCU_HIGH  0x14
+
+/*
+ * Interrupt register offsets
+ * Bank : 0x0E
+ */
+#define AB8500_IT_SOURCE2_REG          0x01
+#define AB8500_IT_SOURCE21_REG         0x14
+
+/*
+ * RTC register offsets
+ * Bank: 0x0F
+ */
+#define AB8500_RTC_BACKUP_CHG_REG      0x0C
+#define AB8500_RTC_CC_CONF_REG         0x01
+#define AB8500_RTC_CTRL_REG            0x0B
+
+/*
+ * OTP register offsets
+ * Bank : 0x15
+ */
+#define AB8500_OTP_CONF_15             0x0E
+
+/* GPADC constants from AB8500 spec, UM0836 */
+#define ADC_RESOLUTION                 1024
+#define ADC_CH_MAIN_MIN                        0
+#define ADC_CH_MAIN_MAX                        20030
+#define ADC_CH_VBUS_MIN                        0
+#define ADC_CH_VBUS_MAX                        20030
+#define ADC_CH_VBAT_MIN                        2300
+#define ADC_CH_VBAT_MAX                        4800
+#define ADC_CH_BKBAT_MIN               0
+#define ADC_CH_BKBAT_MAX               3200
+
+/* Main charge i/p current */
+#define MAIN_CH_IP_CUR_0P9A            0x80
+#define MAIN_CH_IP_CUR_1P0A            0x90
+#define MAIN_CH_IP_CUR_1P1A            0xA0
+#define MAIN_CH_IP_CUR_1P2A            0xB0
+#define MAIN_CH_IP_CUR_1P3A            0xC0
+#define MAIN_CH_IP_CUR_1P4A            0xD0
+#define MAIN_CH_IP_CUR_1P5A            0xE0
+
+/* ChVoltLevel */
+#define CH_VOL_LVL_3P5                 0x00
+#define CH_VOL_LVL_4P0                 0x14
+#define CH_VOL_LVL_4P05                        0x16
+#define CH_VOL_LVL_4P1                 0x1B
+#define CH_VOL_LVL_4P15                        0x20
+#define CH_VOL_LVL_4P2                 0x25
+#define CH_VOL_LVL_4P6                 0x4D
+
+/* ChOutputCurrentLevel */
+#define CH_OP_CUR_LVL_0P1              0x00
+#define CH_OP_CUR_LVL_0P2              0x01
+#define CH_OP_CUR_LVL_0P3              0x02
+#define CH_OP_CUR_LVL_0P4              0x03
+#define CH_OP_CUR_LVL_0P5              0x04
+#define CH_OP_CUR_LVL_0P6              0x05
+#define CH_OP_CUR_LVL_0P7              0x06
+#define CH_OP_CUR_LVL_0P8              0x07
+#define CH_OP_CUR_LVL_0P9              0x08
+#define CH_OP_CUR_LVL_1P4              0x0D
+#define CH_OP_CUR_LVL_1P5              0x0E
+#define CH_OP_CUR_LVL_1P6              0x0F
+
+/* BTEMP High thermal limits */
+#define BTEMP_HIGH_TH_57_0             0x00
+#define BTEMP_HIGH_TH_52               0x01
+#define BTEMP_HIGH_TH_57_1             0x02
+#define BTEMP_HIGH_TH_62               0x03
+
+/* current is mA */
+#define USB_0P1A                       100
+#define USB_0P2A                       200
+#define USB_0P3A                       300
+#define USB_0P4A                       400
+#define USB_0P5A                       500
+
+#define LOW_BAT_3P1V                   0x20
+#define LOW_BAT_2P3V                   0x00
+#define LOW_BAT_RESET                  0x01
+#define LOW_BAT_ENABLE                 0x01
+
+/* Backup battery constants */
+#define BUP_ICH_SEL_50UA               0x00
+#define BUP_ICH_SEL_150UA              0x04
+#define BUP_ICH_SEL_300UA              0x08
+#define BUP_ICH_SEL_700UA              0x0C
+
+#define BUP_VCH_SEL_2P5V               0x00
+#define BUP_VCH_SEL_2P6V               0x01
+#define BUP_VCH_SEL_2P8V               0x02
+#define BUP_VCH_SEL_3P1V               0x03
+
+/* Battery OVV constants */
+#define BATT_OVV_ENA                   0x02
+#define BATT_OVV_TH_3P7                        0x00
+#define BATT_OVV_TH_4P75               0x01
+
+/* A value to indicate over voltage */
+#define BATT_OVV_VALUE                 4750
+
+/* VBUS OVV constants */
+#define VBUS_OVV_SELECT_MASK           0x78
+#define VBUS_OVV_SELECT_5P6V           0x00
+#define VBUS_OVV_SELECT_5P7V           0x08
+#define VBUS_OVV_SELECT_5P8V           0x10
+#define VBUS_OVV_SELECT_5P9V           0x18
+#define VBUS_OVV_SELECT_6P0V           0x20
+#define VBUS_OVV_SELECT_6P1V           0x28
+#define VBUS_OVV_SELECT_6P2V           0x30
+#define VBUS_OVV_SELECT_6P3V           0x38
+
+#define VBUS_AUTO_IN_CURR_LIM_ENA      0x04
+
+/* Fuel Gauge constants */
+#define RESET_ACCU                     0x02
+#define READ_REQ                       0x01
+#define CC_DEEP_SLEEP_ENA              0x02
+#define CC_PWR_UP_ENA                  0x01
+#define CC_SAMPLES_40                  0x28
+#define RD_NCONV_ACCU_REQ              0x01
+#define CC_CALIB                       0x08
+#define CC_INTAVGOFFSET_ENA            0x10
+#define CC_MUXOFFSET                   0x80
+#define CC_INT_CAL_N_AVG_MASK          0x60
+#define CC_INT_CAL_SAMPLES_16          0x40
+#define CC_INT_CAL_SAMPLES_8           0x20
+#define CC_INT_CAL_SAMPLES_4           0x00
+
+/* RTC constants */
+#define RTC_BUP_CH_ENA                 0x10
+
+/* BatCtrl Current Source Constants */
+#define BAT_CTRL_7U_ENA                        0x01
+#define BAT_CTRL_20U_ENA               0x02
+#define BAT_CTRL_CMP_ENA               0x04
+#define FORCE_BAT_CTRL_CMP_HIGH                0x08
+#define BAT_CTRL_PULL_UP_ENA           0x10
+
+/* Battery type */
+#define BATTERY_UNKNOWN                        00
+
+/**
+ * struct res_to_temp - defines one point in a temp to res curve. To
+ * be used in battery packs that combines the identification resistor with a
+ * NTC resistor.
+ * @temp:                      battery pack temperature in Celcius
+ * @resist:                    NTC resistor net total resistance
+ */
+struct res_to_temp {
+       int temp;
+       int resist;
+};
+
+/**
+ * struct batres_vs_temp - defines one point in a temp vs battery internal
+ * resistance curve.
+ * @temp:                      battery pack temperature in Celcius
+ * @resist:                    battery internal reistance in mOhm
+ */
+struct batres_vs_temp {
+       int temp;
+       int resist;
+};
+
+/* Forward declaration */
+struct ab8500_fg;
+
+/**
+ * struct ab8500_fg_parameters - Fuel gauge algorithm parameters, in seconds
+ * if not specified
+ * @recovery_sleep_timer:      Time between measurements while recovering
+ * @recovery_total_time:       Total recovery time
+ * @init_timer:                        Measurement interval during startup
+ * @init_discard_time:         Time we discard voltage measurement at startup
+ * @init_total_time:           Total init time during startup
+ * @high_curr_time:            Time current has to be high to go to recovery
+ * @accu_charging:             FG accumulation time while charging
+ * @accu_high_curr:            FG accumulation time in high current mode
+ * @high_curr_threshold:       High current threshold, in mA
+ * @lowbat_threshold:          Low battery threshold, in mV
+ * @battok_falling_th_sel0     Threshold in mV for battOk signal sel0
+ *                             Resolution in 50 mV step.
+ * @battok_raising_th_sel1     Threshold in mV for battOk signal sel1
+ *                             Resolution in 50 mV step.
+ * @user_cap_limit             Capacity reported from user must be within this
+ *                             limit to be considered as sane, in percentage
+ *                             points.
+ * @maint_thres                        This is the threshold where we stop reporting
+ *                             battery full while in maintenance, in per cent
+ */
+struct ab8500_fg_parameters {
+       int recovery_sleep_timer;
+       int recovery_total_time;
+       int init_timer;
+       int init_discard_time;
+       int init_total_time;
+       int high_curr_time;
+       int accu_charging;
+       int accu_high_curr;
+       int high_curr_threshold;
+       int lowbat_threshold;
+       int battok_falling_th_sel0;
+       int battok_raising_th_sel1;
+       int user_cap_limit;
+       int maint_thres;
+};
+
+/**
+ * struct ab8500_charger_maximization - struct used by the board config.
+ * @use_maxi:          Enable maximization for this battery type
+ * @maxi_chg_curr:     Maximum charger current allowed
+ * @maxi_wait_cycles:  cycles to wait before setting charger current
+ * @charger_curr_step  delta between two charger current settings (mA)
+ */
+struct ab8500_maxim_parameters {
+       bool ena_maxi;
+       int chg_curr;
+       int wait_cycles;
+       int charger_curr_step;
+};
+
+/**
+ * struct ab8500_bm_capacity_levels - ab8500 capacity level data
+ * @critical:          critical capacity level in percent
+ * @low:               low capacity level in percent
+ * @normal:            normal capacity level in percent
+ * @high:              high capacity level in percent
+ * @full:              full capacity level in percent
+ */
+struct ab8500_bm_capacity_levels {
+       int critical;
+       int low;
+       int normal;
+       int high;
+       int full;
+};
+
+/**
+ * struct ab8500_bm_charger_parameters - Charger specific parameters
+ * @usb_volt_max:      maximum allowed USB charger voltage in mV
+ * @usb_curr_max:      maximum allowed USB charger current in mA
+ * @ac_volt_max:       maximum allowed AC charger voltage in mV
+ * @ac_curr_max:       maximum allowed AC charger current in mA
+ */
+struct ab8500_bm_charger_parameters {
+       int usb_volt_max;
+       int usb_curr_max;
+       int ac_volt_max;
+       int ac_curr_max;
+};
+
+/**
+ * struct ab8500_bm_data - ab8500 battery management data
+ * @temp_under         under this temp, charging is stopped
+ * @temp_low           between this temp and temp_under charging is reduced
+ * @temp_high          between this temp and temp_over charging is reduced
+ * @temp_over          over this temp, charging is stopped
+ * @temp_interval_chg  temperature measurement interval in s when charging
+ * @temp_interval_nochg        temperature measurement interval in s when not charging
+ * @main_safety_tmr_h  safety timer for main charger
+ * @usb_safety_tmr_h   safety timer for usb charger
+ * @bkup_bat_v         voltage which we charge the backup battery with
+ * @bkup_bat_i         current which we charge the backup battery with
+ * @no_maintenance     indicates that maintenance charging is disabled
+ * @adc_therm          placement of thermistor, batctrl or battemp adc
+ * @chg_unknown_bat    flag to enable charging of unknown batteries
+ * @enable_overshoot   flag to enable VBAT overshoot control
+ * @fg_res             resistance of FG resistor in 0.1mOhm
+ * @n_btypes           number of elements in array bat_type
+ * @batt_id            index of the identified battery in array bat_type
+ * @interval_charging  charge alg cycle period time when charging (sec)
+ * @interval_not_charging charge alg cycle period time when not charging (sec)
+ * @temp_hysteresis    temperature hysteresis
+ * @gnd_lift_resistance        Battery ground to phone ground resistance (mOhm)
+ * @maxi:              maximization parameters
+ * @cap_levels         capacity in percent for the different capacity levels
+ * @bat_type           table of supported battery types
+ * @chg_params         charger parameters
+ * @fg_params          fuel gauge parameters
+ */
+struct ab8500_bm_data {
+       int temp_under;
+       int temp_low;
+       int temp_high;
+       int temp_over;
+       int temp_interval_chg;
+       int temp_interval_nochg;
+       int main_safety_tmr_h;
+       int usb_safety_tmr_h;
+       int bkup_bat_v;
+       int bkup_bat_i;
+       bool no_maintenance;
+       bool chg_unknown_bat;
+       bool enable_overshoot;
+       enum abx500_adc_therm adc_therm;
+       int fg_res;
+       int n_btypes;
+       int batt_id;
+       int interval_charging;
+       int interval_not_charging;
+       int temp_hysteresis;
+       int gnd_lift_resistance;
+       const struct ab8500_maxim_parameters *maxi;
+       const struct ab8500_bm_capacity_levels *cap_levels;
+       const struct ab8500_bm_charger_parameters *chg_params;
+       const struct ab8500_fg_parameters *fg_params;
+};
+
+struct ab8500_charger_platform_data {
+       char **supplied_to;
+       size_t num_supplicants;
+       bool autopower_cfg;
+};
+
+struct ab8500_btemp_platform_data {
+       char **supplied_to;
+       size_t num_supplicants;
+};
+
+struct ab8500_fg_platform_data {
+       char **supplied_to;
+       size_t num_supplicants;
+};
+
+struct ab8500_chargalg_platform_data {
+       char **supplied_to;
+       size_t num_supplicants;
+};
+struct ab8500_btemp;
+struct ab8500_gpadc;
+struct ab8500_fg;
+#ifdef CONFIG_AB8500_BM
+void ab8500_fg_reinit(void);
+void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA);
+struct ab8500_btemp *ab8500_btemp_get(void);
+int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp);
+struct ab8500_fg *ab8500_fg_get(void);
+int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev);
+int ab8500_fg_inst_curr_start(struct ab8500_fg *di);
+int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res);
+int ab8500_fg_inst_curr_done(struct ab8500_fg *di);
+
+#else
+int ab8500_fg_inst_curr_done(struct ab8500_fg *di)
+{
+}
+static void ab8500_fg_reinit(void)
+{
+}
+static void ab8500_charger_usb_state_changed(u8 bm_usb_state, u16 mA)
+{
+}
+static struct ab8500_btemp *ab8500_btemp_get(void)
+{
+       return NULL;
+}
+static int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp)
+{
+       return 0;
+}
+struct ab8500_fg *ab8500_fg_get(void)
+{
+       return NULL;
+}
+static int ab8500_fg_inst_curr_blocking(struct ab8500_fg *dev)
+{
+       return -ENODEV;
+}
+
+static inline int ab8500_fg_inst_curr_start(struct ab8500_fg *di)
+{
+       return -ENODEV;
+}
+
+static inline int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res)
+{
+       return -ENODEV;
+}
+
+#endif
+#endif /* _AB8500_BM_H */
diff --git a/include/linux/mfd/abx500/ux500_chargalg.h b/include/linux/mfd/abx500/ux500_chargalg.h
new file mode 100644 (file)
index 0000000..9b07725
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ * Author: Johan Gardsmark <johan.gardsmark@stericsson.com> for ST-Ericsson.
+ * License terms:  GNU General Public License (GPL), version 2
+ */
+
+#ifndef _UX500_CHARGALG_H
+#define _UX500_CHARGALG_H
+
+#include <linux/power_supply.h>
+
+#define psy_to_ux500_charger(x) container_of((x), \
+               struct ux500_charger, psy)
+
+/* Forward declaration */
+struct ux500_charger;
+
+struct ux500_charger_ops {
+       int (*enable) (struct ux500_charger *, int, int, int);
+       int (*kick_wd) (struct ux500_charger *);
+       int (*update_curr) (struct ux500_charger *, int);
+};
+
+/**
+ * struct ux500_charger - power supply ux500 charger sub class
+ * @psy                        power supply base class
+ * @ops                        ux500 charger operations
+ * @max_out_volt       maximum output charger voltage in mV
+ * @max_out_curr       maximum output charger current in mA
+ */
+struct ux500_charger {
+       struct power_supply psy;
+       struct ux500_charger_ops ops;
+       int max_out_volt;
+       int max_out_curr;
+};
+
+#endif
index fe99211fb2b8c99cac624244b02d9b819ce3a9d7..e01b167e66f068223f86321109b77687ce5ef50c 100644 (file)
@@ -27,6 +27,8 @@
 #define MAX17042_BATTERY_FULL  (100)
 #define MAX17042_DEFAULT_SNS_RESISTOR  (10000)
 
+#define MAX17042_CHARACTERIZATION_DATA_SIZE 48
+
 enum max17042_register {
        MAX17042_STATUS         = 0x00,
        MAX17042_VALRT_Th       = 0x01,
@@ -40,11 +42,11 @@ enum max17042_register {
        MAX17042_VCELL          = 0x09,
        MAX17042_Current        = 0x0A,
        MAX17042_AvgCurrent     = 0x0B,
-       MAX17042_Qresidual      = 0x0C,
+
        MAX17042_SOC            = 0x0D,
        MAX17042_AvSOC          = 0x0E,
        MAX17042_RemCap         = 0x0F,
-       MAX17402_FullCAP        = 0x10,
+       MAX17042_FullCAP        = 0x10,
        MAX17042_TTE            = 0x11,
        MAX17042_V_empty        = 0x12,
 
@@ -62,14 +64,14 @@ enum max17042_register {
        MAX17042_AvCap          = 0x1F,
        MAX17042_ManName        = 0x20,
        MAX17042_DevName        = 0x21,
-       MAX17042_DevChem        = 0x22,
 
+       MAX17042_FullCAPNom     = 0x23,
        MAX17042_TempNom        = 0x24,
-       MAX17042_TempCold       = 0x25,
+       MAX17042_TempLim        = 0x25,
        MAX17042_TempHot        = 0x26,
        MAX17042_AIN            = 0x27,
        MAX17042_LearnCFG       = 0x28,
-       MAX17042_SHFTCFG        = 0x29,
+       MAX17042_FilterCFG      = 0x29,
        MAX17042_RelaxCFG       = 0x2A,
        MAX17042_MiscCFG        = 0x2B,
        MAX17042_TGAIN          = 0x2C,
@@ -77,22 +79,41 @@ enum max17042_register {
        MAX17042_CGAIN          = 0x2E,
        MAX17042_COFF           = 0x2F,
 
-       MAX17042_Q_empty        = 0x33,
+       MAX17042_MaskSOC        = 0x32,
+       MAX17042_SOC_empty      = 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_EmptyTempCo    = 0x3A,
+       MAX17042_K_empty0       = 0x3B,
        MAX17042_TaskPeriod     = 0x3C,
        MAX17042_FSTAT          = 0x3D,
 
        MAX17042_SHDNTIMER      = 0x3F,
 
-       MAX17042_VFRemCap       = 0x4A,
+       MAX17042_dQacc          = 0x45,
+       MAX17042_dPacc          = 0x46,
+
+       MAX17042_VFSOC0         = 0x48,
 
        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,
 };
 
 /*
@@ -105,10 +126,64 @@ struct max17042_reg_data {
        u16 data;
 };
 
+struct max17042_config_data {
+       /* External current sense resistor value in milli-ohms */
+       u32     cur_sense_val;
+
+       /* A/D measurement */
+       u16     tgain;          /* 0x2C */
+       u16     toff;           /* 0x2D */
+       u16     cgain;          /* 0x2E */
+       u16     coff;           /* 0x2F */
+
+       /* Alert / Status */
+       u16     valrt_thresh;   /* 0x01 */
+       u16     talrt_thresh;   /* 0x02 */
+       u16     soc_alrt_thresh;        /* 0x03 */
+       u16     config;         /* 0x01D */
+       u16     shdntimer;      /* 0x03F */
+
+       /* App data */
+       u16     design_cap;     /* 0x18 */
+       u16     ichgt_term;     /* 0x1E */
+
+       /* MG3 config */
+       u16     at_rate;        /* 0x04 */
+       u16     learn_cfg;      /* 0x28 */
+       u16     filter_cfg;     /* 0x29 */
+       u16     relax_cfg;      /* 0x2A */
+       u16     misc_cfg;       /* 0x2B */
+       u16     masksoc;        /* 0x32 */
+
+       /* MG3 save and restore */
+       u16     fullcap;        /* 0x10 */
+       u16     fullcapnom;     /* 0x23 */
+       u16     socempty;       /* 0x33 */
+       u16     lavg_empty;     /* 0x36 */
+       u16     dqacc;          /* 0x45 */
+       u16     dpacc;          /* 0x46 */
+
+       /* Cell technology from power_supply.h */
+       u16     cell_technology;
+
+       /* Cell Data */
+       u16     vempty;         /* 0x12 */
+       u16     temp_nom;       /* 0x24 */
+       u16     temp_lim;       /* 0x25 */
+       u16     fctc;           /* 0x37 */
+       u16     rcomp0;         /* 0x38 */
+       u16     tcompc0;        /* 0x39 */
+       u16     empty_tempco;   /* 0x3A */
+       u16     kempty0;        /* 0x3B */
+       u16     cell_char_tbl[MAX17042_CHARACTERIZATION_DATA_SIZE];
+} __packed;
+
 struct max17042_platform_data {
        struct max17042_reg_data *init_data;
+       struct max17042_config_data *config_data;
        int num_init_data; /* Number of enties in init_data array */
        bool enable_current_sense;
+       bool enable_por_init; /* Use POR init from Maxim appnote */
 
        /*
         * R_sns in micro-ohms.
diff --git a/include/linux/power/smb347-charger.h b/include/linux/power/smb347-charger.h
new file mode 100644 (file)
index 0000000..b3cb20d
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * Summit Microelectronics SMB347 Battery Charger Driver
+ *
+ * Copyright (C) 2011, Intel Corporation
+ *
+ * Authors: Bruce E. Robertson <bruce.e.robertson@intel.com>
+ *          Mika Westerberg <mika.westerberg@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef SMB347_CHARGER_H
+#define SMB347_CHARGER_H
+
+#include <linux/types.h>
+#include <linux/power_supply.h>
+
+enum {
+       /* use the default compensation method */
+       SMB347_SOFT_TEMP_COMPENSATE_DEFAULT = -1,
+
+       SMB347_SOFT_TEMP_COMPENSATE_NONE,
+       SMB347_SOFT_TEMP_COMPENSATE_CURRENT,
+       SMB347_SOFT_TEMP_COMPENSATE_VOLTAGE,
+};
+
+/* Use default factory programmed value for hard/soft temperature limit */
+#define SMB347_TEMP_USE_DEFAULT                -273
+
+/*
+ * Charging enable can be controlled by software (via i2c) by
+ * smb347-charger driver or by EN pin (active low/high).
+ */
+enum smb347_chg_enable {
+       SMB347_CHG_ENABLE_SW,
+       SMB347_CHG_ENABLE_PIN_ACTIVE_LOW,
+       SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH,
+};
+
+/**
+ * struct smb347_charger_platform_data - platform data for SMB347 charger
+ * @battery_info: Information about the battery
+ * @max_charge_current: maximum current (in uA) the battery can be charged
+ * @max_charge_voltage: maximum voltage (in uV) the battery can be charged
+ * @pre_charge_current: current (in uA) to use in pre-charging phase
+ * @termination_current: current (in uA) used to determine when the
+ *                      charging cycle terminates
+ * @pre_to_fast_voltage: voltage (in uV) treshold used for transitioning to
+ *                      pre-charge to fast charge mode
+ * @mains_current_limit: maximum input current drawn from AC/DC input (in uA)
+ * @usb_hc_current_limit: maximum input high current (in uA) drawn from USB
+ *                       input
+ * @chip_temp_threshold: die temperature where device starts limiting charge
+ *                      current [%100 - %130] (in degree C)
+ * @soft_cold_temp_limit: soft cold temperature limit [%0 - %15] (in degree C),
+ *                       granularity is 5 deg C.
+ * @soft_hot_temp_limit: soft hot temperature limit [%40 - %55] (in degree  C),
+ *                      granularity is 5 deg C.
+ * @hard_cold_temp_limit: hard cold temperature limit [%-5 - %10] (in degree C),
+ *                       granularity is 5 deg C.
+ * @hard_hot_temp_limit: hard hot temperature limit [%50 - %65] (in degree C),
+ *                      granularity is 5 deg C.
+ * @suspend_on_hard_temp_limit: suspend charging when hard limit is hit
+ * @soft_temp_limit_compensation: compensation method when soft temperature
+ *                               limit is hit
+ * @charge_current_compensation: current (in uA) for charging compensation
+ *                              current when temperature hits soft limits
+ * @use_mains: AC/DC input can be used
+ * @use_usb: USB input can be used
+ * @use_usb_otg: USB OTG output can be used (not implemented yet)
+ * @irq_gpio: GPIO number used for interrupts (%-1 if not used)
+ * @enable_control: how charging enable/disable is controlled
+ *                 (driver/pin controls)
+ *
+ * @use_main, @use_usb, and @use_usb_otg are means to enable/disable
+ * hardware support for these. This is useful when we want to have for
+ * example OTG charging controlled via OTG transceiver driver and not by
+ * the SMB347 hardware.
+ *
+ * Hard and soft temperature limit values are given as described in the
+ * device data sheet and assuming NTC beta value is %3750. Even if this is
+ * not the case, these values should be used. They can be mapped to the
+ * corresponding NTC beta values with the help of table %2 in the data
+ * sheet. So for example if NTC beta is %3375 and we want to program hard
+ * hot limit to be %53 deg C, @hard_hot_temp_limit should be set to %50.
+ *
+ * If zero value is given in any of the current and voltage values, the
+ * factory programmed default will be used. For soft/hard temperature
+ * values, pass in %SMB347_TEMP_USE_DEFAULT instead.
+ */
+struct smb347_charger_platform_data {
+       struct power_supply_info battery_info;
+       unsigned int    max_charge_current;
+       unsigned int    max_charge_voltage;
+       unsigned int    pre_charge_current;
+       unsigned int    termination_current;
+       unsigned int    pre_to_fast_voltage;
+       unsigned int    mains_current_limit;
+       unsigned int    usb_hc_current_limit;
+       unsigned int    chip_temp_threshold;
+       int             soft_cold_temp_limit;
+       int             soft_hot_temp_limit;
+       int             hard_cold_temp_limit;
+       int             hard_hot_temp_limit;
+       bool            suspend_on_hard_temp_limit;
+       unsigned int    soft_temp_limit_compensation;
+       unsigned int    charge_current_compensation;
+       bool            use_mains;
+       bool            use_usb;
+       bool            use_usb_otg;
+       int             irq_gpio;
+       enum smb347_chg_enable enable_control;
+};
+
+#endif /* SMB347_CHARGER_H */