aboutsummaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-03-30 16:09:02 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2012-03-30 16:09:02 -0700
commit919c840167ec93167e00ca817aa4627170419ebf (patch)
tree5162f6a39541f609a949c5d75c4fdaed523d20af /drivers/power
parenta9d38a4f2da6c49a257253a9fdef7a6bcb0e0e4f (diff)
parent5cdd4d7fa5424f2018193a0c2af3bef9036c973e (diff)
Merge tag 'for-v3.4-rc1' of git://git.infradead.org/battery-2.6
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 ...
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Kconfig21
-rw-r--r--drivers/power/Makefile2
-rw-r--r--drivers/power/ab8500_btemp.c1124
-rw-r--r--drivers/power/ab8500_charger.c2789
-rw-r--r--drivers/power/ab8500_fg.c2637
-rw-r--r--drivers/power/abx500_chargalg.c1921
-rw-r--r--drivers/power/charger-manager.c67
-rw-r--r--drivers/power/da9052-battery.c15
-rw-r--r--drivers/power/ds2782_battery.c13
-rw-r--r--drivers/power/isp1704_charger.c1
-rw-r--r--drivers/power/lp8727_charger.c131
-rw-r--r--drivers/power/max17040_battery.c13
-rw-r--r--drivers/power/max17042_battery.c508
-rw-r--r--drivers/power/sbs-battery.c13
-rw-r--r--drivers/power/smb347-charger.c1294
-rw-r--r--drivers/power/z2_battery.c14
16 files changed, 10368 insertions, 195 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 459f66437fe9..99dc29f2f2f2 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -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
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index c590fa533406..b6b243416c0e 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -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
index 000000000000..d8bb99394ac0
--- /dev/null
+++ b/drivers/power/ab8500_btemp.c
@@ -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
index 000000000000..e2b4accbec88
--- /dev/null
+++ b/drivers/power/ab8500_charger.c
@@ -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
index 000000000000..c22f2f05657e
--- /dev/null
+++ b/drivers/power/ab8500_fg.c
@@ -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
index 000000000000..804b88c760d6
--- /dev/null
+++ b/drivers/power/abx500_chargalg.c
@@ -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");
diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c
index 88fd9710bda2..9eca9f1ff0ea 100644
--- a/drivers/power/charger-manager.c
+++ b/drivers/power/charger-manager.c
@@ -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) {
diff --git a/drivers/power/da9052-battery.c b/drivers/power/da9052-battery.c
index e8ea47a53dee..a5f6a0ec1572 100644
--- a/drivers/power/da9052-battery.c
+++ b/drivers/power/da9052-battery.c
@@ -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>");
diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c
index bfbce5de49da..6bb6e2f5ea81 100644
--- a/drivers/power/ds2782_battery.c
+++ b/drivers/power/ds2782_battery.c
@@ -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");
diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c
index 1289a5f790a1..39eb50f35f09 100644
--- a/drivers/power/isp1704_charger.c
+++ b/drivers/power/isp1704_charger.c
@@ -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;
}
diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c
index c53dd1292f81..d8b75780bfef 100644
--- a/drivers/power/lp8727_charger.c
+++ b/drivers/power/lp8727_charger.c
@@ -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");
diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c
index 2f2f9a6f54fa..c284143cfcd7 100644
--- a/drivers/power/max17040_battery.c
+++ b/drivers/power/max17040_battery.c
@@ -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");
diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c
index 86acee2f9889..04620c2cb388 100644
--- a/drivers/power/max17042_battery.c
+++ b/drivers/power/max17042_battery.c
@@ -26,14 +26,47 @@
#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");
diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c
index 9ff8af069da6..06b659d91790 100644
--- a/drivers/power/sbs-battery.c
+++ b/drivers/power/sbs-battery.c
@@ -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
index 000000000000..ce1694d1a365
--- /dev/null
+++ b/drivers/power/smb347-charger.c
@@ -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");
diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c
index 636ebb2a0e80..8c9a607ea77a 100644
--- a/drivers/power/z2_battery.c
+++ b/drivers/power/z2_battery.c
@@ -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>");