summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Kconfig19
-rw-r--r--drivers/power/Makefile1
-rw-r--r--drivers/power/ab5500_btemp.c994
-rw-r--r--drivers/power/ab5500_charger.c1820
-rw-r--r--drivers/power/ab5500_fg.c1954
-rw-r--r--drivers/power/ab8500_btemp.c19
-rw-r--r--drivers/power/ab8500_charger.c198
-rw-r--r--drivers/power/ab8500_fg.c12
-rw-r--r--drivers/power/abx500_chargalg.c7
9 files changed, 4962 insertions, 62 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 99dc29f2f2f..56dfe432b74 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -307,4 +307,23 @@ config AB8500_BATTERY_THERM_ON_BATCTRL
help
Say Y to enable battery temperature measurements using
thermistor connected on BATCTRL ADC.
+
+config AB8500_9100_LI_ION_BATTERY
+ bool "Enable support of the 9100 Li-ion battery charging"
+ depends on AB8500_BM
+ help
+ Say Y to enable support of the 9100 Li-ion battery charging.
+
+config AB5500_BM
+ bool "AB5500 Battery Management Driver"
+ depends on AB5500_CORE && AB5500_GPADC && MACH_U5500
+ help
+ Say Y to include support for AB5500 battery management.
+
+config AB5500_BATTERY_THERM_ON_BATCTRL
+ bool "Thermistor connected on BATCTRL ADC"
+ depends on AB5500_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 b6b243416c0..af27f1d08aa 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -41,6 +41,7 @@ obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
+obj-$(CONFIG_AB5500_BM) += ab5500_charger.o ab5500_btemp.o ab5500_fg.o abx500_chargalg.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/ab5500_btemp.c b/drivers/power/ab5500_btemp.c
new file mode 100644
index 00000000000..3709299d6cb
--- /dev/null
+++ b/drivers/power/ab5500_btemp.c
@@ -0,0 +1,994 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Battery temperature driver for ab5500
+ *
+ * License Terms: GNU General Public License v2
+ * Authors:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@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.h>
+#include <linux/mfd/abx500/ab5500.h>
+#include <linux/mfd/abx500/ab5500-bm.h>
+#include <linux/mfd/abx500/ab5500-gpadc.h>
+
+#define BTEMP_THERMAL_LOW_LIMIT -10
+#define BTEMP_THERMAL_MED_LIMIT 0
+#define BTEMP_THERMAL_HIGH_LIMIT_62 62
+
+#define BTEMP_BATCTRL_CURR_SRC_7UA 7
+#define BTEMP_BATCTRL_CURR_SRC_15UA 15
+#define BTEMP_BATCTRL_CURR_SRC_20UA 20
+
+#define UART_MODE 0x0F
+#define BAT_CUR_SRC 0x1F
+#define RESIS_ID_MODE 0x03
+#define RESET 0x00
+#define ADOUT_10K_PULL_UP 0x07
+
+#define to_ab5500_btemp_device_info(x) container_of((x), \
+ struct ab5500_btemp, btemp_psy);
+
+/**
+ * struct ab5500_btemp_interrupts - ab5500 interrupts
+ * @name: name of the interrupt
+ * @isr function pointer to the isr
+ */
+struct ab5500_btemp_interrupts {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct ab5500_btemp_events {
+ bool batt_rem;
+ bool usb_conn;
+};
+
+/**
+ * struct ab5500_btemp - ab5500 BTEMP device information
+ * @dev: Pointer to the structure device
+ * @chip_id: Chip-Id of the AB5500
+ * @curr_source: What current source we use, in uA
+ * @bat_temp: Battery temperature in degree Celcius
+ * @prev_bat_temp Last dispatched battery temperature
+ * @node: struct of type list_head
+ * @parent: Pointer to the struct ab5500
+ * @gpadc: Pointer to the struct gpadc
+ * @gpadc-auto: Pointer to the struct adc_auto_input
+ * @pdata: Pointer to the ab5500_btemp platform data
+ * @bat: Pointer to the ab5500_bm platform data
+ * @btemp_psy: Structure for BTEMP specific battery properties
+ * @events: Structure for information about events triggered
+ * @btemp_wq: Work queue for measuring the temperature periodically
+ * @btemp_periodic_work: Work for measuring the temperature periodically
+ */
+struct ab5500_btemp {
+ struct device *dev;
+ u8 chip_id;
+ int curr_source;
+ int bat_temp;
+ int prev_bat_temp;
+ struct list_head node;
+ struct ab5500 *parent;
+ struct ab5500_gpadc *gpadc;
+ struct adc_auto_input *gpadc_auto;
+ struct abx500_btemp_platform_data *pdata;
+ struct abx500_bm_data *bat;
+ struct power_supply btemp_psy;
+ struct ab5500_btemp_events events;
+ struct workqueue_struct *btemp_wq;
+ struct delayed_work btemp_periodic_work;
+};
+
+/* BTEMP power supply properties */
+static enum power_supply_property ab5500_btemp_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static LIST_HEAD(ab5500_btemp_list);
+
+static int ab5500_btemp_bat_temp_trig(int mux);
+
+struct ab5500_btemp *ab5500_btemp_get(void)
+{
+ struct ab5500_btemp *di;
+ di = list_first_entry(&ab5500_btemp_list, struct ab5500_btemp, node);
+
+ return di;
+}
+
+/**
+ * ab5500_btemp_get_batctrl_temp() - get the temperature
+ * @di: pointer to the ab5500_btemp structure
+ *
+ * Returns the batctrl temperature in millidegrees
+ */
+int ab5500_btemp_get_batctrl_temp(struct ab5500_btemp *di)
+{
+ return di->bat_temp * 1000;
+}
+
+/**
+ * ab5500_btemp_volt_to_res() - convert batctrl voltage to resistance
+ * @di: pointer to the ab5500_btemp structure
+ * @volt: measured batctrl/btemp_ball voltage
+ * @batcrtl: batctrl/btemp_ball node
+ *
+ * This function returns the battery resistance that is
+ * derived from the BATCTRL/BTEMP_BALL voltage.
+ * Returns value in Ohms.
+ */
+static int ab5500_btemp_volt_to_res(struct ab5500_btemp *di,
+ int volt, bool batctrl)
+{
+ int rbs;
+
+ if (batctrl) {
+ /*
+ * If the battery has internal NTC, we use the current
+ * source to calculate the resistance, 7uA or 20uA
+ */
+ rbs = volt * 1000 / di->curr_source;
+ } else {
+ /*
+ * BTEMP_BALL is internally
+ * connected to 1.8V through a 10k resistor
+ */
+ rbs = (10000 * volt) / (1800 - volt);
+ }
+ return rbs;
+}
+
+/**
+ * ab5500_btemp_read_batctrl_voltage() - measure batctrl voltage
+ * @di: pointer to the ab5500_btemp structure
+ *
+ * This function returns the voltage on BATCTRL. Returns value in mV.
+ */
+static int ab5500_btemp_read_batctrl_voltage(struct ab5500_btemp *di)
+{
+ int vbtemp;
+ static int prev;
+
+ vbtemp = ab5500_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;
+}
+
+/**
+ * ab5500_btemp_curr_source_enable() - enable/disable batctrl current source
+ * @di: pointer to the ab5500_btemp structure
+ * @enable: enable or disable the current source
+ *
+ * Enable or disable the current sources for the BatCtrl AD channel
+ */
+static int ab5500_btemp_curr_source_enable(struct ab5500_btemp *di,
+ bool enable)
+{
+ int ret = 0;
+
+ /* Only do this for batteries with internal NTC */
+ if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) {
+
+ dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source);
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_UART,
+ UART_MODE, RESIS_ID_MODE);
+ if (ret) {
+ dev_err(di->dev,
+ "%s failed setting resistance identification mode\n",
+ __func__);
+ return ret;
+ }
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_URI,
+ BAT_CUR_SRC, BAT_CTRL_15U_ENA);
+ 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,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_URI,
+ BAT_CUR_SRC, RESET);
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling current source\n",
+ __func__);
+ goto disable_curr_source;
+ }
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_UART,
+ UART_MODE, RESET);
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling force comp\n",
+ __func__);
+ }
+ }
+ return ret;
+disable_curr_source:
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_URI,
+ BAT_CUR_SRC, RESET);
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling current source\n",
+ __func__);
+ }
+ return ret;
+}
+
+/**
+ * ab5500_btemp_get_batctrl_res() - get battery resistance
+ * @di: pointer to the ab5500_btemp structure
+ *
+ * This function returns the battery pack identification resistance.
+ * Returns value in Ohms.
+ */
+static int ab5500_btemp_get_batctrl_res(struct ab5500_btemp *di)
+{
+ int ret;
+ int batctrl;
+ int res;
+
+ ret = ab5500_btemp_curr_source_enable(di, true);
+ /* TODO: This delay has to be optimised */
+ msleep(100);
+ if (ret) {
+ dev_err(di->dev, "%s curr source enable failed\n", __func__);
+ return ret;
+ }
+
+ batctrl = ab5500_btemp_read_batctrl_voltage(di);
+ res = ab5500_btemp_volt_to_res(di, batctrl, true);
+
+ ret = ab5500_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 ",
+ __func__, batctrl, res);
+
+ return res;
+}
+
+/**
+ * ab5500_btemp_get_btemp_ball_res() - get battery resistance
+ * @di: pointer to the ab5500_btemp structure
+ *
+ * This function returns the battery pack identification
+ * resistance using resistor pull-up mode. Returns value in Ohms.
+ */
+static int ab5500_btemp_get_btemp_ball_res(struct ab5500_btemp *di)
+{
+ int ret, vntc;
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_UART,
+ UART_MODE, ADOUT_10K_PULL_UP);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to enable 10k pull up to Vadout\n");
+ return ret;
+ }
+
+ vntc = ab5500_gpadc_convert(di->gpadc, BTEMP_BALL);
+ if (vntc < 0) {
+ dev_err(di->dev, "%s gpadc conversion failed,"
+ " using previous value\n", __func__);
+ return vntc;
+ }
+
+ return ab5500_btemp_volt_to_res(di, vntc, false);
+}
+
+/**
+ * ab5500_btemp_temp_to_res() - temperature to resistance
+ * @di: pointer to the ab5500_btemp structure
+ * @tbl: pointer to the resiatance to temperature table
+ * @tbl_size: size of the resistance to temperature table
+ * @temp: temperature to calculate the resistance from
+ *
+ * This function returns the battery resistance in ohms
+ * based on temperature.
+ */
+static int ab5500_btemp_temp_to_res(struct ab5500_btemp *di,
+ const struct abx500_res_to_temp *tbl, int tbl_size, int temp)
+{
+ int i, res;
+ /*
+ * 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 (temp < tbl[0].temp)
+ i = 0;
+ else if (temp >= tbl[tbl_size - 1].temp)
+ i = tbl_size - 2;
+ else {
+ i = 0;
+ while (!(temp >= tbl[i].temp &&
+ temp < tbl[i + 1].temp))
+ i++;
+ }
+
+ res = tbl[i].resist + ((tbl[i + 1].resist - tbl[i].resist) *
+ (temp - tbl[i].temp)) / (tbl[i + 1].temp - tbl[i].temp);
+ return res;
+}
+
+/**
+ * ab5500_btemp_temp_to_volt() - temperature to adc voltage
+ * @di: pointer to the ab5500_btemp structure
+ * @temp: temperature to calculate the voltage from
+ *
+ * This function returns the adc voltage in millivolts
+ * based on temperature.
+ */
+static int ab5500_btemp_temp_to_volt(struct ab5500_btemp *di, int temp)
+{
+ int res, id;
+
+ id = di->bat->batt_id;
+ res = ab5500_btemp_temp_to_res(di,
+ di->bat->bat_type[id].r_to_t_tbl,
+ di->bat->bat_type[id].n_temp_tbl_elements,
+ temp);
+ /*
+ * BTEMP_BALL is internally connected to 1.8V
+ * through a 10k resistor
+ */
+ return((1800 * res) / (10000 + res));
+}
+
+/**
+ * ab5500_btemp_res_to_temp() - resistance to temperature
+ * @di: pointer to the ab5500_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 ab5500_btemp_res_to_temp(struct ab5500_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;
+}
+
+/**
+ * ab5500_btemp_measure_temp() - measure battery temperature
+ * @di: pointer to the ab5500_btemp structure
+ *
+ * Returns battery temperature (on success) else the previous temperature
+ */
+static int ab5500_btemp_measure_temp(struct ab5500_btemp *di)
+{
+ int temp, rbat;
+ u8 id;
+
+ id = di->bat->batt_id;
+ if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL &&
+ id != BATTERY_UNKNOWN && !di->bat->auto_trig)
+ rbat = ab5500_btemp_get_batctrl_res(di);
+ else
+ rbat = ab5500_btemp_get_btemp_ball_res(di);
+
+ if (rbat < 0) {
+ dev_err(di->dev, "%s failed to get resistance\n", __func__);
+ /*
+ * Return out-of-range temperature so that
+ * charging is stopped
+ */
+ return BTEMP_THERMAL_LOW_LIMIT;
+ }
+
+ temp = ab5500_btemp_res_to_temp(di,
+ di->bat->bat_type[id].r_to_t_tbl,
+ di->bat->bat_type[id].n_temp_tbl_elements, rbat);
+ dev_dbg(di->dev, "Battery temperature is %d\n", temp);
+
+ return temp;
+}
+
+/**
+ * ab5500_btemp_id() - Identify the connected battery
+ * @di: pointer to the ab5500_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 ab5500_btemp_id(struct ab5500_btemp *di)
+{
+ int res;
+ u8 i;
+
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
+ di->bat->batt_id = BATTERY_UNKNOWN;
+
+ res = ab5500_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 15uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_15UA;
+ }
+
+ return di->bat->batt_id;
+}
+
+/**
+ * ab5500_btemp_periodic_work() - Measuring the temperature periodically
+ * @work: pointer to the work_struct structure
+ *
+ * Work function for measuring the temperature periodically
+ */
+static void ab5500_btemp_periodic_work(struct work_struct *work)
+{
+ struct ab5500_btemp *di = container_of(work,
+ struct ab5500_btemp, btemp_periodic_work.work);
+
+ di->bat_temp = ab5500_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);
+ }
+ di->bat->temp_now = di->bat_temp;
+
+ if (!di->bat->auto_trig) {
+ /* Check for temperature limits */
+ if (di->bat_temp <= BTEMP_THERMAL_LOW_LIMIT) {
+ dev_err(di->dev,
+ "battery temp less than lower threshold\n");
+ power_supply_changed(&di->btemp_psy);
+ } else if (di->bat_temp >= BTEMP_THERMAL_HIGH_LIMIT_62) {
+ dev_err(di->dev,
+ "battery temp greater them max threshold\n");
+ power_supply_changed(&di->btemp_psy);
+ }
+
+ /* Schedule a new measurement */
+ if (di->events.usb_conn)
+ queue_delayed_work(di->btemp_wq,
+ &di->btemp_periodic_work,
+ round_jiffies(di->bat->interval_charging * HZ));
+ else
+ queue_delayed_work(di->btemp_wq,
+ &di->btemp_periodic_work,
+ round_jiffies(di->bat->interval_not_charging * HZ));
+ } else {
+ /* Schedule a new measurement */
+ queue_delayed_work(di->btemp_wq,
+ &di->btemp_periodic_work,
+ round_jiffies(di->bat->interval_charging * HZ));
+ }
+}
+
+/**
+ * ab5500_btemp_batt_removal_handler() - battery removal detected
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab5500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab5500_btemp_batt_removal_handler(int irq, void *_di)
+{
+ struct ab5500_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;
+}
+
+/**
+ * ab5500_btemp_batt_attach_handler() - battery insertion detected
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab5500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab5500_btemp_batt_attach_handler(int irq, void *_di)
+{
+ struct ab5500_btemp *di = _di;
+ dev_err(di->dev, "Battery attached!\n");
+
+ di->events.batt_rem = false;
+ power_supply_changed(&di->btemp_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab5500_btemp_periodic() - Periodic temperature measurements
+ * @di: pointer to the ab5500_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 ab5500_btemp_periodic(struct ab5500_btemp *di,
+ bool enable)
+{
+ dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n",
+ enable);
+
+ if (enable)
+ queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0);
+ else
+ cancel_delayed_work_sync(&di->btemp_periodic_work);
+}
+
+/**
+ * ab5500_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 ab5500_btemp_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab5500_btemp *di;
+
+ di = to_ab5500_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:
+ if (di->bat->batt_id == BATTERY_UNKNOWN)
+ /*
+ * In case the battery is not identified, its assumed that
+ * we are using the power supply and since no monitoring is
+ * done for the same, a nominal temp is hardocded.
+ */
+ val->intval = 250;
+ else
+ val->intval = di->bat_temp * 10;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ab5500_btemp_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext;
+ struct ab5500_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_ab5500_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_USB:
+ /* USB disconnected */
+ if (!ret.intval && di->events.usb_conn) {
+ di->events.usb_conn = false;
+ if (di->bat->auto_trig)
+ ab5500_btemp_periodic(di,
+ false);
+ }
+ /* USB connected */
+ else if (ret.intval && !di->events.usb_conn) {
+ di->events.usb_conn = true;
+ if (di->bat->auto_trig)
+ ab5500_btemp_periodic(di, true);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ab5500_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 ab5500_btemp_external_power_changed(struct power_supply *psy)
+{
+ struct ab5500_btemp *di = to_ab5500_btemp_device_info(psy);
+
+ class_for_each_device(power_supply_class, NULL,
+ &di->btemp_psy, ab5500_btemp_get_ext_psy_data);
+}
+
+/* ab5500 btemp driver interrupts and their respective isr */
+static struct ab5500_btemp_interrupts ab5500_btemp_irq[] = {
+ {"BATT_REMOVAL", ab5500_btemp_batt_removal_handler},
+ {"BATT_ATTACH", ab5500_btemp_batt_attach_handler},
+};
+
+static int ab5500_btemp_bat_temp_trig(int mux)
+{
+ struct ab5500_btemp *di = ab5500_btemp_get();
+ int temp = ab5500_btemp_measure_temp(di);
+
+ if (temp < (BTEMP_THERMAL_LOW_LIMIT+1)) {
+ dev_err(di->dev,
+ "battery temp less than lower threshold (-10 deg cel)\n");
+ power_supply_changed(&di->btemp_psy);
+ } else if (temp > (BTEMP_THERMAL_HIGH_LIMIT_62-1)) {
+ dev_err(di->dev, "battery temp greater them max threshold\n");
+ power_supply_changed(&di->btemp_psy);
+ }
+
+ return 0;
+}
+
+static int ab5500_btemp_auto_temp(struct ab5500_btemp *di)
+{
+ struct adc_auto_input *auto_ip;
+ int ret = 0;
+
+ auto_ip = kzalloc(sizeof(struct adc_auto_input), GFP_KERNEL);
+ if (!auto_ip) {
+ dev_err(di->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ auto_ip->mux = BTEMP_BALL;
+ auto_ip->freq = MS500;
+ auto_ip->min = ab5500_btemp_temp_to_volt(di,
+ BTEMP_THERMAL_HIGH_LIMIT_62);
+ auto_ip->max = ab5500_btemp_temp_to_volt(di,
+ BTEMP_THERMAL_LOW_LIMIT);
+ auto_ip->auto_adc_callback = ab5500_btemp_bat_temp_trig;
+ di->gpadc_auto = auto_ip;
+ ret = ab5500_gpadc_convert_auto(di->gpadc, di->gpadc_auto);
+ if (ret)
+ dev_err(di->dev,
+ "failed to set auto trigger for battery temp\n");
+ return ret;
+}
+
+#if defined(CONFIG_PM)
+static int ab5500_btemp_resume(struct platform_device *pdev)
+{
+ struct ab5500_btemp *di = platform_get_drvdata(pdev);
+
+ if (di->events.usb_conn)
+ ab5500_btemp_periodic(di, true);
+
+ return 0;
+}
+
+static int ab5500_btemp_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ab5500_btemp *di = platform_get_drvdata(pdev);
+
+ if (di->events.usb_conn)
+ ab5500_btemp_periodic(di, false);
+
+ return 0;
+}
+#else
+#define ab5500_btemp_suspend NULL
+#define ab5500_btemp_resume NULL
+#endif
+
+static int __devexit ab5500_btemp_remove(struct platform_device *pdev)
+{
+ struct ab5500_btemp *di = platform_get_drvdata(pdev);
+ int i, irq;
+
+ /* Disable interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab5500_btemp_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab5500_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->gpadc_auto);
+ kfree(di);
+
+ return 0;
+}
+
+static int __devinit ab5500_btemp_probe(struct platform_device *pdev)
+{
+ int irq, i, ret = 0;
+ struct abx500_bm_plat_data *plat_data;
+
+ struct ab5500_btemp *di =
+ kzalloc(sizeof(struct ab5500_btemp), GFP_KERNEL);
+ if (!di)
+ return -ENOMEM;
+
+ /* get parent data */
+ di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+ di->gpadc = ab5500_gpadc_get("ab5500-adc.0");
+
+ plat_data = pdev->dev.platform_data;
+ di->pdata = plat_data->btemp;
+ di->bat = plat_data->battery;
+
+ /* get btemp specific platform data */
+ if (!di->pdata) {
+ dev_err(di->dev, "no btemp platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ /* get battery specific platform data */
+ 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 = "ab5500_btemp";
+ di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->btemp_psy.properties = ab5500_btemp_props;
+ di->btemp_psy.num_properties = ARRAY_SIZE(ab5500_btemp_props);
+ di->btemp_psy.get_property = ab5500_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 =
+ ab5500_btemp_external_power_changed;
+
+
+ /* Create a work queue for the btemp */
+ di->btemp_wq =
+ create_singlethread_workqueue("ab5500_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,
+ ab5500_btemp_periodic_work);
+
+ /* Get Chip ID of the ABB ASIC */
+ ret = abx500_get_chip_id(di->dev);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to get chip ID\n");
+ goto free_btemp_wq;
+ }
+ di->chip_id = ret;
+ dev_dbg(di->dev, "ab5500 CID is: 0x%02x\n",
+ di->chip_id);
+
+ /* Identify the battery */
+ if (ab5500_btemp_id(di) < 0)
+ dev_warn(di->dev, "failed to identify the battery\n");
+
+ /* Measure temperature once initially */
+ di->bat_temp = ab5500_btemp_measure_temp(di);
+ di->bat->temp_now = di->bat_temp;
+
+ /* 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(ab5500_btemp_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab5500_btemp_irq[i].name);
+ ret = request_threaded_irq(irq, NULL, ab5500_btemp_irq[i].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND,
+ ab5500_btemp_irq[i].name, di);
+
+ if (ret) {
+ dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+ , ab5500_btemp_irq[i].name, irq, ret);
+ goto free_irq;
+ }
+ dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+ ab5500_btemp_irq[i].name, irq, ret);
+ }
+
+ if (!di->bat->auto_trig) {
+ /* Schedule monitoring work only if battery type is known */
+ if (di->bat->batt_id != BATTERY_UNKNOWN)
+ queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0);
+ } else {
+ ret = ab5500_btemp_auto_temp(di);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to register auto trigger for battery temp\n");
+ goto free_irq;
+ }
+ }
+
+ platform_set_drvdata(pdev, di);
+ list_add_tail(&di->node, &ab5500_btemp_list);
+
+ dev_info(di->dev, "probe success\n");
+ 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, ab5500_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 ab5500_btemp_driver = {
+ .probe = ab5500_btemp_probe,
+ .remove = __devexit_p(ab5500_btemp_remove),
+ .suspend = ab5500_btemp_suspend,
+ .resume = ab5500_btemp_resume,
+ .driver = {
+ .name = "ab5500-btemp",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ab5500_btemp_init(void)
+{
+ return platform_driver_register(&ab5500_btemp_driver);
+}
+
+static void __exit ab5500_btemp_exit(void)
+{
+ platform_driver_unregister(&ab5500_btemp_driver);
+}
+
+subsys_initcall_sync(ab5500_btemp_init);
+module_exit(ab5500_btemp_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
+MODULE_ALIAS("platform:ab5500-btemp");
+MODULE_DESCRIPTION("AB5500 battery temperature driver");
diff --git a/drivers/power/ab5500_charger.c b/drivers/power/ab5500_charger.c
new file mode 100644
index 00000000000..b90c51a4f31
--- /dev/null
+++ b/drivers/power/ab5500_charger.c
@@ -0,0 +1,1820 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2011
+ *
+ * Charger driver for AB5500
+ *
+ * License Terms: GNU General Public License v2
+ * Authors:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@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.h>
+#include <linux/mfd/abx500/ab5500.h>
+#include <linux/mfd/abx500/ab5500-bm.h>
+#include <linux/mfd/abx500/ab5500-gpadc.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/usb/otg.h>
+
+/* Charger constants */
+#define NO_PW_CONN 0
+#define USB_PW_CONN 2
+
+/* HW failure constants */
+#define VBUS_CH_NOK 0x0A
+#define VBUS_OVV_TH 0x06
+
+/* AB5500 Charger constants */
+#define AB5500_USB_LINK_STATUS 0x78
+#define CHARGER_REV_SUP 0x10
+#define SW_EOC 0x40
+#define USB_CHAR_DET 0x02
+#define VBUS_RISING 0x20
+#define VBUS_FALLING 0x40
+#define USB_LINK_UPDATE 0x02
+#define USB_CH_TH_PROT_LOW 0x02
+#define USB_CH_TH_PROT_HIGH 0x01
+#define USB_ID_HOST_DET_ENA_MASK 0x02
+#define USB_ID_HOST_DET_ENA 0x02
+#define USB_ID_DEVICE_DET_ENA_MASK 0x01
+#define USB_ID_DEVICE_DET_ENA 0x01
+#define CHARGER_ISET_IN_1_1A 0x0C
+#define LED_ENABLE 0x01
+#define RESET 0x00
+#define SSW_ENABLE_REBOOT 0x80
+#define SSW_REBOOT_EN 0x40
+#define SSW_CONTROL_AUTOC 0x04
+#define SSW_PSEL_480S 0x00
+
+/* UsbLineStatus register - usb types */
+enum ab5500_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 ab5500_usb_state {
+ AB5500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */
+ AB5500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */
+ AB5500_BM_USB_STATE_CONFIGURED,
+ AB5500_BM_USB_STATE_SUSPEND,
+ AB5500_BM_USB_STATE_RESUME,
+ AB5500_BM_USB_STATE_MAX,
+};
+
+/* VBUS input current limits supported in AB5500 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 to_ab5500_charger_usb_device_info(x) container_of((x), \
+ struct ab5500_charger, usb_chg)
+
+/**
+ * struct ab5500_charger_interrupts - ab5500 interupts
+ * @name: name of the interrupt
+ * @isr function pointer to the isr
+ */
+struct ab5500_charger_interrupts {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct ab5500_charger_info {
+ int charger_connected;
+ int charger_online;
+ int charger_voltage;
+ int cv_active;
+ bool wd_expired;
+};
+
+struct ab5500_charger_event_flags {
+ bool usb_thermal_prot;
+ bool vbus_ovv;
+ bool usbchargernotok;
+ bool vbus_collapse;
+};
+
+struct ab5500_charger_usb_state {
+ bool usb_changed;
+ int usb_current;
+ enum ab5500_usb_state state;
+ spinlock_t usb_lock;
+};
+
+/**
+ * struct ab5500_charger - ab5500 Charger device information
+ * @dev: Pointer to the structure device
+ * @chip_id: Chip-Id of the ab5500
+ * @max_usb_in_curr: Max USB charger input current
+ * @vbus_detected: VBUS detected
+ * @vbus_detected_start:
+ * VBUS detected during startup
+ * @parent: Pointer to the struct ab5500
+ * @gpadc: Pointer to the struct gpadc
+ * @pdata: Pointer to the ab5500_charger platform data
+ * @bat: Pointer to the ab5500_bm platform data
+ * @flags: Structure for information about events triggered
+ * @usb_state: Structure for usb stack information
+ * @usb_chg: USB charger power supply
+ * @ac: Structure that holds the AC charger properties
+ * @usb: Structure that holds the USB charger properties
+ * @charger_wq: Work queue for the IRQs and checking HW state
+ * @check_hw_failure_work: Work for checking HW state
+ * @check_usbchgnotok_work: Work for checking USB charger not ok status
+ * @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
+ * @ otg: pointer to struct otg_transceiver, used to
+ * notify the current during a standard host
+ * charger.
+ * @nb: structture of type notifier_block, which has
+ * a function pointer referenced by usb driver.
+ */
+struct ab5500_charger {
+ struct device *dev;
+ u8 chip_id;
+ int max_usb_in_curr;
+ bool vbus_detected;
+ bool vbus_detected_start;
+ struct ab5500 *parent;
+ struct ab5500_gpadc *gpadc;
+ struct abx500_charger_platform_data *pdata;
+ struct abx500_bm_data *bat;
+ struct ab5500_charger_event_flags flags;
+ struct ab5500_charger_usb_state usb_state;
+ struct ux500_charger usb_chg;
+ struct ab5500_charger_info usb;
+ struct workqueue_struct *charger_wq;
+ struct delayed_work check_hw_failure_work;
+ struct delayed_work check_usbchgnotok_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_usb_thermal_prot_work;
+ struct otg_transceiver *otg;
+ struct notifier_block nb;
+};
+
+/* USB properties */
+static enum power_supply_property ab5500_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_CURRENT_NOW,
+};
+
+/**
+ * ab5500_charger_get_vbus_voltage() - get vbus voltage
+ * @di: pointer to the ab5500_charger structure
+ *
+ * This function returns the vbus voltage.
+ * Returns vbus voltage (on success)
+ */
+static int ab5500_charger_get_vbus_voltage(struct ab5500_charger *di)
+{
+ int vch;
+
+ /* Only measure voltage if the charger is connected */
+ if (di->usb.charger_connected) {
+ vch = ab5500_gpadc_convert(di->gpadc, VBUS_V);
+ if (vch < 0)
+ dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+ } else {
+ vch = 0;
+ }
+ return vch;
+}
+
+/**
+ * ab5500_charger_get_usb_current() - get usb charger current
+ * @di: pointer to the ab5500_charger structure
+ *
+ * This function returns the usb charger current.
+ * Returns usb current (on success) and error code on failure
+ */
+static int ab5500_charger_get_usb_current(struct ab5500_charger *di)
+{
+ int ich;
+
+ /* Only measure current if the charger is online */
+ if (di->usb.charger_online) {
+ ich = ab5500_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;
+}
+
+/**
+ * ab5500_charger_detect_chargers() - Detect the connected chargers
+ * @di: pointer to the ab5500_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
+ * USB_PW_CONN if the USB power supply is connected
+ */
+static int ab5500_charger_detect_chargers(struct ab5500_charger *di)
+{
+ int result = NO_PW_CONN;
+ int ret;
+ u8 val;
+ /* Check for USB charger */
+ /*
+ * TODO: Since there are no status register validating by
+ * reading the IT souce registers
+ */
+ ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_IT,
+ AB5500_IT_SOURCE8, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab5500 read failed\n", __func__);
+ return ret;
+ }
+
+ if (val & VBUS_RISING)
+ result |= USB_PW_CONN;
+ else if (val & VBUS_FALLING)
+ result = NO_PW_CONN;
+
+ return result;
+}
+
+/**
+ * ab5500_charger_max_usb_curr() - get the max curr for the USB type
+ * @di: pointer to the ab5500_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 ab5500_charger_max_usb_curr(struct ab5500_charger *di,
+ enum ab5500_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;
+}
+
+/**
+ * ab5500_charger_read_usb_type() - read the type of usb connected
+ * @di: pointer to the ab5500_charger structure
+ *
+ * Detect the type of the plugged USB
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab5500_charger_read_usb_type(struct ab5500_charger *di)
+{
+ int ret;
+ u8 val;
+
+ ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_USB,
+ AB5500_USB_LINE_STATUS, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab5500 read failed\n", __func__);
+ return ret;
+ }
+
+ /* get the USB type */
+ val = (val & AB5500_USB_LINK_STATUS) >> 3;
+ ret = ab5500_charger_max_usb_curr(di,
+ (enum ab5500_charger_link_status) val);
+
+ return ret;
+}
+
+static int ab5500_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 ab5500
+ * Values taken from the AB5500 product specification manual
+ */
+static int ab5500_charger_current_map[] = {
+ 100 ,
+ 200 ,
+ 300 ,
+ 400 ,
+ 500 ,
+ 600 ,
+ 700 ,
+ 800 ,
+ 900 ,
+ 1000,
+ 1100,
+ 1200,
+ 1300,
+ 1400,
+ 1500,
+ 1500,
+};
+
+static int ab5500_icsr_current_map[] = {
+ 50,
+ 93,
+ 193,
+ 290,
+ 380,
+ 450,
+ 500 ,
+ 600 ,
+ 700 ,
+ 800 ,
+ 900 ,
+ 1000,
+ 1100,
+ 1300,
+ 1400,
+ 1500,
+};
+
+static int ab5500_cvrec_voltage_map[] = {
+ 3300,
+ 3325,
+ 3350,
+ 3375,
+ 3400,
+ 3425,
+ 3450,
+ 3475,
+ 3500,
+ 3525,
+ 3550,
+ 3575,
+ 3600,
+ 3625,
+ 3650,
+ 3675,
+ 3700,
+ 3725,
+ 3750,
+ 3775,
+ 3800,
+ 3825,
+ 3850,
+ 3875,
+ 3900,
+ 3925,
+ 4000,
+ 4025,
+ 4050,
+ 4075,
+ 4100,
+ 4125,
+ 4150,
+ 4175,
+ 4200,
+ 4225,
+ 4250,
+ 4275,
+ 4300,
+ 4325,
+ 4350,
+ 4375,
+ 4400,
+ 4425,
+ 4450,
+ 4475,
+ 4500,
+ 4525,
+ 4550,
+ 4575,
+ 4600,
+};
+
+static int ab5500_cvrec_voltage_to_regval(int voltage)
+{
+ int i;
+
+ /* Special case for voltage below 3.3V */
+ if (voltage < ab5500_cvrec_voltage_map[0])
+ return 0;
+
+ for (i = 1; i < ARRAY_SIZE(ab5500_cvrec_voltage_map); i++) {
+ if (voltage < ab5500_cvrec_voltage_map[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = ARRAY_SIZE(ab5500_cvrec_voltage_map) - 1;
+ if (voltage == ab5500_cvrec_voltage_map[i])
+ return i;
+ else
+ return -1;
+}
+
+static int ab5500_voltage_to_regval(int voltage)
+{
+ int i;
+
+ /* Special case for voltage below 3.3V */
+ if (voltage < ab5500_charger_voltage_map[0])
+ return 0;
+
+ for (i = 1; i < ARRAY_SIZE(ab5500_charger_voltage_map); i++) {
+ if (voltage < ab5500_charger_voltage_map[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = ARRAY_SIZE(ab5500_charger_voltage_map) - 1;
+ if (voltage == ab5500_charger_voltage_map[i])
+ return i;
+ else
+ return -1;
+}
+
+static int ab5500_icsr_curr_to_regval(int curr)
+{
+ int i;
+
+ if (curr < ab5500_icsr_current_map[0])
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ab5500_icsr_current_map); i++) {
+ if (curr < ab5500_icsr_current_map[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = ARRAY_SIZE(ab5500_icsr_current_map) - 1;
+ if (curr == ab5500_icsr_current_map[i])
+ return i;
+ else
+ return -1;
+}
+
+static int ab5500_current_to_regval(int curr)
+{
+ int i;
+
+ if (curr < ab5500_charger_current_map[0])
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ab5500_charger_current_map); i++) {
+ if (curr < ab5500_charger_current_map[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = ARRAY_SIZE(ab5500_charger_current_map) - 1;
+ if (curr == ab5500_charger_current_map[i])
+ return i;
+ else
+ return -1;
+}
+
+/**
+ * ab5500_charger_get_usb_cur() - get usb current
+ * @di: pointer to the ab5500_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 ab5500_charger_get_usb_cur(struct ab5500_charger *di)
+{
+ switch (di->usb_state.usb_current) {
+ case 50:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+ break;
+ 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;
+}
+
+/**
+ * ab5500_charger_set_vbus_in_curr() - set VBUS input current limit
+ * @di: pointer to the ab5500_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 ab5500_charger_set_vbus_in_curr(struct ab5500_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);
+
+ input_curr_index = ab5500_icsr_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, AB5500_BANK_CHG,
+ AB5500_ICSR, input_curr_index);
+ if (ret)
+ dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__);
+
+ return ret;
+}
+
+/**
+ * ab5500_charger_usb_en() - enable usb charging
+ * @di: pointer to the ab5500_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 ab5500_charger_usb_en(struct ux500_charger *charger,
+ int enable, int vset, int ich_out)
+{
+ int ret;
+ int volt_index;
+ int curr_index;
+
+ struct ab5500_charger *di = to_ab5500_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;
+ }
+
+ /* Enable USB charging */
+ dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out);
+
+ volt_index = ab5500_voltage_to_regval(vset);
+ curr_index = ab5500_current_to_regval(ich_out) ;
+
+ /* ChVoltLevel: max voltage upto which battery can be charged */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_VSRC, (u8) volt_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+
+ /* current that can be drawn from the usb */
+ ret = ab5500_charger_set_vbus_in_curr(di, ich_out);
+ if (ret) {
+ dev_err(di->dev, "%s setting icsr failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+
+ /* ChOutputCurentLevel: protected output current */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_OCSRV, (u8) curr_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+
+ /*
+ * Battery voltage when charging should be resumed after
+ * completion of charging
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_CVREC,
+ ab5500_cvrec_voltage_to_regval(
+ di->bat->bat_type[di->bat->batt_id].recharge_vol));
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+ /*
+ * Battery temperature:
+ * Input to the TBDATA register corresponds to the battery
+ * temperature(temp being multiples of 2)
+ * In order to obatain the value to be written to this reg
+ * divide the temperature obtained from gpadc by 2
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_TBDATA,
+ di->bat->temp_now / 2);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+
+ /* If success power on charging LED indication */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_LEDT, LED_ENABLE);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+
+ /*
+ * Register DCIOCURRENT is one among the charging watchdog
+ * rekick sequence, hence irrespective of usb charging this
+ * register will have to be written.
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_DCIOCURRENT,
+ RESET);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+
+ di->usb.charger_online = 1;
+ } else {
+ /* ChVoltLevel: max voltage upto which battery can be charged */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_VSRC, RESET);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+ /* USBChInputCurr: current that can be drawn from the usb */
+ ret = ab5500_charger_set_vbus_in_curr(di, RESET);
+ if (ret) {
+ dev_err(di->dev, "%s resetting icsr failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+ /* If success power off charging LED indication */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_LEDT, RESET);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+ di->usb.charger_online = 0;
+ di->usb.wd_expired = false;
+ dev_dbg(di->dev, "%s Disabled USB charging\n", __func__);
+ }
+ power_supply_changed(&di->usb_chg.psy);
+
+ return ret;
+}
+
+/**
+ * ab5500_charger_watchdog_kick() - kick charger watchdog
+ * @di: pointer to the ab5500_charger structure
+ *
+ * Kick charger watchdog
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab5500_charger_watchdog_kick(struct ux500_charger *charger)
+{
+ int ret;
+ struct ab5500_charger *di;
+ int volt_index, curr_index;
+ u8 value = 0;
+
+ /* TODO: update */
+ if (charger->psy.type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab5500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = abx500_get_register_interruptible(di->dev, AB5500_BANK_STARTUP,
+ AB5500_MCB, &value);
+ if (ret)
+ dev_err(di->dev, "Failed to read!\n");
+
+ value = value | (SSW_ENABLE_REBOOT | SSW_REBOOT_EN |
+ SSW_CONTROL_AUTOC | SSW_PSEL_480S);
+ ret = abx500_set_register_interruptible(di->dev, AB5500_BANK_STARTUP,
+ AB5500_MCB, value);
+ if (ret)
+ dev_err(di->dev, "Failed to kick WD!\n");
+
+ volt_index = ab5500_voltage_to_regval(
+ di->bat->bat_type[di->bat->batt_id].normal_vol_lvl);
+ curr_index = ab5500_current_to_regval(di->max_usb_in_curr);
+
+ /* ChVoltLevel: max voltage upto which battery can be charged */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_VSRC, (u8) volt_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__);
+ return ret;
+ }
+
+ /* current that can be drawn from the usb */
+ ret = ab5500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
+ if (ret) {
+ dev_err(di->dev, "%s setting icsr failed %d\n",
+ __func__, __LINE__);
+ return ret;
+ }
+
+ /* ChOutputCurentLevel: protected output current */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_OCSRV, (u8) curr_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__);
+ return ret;
+ }
+
+ /*
+ * Battery voltage when charging should be resumed after
+ * completion of charging
+ */
+ /* Charger_Vrechar[5:0] = '4.025 V' */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_CVREC,
+ ab5500_cvrec_voltage_to_regval(
+ di->bat->bat_type[di->bat->batt_id].recharge_vol));
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__);
+ return ret;
+ }
+ /*
+ * Battery temperature:
+ * Input to the TBDATA register corresponds to the battery
+ * temperature(temp being multiples of 2)
+ * In order to obatain the value to be written to this reg
+ * divide the temperature obtained from gpadc by 2
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_TBDATA,
+ di->bat->temp_now / 2);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__);
+ return ret;
+ }
+ /*
+ * Register DCIOCURRENT is one among the charging watchdog
+ * rekick sequence, hence irrespective of usb charging this
+ * register will have to be written.
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_DCIOCURRENT,
+ RESET);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__);
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * ab5500_charger_update_charger_current() - update charger current
+ * @di: pointer to the ab5500_charger structure
+ *
+ * Update the charger output current for the specified charger
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab5500_charger_update_charger_current(struct ux500_charger *charger,
+ int ich_out)
+{
+ int ret = 0;
+ int curr_index;
+ struct ab5500_charger *di;
+
+ if (charger->psy.type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab5500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ curr_index = ab5500_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, AB5500_BANK_CHG,
+ AB5500_OCSRV, (u8) curr_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed %d\n", __func__, __LINE__);
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * ab5500_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 ab5500_charger_check_hw_failure_work(struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab5500_charger *di = container_of(work,
+ struct ab5500_charger, check_hw_failure_work.work);
+
+ /* Check if the status bits for HW failure is still active */
+ if (di->flags.vbus_ovv) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_USB, AB5500_USB_PHY_STATUS,
+ &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab5500 read failed\n", __func__);
+ return;
+ }
+ if (!(reg_value & VBUS_OVV_TH)) {
+ di->flags.vbus_ovv = false;
+ power_supply_changed(&di->usb_chg.psy);
+ }
+ }
+ /* If we still have a failure, schedule a new check */
+ if (di->flags.vbus_ovv) {
+ queue_delayed_work(di->charger_wq,
+ &di->check_hw_failure_work, round_jiffies(HZ));
+ }
+}
+
+/**
+ * ab5500_charger_detect_usb_type_work() - work to detect USB type
+ * @work: Pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+void ab5500_charger_detect_usb_type_work(struct work_struct *work)
+{
+ int ret;
+
+ struct ab5500_charger *di = container_of(work,
+ struct ab5500_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 = ab5500_charger_detect_chargers(di);
+ if (ret < 0)
+ return;
+
+ if (!(ret & USB_PW_CONN)) {
+ di->vbus_detected = 0;
+ di->usb.charger_connected = 0;
+ power_supply_changed(&di->usb_chg.psy);
+ } else {
+ di->vbus_detected = 1;
+ }
+}
+
+/**
+ * ab5500_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 ab5500_charger_usb_link_status_work(struct work_struct *work)
+{
+ int ret;
+
+ struct ab5500_charger *di = container_of(work,
+ struct ab5500_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 = ab5500_charger_detect_chargers(di);
+ if (ret < 0)
+ return;
+
+ if (!(ret & USB_PW_CONN)) {
+ di->vbus_detected = 0;
+ di->usb.charger_connected = 0;
+ power_supply_changed(&di->usb_chg.psy);
+ } else {
+ di->vbus_detected = 1;
+ ret = ab5500_charger_read_usb_type(di);
+ if (!ret) {
+ /* Update maximum input current */
+ ret = ab5500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr);
+ if (ret)
+ return;
+
+ di->usb.charger_connected = 1;
+ power_supply_changed(&di->usb_chg.psy);
+ } else if (ret == -ENXIO) {
+ /* No valid charger type detected */
+ di->usb.charger_connected = 0;
+ power_supply_changed(&di->usb_chg.psy);
+ }
+ }
+}
+
+static void ab5500_charger_usb_state_changed_work(struct work_struct *work)
+{
+ int ret;
+ unsigned long flags;
+ struct ab5500_charger *di = container_of(work,
+ struct ab5500_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 AB5500_BM_USB_STATE_RESET_HS:
+ case AB5500_BM_USB_STATE_RESET_FS:
+ case AB5500_BM_USB_STATE_SUSPEND:
+ case AB5500_BM_USB_STATE_MAX:
+ di->usb.charger_connected = 0;
+ power_supply_changed(&di->usb_chg.psy);
+ break;
+
+ case AB5500_BM_USB_STATE_RESUME:
+ /*
+ * when suspend->resume there should be delay
+ * of 1sec for enabling charging
+ */
+ msleep(1000);
+ /* Intentional fall through */
+ case AB5500_BM_USB_STATE_CONFIGURED:
+ /*
+ * USB is configured, enable charging with the charging
+ * input current obtained from USB driver
+ */
+ if (!ab5500_charger_get_usb_cur(di)) {
+ /* Update maximum input current */
+ ret = ab5500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr);
+ if (ret)
+ return;
+
+ di->usb.charger_connected = 1;
+ power_supply_changed(&di->usb_chg.psy);
+ }
+ break;
+
+ default:
+ break;
+ };
+}
+
+/**
+ * ab5500_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 ab5500_charger_check_usbchargernotok_work(struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+ bool prev_status;
+
+ struct ab5500_charger *di = container_of(work,
+ struct ab5500_charger, check_usbchgnotok_work.work);
+
+ /* Check if the status bit for usbchargernotok is still active */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_USB, AB5500_CHGFSM_CHARGER_DETECT, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab5500 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)
+ power_supply_changed(&di->usb_chg.psy);
+}
+
+/**
+ * ab5500_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 ab5500_charger_check_usb_thermal_prot_work(
+ struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab5500_charger *di = container_of(work,
+ struct ab5500_charger, check_usb_thermal_prot_work);
+
+ /* Check if the status bit for usb_thermal_prot is still active */
+ /* TODO: Interrupt source reg 15 bit 4 */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_USB, AB5500_CHGFSM_USB_BTEMP_CURR_LIM, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab5500 read failed\n", __func__);
+ return;
+ }
+ if (reg_value & USB_CH_TH_PROT_LOW || reg_value & USB_CH_TH_PROT_HIGH)
+ di->flags.usb_thermal_prot = true;
+ else
+ di->flags.usb_thermal_prot = false;
+
+ power_supply_changed(&di->usb_chg.psy);
+}
+
+/**
+ * ab5500_charger_vbusdetf_handler() - VBUS falling detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab5500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab5500_charger_vbusdetf_handler(int irq, void *_di)
+{
+ struct ab5500_charger *di = _di;
+
+ dev_dbg(di->dev, "VBUS falling detected\n");
+ queue_work(di->charger_wq, &di->detect_usb_type_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab5500_charger_vbusdetr_handler() - VBUS rising detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab5500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab5500_charger_vbusdetr_handler(int irq, void *_di)
+{
+ struct ab5500_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;
+}
+
+/**
+ * ab5500_charger_usblinkstatus_handler() - USB link status has changed
+ * @irq: interrupt number
+ * @_di: pointer to the ab5500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab5500_charger_usblinkstatus_handler(int irq, void *_di)
+{
+ struct ab5500_charger *di = _di;
+
+ dev_dbg(di->dev, "USB link status changed\n");
+
+ if (!di->usb.charger_online)
+ queue_work(di->charger_wq, &di->usb_link_status_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab5500_charger_usbchthprotr_handler() - Die temp is above usb charger
+ * thermal protection threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab5500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab5500_charger_usbchthprotr_handler(int irq, void *_di)
+{
+ struct ab5500_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;
+}
+
+/**
+ * ab5500_charger_usbchargernotokr_handler() - USB charger not ok detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab5500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab5500_charger_usbchargernotokr_handler(int irq, void *_di)
+{
+ struct ab5500_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;
+}
+
+/**
+ * ab5500_charger_chwdexp_handler() - Charger watchdog expired
+ * @irq: interrupt number
+ * @_di: pointer to the ab5500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab5500_charger_chwdexp_handler(int irq, void *_di)
+{
+ struct ab5500_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->usb.charger_online) {
+ di->usb.wd_expired = true;
+ power_supply_changed(&di->usb_chg.psy);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab5500_charger_vbusovv_handler() - VBUS overvoltage detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab5500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab5500_charger_vbusovv_handler(int irq, void *_di)
+{
+ struct ab5500_charger *di = _di;
+
+ dev_dbg(di->dev, "VBUS overvoltage detected\n");
+ di->flags.vbus_ovv = true;
+ power_supply_changed(&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;
+}
+
+/**
+ * ab5500_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 ab5500_charger_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab5500_charger *di;
+
+ di = to_ab5500_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->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 = ab5500_charger_get_vbus_voltage(di);
+ val->intval = di->usb.charger_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = ab5500_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;
+}
+
+/**
+ * ab5500_charger_hw_registers() - Set up charger related registers
+ * @di: pointer to the ab5500_charger structure
+ *
+ * Set up charger OVV, watchdog and maximum voltage registers as well as
+ * charging of the backup battery
+ */
+static int ab5500_charger_init_hw_registers(struct ab5500_charger *di)
+{
+ int ret = 0;
+
+ /* Enable ID Host and Device detection */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_USB, AB5500_USB_OTG_CTRL,
+ USB_ID_HOST_DET_ENA_MASK, USB_ID_HOST_DET_ENA);
+ if (ret) {
+ dev_err(di->dev, "failed to enable usb charger detection\n");
+ goto out;
+ }
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_USB, AB5500_USB_OTG_CTRL,
+ USB_ID_DEVICE_DET_ENA_MASK, USB_ID_DEVICE_DET_ENA);
+ if (ret) {
+ dev_err(di->dev, "failed to enable usb charger detection\n");
+ goto out;
+ }
+
+ /* Over current protection for reverse supply */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_CREVS, CHARGER_REV_SUP,
+ CHARGER_REV_SUP);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to enable over current protection for reverse supply\n");
+ goto out;
+ }
+
+ /* Enable SW EOC at flatcurrent detection */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_CHG, AB5500_CCTRL, SW_EOC, SW_EOC);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to enable end of charge at flatcurrent detection\n");
+ goto out;
+ }
+out:
+ return ret;
+}
+
+/*
+ * ab5500 charger driver interrupts and their respective isr
+ */
+static struct ab5500_charger_interrupts ab5500_charger_irq[] = {
+ {"VBUS_FALLING", ab5500_charger_vbusdetf_handler},
+ {"VBUS_RISING", ab5500_charger_vbusdetr_handler},
+ {"USB_LINK_UPDATE", ab5500_charger_usblinkstatus_handler},
+ {"USB_CH_TH_PROTECTION", ab5500_charger_usbchthprotr_handler},
+ {"USB_CH_NOT_OK", ab5500_charger_usbchargernotokr_handler},
+ {"OVV", ab5500_charger_vbusovv_handler},
+ /* TODO: Interrupt missing, will be available in cut 2 */
+ /*{"CHG_SW_TIMER_OUT", ab5500_charger_chwdexp_handler},*/
+};
+
+static int ab5500_charger_usb_notifier_call(struct notifier_block *nb,
+ unsigned long event, void *power)
+{
+ struct ab5500_charger *di =
+ container_of(nb, struct ab5500_charger, nb);
+ enum ab5500_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 = AB5500_BM_USB_STATE_RESUME;
+ else if (mA == 0)
+ bm_usb_state = AB5500_BM_USB_STATE_RESET_HS;
+ else if (mA == 2)
+ bm_usb_state = AB5500_BM_USB_STATE_SUSPEND;
+ else if (mA >= 8) /* 8, 100, 500 */
+ bm_usb_state = AB5500_BM_USB_STATE_CONFIGURED;
+ else /* Should never occur */
+ bm_usb_state = AB5500_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;
+ di->usb_state.state = bm_usb_state;
+ di->usb_state.usb_current = mA;
+ spin_unlock(&di->usb_state.usb_lock);
+
+ queue_work(di->charger_wq, &di->usb_state_changed_work);
+
+ return NOTIFY_OK;
+}
+
+#if defined(CONFIG_PM)
+static int ab5500_charger_resume(struct platform_device *pdev)
+{
+ struct ab5500_charger *di = platform_get_drvdata(pdev);
+
+ /* If we still have a HW failure, schedule a new check */
+ if (di->flags.usbchargernotok || di->flags.vbus_ovv) {
+ queue_delayed_work(di->charger_wq,
+ &di->check_hw_failure_work, 0);
+ }
+
+ return 0;
+}
+
+static int ab5500_charger_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ab5500_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 ab5500_charger_suspend NULL
+#define ab5500_charger_resume NULL
+#endif
+
+static int __devexit ab5500_charger_remove(struct platform_device *pdev)
+{
+ struct ab5500_charger *di = platform_get_drvdata(pdev);
+ int i, irq;
+
+ /* Disable USB charging */
+ ab5500_charger_usb_en(&di->usb_chg, false, 0, 0);
+
+ /* Disable interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab5500_charger_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name);
+ free_irq(irq, di);
+ }
+
+ otg_unregister_notifier(di->otg, &di->nb);
+ otg_put_transceiver(di->otg);
+
+ /* Delete the work queue */
+ destroy_workqueue(di->charger_wq);
+
+ flush_scheduled_work();
+ power_supply_unregister(&di->usb_chg.psy);
+ platform_set_drvdata(pdev, NULL);
+ kfree(di);
+
+ return 0;
+}
+
+static int __devinit ab5500_charger_probe(struct platform_device *pdev)
+{
+ int irq, i, charger_status, ret = 0;
+ struct abx500_bm_plat_data *plat_data;
+
+ struct ab5500_charger *di =
+ kzalloc(sizeof(struct ab5500_charger), GFP_KERNEL);
+ if (!di)
+ return -ENOMEM;
+
+ /* get parent data */
+ di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+ di->gpadc = ab5500_gpadc_get("ab5500-adc.0");
+
+ /* initialize lock */
+ spin_lock_init(&di->usb_state.usb_lock);
+
+ plat_data = pdev->dev.platform_data;
+ di->pdata = plat_data->charger;
+ di->bat = plat_data->battery;
+
+ /* get charger specific platform data */
+ if (!di->pdata) {
+ dev_err(di->dev, "no charger platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ /* get battery specific platform data */
+ if (!di->bat) {
+ dev_err(di->dev, "no battery platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+ /* USB supply */
+ /* power_supply base class */
+ di->usb_chg.psy.name = "ab5500_usb";
+ di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB;
+ di->usb_chg.psy.properties = ab5500_charger_usb_props;
+ di->usb_chg.psy.num_properties = ARRAY_SIZE(ab5500_charger_usb_props);
+ di->usb_chg.psy.get_property = ab5500_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 = &ab5500_charger_usb_en;
+ di->usb_chg.ops.kick_wd = &ab5500_charger_watchdog_kick;
+ di->usb_chg.ops.update_curr = &ab5500_charger_update_charger_current;
+ di->usb_chg.max_out_volt = ab5500_charger_voltage_map[
+ ARRAY_SIZE(ab5500_charger_voltage_map) - 1];
+ di->usb_chg.max_out_curr = ab5500_charger_current_map[
+ ARRAY_SIZE(ab5500_charger_current_map) - 1];
+
+
+ /* Create a work queue for the charger */
+ di->charger_wq =
+ create_singlethread_workqueue("ab5500_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,
+ ab5500_charger_check_hw_failure_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&di->check_usbchgnotok_work,
+ ab5500_charger_check_usbchargernotok_work);
+
+ /* Init work for charger detection */
+ INIT_WORK(&di->usb_link_status_work,
+ ab5500_charger_usb_link_status_work);
+ INIT_WORK(&di->detect_usb_type_work,
+ ab5500_charger_detect_usb_type_work);
+
+ INIT_WORK(&di->usb_state_changed_work,
+ ab5500_charger_usb_state_changed_work);
+
+ /* Init work for checking HW status */
+ INIT_WORK(&di->check_usb_thermal_prot_work,
+ ab5500_charger_check_usb_thermal_prot_work);
+
+ /* Get Chip ID of the ABB ASIC */
+ ret = abx500_get_chip_id(di->dev);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to get chip ID\n");
+ goto free_charger_wq;
+ }
+ di->chip_id = ret;
+ dev_dbg(di->dev, "AB5500 CID is: 0x%02x\n", di->chip_id);
+
+ /* Initialize OVV, and other registers */
+ ret = ab5500_charger_init_hw_registers(di);
+ if (ret) {
+ dev_err(di->dev, "failed to initialize ABB registers\n");
+ goto free_device_info;
+ }
+
+ /* 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_device_info;
+ }
+
+ di->otg = otg_get_transceiver();
+ if (!di->otg) {
+ dev_err(di->dev, "failed to get otg transceiver\n");
+ goto free_usb;
+ }
+ di->nb.notifier_call = ab5500_charger_usb_notifier_call;
+ ret = otg_register_notifier(di->otg, &di->nb);
+ if (ret) {
+ dev_err(di->dev, "failed to register otg notifier\n");
+ goto put_otg_transceiver;
+ }
+
+ /* Identify the connected charger types during startup */
+ charger_status = ab5500_charger_detect_chargers(di);
+ 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->usb_link_status_work);
+ }
+
+ /* Register interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab5500_charger_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name);
+ ret = request_threaded_irq(irq, NULL, ab5500_charger_irq[i].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND,
+ ab5500_charger_irq[i].name, di);
+
+ if (ret != 0) {
+ dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+ , ab5500_charger_irq[i].name, irq, ret);
+ goto free_irq;
+ }
+ dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+ ab5500_charger_irq[i].name, irq, ret);
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ dev_info(di->dev, "probe success\n");
+ return ret;
+
+free_irq:
+ otg_unregister_notifier(di->otg, &di->nb);
+
+ /* We also have to free all successfully registered irqs */
+ for (i = i - 1; i >= 0; i--) {
+ irq = platform_get_irq_byname(pdev, ab5500_charger_irq[i].name);
+ free_irq(irq, di);
+ }
+put_otg_transceiver:
+ otg_put_transceiver(di->otg);
+free_usb:
+ power_supply_unregister(&di->usb_chg.psy);
+free_charger_wq:
+ destroy_workqueue(di->charger_wq);
+free_device_info:
+ kfree(di);
+
+ return ret;
+}
+
+static struct platform_driver ab5500_charger_driver = {
+ .probe = ab5500_charger_probe,
+ .remove = __devexit_p(ab5500_charger_remove),
+ .suspend = ab5500_charger_suspend,
+ .resume = ab5500_charger_resume,
+ .driver = {
+ .name = "ab5500-charger",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ab5500_charger_init(void)
+{
+ return platform_driver_register(&ab5500_charger_driver);
+}
+
+static void __exit ab5500_charger_exit(void)
+{
+ platform_driver_unregister(&ab5500_charger_driver);
+}
+
+subsys_initcall_sync(ab5500_charger_init);
+module_exit(ab5500_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
+MODULE_ALIAS("platform:ab5500-charger");
+MODULE_DESCRIPTION("AB5500 charger management driver");
diff --git a/drivers/power/ab5500_fg.c b/drivers/power/ab5500_fg.c
new file mode 100644
index 00000000000..c74d351bd8b
--- /dev/null
+++ b/drivers/power/ab5500_fg.c
@@ -0,0 +1,1954 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2011
+ *
+ * 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
+ * Authors:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@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/slab.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+#include <linux/mfd/abx500/ab5500-gpadc.h>
+#include <linux/mfd/abx500/ab5500-bm.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab5500.h>
+
+static LIST_HEAD(ab5500_fg_list);
+
+/* U5500 Constants */
+#define FG_ON_MASK 0x04
+#define FG_ON 0x04
+#define FG_ACC_RESET_ON_READ_MASK 0x08
+#define FG_ACC_RESET_ON_READ 0x08
+#define EN_READOUT_MASK 0x01
+#define EN_READOUT 0x01
+#define EN_ACC_RESET_ON_READ 0x08
+#define ACC_RESET_ON_READ 0x08
+#define RESET 0x00
+#define EOC_52_mA 0x04
+#define MILLI_TO_MICRO 1000
+#define FG_LSB_IN_MA 770
+#define QLSB_NANO_AMP_HOURS_X100 5353
+#define SEC_TO_SAMPLE(S) (S * 4)
+#define NBR_AVG_SAMPLES 20
+#define LOW_BAT_CHECK_INTERVAL (2 * HZ)
+#define FG_PERIODIC_START_INTERVAL (250 * HZ)/1000 /* 250 msec */
+
+#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */
+
+#define interpolate(x, x1, y1, x2, y2) \
+ ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1))));
+
+#define to_ab5500_fg_device_info(x) container_of((x), \
+ struct ab5500_fg, fg_psy);
+
+/**
+ * struct ab5500_fg_interrupts - ab5500 fg interupts
+ * @name: name of the interrupt
+ * @isr function pointer to the isr
+ */
+struct ab5500_fg_interrupts {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+enum ab5500_fg_discharge_state {
+ AB5500_FG_DISCHARGE_INIT,
+ AB5500_FG_DISCHARGE_INITMEASURING,
+ AB5500_FG_DISCHARGE_INIT_RECOVERY,
+ AB5500_FG_DISCHARGE_RECOVERY,
+ AB5500_FG_DISCHARGE_READOUT,
+ AB5500_FG_DISCHARGE_WAKEUP,
+};
+
+static char *discharge_state[] = {
+ "DISCHARGE_INIT",
+ "DISCHARGE_INITMEASURING",
+ "DISCHARGE_INIT_RECOVERY",
+ "DISCHARGE_RECOVERY",
+ "DISCHARGE_READOUT",
+ "DISCHARGE_WAKEUP",
+};
+
+enum ab5500_fg_charge_state {
+ AB5500_FG_CHARGE_INIT,
+ AB5500_FG_CHARGE_READOUT,
+};
+
+static char *charge_state[] = {
+ "CHARGE_INIT",
+ "CHARGE_READOUT",
+};
+
+enum ab5500_fg_calibration_state {
+ AB5500_FG_CALIB_INIT,
+ AB5500_FG_CALIB_WAIT,
+ AB5500_FG_CALIB_END,
+};
+
+struct ab5500_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 ab5500_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;
+};
+
+struct ab5500_fg_flags {
+ bool fg_enabled;
+ bool conv_done;
+ bool charging;
+ bool fully_charged;
+ bool low_bat_delay;
+ bool low_bat;
+ bool bat_ovv;
+ bool batt_unknown;
+ bool calibrate;
+};
+
+/**
+ * struct ab5500_fg - ab5500 FG device information
+ * @dev: Pointer to the structure device
+ * @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
+ * @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
+ * @v_to_cap: capacity based on battery voltage
+ * @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
+ * @calib_state State during offset calibration
+ * @discharge_state: Current discharge state
+ * @charge_state: Current charge state
+ * @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 ab5500
+ * @gpadc: Pointer to the struct gpadc
+ * @gpadc_auto: Pointer tot he struct adc_auto_input
+ * @pdata: Pointer to the ab5500_fg platform data
+ * @bat: Pointer to the ab5500_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 to reset and re-initialize fuel gauge
+ * @fg_work: Work to run the FG algorithm instantly
+ * @fg_acc_cur_work: Work to read the FG accumulator
+ * @cc_lock: Mutex for locking the CC
+ * @node: struct of type list_head
+ */
+struct ab5500_fg {
+ struct device *dev;
+ int vbat;
+ int vbat_nom;
+ int inst_curr;
+ int avg_curr;
+ int fg_samples;
+ int accu_charge;
+ int recovery_cnt;
+ int high_curr_cnt;
+ int init_cnt;
+ int v_to_cap;
+ bool recovery_needed;
+ bool high_curr_mode;
+ bool init_capacity;
+ enum ab5500_fg_calibration_state calib_state;
+ enum ab5500_fg_discharge_state discharge_state;
+ enum ab5500_fg_charge_state charge_state;
+ struct ab5500_fg_flags flags;
+ struct ab5500_fg_battery_capacity bat_cap;
+ struct ab5500_fg_avg_cap avg_cap;
+ struct ab5500 *parent;
+ struct ab5500_gpadc *gpadc;
+ struct adc_auto_input *gpadc_auto;
+ 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 delayed_work fg_acc_cur_work;
+ struct mutex cc_lock;
+ struct list_head node;
+ struct timer_list avg_current_timer;
+};
+
+/* Main battery properties */
+static enum power_supply_property ab5500_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,
+};
+
+/* Function Prototype */
+static int ab5500_fg_bat_v_trig(int mux);
+
+static int prev_samples, prev_val;
+
+struct ab5500_fg *ab5500_fg_get(void)
+{
+ struct ab5500_fg *di;
+ di = list_first_entry(&ab5500_fg_list, struct ab5500_fg, node);
+
+ return di;
+}
+
+/**
+ * ab5500_fg_is_low_curr() - Low or high current mode
+ * @di: pointer to the ab5500_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 ab5500_fg_is_low_curr(struct ab5500_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;
+}
+
+/**
+ * ab5500_fg_add_cap_sample() - Add capacity to average filter
+ * @di: pointer to the ab5500_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 ab5500_fg_add_cap_sample(struct ab5500_fg *di, int sample)
+{
+ struct timespec ts;
+ struct ab5500_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;
+}
+
+/**
+ * ab5500_fg_clear_cap_samples() - Clear average filter
+ * @di: pointer to the ab5500_fg structure
+ *
+ * The capacity filter is is reset to zero.
+ */
+static void ab5500_fg_clear_cap_samples(struct ab5500_fg *di)
+{
+ int i;
+ struct ab5500_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;
+ }
+}
+
+
+/**
+ * ab5500_fg_fill_cap_sample() - Fill average filter
+ * @di: pointer to the ab5500_fg structure
+ * @sample: the capacity in mAh to fill the filter with
+ *
+ * The capacity filter is filled with a capacity in mAh
+ */
+static void ab5500_fg_fill_cap_sample(struct ab5500_fg *di, int sample)
+{
+ int i;
+ struct timespec ts;
+ struct ab5500_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;
+}
+
+/**
+ * ab5500_fg_coulomb_counter() - enable coulomb counter
+ * @di: pointer to the ab5500_fg structure
+ * @enable: enable/disable
+ *
+ * Enable/Disable coulomb counter.
+ * On failure returns negative value.
+ */
+static int ab5500_fg_coulomb_counter(struct ab5500_fg *di, bool enable)
+{
+ int ret = 0;
+ mutex_lock(&di->cc_lock);
+ if (enable) {
+ /* Power-up the CC */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A,
+ (FG_ON | FG_ACC_RESET_ON_READ));
+ if (ret)
+ goto cc_err;
+
+ di->flags.fg_enabled = true;
+ } else {
+ /* Stop the CC */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A,
+ FG_ON_MASK, RESET);
+ 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;
+}
+
+/**
+ * ab5500_fg_inst_curr() - battery instantaneous current
+ * @di: pointer to the ab5500_fg structure
+ *
+ * Returns battery instantenous current(on success) else error code
+ */
+static int ab5500_fg_inst_curr(struct ab5500_fg *di)
+{
+ u8 low, high;
+ static int val;
+ int ret = 0;
+ bool fg_off = false;
+
+ if (!di->flags.fg_enabled) {
+ fg_off = true;
+ /* Power-up the CC */
+ ab5500_fg_coulomb_counter(di, true);
+ msleep(250);
+ }
+
+ mutex_lock(&di->cc_lock);
+
+ /* Enable read request */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_B,
+ EN_READOUT_MASK, EN_READOUT);
+ if (ret)
+ goto inst_curr_err;
+
+ /* Read CC Sample conversion value Low and high */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FGDIR_READ0, &low);
+ if (ret < 0)
+ goto inst_curr_err;
+
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FGDIR_READ1, &high);
+ if (ret < 0)
+ goto inst_curr_err;
+
+ /*
+ * 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
+ * R(FGSENSE) = 20 mOhm
+ * Scaling of LSB: This corresponds fro R(FGSENSE) to a current of
+ * I = Q/t = 192.7 uC * 4 Hz = 0.77mA
+ */
+ val = (val * 770) / 1000;
+
+ mutex_unlock(&di->cc_lock);
+
+ if (fg_off) {
+ dev_dbg(di->dev, "%s Disable FG\n", __func__);
+ /* Power-off the CC */
+ ab5500_fg_coulomb_counter(di, false);
+ }
+
+ return val;
+
+inst_curr_err:
+ dev_err(di->dev, "%s Get instanst current failed\n", __func__);
+ mutex_unlock(&di->cc_lock);
+ return ret;
+}
+
+static void ab5500_fg_acc_cur_timer_expired(unsigned long data)
+{
+ struct ab5500_fg *di = (struct ab5500_fg *) data;
+ dev_dbg(di->dev, "Avg current timer expired\n");
+
+ /* Trigger execution of the algorithm instantly */
+ queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work, 0);
+}
+
+/**
+ * ab5500_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 ab5500_fg_acc_cur_work(struct work_struct *work)
+{
+ int val, raw_val, sample;
+ int ret;
+ u8 low, med, high, cnt_low, cnt_high;
+
+ struct ab5500_fg *di = container_of(work,
+ struct ab5500_fg, fg_acc_cur_work.work);
+
+ if (!di->flags.fg_enabled) {
+ /* Power-up the CC */
+ ab5500_fg_coulomb_counter(di, true);
+ msleep(250);
+ }
+ mutex_lock(&di->cc_lock);
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_C,
+ EN_READOUT_MASK, EN_READOUT);
+ if (ret < 0)
+ goto exit;
+ /* If charging read charging registers for accumulated values */
+ if (di->flags.charging) {
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A,
+ ACC_RESET_ON_READ, EN_ACC_RESET_ON_READ);
+ if (ret < 0)
+ goto exit;
+ /* Read CC Sample conversion value Low and high */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_CH0, &low);
+ if (ret < 0)
+ goto exit;
+
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_CH1, &med);
+ if (ret < 0)
+ goto exit;
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_CH2, &high);
+ if (ret < 0)
+ goto exit;
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_VAL_COUNT0, &cnt_low);
+ if (ret < 0)
+ goto exit;
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_VAL_COUNT1, &cnt_high);
+ if (ret < 0)
+ goto exit;
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A,
+ ACC_RESET_ON_READ, RESET);
+ if (ret < 0)
+ goto exit;
+ queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work,
+ di->bat->interval_charging * HZ);
+ } else { /* discharging */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A,
+ ACC_RESET_ON_READ, EN_ACC_RESET_ON_READ);
+ if (ret < 0)
+ goto exit;
+ /* Read CC Sample conversion value Low and high */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_DIS_CH0, &low);
+ if (ret < 0)
+ goto exit;
+
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_DIS_CH1, &med);
+ if (ret < 0)
+ goto exit;
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_DIS_CH2, &high);
+ if (ret < 0)
+ goto exit;
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_VAL_COUNT0, &cnt_low);
+ if (ret < 0)
+ goto exit;
+ ret = abx500_get_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC,
+ AB5500_FG_VAL_COUNT1, &cnt_high);
+ if (ret < 0)
+ goto exit;
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A,
+ ACC_RESET_ON_READ, RESET);
+ if (ret < 0)
+ goto exit;
+ queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work,
+ di->bat->interval_not_charging * HZ);
+ }
+ di->fg_samples = (cnt_low | (cnt_high << 8));
+ /*
+ * TODO: Workaround due to the hardware issue that accumulator is not
+ * reset after setting reset_on_read bit and reading the accumulator
+ * Registers.
+ */
+ if (prev_samples > di->fg_samples) {
+ /* overflow has occured */
+ sample = (0xFFFF - prev_samples) + di->fg_samples;
+ } else
+ sample = di->fg_samples - prev_samples;
+ prev_samples = di->fg_samples;
+ di->fg_samples = sample;
+ val = (low | (med << 8) | (high << 16));
+ /*
+ * TODO: Workaround due to the hardware issue that accumulator is not
+ * reset after setting reset_on_read bit and reading the accumulator
+ * Registers.
+ */
+ if (prev_val > val)
+ raw_val = (0xFFFFFF - prev_val) + val;
+ else
+ raw_val = val - prev_val;
+ prev_val = val;
+ val = raw_val;
+
+ if (di->fg_samples) {
+ di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X100)/100000;
+ di->avg_curr = (val * FG_LSB_IN_MA) / (di->fg_samples * 1000);
+ } else
+ dev_err(di->dev,
+ "samples is zero, using previous calculated average current\n");
+ di->flags.conv_done = true;
+ di->calib_state = AB5500_FG_CALIB_END;
+
+ 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);
+}
+
+/**
+ * ab5500_fg_bat_voltage() - get battery voltage
+ * @di: pointer to the ab5500_fg structure
+ *
+ * Returns battery voltage(on success) else error code
+ */
+static int ab5500_fg_bat_voltage(struct ab5500_fg *di)
+{
+ int vbat;
+ static int prev;
+
+ vbat = ab5500_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;
+}
+
+/**
+ * ab5500_fg_volt_to_capacity() - Voltage based capacity
+ * @di: pointer to the ab5500_fg structure
+ * @voltage: The voltage to convert to a capacity
+ *
+ * Returns battery capacity in per mille based on voltage
+ */
+static int ab5500_fg_volt_to_capacity(struct ab5500_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 (di->vbat < tbl[i].voltage && di->vbat > tbl[i+1].voltage)
+ di->v_to_cap = tbl[i].capacity;
+ }
+
+ 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;
+}
+
+/**
+ * ab5500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity
+ * @di: pointer to the ab5500_fg structure
+ *
+ * Returns battery capacity based on battery voltage that is not compensated
+ * for the voltage drop due to the load
+ */
+static int ab5500_fg_uncomp_volt_to_capacity(struct ab5500_fg *di)
+{
+ di->vbat = ab5500_fg_bat_voltage(di);
+ return ab5500_fg_volt_to_capacity(di, di->vbat);
+}
+
+/**
+ * ab5500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity
+ * @di: pointer to the ab5500_fg structure
+ *
+ * Returns battery capacity based on battery voltage that is load compensated
+ * for the voltage drop
+ */
+static int ab5500_fg_load_comp_volt_to_capacity(struct ab5500_fg *di)
+{
+ int vbat_comp;
+
+ di->inst_curr = ab5500_fg_inst_curr(di);
+ di->vbat = ab5500_fg_bat_voltage(di);
+
+ /* Use Ohms law to get the load compensated voltage */
+ vbat_comp = di->vbat - (di->inst_curr *
+ di->bat->bat_type[di->bat->batt_id].battery_resistance) / 1000;
+
+ dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, "
+ "R: %dmOhm, Current: %dmA\n",
+ __func__,
+ di->vbat,
+ vbat_comp,
+ di->bat->bat_type[di->bat->batt_id].battery_resistance,
+ di->inst_curr);
+
+ return ab5500_fg_volt_to_capacity(di, vbat_comp);
+}
+
+/**
+ * ab5500_fg_convert_mah_to_permille() - Capacity in mAh to permille
+ * @di: pointer to the ab5500_fg structure
+ * @cap_mah: capacity in mAh
+ *
+ * Converts capacity in mAh to capacity in permille
+ */
+static int ab5500_fg_convert_mah_to_permille(struct ab5500_fg *di, int cap_mah)
+{
+ return (cap_mah * 1000) / di->bat_cap.max_mah_design;
+}
+
+/**
+ * ab5500_fg_convert_permille_to_mah() - Capacity in permille to mAh
+ * @di: pointer to the ab5500_fg structure
+ * @cap_pm: capacity in permille
+ *
+ * Converts capacity in permille to capacity in mAh
+ */
+static int ab5500_fg_convert_permille_to_mah(struct ab5500_fg *di, int cap_pm)
+{
+ return cap_pm * di->bat_cap.max_mah_design / 1000;
+}
+
+/**
+ * ab5500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh
+ * @di: pointer to the ab5500_fg structure
+ * @cap_mah: capacity in mAh
+ *
+ * Converts capacity in mAh to capacity in uWh
+ */
+static int ab5500_fg_convert_mah_to_uwh(struct ab5500_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;
+}
+
+/**
+ * ab5500_fg_calc_cap_charging() - Calculate remaining capacity while charging
+ * @di: pointer to the ab5500_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 ab5500_fg_calc_cap_charging(struct ab5500_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% as long as the algorithm
+ * reports that it's full.
+ */
+ if (di->bat_cap.mah >= di->bat_cap.max_mah_design ||
+ di->flags.fully_charged)
+ di->bat_cap.mah = di->bat_cap.max_mah_design;
+
+ ab5500_fg_fill_cap_sample(di, di->bat_cap.mah);
+ di->bat_cap.permille =
+ ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+
+ /* We need to update battery voltage and inst current when charging */
+ di->vbat = ab5500_fg_bat_voltage(di);
+ di->inst_curr = ab5500_fg_inst_curr(di);
+
+ return di->bat_cap.mah;
+}
+
+/**
+ * ab5500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage
+ * @di: pointer to the ab5500_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 ab5500_fg_calc_cap_discharge_voltage(struct ab5500_fg *di, bool comp)
+{
+ int permille, mah;
+
+ if (comp)
+ permille = ab5500_fg_load_comp_volt_to_capacity(di);
+ else
+ permille = ab5500_fg_uncomp_volt_to_capacity(di);
+
+ mah = ab5500_fg_convert_permille_to_mah(di, permille);
+
+ di->bat_cap.mah = ab5500_fg_add_cap_sample(di, mah);
+ di->bat_cap.permille =
+ ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+
+ return di->bat_cap.mah;
+}
+
+/**
+ * ab5500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG
+ * @di: pointer to the ab5500_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 ab5500_fg_calc_cap_discharge_fg(struct ab5500_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 = ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+ permille_volt = ab5500_fg_uncomp_volt_to_capacity(di);
+
+ if (permille < permille_volt) {
+ di->bat_cap.permille = permille_volt;
+ di->bat_cap.mah = ab5500_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);
+
+ ab5500_fg_fill_cap_sample(di, di->bat_cap.mah);
+ } else {
+ ab5500_fg_fill_cap_sample(di, di->bat_cap.mah);
+ di->bat_cap.permille =
+ ab5500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+ }
+
+ return di->bat_cap.mah;
+}
+
+/**
+ * ab5500_fg_capacity_level() - Get the battery capacity level
+ * @di: pointer to the ab5500_fg structure
+ *
+ * Get the battery capacity level based on the capacity in percent
+ */
+static int ab5500_fg_capacity_level(struct ab5500_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;
+}
+
+/**
+ * ab5500_fg_check_capacity_limits() - Check if capacity has changed
+ * @di: pointer to the ab5500_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 ab5500_fg_check_capacity_limits(struct ab5500_fg *di, bool init)
+{
+ bool changed = false;
+
+ di->bat_cap.level = ab5500_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->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);
+
+}
+
+static void ab5500_fg_charge_state_to(struct ab5500_fg *di,
+ enum ab5500_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 ab5500_fg_discharge_state_to(struct ab5500_fg *di,
+ enum ab5500_fg_charge_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;
+}
+
+/**
+ * ab5500_fg_algorithm_charging() - FG algorithm for when charging
+ * @di: pointer to the ab5500_fg structure
+ *
+ * Battery capacity calculation state machine for when we're charging
+ */
+static void ab5500_fg_algorithm_charging(struct ab5500_fg *di)
+{
+ /*
+ * If we change to discharge mode
+ * we should start with recovery
+ */
+ if (di->discharge_state != AB5500_FG_DISCHARGE_INIT_RECOVERY)
+ ab5500_fg_discharge_state_to(di,
+ AB5500_FG_DISCHARGE_INIT_RECOVERY);
+
+ switch (di->charge_state) {
+ case AB5500_FG_CHARGE_INIT:
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bat->fg_params->accu_charging);
+
+ ab5500_fg_coulomb_counter(di, true);
+ ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_READOUT);
+
+ break;
+
+ case AB5500_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);
+
+ ab5500_fg_calc_cap_charging(di);
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* Check capacity limits */
+ ab5500_fg_check_capacity_limits(di, false);
+}
+
+/**
+ * ab5500_fg_algorithm_discharging() - FG algorithm for when discharging
+ * @di: pointer to the ab5500_fg structure
+ *
+ * Battery capacity calculation state machine for when we're discharging
+ */
+static void ab5500_fg_algorithm_discharging(struct ab5500_fg *di)
+{
+ int sleep_time;
+
+ /* If we change to charge mode we should start with init */
+ if (di->charge_state != AB5500_FG_CHARGE_INIT)
+ ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_INIT);
+
+ switch (di->discharge_state) {
+ case AB5500_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);
+ ab5500_fg_coulomb_counter(di, true);
+ ab5500_fg_discharge_state_to(di,
+ AB5500_FG_DISCHARGE_INITMEASURING);
+
+ /* Intentional fallthrough */
+ case AB5500_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) {
+
+ ab5500_fg_calc_cap_discharge_voltage(di, true);
+
+ ab5500_fg_check_capacity_limits(di, true);
+ }
+
+ di->init_cnt += sleep_time;
+ if (di->init_cnt >
+ di->bat->fg_params->init_total_time) {
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bat->fg_params->accu_high_curr);
+
+ ab5500_fg_coulomb_counter(di, true);
+ ab5500_fg_discharge_state_to(di,
+ AB5500_FG_DISCHARGE_READOUT);
+ }
+
+ break;
+
+ case AB5500_FG_DISCHARGE_INIT_RECOVERY:
+ di->recovery_cnt = 0;
+ di->recovery_needed = true;
+ ab5500_fg_discharge_state_to(di,
+ AB5500_FG_DISCHARGE_RECOVERY);
+
+ /* Intentional fallthrough */
+
+ case AB5500_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 = ab5500_fg_inst_curr(di);
+
+ if (ab5500_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);
+ ab5500_fg_coulomb_counter(di, true);
+ ab5500_fg_discharge_state_to(di,
+ AB5500_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);
+ ab5500_fg_coulomb_counter(di, true);
+ ab5500_fg_discharge_state_to(di,
+ AB5500_FG_DISCHARGE_READOUT);
+ }
+
+ break;
+
+ case AB5500_FG_DISCHARGE_READOUT:
+ di->inst_curr = ab5500_fg_inst_curr(di);
+
+ if (ab5500_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) {
+ ab5500_fg_discharge_state_to(di,
+ AB5500_FG_DISCHARGE_RECOVERY);
+
+ queue_delayed_work(di->fg_wq,
+ &di->fg_periodic_work,
+ 0);
+
+ break;
+ }
+
+ ab5500_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;
+
+ ab5500_fg_calc_cap_discharge_fg(di);
+ }
+
+ ab5500_fg_check_capacity_limits(di, false);
+
+ break;
+
+ case AB5500_FG_DISCHARGE_WAKEUP:
+ ab5500_fg_coulomb_counter(di, true);
+ di->inst_curr = ab5500_fg_inst_curr(di);
+
+ ab5500_fg_calc_cap_discharge_voltage(di, true);
+
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bat->fg_params->accu_high_curr);
+ /* Re-program number of samples set above */
+ ab5500_fg_coulomb_counter(di, true);
+ ab5500_fg_discharge_state_to(di, AB5500_FG_DISCHARGE_READOUT);
+
+ ab5500_fg_check_capacity_limits(di, false);
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * ab5500_fg_algorithm_calibrate() - Internal columb counter offset calibration
+ * @di: pointer to the ab5500_fg structure
+ *
+ */
+static void ab5500_fg_algorithm_calibrate(struct ab5500_fg *di)
+{
+ int ret;
+
+ switch (di->calib_state) {
+ case AB5500_FG_CALIB_INIT:
+ dev_dbg(di->dev, "Calibration ongoing...\n");
+ /* TODO: For Cut 1.1 no calibration */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_CONTROL_A,
+ FG_ACC_RESET_ON_READ_MASK, FG_ACC_RESET_ON_READ);
+ if (ret)
+ goto err;
+ di->calib_state = AB5500_FG_CALIB_WAIT;
+ break;
+ case AB5500_FG_CALIB_END:
+ di->flags.calibrate = false;
+ dev_dbg(di->dev, "Calibration done...\n");
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ break;
+ case AB5500_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 = AB5500_FG_CALIB_INIT;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+}
+
+/**
+ * ab5500_fg_algorithm() - Entry point for the FG algorithm
+ * @di: pointer to the ab5500_fg structure
+ *
+ * Entry point for the battery capacity calculation state machine
+ */
+static void ab5500_fg_algorithm(struct ab5500_fg *di)
+{
+ if (di->flags.calibrate)
+ ab5500_fg_algorithm_calibrate(di);
+ else {
+ if (di->flags.charging)
+ ab5500_fg_algorithm_charging(di);
+ else
+ ab5500_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);
+}
+
+/**
+ * ab5500_fg_periodic_work() - Run the FG state machine periodically
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for periodic work
+ */
+static void ab5500_fg_periodic_work(struct work_struct *work)
+{
+ struct ab5500_fg *di = container_of(work, struct ab5500_fg,
+ fg_periodic_work.work);
+
+ if (di->init_capacity) {
+ /* A dummy read that will return 0 */
+ di->inst_curr = ab5500_fg_inst_curr(di);
+ /* Get an initial capacity calculation */
+ ab5500_fg_calc_cap_discharge_voltage(di, true);
+ ab5500_fg_check_capacity_limits(di, true);
+ di->init_capacity = false;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ } else
+ ab5500_fg_algorithm(di);
+}
+
+/**
+ * ab5500_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 ab5500_fg_low_bat_work(struct work_struct *work)
+{
+ int vbat;
+
+ struct ab5500_fg *di = container_of(work, struct ab5500_fg,
+ fg_low_bat_work.work);
+
+ vbat = ab5500_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));
+ power_supply_changed(&di->fg_psy);
+ } else {
+ di->flags.low_bat = false;
+ dev_warn(di->dev, "Battery voltage OK again\n");
+ power_supply_changed(&di->fg_psy);
+ }
+
+ /* This is needed to dispatch LOW_BAT */
+ ab5500_fg_check_capacity_limits(di, false);
+
+ /* Set this flag to check if LOW_BAT IRQ still occurs */
+ di->flags.low_bat_delay = false;
+}
+
+/**
+ * ab5500_fg_instant_work() - Run the FG state machine instantly
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for instant work
+ */
+static void ab5500_fg_instant_work(struct work_struct *work)
+{
+ struct ab5500_fg *di = container_of(work, struct ab5500_fg, fg_work);
+
+ ab5500_fg_algorithm(di);
+}
+
+/**
+ * ab5500_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 ab5500_fg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab5500_fg *di;
+
+ di = to_ab5500_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 = 47500000;
+ else {
+ di->vbat = ab5500_gpadc_convert
+ (di->gpadc, MAIN_BAT_V);
+ val->intval = di->vbat * 1000;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ di->inst_curr = ab5500_fg_inst_curr(di);
+ 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 = ab5500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.max_mah_design);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ val->intval = ab5500_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)
+ val->intval = ab5500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.max_mah);
+ else
+ val->intval = ab5500_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)
+ 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)
+ 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)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+ else
+ val->intval = di->bat_cap.prev_level;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ab5500_fg_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext;
+ struct ab5500_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_ab5500_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;
+ /* 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 (ret.intval)
+ di->flags.batt_unknown = false;
+ else
+ di->flags.batt_unknown = true;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ab5500_fg_init_hw_registers() - Set up FG related registers
+ * @di: pointer to the ab5500_fg structure
+ *
+ * Set up battery OVV, low battery voltage registers
+ */
+static int ab5500_fg_init_hw_registers(struct ab5500_fg *di)
+{
+ int ret;
+ struct adc_auto_input *auto_ip;
+
+ auto_ip = kzalloc(sizeof(struct adc_auto_input), GFP_KERNEL);
+ if (!auto_ip) {
+ dev_err(di->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ auto_ip->mux = MAIN_BAT_V;
+ auto_ip->freq = MS500;
+ auto_ip->min = di->bat->fg_params->lowbat_threshold;
+ auto_ip->max = di->bat->fg_params->overbat_threshold;
+ auto_ip->auto_adc_callback = ab5500_fg_bat_v_trig;
+ di->gpadc_auto = auto_ip;
+ ret = ab5500_gpadc_convert_auto(di->gpadc, di->gpadc_auto);
+ if (ret)
+ dev_err(di->dev,
+ "failed to set auto trigger for battery votlage\n");
+ /* set End Of Charge current to 247mA */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB5500_BANK_FG_BATTCOM_ACC, AB5500_FG_EOC, EOC_52_mA);
+ return ret;
+}
+
+static int ab5500_fg_bat_v_trig(int mux)
+{
+ struct ab5500_fg *di = ab5500_fg_get();
+
+ di->vbat = ab5500_gpadc_convert(di->gpadc, MAIN_BAT_V);
+
+ /* check if the battery voltage is below low threshold */
+ if (di->vbat < di->bat->fg_params->lowbat_threshold) {
+ 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));
+ power_supply_changed(&di->fg_psy);
+ }
+ /* check if battery votlage is above OVV */
+ else if (di->vbat > di->bat->fg_params->overbat_threshold) {
+ dev_warn(di->dev, "Battery OVV\n");
+ di->flags.bat_ovv = true;
+
+ power_supply_changed(&di->fg_psy);
+ } else
+ dev_err(di->dev,
+ "Invalid gpadc auto trigger for battery voltage\n");
+
+ kfree(di->gpadc_auto);
+ ab5500_fg_init_hw_registers(di);
+ return 0;
+}
+
+/**
+ * ab5500_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 ab5500_fg_external_power_changed(struct power_supply *psy)
+{
+ struct ab5500_fg *di = to_ab5500_fg_device_info(psy);
+
+ class_for_each_device(power_supply_class, NULL,
+ &di->fg_psy, ab5500_fg_get_ext_psy_data);
+}
+
+/**
+ * abab5500_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 ab5500_fg_reinit_work(struct work_struct *work)
+{
+ struct ab5500_fg *di = container_of(work, struct ab5500_fg,
+ fg_reinit_work.work);
+
+ if (di->flags.calibrate == false) {
+ dev_dbg(di->dev, "Resetting FG state machine to init.\n");
+ ab5500_fg_clear_cap_samples(di);
+ ab5500_fg_calc_cap_discharge_voltage(di, true);
+ ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_INIT);
+ ab5500_fg_discharge_state_to(di, AB5500_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));
+ }
+}
+
+/**
+ * ab5500_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 ab5500_fg_reinit(void)
+{
+ struct ab5500_fg *di = ab5500_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);
+}
+
+#if defined(CONFIG_PM)
+static int ab5500_fg_resume(struct platform_device *pdev)
+{
+ struct ab5500_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) {
+ ab5500_fg_discharge_state_to(di, AB5500_FG_DISCHARGE_WAKEUP);
+ queue_work(di->fg_wq, &di->fg_work);
+ }
+
+ return 0;
+}
+
+static int ab5500_fg_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ab5500_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)
+ ab5500_fg_coulomb_counter(di, false);
+
+ return 0;
+}
+#else
+#define ab5500_fg_suspend NULL
+#define ab5500_fg_resume NULL
+#endif
+
+static int __devexit ab5500_fg_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct ab5500_fg *di = platform_get_drvdata(pdev);
+
+ /* Disable coulomb counter */
+ ret = ab5500_fg_coulomb_counter(di, false);
+ if (ret)
+ dev_err(di->dev, "failed to disable coulomb counter\n");
+
+ destroy_workqueue(di->fg_wq);
+
+ flush_scheduled_work();
+ power_supply_unregister(&di->fg_psy);
+ platform_set_drvdata(pdev, NULL);
+ kfree(di->gpadc_auto);
+ kfree(di);
+ return ret;
+}
+
+static int __devinit ab5500_fg_probe(struct platform_device *pdev)
+{
+ struct abx500_bm_plat_data *plat_data;
+ int ret = 0;
+
+ struct ab5500_fg *di =
+ kzalloc(sizeof(struct ab5500_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 = ab5500_gpadc_get("ab5500-adc.0");
+
+ plat_data = pdev->dev.platform_data;
+ di->pdata = plat_data->fg;
+ di->bat = plat_data->battery;
+
+ /* get fg specific platform data */
+ if (!di->pdata) {
+ dev_err(di->dev, "no fg platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ /* get battery specific platform data */
+ if (!di->bat) {
+ dev_err(di->dev, "no battery platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+ /* powerup fg to start sampling */
+ ab5500_fg_coulomb_counter(di, true);
+
+ di->fg_psy.name = "ab5500_fg";
+ di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->fg_psy.properties = ab5500_fg_props;
+ di->fg_psy.num_properties = ARRAY_SIZE(ab5500_fg_props);
+ di->fg_psy.get_property = ab5500_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 = ab5500_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;
+
+ ab5500_fg_charge_state_to(di, AB5500_FG_CHARGE_INIT);
+ ab5500_fg_discharge_state_to(di, AB5500_FG_DISCHARGE_INIT);
+
+ /* Create a work queue for running the FG algorithm */
+ di->fg_wq = create_singlethread_workqueue("ab5500_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, ab5500_fg_instant_work);
+
+ /* Init work for getting the battery accumulated current */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->fg_acc_cur_work,
+ ab5500_fg_acc_cur_work);
+
+ /* Init work for reinitialising the fg algorithm */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->fg_reinit_work,
+ ab5500_fg_reinit_work);
+
+ /* Work delayed Queue to run the state machine */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work,
+ ab5500_fg_periodic_work);
+
+ /* Work to check low battery condition */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->fg_low_bat_work,
+ ab5500_fg_low_bat_work);
+
+ list_add_tail(&di->node, &ab5500_fg_list);
+
+ /* Consider battery unknown until we're informed otherwise */
+ di->flags.batt_unknown = true;
+
+ /* 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_fg_wq;
+ }
+
+ /* Initialize OVV, and other registers */
+ ret = ab5500_fg_init_hw_registers(di);
+ if (ret) {
+ dev_err(di->dev, "failed to initialize registers\n");
+ goto pow_unreg;
+ }
+
+ di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer);
+
+ /* Initilialize avg current timer */
+ init_timer(&di->avg_current_timer);
+ di->avg_current_timer.function = ab5500_fg_acc_cur_timer_expired;
+ di->avg_current_timer.data = (unsigned long) di;
+ di->avg_current_timer.expires = 60 * HZ;
+ if (!timer_pending(&di->avg_current_timer))
+ add_timer(&di->avg_current_timer);
+ else
+ mod_timer(&di->avg_current_timer, 60 * HZ);
+
+ platform_set_drvdata(pdev, di);
+
+ /* Calibrate the fg first time */
+ di->flags.calibrate = true;
+ di->calib_state = AB5500_FG_CALIB_INIT;
+ /* Run the FG algorithm */
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work,
+ FG_PERIODIC_START_INTERVAL);
+ queue_delayed_work(di->fg_wq, &di->fg_acc_cur_work,
+ FG_PERIODIC_START_INTERVAL);
+
+ dev_info(di->dev, "probe success\n");
+ return ret;
+
+pow_unreg:
+ power_supply_unregister(&di->fg_psy);
+free_fg_wq:
+ destroy_workqueue(di->fg_wq);
+free_device_info:
+ kfree(di);
+
+ return ret;
+}
+
+static struct platform_driver ab5500_fg_driver = {
+ .probe = ab5500_fg_probe,
+ .remove = __devexit_p(ab5500_fg_remove),
+ .suspend = ab5500_fg_suspend,
+ .resume = ab5500_fg_resume,
+ .driver = {
+ .name = "ab5500-fg",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ab5500_fg_init(void)
+{
+ return platform_driver_register(&ab5500_fg_driver);
+}
+
+static void __exit ab5500_fg_exit(void)
+{
+ platform_driver_unregister(&ab5500_fg_driver);
+}
+
+subsys_initcall_sync(ab5500_fg_init);
+module_exit(ab5500_fg_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
+MODULE_ALIAS("platform:ab5500-fg");
+MODULE_DESCRIPTION("AB5500 Fuel Gauge driver");
diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c
index d8bb99394ac..5e5700cf24a 100644
--- a/drivers/power/ab8500_btemp.c
+++ b/drivers/power/ab8500_btemp.c
@@ -83,6 +83,7 @@ struct ab8500_btemp_ranges {
* @btemp_ranges: Battery temperature range structure
* @btemp_wq: Work queue for measuring the temperature periodically
* @btemp_periodic_work: Work for measuring the temperature periodically
+ * @initialized: True if battery id read.
*/
struct ab8500_btemp {
struct device *dev;
@@ -100,6 +101,7 @@ struct ab8500_btemp {
struct ab8500_btemp_ranges btemp_ranges;
struct workqueue_struct *btemp_wq;
struct delayed_work btemp_periodic_work;
+ bool initialized;
};
/* BTEMP power supply properties */
@@ -569,6 +571,13 @@ static void ab8500_btemp_periodic_work(struct work_struct *work)
struct ab8500_btemp *di = container_of(work,
struct ab8500_btemp, btemp_periodic_work.work);
+ if (!di->initialized) {
+ di->initialized = true;
+ /* Identify the battery */
+ if (ab8500_btemp_id(di) < 0)
+ dev_warn(di->dev, "failed to identify the battery\n");
+ }
+
di->bat_temp = ab8500_btemp_measure_temp(di);
if (di->bat_temp != di->prev_bat_temp) {
@@ -964,7 +973,7 @@ 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_platform_data *plat_data;
struct ab8500_btemp *di =
kzalloc(sizeof(struct ab8500_btemp), GFP_KERNEL);
@@ -976,8 +985,10 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev)
di->parent = dev_get_drvdata(pdev->dev.parent);
di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+ di->initialized = false;
+
/* get btemp specific platform data */
- plat_data = pdev->dev.platform_data;
+ plat_data = dev_get_platdata(di->parent->dev);
di->pdata = plat_data->btemp;
if (!di->pdata) {
dev_err(di->dev, "no btemp platform data supplied\n");
@@ -1017,10 +1028,6 @@ static int __devinit ab8500_btemp_probe(struct platform_device *pdev)
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;
diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c
index e2b4accbec8..19f62729a0a 100644
--- a/drivers/power/ab8500_charger.c
+++ b/drivers/power/ab8500_charger.c
@@ -29,6 +29,7 @@
#include <linux/mfd/abx500/ab8500-gpadc.h>
#include <linux/mfd/abx500/ux500_chargalg.h>
#include <linux/usb/otg.h>
+#include <asm/mach-types.h>
/* Charger constants */
#define NO_PW_CONN 0
@@ -77,6 +78,9 @@
/* Lowest charger voltage is 3.39V -> 0x4E */
#define LOW_VOLT_REG 0x4E
+/* Step up/down delay in us */
+#define STEP_UDELAY 1000
+
/* UsbLineStatus register - usb types */
enum ab8500_charger_link_status {
USB_STAT_NOT_CONFIGURED,
@@ -934,6 +938,88 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
}
/**
+ * ab8500_charger_set_current() - set charger current
+ * @di: pointer to the ab8500_charger structure
+ * @ich: charger current, in mA
+ * @reg: select what charger register to set
+ *
+ * Set charger current.
+ * There is no state machine in the AB to step up/down the charger
+ * current to avoid dips and spikes on MAIN, VBUS and VBAT when
+ * charging is started. Instead we need to implement
+ * this charger current step-up/down here.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_current(struct ab8500_charger *di,
+ int ich, int reg)
+{
+ int ret, i;
+ int curr_index, prev_curr_index, shift_value;
+ u8 reg_value;
+
+ switch (reg) {
+ case AB8500_MCH_IPT_CURLVL_REG:
+ shift_value = MAIN_CH_INPUT_CURR_SHIFT;
+ curr_index = ab8500_current_to_regval(ich);
+ break;
+ case AB8500_USBCH_IPT_CRNTLVL_REG:
+ shift_value = VBUS_IN_CURR_LIM_SHIFT;
+ curr_index = ab8500_vbus_in_curr_to_regval(ich);
+ break;
+ case AB8500_CH_OPT_CRNTLVL_REG:
+ shift_value = 0;
+ curr_index = ab8500_current_to_regval(ich);
+ break;
+ default:
+ dev_err(di->dev, "%s current register not valid\n", __func__);
+ return -ENXIO;
+ }
+
+ if (curr_index < 0) {
+ dev_err(di->dev, "requested current limit out-of-range\n");
+ return -ENXIO;
+ }
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ reg, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s read failed\n", __func__);
+ return ret;
+ }
+ prev_curr_index = (reg_value >> shift_value);
+
+ /* only update current if it's been changed */
+ if (prev_curr_index == curr_index)
+ return 0;
+
+ dev_dbg(di->dev, "%s set charger current: %d mA for reg: 0x%02x\n",
+ __func__, ich, reg);
+
+ if (prev_curr_index > curr_index) {
+ for (i = prev_curr_index - 1; i >= curr_index; i--) {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, reg, (u8) i << shift_value);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ usleep_range(STEP_UDELAY, STEP_UDELAY * 2);
+ }
+ } else {
+ for (i = prev_curr_index + 1; i <= curr_index; i++) {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, reg, (u8) i << shift_value);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ usleep_range(STEP_UDELAY, STEP_UDELAY * 2);
+ }
+ }
+ return ret;
+}
+
+/**
* ab8500_charger_set_vbus_in_curr() - set VBUS input current limit
* @di: pointer to the ab8500_charger structure
* @ich_in: charger input current limit
@@ -944,8 +1030,6 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
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 */
@@ -964,19 +1048,38 @@ static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
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;
- }
+ return ab8500_charger_set_current(di, min_value,
+ AB8500_USBCH_IPT_CRNTLVL_REG);
+}
- 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__);
+/**
+ * ab8500_charger_set_main_in_curr() - set main charger input current
+ * @di: pointer to the ab8500_charger structure
+ * @ich_in: input charger current, in mA
+ *
+ * Set main charger input current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_main_in_curr(struct ab8500_charger *di,
+ int ich_in)
+{
+ return ab8500_charger_set_current(di, ich_in,
+ AB8500_MCH_IPT_CURLVL_REG);
+}
- return ret;
+/**
+ * ab8500_charger_set_output_curr() - set charger output current
+ * @di: pointer to the ab8500_charger structure
+ * @ich_out: output charger current, in mA
+ *
+ * Set charger output current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_output_curr(struct ab8500_charger *di,
+ int ich_out)
+{
+ return ab8500_charger_set_current(di, ich_out,
+ AB8500_CH_OPT_CRNTLVL_REG);
}
/**
@@ -1088,18 +1191,19 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
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);
+ ret = ab8500_charger_set_main_in_curr(di,
+ di->bat->chg_params->ac_curr_max);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s Failed to set MainChInputCurr\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);
+ ret = ab8500_charger_set_output_curr(di, iset);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
@@ -1156,12 +1260,11 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger,
return ret;
}
- ret = abx500_set_register_interruptible(di->dev,
- AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1);
+ ret = ab8500_charger_set_output_curr(di, 0);
if (ret) {
- dev_err(di->dev,
- "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
} else {
@@ -1264,10 +1367,11 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger,
return ret;
}
/* ChOutputCurentLevel: protected output current */
- ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
- AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+ ret = ab8500_charger_set_output_curr(di, ich_out);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
/* Check if VBAT overshoot control should be enabled */
@@ -1364,7 +1468,6 @@ 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)
@@ -1374,18 +1477,11 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *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);
+ ret = ab8500_charger_set_output_curr(di, ich_out);
if (ret) {
- dev_err(di->dev, "%s write failed\n", __func__);
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
return ret;
}
@@ -2354,11 +2450,18 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
}
/* 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 (machine_is_snowball())
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_BACKUP_CHG_REG,
+ BUP_VCH_SEL_3P1V |
+ BUP_ICH_SEL_150UA);
+ else
+ 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;
@@ -2534,7 +2637,7 @@ static int __devexit ab8500_charger_remove(struct platform_device *pdev)
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_platform_data *plat_data;
struct ab8500_charger *di =
kzalloc(sizeof(struct ab8500_charger), GFP_KERNEL);
@@ -2550,7 +2653,8 @@ static int __devinit ab8500_charger_probe(struct platform_device *pdev)
spin_lock_init(&di->usb_state.usb_lock);
/* get charger specific platform data */
- plat_data = pdev->dev.platform_data;
+ plat_data = dev_get_platdata(di->parent->dev);
+
di->pdata = plat_data->charger;
if (!di->pdata) {
diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c
index c22f2f05657..798f5f7cef4 100644
--- a/drivers/power/ab8500_fg.c
+++ b/drivers/power/ab8500_fg.c
@@ -485,8 +485,9 @@ static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable)
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);
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ (RESET_ACCU | READ_REQ), 0);
if (ret)
goto cc_err;
@@ -1404,8 +1405,7 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
sleep_time = di->bat->fg_params->init_timer;
/* Discard the first [x] seconds */
- if (di->init_cnt >
- di->bat->fg_params->init_discard_time) {
+ 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);
@@ -2446,7 +2446,7 @@ 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_platform_data *plat_data;
struct ab8500_fg *di =
kzalloc(sizeof(struct ab8500_fg), GFP_KERNEL);
@@ -2461,7 +2461,7 @@ static int __devinit ab8500_fg_probe(struct platform_device *pdev)
di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
/* get fg specific platform data */
- plat_data = pdev->dev.platform_data;
+ plat_data = dev_get_platdata(di->parent->dev);
di->pdata = plat_data->fg;
if (!di->pdata) {
dev_err(di->dev, "no fg platform data supplied\n");
diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c
index 804b88c760d..032b27d35bf 100644
--- a/drivers/power/abx500_chargalg.c
+++ b/drivers/power/abx500_chargalg.c
@@ -220,6 +220,7 @@ enum maxim_ret {
*/
struct abx500_chargalg {
struct device *dev;
+ struct ab8500 *parent;
int charge_status;
int eoc_cnt;
int rch_cnt;
@@ -1802,7 +1803,7 @@ static int __devexit abx500_chargalg_remove(struct platform_device *pdev)
static int __devinit abx500_chargalg_probe(struct platform_device *pdev)
{
- struct abx500_bm_plat_data *plat_data;
+ struct ab8500_platform_data *plat_data;
int ret = 0;
struct abx500_chargalg *di =
@@ -1812,8 +1813,8 @@ static int __devinit abx500_chargalg_probe(struct platform_device *pdev)
/* get device struct */
di->dev = &pdev->dev;
-
- plat_data = pdev->dev.platform_data;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+ plat_data = dev_get_platdata(di->parent->dev);
di->pdata = plat_data->chargalg;
di->bat = plat_data->battery;