Commit 919c8401 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'for-v3.4-rc1' of git://git.infradead.org/battery-2.6

Pull battery updates from Anton Vorontsov:
 "Various small bugfixes and enhancements, plus two new drivers:
   - A quite complex ab8500 charger driver, submitted by Arun Murthy @
     ST-Ericsson;
   - Summit Microelectronics SMB347 Battery Charger, submitted by Bruce
     E Robertson and Alan Cox @ Intel.

  And that's all."

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