aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/power
diff options
context:
space:
mode:
authorroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
committerroot <root@artemis.panaceas.org>2015-12-25 04:40:36 +0000
commit849369d6c66d3054688672f97d31fceb8e8230fb (patch)
tree6135abc790ca67dedbe07c39806591e70eda81ce /drivers/power
downloadlinux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.gz
linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.tar.bz2
linux-3.0.35-kobo-849369d6c66d3054688672f97d31fceb8e8230fb.zip
initial_commit
Diffstat (limited to 'drivers/power')
-rwxr-xr-xdrivers/power/Kconfig260
-rwxr-xr-xdrivers/power/Makefile41
-rw-r--r--drivers/power/apm_power.c375
-rw-r--r--drivers/power/bq20z75.c709
-rw-r--r--drivers/power/bq27x00_battery.c862
-rw-r--r--drivers/power/collie_battery.c417
-rw-r--r--drivers/power/da9030_battery.c606
-rw-r--r--drivers/power/da9052-battery.c844
-rw-r--r--drivers/power/ds2760_battery.c657
-rw-r--r--drivers/power/ds2780_battery.c869
-rw-r--r--drivers/power/ds2782_battery.c421
-rw-r--r--drivers/power/gpio-charger.c203
-rw-r--r--drivers/power/intel_mid_battery.c797
-rw-r--r--drivers/power/isp1704_charger.c512
-rw-r--r--drivers/power/jz4740-battery.c459
-rw-r--r--drivers/power/max17040_battery.c308
-rw-r--r--drivers/power/max17042_battery.c239
-rwxr-xr-xdrivers/power/max8903_battery.c748
-rw-r--r--drivers/power/max8903_charger.c506
-rw-r--r--drivers/power/max8925_power.c529
-rw-r--r--drivers/power/olpc_battery.c618
-rw-r--r--drivers/power/pcf50633-charger.c492
-rw-r--r--drivers/power/pda_power.c515
-rw-r--r--drivers/power/pmu_battery.c216
-rw-r--r--drivers/power/power_supply.h38
-rw-r--r--drivers/power/power_supply_core.c288
-rw-r--r--drivers/power/power_supply_leds.c179
-rw-r--r--drivers/power/power_supply_sysfs.c305
-rwxr-xr-xdrivers/power/ricoh619-battery.c6361
-rw-r--r--drivers/power/s3c_adc_battery.c437
-rwxr-xr-xdrivers/power/sabresd_battery.c985
-rw-r--r--drivers/power/test_power.c419
-rw-r--r--drivers/power/tosa_battery.c485
-rw-r--r--drivers/power/twl4030_charger.c578
-rw-r--r--drivers/power/wm831x_backup.c234
-rw-r--r--drivers/power/wm831x_power.c641
-rw-r--r--drivers/power/wm8350_power.c539
-rw-r--r--drivers/power/wm97xx_battery.c309
-rw-r--r--drivers/power/z2_battery.c335
39 files changed, 24336 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
new file mode 100755
index 00000000..9a7d529d
--- /dev/null
+++ b/drivers/power/Kconfig
@@ -0,0 +1,260 @@
+menuconfig POWER_SUPPLY
+ tristate "Power supply class support"
+ help
+ Say Y here to enable power supply class support. This allows
+ power supply (batteries, AC, USB) monitoring by userspace
+ via sysfs and uevent (if available) and/or APM kernel interface
+ (if selected below).
+
+if POWER_SUPPLY
+
+config POWER_SUPPLY_DEBUG
+ bool "Power supply debug"
+ help
+ Say Y here to enable debugging messages for power supply class
+ and drivers.
+
+config PDA_POWER
+ tristate "Generic PDA/phone power driver"
+ depends on !S390
+ help
+ Say Y here to enable generic power driver for PDAs and phones with
+ one or two external power supplies (AC/USB) connected to main and
+ backup batteries, and optional builtin charger.
+
+config APM_POWER
+ tristate "APM emulation for class batteries"
+ depends on APM_EMULATION
+ help
+ Say Y here to enable support APM status emulation using
+ battery class devices.
+
+config MAX8925_POWER
+ tristate "MAX8925 battery charger support"
+ depends on MFD_MAX8925
+ help
+ Say Y here to enable support for the battery charger in the Maxim
+ MAX8925 PMIC.
+
+config WM831X_BACKUP
+ tristate "WM831X backup battery charger support"
+ depends on MFD_WM831X
+ help
+ Say Y here to enable support for the backup battery charger
+ in the Wolfson Microelectronics WM831x PMICs.
+
+config WM831X_POWER
+ tristate "WM831X PMU support"
+ depends on MFD_WM831X
+ help
+ Say Y here to enable support for the power management unit
+ provided by Wolfson Microelectronics WM831x PMICs.
+
+config WM8350_POWER
+ tristate "WM8350 PMU support"
+ depends on MFD_WM8350
+ help
+ Say Y here to enable support for the power management unit
+ provided by the Wolfson Microelectronics WM8350 PMIC.
+
+config TEST_POWER
+ tristate "Test power driver"
+ help
+ This driver is used for testing. It's safe to say M here.
+
+config BATTERY_DS2760
+ tristate "DS2760 battery driver (HP iPAQ & others)"
+ depends on W1 && W1_SLAVE_DS2760
+ help
+ Say Y here to enable support for batteries with ds2760 chip.
+
+config BATTERY_DS2780
+ tristate "DS2780 battery driver"
+ select W1
+ select W1_SLAVE_DS2780
+ help
+ Say Y here to enable support for batteries with ds2780 chip.
+
+config BATTERY_DS2782
+ tristate "DS2782/DS2786 standalone gas-gauge"
+ depends on I2C
+ help
+ Say Y here to enable support for the DS2782/DS2786 standalone battery
+ gas-gauge.
+
+config BATTERY_PMU
+ tristate "Apple PMU battery"
+ depends on PPC32 && ADB_PMU
+ help
+ Say Y here to expose battery information on Apple machines
+ through the generic battery class.
+
+config BATTERY_OLPC
+ tristate "One Laptop Per Child battery"
+ depends on X86_32 && OLPC
+ help
+ Say Y to enable support for the battery on the OLPC laptop.
+
+config BATTERY_TOSA
+ tristate "Sharp SL-6000 (tosa) battery"
+ depends on MACH_TOSA && MFD_TC6393XB && TOUCHSCREEN_WM97XX
+ help
+ Say Y to enable support for the battery on the Sharp Zaurus
+ SL-6000 (tosa) models.
+
+config BATTERY_COLLIE
+ tristate "Sharp SL-5500 (collie) battery"
+ depends on SA1100_COLLIE && MCP_UCB1200
+ help
+ Say Y to enable support for the battery on the Sharp Zaurus
+ SL-5500 (collie) models.
+
+config BATTERY_WM97XX
+ bool "WM97xx generic battery driver"
+ depends on TOUCHSCREEN_WM97XX=y
+ help
+ Say Y to enable support for battery measured by WM97xx aux port.
+
+config BATTERY_BQ20Z75
+ tristate "TI BQ20z75 gas gauge"
+ depends on I2C
+ help
+ Say Y to include support for TI BQ20z75 SBS-compliant
+ gas gauge and protection IC.
+
+config BATTERY_BQ27x00
+ tristate "BQ27x00 battery driver"
+ help
+ Say Y here to enable support for batteries with BQ27x00 (I2C/HDQ) chips.
+
+config BATTERY_BQ27X00_I2C
+ bool "BQ27200/BQ27500 support"
+ depends on BATTERY_BQ27x00
+ depends on I2C
+ default y
+ help
+ Say Y here to enable support for batteries with BQ27x00 (I2C) chips.
+
+config BATTERY_BQ27X00_PLATFORM
+ bool "BQ27000 support"
+ depends on BATTERY_BQ27x00
+ default y
+ help
+ Say Y here to enable support for batteries with BQ27000 (HDQ) chips.
+
+config BATTERY_DA9030
+ tristate "DA9030 battery driver"
+ depends on PMIC_DA903X
+ help
+ Say Y here to enable support for batteries charger integrated into
+ DA9030 PMIC.
+
+config BATTERY_MAX17040
+ tristate "Maxim MAX17040 Fuel Gauge"
+ depends on I2C
+ help
+ MAX17040 is fuel-gauge systems for lithium-ion (Li+) batteries
+ in handheld and portable equipment. The MAX17040 is configured
+ to operate with a single lithium cell
+
+config BATTERY_MAX17042
+ tristate "Maxim MAX17042/8997/8966 Fuel Gauge"
+ depends on I2C
+ help
+ MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries
+ in handheld and portable equipment. The MAX17042 is configured
+ to operate with a single lithium cell. MAX8997 and MAX8966 are
+ multi-function devices that include fuel gauages that are compatible
+ with MAX17042.
+
+config BATTERY_Z2
+ tristate "Z2 battery driver"
+ depends on I2C && MACH_ZIPIT2
+ help
+ Say Y to include support for the battery on the Zipit Z2.
+
+config BATTERY_S3C_ADC
+ tristate "Battery driver for Samsung ADC based monitoring"
+ depends on S3C_ADC
+ help
+ Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery
+
+config CHARGER_PCF50633
+ tristate "NXP PCF50633 MBC"
+ depends on MFD_PCF50633
+ help
+ Say Y to include support for NXP PCF50633 Main Battery Charger.
+
+config BATTERY_JZ4740
+ tristate "Ingenic JZ4740 battery"
+ depends on MACH_JZ4740
+ depends on MFD_JZ4740_ADC
+ help
+ Say Y to enable support for the battery on Ingenic JZ4740 based
+ boards.
+
+ This driver can be build as a module. If so, the module will be
+ called jz4740-battery.
+
+config BATTERY_INTEL_MID
+ tristate "Battery driver for Intel MID platforms"
+ depends on INTEL_SCU_IPC && SPI
+ help
+ Say Y here to enable the battery driver on Intel MID
+ platforms.
+
+config CHARGER_ISP1704
+ tristate "ISP1704 USB Charger Detection"
+ depends on USB_OTG_UTILS
+ help
+ Say Y to enable support for USB Charger Detection with
+ ISP1707/ISP1704 USB transceivers.
+
+config CHARGER_MAX8903
+ tristate "MAX8903 Battery DC-DC Charger for USB and Adapter Power"
+ depends on GENERIC_HARDIRQS
+ help
+ Say Y to enable support for the MAX8903 DC-DC charger and sysfs.
+ The driver supports controlling charger-enable and current-limit
+ pins based on the status of charger connections with interrupt
+ handlers.
+
+config SABRESD_MAX8903
+ tristate "Sabresd Board Battery DC-DC Charger for USB and Adapter Power"
+ depends on GENERIC_HARDIRQS
+ help
+ Say Y to enable support for the MAX8903 DC-DC charger and sysfs on
+ sabresd board.The driver supports controlling charger and battery
+ based on the status of charger connections with interrupt handlers.
+
+config CHARGER_TWL4030
+ tristate "OMAP TWL4030 BCI charger driver"
+ depends on TWL4030_CORE
+ help
+ Say Y here to enable support for TWL4030 Battery Charge Interface.
+
+config CHARGER_GPIO
+ tristate "GPIO charger"
+ depends on GPIOLIB
+ help
+ Say Y to include support for chargers which report their online status
+ through a GPIO pin.
+
+ This driver can be build as a module. If so, the module will be
+ called gpio-charger.
+
+config BATTERY_DA9052
+ tristate "Dialog DA9052 Battery"
+ depends on PMIC_DIALOG
+ help
+ Say Y here to enable support for batteries charger integrated into
+ DA9052 PMIC.
+
+config BATTERY_RICOH619
+ tristate "Ricoh R5T619 PMIC battery driver"
+ depends on MFD_RICOH619 && I2C && GENERIC_HARDIRQS
+ help
+ Say Y to enable support for the battery control of the Ricoh R5T619
+ Power Management device.
+
+endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
new file mode 100755
index 00000000..64fdfdba
--- /dev/null
+++ b/drivers/power/Makefile
@@ -0,0 +1,41 @@
+ccflags-$(CONFIG_POWER_SUPPLY_DEBUG) := -DDEBUG
+
+power_supply-y := power_supply_core.o
+power_supply-$(CONFIG_SYSFS) += power_supply_sysfs.o
+power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o
+
+obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
+
+obj-$(CONFIG_PDA_POWER) += pda_power.o
+obj-$(CONFIG_APM_POWER) += apm_power.o
+obj-$(CONFIG_MAX8925_POWER) += max8925_power.o
+obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o
+obj-$(CONFIG_WM831X_POWER) += wm831x_power.o
+obj-$(CONFIG_WM8350_POWER) += wm8350_power.o
+obj-$(CONFIG_TEST_POWER) += test_power.o
+
+obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
+obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
+obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o
+obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
+obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
+obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
+obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
+obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
+obj-$(CONFIG_BATTERY_BQ20Z75) += bq20z75.o
+obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o
+obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
+obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
+obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
+obj-$(CONFIG_BATTERY_Z2) += z2_battery.o
+obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o
+obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
+obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o
+obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o
+obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
+obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
+obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
+obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
+obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o
+obj-$(CONFIG_SABRESD_MAX8903) += sabresd_battery.o
+obj-$(CONFIG_BATTERY_RICOH619) += ricoh619-battery.o
diff --git a/drivers/power/apm_power.c b/drivers/power/apm_power.c
new file mode 100644
index 00000000..dc628cb2
--- /dev/null
+++ b/drivers/power/apm_power.c
@@ -0,0 +1,375 @@
+/*
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2007 Eugeny Boger <eugenyboger@dgap.mipt.ru>
+ *
+ * Author: Eugeny Boger <eugenyboger@dgap.mipt.ru>
+ *
+ * Use consistent with the GNU GPL is permitted,
+ * provided that this copyright notice is
+ * preserved in its entirety in all copies and derived works.
+ */
+
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/apm-emulation.h>
+
+
+#define PSY_PROP(psy, prop, val) psy->get_property(psy, \
+ POWER_SUPPLY_PROP_##prop, val)
+
+#define _MPSY_PROP(prop, val) main_battery->get_property(main_battery, \
+ prop, val)
+
+#define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val)
+
+static DEFINE_MUTEX(apm_mutex);
+static struct power_supply *main_battery;
+
+enum apm_source {
+ SOURCE_ENERGY,
+ SOURCE_CHARGE,
+ SOURCE_VOLTAGE,
+};
+
+struct find_bat_param {
+ struct power_supply *main;
+ struct power_supply *bat;
+ struct power_supply *max_charge_bat;
+ struct power_supply *max_energy_bat;
+ union power_supply_propval full;
+ int max_charge;
+ int max_energy;
+};
+
+static int __find_main_battery(struct device *dev, void *data)
+{
+ struct find_bat_param *bp = (struct find_bat_param *)data;
+
+ bp->bat = dev_get_drvdata(dev);
+
+ if (bp->bat->use_for_apm) {
+ /* nice, we explicitly asked to report this battery. */
+ bp->main = bp->bat;
+ return 1;
+ }
+
+ if (!PSY_PROP(bp->bat, CHARGE_FULL_DESIGN, &bp->full) ||
+ !PSY_PROP(bp->bat, CHARGE_FULL, &bp->full)) {
+ if (bp->full.intval > bp->max_charge) {
+ bp->max_charge_bat = bp->bat;
+ bp->max_charge = bp->full.intval;
+ }
+ } else if (!PSY_PROP(bp->bat, ENERGY_FULL_DESIGN, &bp->full) ||
+ !PSY_PROP(bp->bat, ENERGY_FULL, &bp->full)) {
+ if (bp->full.intval > bp->max_energy) {
+ bp->max_energy_bat = bp->bat;
+ bp->max_energy = bp->full.intval;
+ }
+ }
+ return 0;
+}
+
+static void find_main_battery(void)
+{
+ struct find_bat_param bp;
+ int error;
+
+ memset(&bp, 0, sizeof(struct find_bat_param));
+ main_battery = NULL;
+ bp.main = main_battery;
+
+ error = class_for_each_device(power_supply_class, NULL, &bp,
+ __find_main_battery);
+ if (error) {
+ main_battery = bp.main;
+ return;
+ }
+
+ if ((bp.max_energy_bat && bp.max_charge_bat) &&
+ (bp.max_energy_bat != bp.max_charge_bat)) {
+ /* try guess battery with more capacity */
+ if (!PSY_PROP(bp.max_charge_bat, VOLTAGE_MAX_DESIGN,
+ &bp.full)) {
+ if (bp.max_energy > bp.max_charge * bp.full.intval)
+ main_battery = bp.max_energy_bat;
+ else
+ main_battery = bp.max_charge_bat;
+ } else if (!PSY_PROP(bp.max_energy_bat, VOLTAGE_MAX_DESIGN,
+ &bp.full)) {
+ if (bp.max_charge > bp.max_energy / bp.full.intval)
+ main_battery = bp.max_charge_bat;
+ else
+ main_battery = bp.max_energy_bat;
+ } else {
+ /* give up, choice any */
+ main_battery = bp.max_energy_bat;
+ }
+ } else if (bp.max_charge_bat) {
+ main_battery = bp.max_charge_bat;
+ } else if (bp.max_energy_bat) {
+ main_battery = bp.max_energy_bat;
+ } else {
+ /* give up, try the last if any */
+ main_battery = bp.bat;
+ }
+}
+
+static int do_calculate_time(int status, enum apm_source source)
+{
+ union power_supply_propval full;
+ union power_supply_propval empty;
+ union power_supply_propval cur;
+ union power_supply_propval I;
+ enum power_supply_property full_prop;
+ enum power_supply_property full_design_prop;
+ enum power_supply_property empty_prop;
+ enum power_supply_property empty_design_prop;
+ enum power_supply_property cur_avg_prop;
+ enum power_supply_property cur_now_prop;
+
+ if (MPSY_PROP(CURRENT_AVG, &I)) {
+ /* if battery can't report average value, use momentary */
+ if (MPSY_PROP(CURRENT_NOW, &I))
+ return -1;
+ }
+
+ if (!I.intval)
+ return 0;
+
+ switch (source) {
+ case SOURCE_CHARGE:
+ full_prop = POWER_SUPPLY_PROP_CHARGE_FULL;
+ full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
+ empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
+ empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
+ cur_avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG;
+ cur_now_prop = POWER_SUPPLY_PROP_CHARGE_NOW;
+ break;
+ case SOURCE_ENERGY:
+ full_prop = POWER_SUPPLY_PROP_ENERGY_FULL;
+ full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
+ empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY;
+ empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
+ cur_avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG;
+ cur_now_prop = POWER_SUPPLY_PROP_ENERGY_NOW;
+ break;
+ case SOURCE_VOLTAGE:
+ full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
+ empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+ empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
+ cur_avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG;
+ cur_now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ break;
+ default:
+ printk(KERN_ERR "Unsupported source: %d\n", source);
+ return -1;
+ }
+
+ if (_MPSY_PROP(full_prop, &full)) {
+ /* if battery can't report this property, use design value */
+ if (_MPSY_PROP(full_design_prop, &full))
+ return -1;
+ }
+
+ if (_MPSY_PROP(empty_prop, &empty)) {
+ /* if battery can't report this property, use design value */
+ if (_MPSY_PROP(empty_design_prop, &empty))
+ empty.intval = 0;
+ }
+
+ if (_MPSY_PROP(cur_avg_prop, &cur)) {
+ /* if battery can't report average value, use momentary */
+ if (_MPSY_PROP(cur_now_prop, &cur))
+ return -1;
+ }
+
+ if (status == POWER_SUPPLY_STATUS_CHARGING)
+ return ((cur.intval - full.intval) * 60L) / I.intval;
+ else
+ return -((cur.intval - empty.intval) * 60L) / I.intval;
+}
+
+static int calculate_time(int status)
+{
+ int time;
+
+ time = do_calculate_time(status, SOURCE_ENERGY);
+ if (time != -1)
+ return time;
+
+ time = do_calculate_time(status, SOURCE_CHARGE);
+ if (time != -1)
+ return time;
+
+ time = do_calculate_time(status, SOURCE_VOLTAGE);
+ if (time != -1)
+ return time;
+
+ return -1;
+}
+
+static int calculate_capacity(enum apm_source source)
+{
+ enum power_supply_property full_prop, empty_prop;
+ enum power_supply_property full_design_prop, empty_design_prop;
+ enum power_supply_property now_prop, avg_prop;
+ union power_supply_propval empty, full, cur;
+ int ret;
+
+ switch (source) {
+ case SOURCE_CHARGE:
+ full_prop = POWER_SUPPLY_PROP_CHARGE_FULL;
+ empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
+ full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
+ empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN;
+ now_prop = POWER_SUPPLY_PROP_CHARGE_NOW;
+ avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG;
+ break;
+ case SOURCE_ENERGY:
+ full_prop = POWER_SUPPLY_PROP_ENERGY_FULL;
+ empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY;
+ full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
+ empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN;
+ now_prop = POWER_SUPPLY_PROP_ENERGY_NOW;
+ avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG;
+ break;
+ case SOURCE_VOLTAGE:
+ full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+ full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
+ empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
+ now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG;
+ break;
+ default:
+ printk(KERN_ERR "Unsupported source: %d\n", source);
+ return -1;
+ }
+
+ if (_MPSY_PROP(full_prop, &full)) {
+ /* if battery can't report this property, use design value */
+ if (_MPSY_PROP(full_design_prop, &full))
+ return -1;
+ }
+
+ if (_MPSY_PROP(avg_prop, &cur)) {
+ /* if battery can't report average value, use momentary */
+ if (_MPSY_PROP(now_prop, &cur))
+ return -1;
+ }
+
+ if (_MPSY_PROP(empty_prop, &empty)) {
+ /* if battery can't report this property, use design value */
+ if (_MPSY_PROP(empty_design_prop, &empty))
+ empty.intval = 0;
+ }
+
+ if (full.intval - empty.intval)
+ ret = ((cur.intval - empty.intval) * 100L) /
+ (full.intval - empty.intval);
+ else
+ return -1;
+
+ if (ret > 100)
+ return 100;
+ else if (ret < 0)
+ return 0;
+
+ return ret;
+}
+
+static void apm_battery_apm_get_power_status(struct apm_power_info *info)
+{
+ union power_supply_propval status;
+ union power_supply_propval capacity, time_to_full, time_to_empty;
+
+ mutex_lock(&apm_mutex);
+ find_main_battery();
+ if (!main_battery) {
+ mutex_unlock(&apm_mutex);
+ return;
+ }
+
+ /* status */
+
+ if (MPSY_PROP(STATUS, &status))
+ status.intval = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ /* ac line status */
+
+ if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) ||
+ (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) ||
+ (status.intval == POWER_SUPPLY_STATUS_FULL))
+ info->ac_line_status = APM_AC_ONLINE;
+ else
+ info->ac_line_status = APM_AC_OFFLINE;
+
+ /* battery life (i.e. capacity, in percents) */
+
+ if (MPSY_PROP(CAPACITY, &capacity) == 0) {
+ info->battery_life = capacity.intval;
+ } else {
+ /* try calculate using energy */
+ info->battery_life = calculate_capacity(SOURCE_ENERGY);
+ /* if failed try calculate using charge instead */
+ if (info->battery_life == -1)
+ info->battery_life = calculate_capacity(SOURCE_CHARGE);
+ if (info->battery_life == -1)
+ info->battery_life = calculate_capacity(SOURCE_VOLTAGE);
+ }
+
+ /* charging status */
+
+ if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
+ info->battery_status = APM_BATTERY_STATUS_CHARGING;
+ } else {
+ if (info->battery_life > 50)
+ info->battery_status = APM_BATTERY_STATUS_HIGH;
+ else if (info->battery_life > 5)
+ info->battery_status = APM_BATTERY_STATUS_LOW;
+ else
+ info->battery_status = APM_BATTERY_STATUS_CRITICAL;
+ }
+ info->battery_flag = info->battery_status;
+
+ /* time */
+
+ info->units = APM_UNITS_MINS;
+
+ if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
+ if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) ||
+ !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full))
+ info->time = time_to_full.intval / 60;
+ else
+ info->time = calculate_time(status.intval);
+ } else {
+ if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) ||
+ !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty))
+ info->time = time_to_empty.intval / 60;
+ else
+ info->time = calculate_time(status.intval);
+ }
+
+ mutex_unlock(&apm_mutex);
+}
+
+static int __init apm_battery_init(void)
+{
+ printk(KERN_INFO "APM Battery Driver\n");
+
+ apm_get_power_status = apm_battery_apm_get_power_status;
+ return 0;
+}
+
+static void __exit apm_battery_exit(void)
+{
+ apm_get_power_status = NULL;
+}
+
+module_init(apm_battery_init);
+module_exit(apm_battery_exit);
+
+MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>");
+MODULE_DESCRIPTION("APM emulation driver for battery monitoring class");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/bq20z75.c b/drivers/power/bq20z75.c
new file mode 100644
index 00000000..506585e3
--- /dev/null
+++ b/drivers/power/bq20z75.c
@@ -0,0 +1,709 @@
+/*
+ * Gas Gauge driver for TI's BQ20Z75
+ *
+ * Copyright (c) 2010, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/power_supply.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+
+#include <linux/power/bq20z75.h>
+
+enum {
+ REG_MANUFACTURER_DATA,
+ REG_TEMPERATURE,
+ REG_VOLTAGE,
+ REG_CURRENT,
+ REG_CAPACITY,
+ REG_TIME_TO_EMPTY,
+ REG_TIME_TO_FULL,
+ REG_STATUS,
+ REG_CYCLE_COUNT,
+ REG_SERIAL_NUMBER,
+ REG_REMAINING_CAPACITY,
+ REG_REMAINING_CAPACITY_CHARGE,
+ REG_FULL_CHARGE_CAPACITY,
+ REG_FULL_CHARGE_CAPACITY_CHARGE,
+ REG_DESIGN_CAPACITY,
+ REG_DESIGN_CAPACITY_CHARGE,
+ REG_DESIGN_VOLTAGE,
+};
+
+/* Battery Mode defines */
+#define BATTERY_MODE_OFFSET 0x03
+#define BATTERY_MODE_MASK 0x8000
+enum bq20z75_battery_mode {
+ BATTERY_MODE_AMPS,
+ BATTERY_MODE_WATTS
+};
+
+/* manufacturer access defines */
+#define MANUFACTURER_ACCESS_STATUS 0x0006
+#define MANUFACTURER_ACCESS_SLEEP 0x0011
+
+/* battery status value bits */
+#define BATTERY_DISCHARGING 0x40
+#define BATTERY_FULL_CHARGED 0x20
+#define BATTERY_FULL_DISCHARGED 0x10
+
+#define BQ20Z75_DATA(_psp, _addr, _min_value, _max_value) { \
+ .psp = _psp, \
+ .addr = _addr, \
+ .min_value = _min_value, \
+ .max_value = _max_value, \
+}
+
+static const struct bq20z75_device_data {
+ enum power_supply_property psp;
+ u8 addr;
+ int min_value;
+ int max_value;
+} bq20z75_data[] = {
+ [REG_MANUFACTURER_DATA] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535),
+ [REG_TEMPERATURE] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535),
+ [REG_VOLTAGE] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000),
+ [REG_CURRENT] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768,
+ 32767),
+ [REG_CAPACITY] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100),
+ [REG_REMAINING_CAPACITY] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535),
+ [REG_REMAINING_CAPACITY_CHARGE] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535),
+ [REG_FULL_CHARGE_CAPACITY] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535),
+ [REG_FULL_CHARGE_CAPACITY_CHARGE] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535),
+ [REG_TIME_TO_EMPTY] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0,
+ 65535),
+ [REG_TIME_TO_FULL] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0,
+ 65535),
+ [REG_STATUS] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535),
+ [REG_CYCLE_COUNT] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535),
+ [REG_DESIGN_CAPACITY] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0,
+ 65535),
+ [REG_DESIGN_CAPACITY_CHARGE] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0,
+ 65535),
+ [REG_DESIGN_VOLTAGE] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0,
+ 65535),
+ [REG_SERIAL_NUMBER] =
+ BQ20Z75_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535),
+};
+
+static enum power_supply_property bq20z75_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+};
+
+struct bq20z75_info {
+ struct i2c_client *client;
+ struct power_supply power_supply;
+ struct bq20z75_platform_data *pdata;
+ bool is_present;
+ bool gpio_detect;
+ bool enable_detection;
+ int irq;
+};
+
+static int bq20z75_read_word_data(struct i2c_client *client, u8 address)
+{
+ struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
+ s32 ret = 0;
+ int retries = 1;
+
+ if (bq20z75_device->pdata)
+ retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1);
+
+ while (retries > 0) {
+ ret = i2c_smbus_read_word_data(client, address);
+ if (ret >= 0)
+ break;
+ retries--;
+ }
+
+ if (ret < 0) {
+ dev_dbg(&client->dev,
+ "%s: i2c read at address 0x%x failed\n",
+ __func__, address);
+ return ret;
+ }
+
+ return le16_to_cpu(ret);
+}
+
+static int bq20z75_write_word_data(struct i2c_client *client, u8 address,
+ u16 value)
+{
+ struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
+ s32 ret = 0;
+ int retries = 1;
+
+ if (bq20z75_device->pdata)
+ retries = max(bq20z75_device->pdata->i2c_retry_count + 1, 1);
+
+ while (retries > 0) {
+ ret = i2c_smbus_write_word_data(client, address,
+ le16_to_cpu(value));
+ if (ret >= 0)
+ break;
+ retries--;
+ }
+
+ if (ret < 0) {
+ dev_dbg(&client->dev,
+ "%s: i2c write to address 0x%x failed\n",
+ __func__, address);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int bq20z75_get_battery_presence_and_health(
+ struct i2c_client *client, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ s32 ret;
+ struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
+
+ if (psp == POWER_SUPPLY_PROP_PRESENT &&
+ bq20z75_device->gpio_detect) {
+ ret = gpio_get_value(
+ bq20z75_device->pdata->battery_detect);
+ if (ret == bq20z75_device->pdata->battery_detect_present)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ bq20z75_device->is_present = val->intval;
+ return ret;
+ }
+
+ /* Write to ManufacturerAccess with
+ * ManufacturerAccess command and then
+ * read the status */
+ ret = bq20z75_write_word_data(client,
+ bq20z75_data[REG_MANUFACTURER_DATA].addr,
+ MANUFACTURER_ACCESS_STATUS);
+ if (ret < 0) {
+ if (psp == POWER_SUPPLY_PROP_PRESENT)
+ val->intval = 0; /* battery removed */
+ return ret;
+ }
+
+ ret = bq20z75_read_word_data(client,
+ bq20z75_data[REG_MANUFACTURER_DATA].addr);
+ if (ret < 0)
+ return ret;
+
+ if (ret < bq20z75_data[REG_MANUFACTURER_DATA].min_value ||
+ ret > bq20z75_data[REG_MANUFACTURER_DATA].max_value) {
+ val->intval = 0;
+ return 0;
+ }
+
+ /* Mask the upper nibble of 2nd byte and
+ * lower byte of response then
+ * shift the result by 8 to get status*/
+ ret &= 0x0F00;
+ ret >>= 8;
+ if (psp == POWER_SUPPLY_PROP_PRESENT) {
+ if (ret == 0x0F)
+ /* battery removed */
+ val->intval = 0;
+ else
+ val->intval = 1;
+ } else if (psp == POWER_SUPPLY_PROP_HEALTH) {
+ if (ret == 0x09)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (ret == 0x0B)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (ret == 0x0C)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ return 0;
+}
+
+static int bq20z75_get_battery_property(struct i2c_client *client,
+ int reg_offset, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ s32 ret;
+
+ ret = bq20z75_read_word_data(client,
+ bq20z75_data[reg_offset].addr);
+ if (ret < 0)
+ return ret;
+
+ /* returned values are 16 bit */
+ if (bq20z75_data[reg_offset].min_value < 0)
+ ret = (s16)ret;
+
+ if (ret >= bq20z75_data[reg_offset].min_value &&
+ ret <= bq20z75_data[reg_offset].max_value) {
+ val->intval = ret;
+ if (psp == POWER_SUPPLY_PROP_STATUS) {
+ if (ret & BATTERY_FULL_CHARGED)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (ret & BATTERY_FULL_DISCHARGED)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (ret & BATTERY_DISCHARGING)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ } else {
+ if (psp == POWER_SUPPLY_PROP_STATUS)
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ else
+ val->intval = 0;
+ }
+
+ return 0;
+}
+
+static void bq20z75_unit_adjustment(struct i2c_client *client,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+#define BASE_UNIT_CONVERSION 1000
+#define BATTERY_MODE_CAP_MULT_WATT (10 * BASE_UNIT_CONVERSION)
+#define TIME_UNIT_CONVERSION 60
+#define TEMP_KELVIN_TO_CELSIUS 2731
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ /* bq20z75 provides energy in units of 10mWh.
+ * Convert to µWh
+ */
+ val->intval *= BATTERY_MODE_CAP_MULT_WATT;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval *= BASE_UNIT_CONVERSION;
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ /* bq20z75 provides battery temperature in 0.1K
+ * so convert it to 0.1°C
+ */
+ val->intval -= TEMP_KELVIN_TO_CELSIUS;
+ break;
+
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+ /* bq20z75 provides time to empty and time to full in minutes.
+ * Convert to seconds
+ */
+ val->intval *= TIME_UNIT_CONVERSION;
+ break;
+
+ default:
+ dev_dbg(&client->dev,
+ "%s: no need for unit conversion %d\n", __func__, psp);
+ }
+}
+
+static enum bq20z75_battery_mode
+bq20z75_set_battery_mode(struct i2c_client *client,
+ enum bq20z75_battery_mode mode)
+{
+ int ret, original_val;
+
+ original_val = bq20z75_read_word_data(client, BATTERY_MODE_OFFSET);
+ if (original_val < 0)
+ return original_val;
+
+ if ((original_val & BATTERY_MODE_MASK) == mode)
+ return mode;
+
+ if (mode == BATTERY_MODE_AMPS)
+ ret = original_val & ~BATTERY_MODE_MASK;
+ else
+ ret = original_val | BATTERY_MODE_MASK;
+
+ ret = bq20z75_write_word_data(client, BATTERY_MODE_OFFSET, ret);
+ if (ret < 0)
+ return ret;
+
+ return original_val & BATTERY_MODE_MASK;
+}
+
+static int bq20z75_get_battery_capacity(struct i2c_client *client,
+ int reg_offset, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ s32 ret;
+ enum bq20z75_battery_mode mode = BATTERY_MODE_WATTS;
+
+ if (power_supply_is_amp_property(psp))
+ mode = BATTERY_MODE_AMPS;
+
+ mode = bq20z75_set_battery_mode(client, mode);
+ if (mode < 0)
+ return mode;
+
+ ret = bq20z75_read_word_data(client, bq20z75_data[reg_offset].addr);
+ if (ret < 0)
+ return ret;
+
+ if (psp == POWER_SUPPLY_PROP_CAPACITY) {
+ /* bq20z75 spec says that this can be >100 %
+ * even if max value is 100 % */
+ val->intval = min(ret, 100);
+ } else
+ val->intval = ret;
+
+ ret = bq20z75_set_battery_mode(client, mode);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static char bq20z75_serial[5];
+static int bq20z75_get_battery_serial_number(struct i2c_client *client,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = bq20z75_read_word_data(client,
+ bq20z75_data[REG_SERIAL_NUMBER].addr);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(bq20z75_serial, "%04x", ret);
+ val->strval = bq20z75_serial;
+
+ return 0;
+}
+
+static int bq20z75_get_property_index(struct i2c_client *client,
+ enum power_supply_property psp)
+{
+ int count;
+ for (count = 0; count < ARRAY_SIZE(bq20z75_data); count++)
+ if (psp == bq20z75_data[count].psp)
+ return count;
+
+ dev_warn(&client->dev,
+ "%s: Invalid Property - %d\n", __func__, psp);
+
+ return -EINVAL;
+}
+
+static int bq20z75_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct bq20z75_info *bq20z75_device = container_of(psy,
+ struct bq20z75_info, power_supply);
+ struct i2c_client *client = bq20z75_device->client;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = bq20z75_get_battery_presence_and_health(client, psp, val);
+ if (psp == POWER_SUPPLY_PROP_PRESENT)
+ return 0;
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = bq20z75_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = bq20z75_get_battery_capacity(client, ret, psp, val);
+ break;
+
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ ret = bq20z75_get_battery_serial_number(client, val);
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_TEMP:
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ ret = bq20z75_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = bq20z75_get_battery_property(client, ret, psp, val);
+ break;
+
+ default:
+ dev_err(&client->dev,
+ "%s: INVALID property\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!bq20z75_device->enable_detection)
+ goto done;
+
+ if (!bq20z75_device->gpio_detect &&
+ bq20z75_device->is_present != (ret >= 0)) {
+ bq20z75_device->is_present = (ret >= 0);
+ power_supply_changed(&bq20z75_device->power_supply);
+ }
+
+done:
+ if (!ret) {
+ /* Convert units to match requirements for power supply class */
+ bq20z75_unit_adjustment(client, psp, val);
+ }
+
+ dev_dbg(&client->dev,
+ "%s: property = %d, value = %x\n", __func__, psp, val->intval);
+
+ if (ret && bq20z75_device->is_present)
+ return ret;
+
+ /* battery not present, so return NODATA for properties */
+ if (ret)
+ return -ENODATA;
+
+ return 0;
+}
+
+static irqreturn_t bq20z75_irq(int irq, void *devid)
+{
+ struct power_supply *battery = devid;
+
+ power_supply_changed(battery);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit bq20z75_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct bq20z75_info *bq20z75_device;
+ struct bq20z75_platform_data *pdata = client->dev.platform_data;
+ int rc;
+ int irq;
+
+ bq20z75_device = kzalloc(sizeof(struct bq20z75_info), GFP_KERNEL);
+ if (!bq20z75_device)
+ return -ENOMEM;
+
+ bq20z75_device->client = client;
+ bq20z75_device->enable_detection = false;
+ bq20z75_device->gpio_detect = false;
+ bq20z75_device->power_supply.name = "battery";
+ bq20z75_device->power_supply.type = POWER_SUPPLY_TYPE_BATTERY;
+ bq20z75_device->power_supply.properties = bq20z75_properties;
+ bq20z75_device->power_supply.num_properties =
+ ARRAY_SIZE(bq20z75_properties);
+ bq20z75_device->power_supply.get_property = bq20z75_get_property;
+
+ if (pdata) {
+ bq20z75_device->gpio_detect =
+ gpio_is_valid(pdata->battery_detect);
+ bq20z75_device->pdata = pdata;
+ }
+
+ i2c_set_clientdata(client, bq20z75_device);
+
+ if (!bq20z75_device->gpio_detect)
+ goto skip_gpio;
+
+ rc = gpio_request(pdata->battery_detect, dev_name(&client->dev));
+ if (rc) {
+ dev_warn(&client->dev, "Failed to request gpio: %d\n", rc);
+ bq20z75_device->gpio_detect = false;
+ goto skip_gpio;
+ }
+
+ rc = gpio_direction_input(pdata->battery_detect);
+ if (rc) {
+ dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc);
+ gpio_free(pdata->battery_detect);
+ bq20z75_device->gpio_detect = false;
+ goto skip_gpio;
+ }
+
+ irq = gpio_to_irq(pdata->battery_detect);
+ if (irq <= 0) {
+ dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq);
+ gpio_free(pdata->battery_detect);
+ bq20z75_device->gpio_detect = false;
+ goto skip_gpio;
+ }
+
+ rc = request_irq(irq, bq20z75_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(&client->dev), &bq20z75_device->power_supply);
+ if (rc) {
+ dev_warn(&client->dev, "Failed to request irq: %d\n", rc);
+ gpio_free(pdata->battery_detect);
+ bq20z75_device->gpio_detect = false;
+ goto skip_gpio;
+ }
+
+ bq20z75_device->irq = irq;
+
+skip_gpio:
+
+ rc = power_supply_register(&client->dev, &bq20z75_device->power_supply);
+ if (rc) {
+ dev_err(&client->dev,
+ "%s: Failed to register power supply\n", __func__);
+ goto exit_psupply;
+ }
+
+ dev_info(&client->dev,
+ "%s: battery gas gauge device registered\n", client->name);
+
+ return 0;
+
+exit_psupply:
+ if (bq20z75_device->irq)
+ free_irq(bq20z75_device->irq, &bq20z75_device->power_supply);
+ if (bq20z75_device->gpio_detect)
+ gpio_free(pdata->battery_detect);
+
+ kfree(bq20z75_device);
+
+ return rc;
+}
+
+static int __devexit bq20z75_remove(struct i2c_client *client)
+{
+ struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
+
+ if (bq20z75_device->irq)
+ free_irq(bq20z75_device->irq, &bq20z75_device->power_supply);
+ if (bq20z75_device->gpio_detect)
+ gpio_free(bq20z75_device->pdata->battery_detect);
+
+ power_supply_unregister(&bq20z75_device->power_supply);
+ kfree(bq20z75_device);
+ bq20z75_device = NULL;
+
+ return 0;
+}
+
+#if defined CONFIG_PM
+static int bq20z75_suspend(struct i2c_client *client,
+ pm_message_t state)
+{
+ struct bq20z75_info *bq20z75_device = i2c_get_clientdata(client);
+ s32 ret;
+
+ /* write to manufacturer access with sleep command */
+ ret = bq20z75_write_word_data(client,
+ bq20z75_data[REG_MANUFACTURER_DATA].addr,
+ MANUFACTURER_ACCESS_SLEEP);
+ if (bq20z75_device->is_present && ret < 0)
+ return ret;
+
+ return 0;
+}
+#else
+#define bq20z75_suspend NULL
+#endif
+/* any smbus transaction will wake up bq20z75 */
+#define bq20z75_resume NULL
+
+static const struct i2c_device_id bq20z75_id[] = {
+ { "bq20z75", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, bq20z75_id);
+
+static struct i2c_driver bq20z75_battery_driver = {
+ .probe = bq20z75_probe,
+ .remove = __devexit_p(bq20z75_remove),
+ .suspend = bq20z75_suspend,
+ .resume = bq20z75_resume,
+ .id_table = bq20z75_id,
+ .driver = {
+ .name = "bq20z75-battery",
+ },
+};
+
+static int __init bq20z75_battery_init(void)
+{
+ return i2c_add_driver(&bq20z75_battery_driver);
+}
+module_init(bq20z75_battery_init);
+
+static void __exit bq20z75_battery_exit(void)
+{
+ i2c_del_driver(&bq20z75_battery_driver);
+}
+module_exit(bq20z75_battery_exit);
+
+MODULE_DESCRIPTION("BQ20z75 battery monitor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c
new file mode 100644
index 00000000..bb16f5b7
--- /dev/null
+++ b/drivers/power/bq27x00_battery.c
@@ -0,0 +1,862 @@
+/*
+ * BQ27x00 battery driver
+ *
+ * Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it>
+ * Copyright (C) 2008 Eurotech S.p.A. <info@eurotech.it>
+ * Copyright (C) 2010-2011 Lars-Peter Clausen <lars@metafoo.de>
+ * Copyright (C) 2011 Pali Rohár <pali.rohar@gmail.com>
+ *
+ * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc.
+ *
+ * This package 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.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+/*
+ * Datasheets:
+ * http://focus.ti.com/docs/prod/folders/print/bq27000.html
+ * http://focus.ti.com/docs/prod/folders/print/bq27500.html
+ */
+
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#include <linux/power/bq27x00_battery.h>
+
+#define DRIVER_VERSION "1.2.0"
+
+#define BQ27x00_REG_TEMP 0x06
+#define BQ27x00_REG_VOLT 0x08
+#define BQ27x00_REG_AI 0x14
+#define BQ27x00_REG_FLAGS 0x0A
+#define BQ27x00_REG_TTE 0x16
+#define BQ27x00_REG_TTF 0x18
+#define BQ27x00_REG_TTECP 0x26
+#define BQ27x00_REG_NAC 0x0C /* Nominal available capaciy */
+#define BQ27x00_REG_LMD 0x12 /* Last measured discharge */
+#define BQ27x00_REG_CYCT 0x2A /* Cycle count total */
+#define BQ27x00_REG_AE 0x22 /* Available enery */
+
+#define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */
+#define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */
+#define BQ27000_FLAG_CHGS BIT(7)
+#define BQ27000_FLAG_FC BIT(5)
+
+#define BQ27500_REG_SOC 0x2C
+#define BQ27500_REG_DCAP 0x3C /* Design capacity */
+#define BQ27500_FLAG_DSC BIT(0)
+#define BQ27500_FLAG_FC BIT(9)
+
+#define BQ27000_RS 20 /* Resistor sense */
+
+struct bq27x00_device_info;
+struct bq27x00_access_methods {
+ int (*read)(struct bq27x00_device_info *di, u8 reg, bool single);
+};
+
+enum bq27x00_chip { BQ27000, BQ27500 };
+
+struct bq27x00_reg_cache {
+ int temperature;
+ int time_to_empty;
+ int time_to_empty_avg;
+ int time_to_full;
+ int charge_full;
+ int cycle_count;
+ int capacity;
+ int flags;
+
+ int current_now;
+};
+
+struct bq27x00_device_info {
+ struct device *dev;
+ int id;
+ enum bq27x00_chip chip;
+
+ struct bq27x00_reg_cache cache;
+ int charge_design_full;
+
+ unsigned long last_update;
+ struct delayed_work work;
+
+ struct power_supply bat;
+
+ struct bq27x00_access_methods bus;
+
+ struct mutex lock;
+};
+
+static enum power_supply_property bq27x00_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+};
+
+static unsigned int poll_interval = 360;
+module_param(poll_interval, uint, 0644);
+MODULE_PARM_DESC(poll_interval, "battery poll interval in seconds - " \
+ "0 disables polling");
+
+/*
+ * Common code for BQ27x00 devices
+ */
+
+static inline int bq27x00_read(struct bq27x00_device_info *di, u8 reg,
+ bool single)
+{
+ return di->bus.read(di, reg, single);
+}
+
+/*
+ * Return the battery Relative State-of-Charge
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_rsoc(struct bq27x00_device_info *di)
+{
+ int rsoc;
+
+ if (di->chip == BQ27500)
+ rsoc = bq27x00_read(di, BQ27500_REG_SOC, false);
+ else
+ rsoc = bq27x00_read(di, BQ27000_REG_RSOC, true);
+
+ if (rsoc < 0)
+ dev_err(di->dev, "error reading relative State-of-Charge\n");
+
+ return rsoc;
+}
+
+/*
+ * Return a battery charge value in µAh
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_charge(struct bq27x00_device_info *di, u8 reg)
+{
+ int charge;
+
+ charge = bq27x00_read(di, reg, false);
+ if (charge < 0) {
+ dev_err(di->dev, "error reading nominal available capacity\n");
+ return charge;
+ }
+
+ if (di->chip == BQ27500)
+ charge *= 1000;
+ else
+ charge = charge * 3570 / BQ27000_RS;
+
+ return charge;
+}
+
+/*
+ * Return the battery Nominal available capaciy in µAh
+ * Or < 0 if something fails.
+ */
+static inline int bq27x00_battery_read_nac(struct bq27x00_device_info *di)
+{
+ return bq27x00_battery_read_charge(di, BQ27x00_REG_NAC);
+}
+
+/*
+ * Return the battery Last measured discharge in µAh
+ * Or < 0 if something fails.
+ */
+static inline int bq27x00_battery_read_lmd(struct bq27x00_device_info *di)
+{
+ return bq27x00_battery_read_charge(di, BQ27x00_REG_LMD);
+}
+
+/*
+ * Return the battery Initial last measured discharge in µAh
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di)
+{
+ int ilmd;
+
+ if (di->chip == BQ27500)
+ ilmd = bq27x00_read(di, BQ27500_REG_DCAP, false);
+ else
+ ilmd = bq27x00_read(di, BQ27000_REG_ILMD, true);
+
+ if (ilmd < 0) {
+ dev_err(di->dev, "error reading initial last measured discharge\n");
+ return ilmd;
+ }
+
+ if (di->chip == BQ27500)
+ ilmd *= 1000;
+ else
+ ilmd = ilmd * 256 * 3570 / BQ27000_RS;
+
+ return ilmd;
+}
+
+/*
+ * Return the battery Cycle count total
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_cyct(struct bq27x00_device_info *di)
+{
+ int cyct;
+
+ cyct = bq27x00_read(di, BQ27x00_REG_CYCT, false);
+ if (cyct < 0)
+ dev_err(di->dev, "error reading cycle count total\n");
+
+ return cyct;
+}
+
+/*
+ * Read a time register.
+ * Return < 0 if something fails.
+ */
+static int bq27x00_battery_read_time(struct bq27x00_device_info *di, u8 reg)
+{
+ int tval;
+
+ tval = bq27x00_read(di, reg, false);
+ if (tval < 0) {
+ dev_err(di->dev, "error reading register %02x: %d\n", reg, tval);
+ return tval;
+ }
+
+ if (tval == 65535)
+ return -ENODATA;
+
+ return tval * 60;
+}
+
+static void bq27x00_update(struct bq27x00_device_info *di)
+{
+ struct bq27x00_reg_cache cache = {0, };
+ bool is_bq27500 = di->chip == BQ27500;
+
+ cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, is_bq27500);
+ if (cache.flags >= 0) {
+ cache.capacity = bq27x00_battery_read_rsoc(di);
+ cache.temperature = bq27x00_read(di, BQ27x00_REG_TEMP, false);
+ cache.time_to_empty = bq27x00_battery_read_time(di, BQ27x00_REG_TTE);
+ cache.time_to_empty_avg = bq27x00_battery_read_time(di, BQ27x00_REG_TTECP);
+ cache.time_to_full = bq27x00_battery_read_time(di, BQ27x00_REG_TTF);
+ cache.charge_full = bq27x00_battery_read_lmd(di);
+ cache.cycle_count = bq27x00_battery_read_cyct(di);
+
+ if (!is_bq27500)
+ cache.current_now = bq27x00_read(di, BQ27x00_REG_AI, false);
+
+ /* We only have to read charge design full once */
+ if (di->charge_design_full <= 0)
+ di->charge_design_full = bq27x00_battery_read_ilmd(di);
+ }
+
+ /* Ignore current_now which is a snapshot of the current battery state
+ * and is likely to be different even between two consecutive reads */
+ if (memcmp(&di->cache, &cache, sizeof(cache) - sizeof(int)) != 0) {
+ di->cache = cache;
+ power_supply_changed(&di->bat);
+ }
+
+ di->last_update = jiffies;
+}
+
+static void bq27x00_battery_poll(struct work_struct *work)
+{
+ struct bq27x00_device_info *di =
+ container_of(work, struct bq27x00_device_info, work.work);
+
+ bq27x00_update(di);
+
+ if (poll_interval > 0) {
+ /* The timer does not have to be accurate. */
+ set_timer_slack(&di->work.timer, poll_interval * HZ / 4);
+ schedule_delayed_work(&di->work, poll_interval * HZ);
+ }
+}
+
+
+/*
+ * Return the battery temperature in tenths of degree Celsius
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_temperature(struct bq27x00_device_info *di,
+ union power_supply_propval *val)
+{
+ if (di->cache.temperature < 0)
+ return di->cache.temperature;
+
+ if (di->chip == BQ27500)
+ val->intval = di->cache.temperature - 2731;
+ else
+ val->intval = ((di->cache.temperature * 5) - 5463) / 2;
+
+ return 0;
+}
+
+/*
+ * Return the battery average current in µA
+ * Note that current can be negative signed as well
+ * Or 0 if something fails.
+ */
+static int bq27x00_battery_current(struct bq27x00_device_info *di,
+ union power_supply_propval *val)
+{
+ int curr;
+
+ if (di->chip == BQ27500)
+ curr = bq27x00_read(di, BQ27x00_REG_AI, false);
+ else
+ curr = di->cache.current_now;
+
+ if (curr < 0)
+ return curr;
+
+ if (di->chip == BQ27500) {
+ /* bq27500 returns signed value */
+ val->intval = (int)((s16)curr) * 1000;
+ } else {
+ if (di->cache.flags & BQ27000_FLAG_CHGS) {
+ dev_dbg(di->dev, "negative current!\n");
+ curr = -curr;
+ }
+
+ val->intval = curr * 3570 / BQ27000_RS;
+ }
+
+ return 0;
+}
+
+static int bq27x00_battery_status(struct bq27x00_device_info *di,
+ union power_supply_propval *val)
+{
+ int status;
+
+ if (di->chip == BQ27500) {
+ if (di->cache.flags & BQ27500_FLAG_FC)
+ status = POWER_SUPPLY_STATUS_FULL;
+ else if (di->cache.flags & BQ27500_FLAG_DSC)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ if (di->cache.flags & BQ27000_FLAG_FC)
+ status = POWER_SUPPLY_STATUS_FULL;
+ else if (di->cache.flags & BQ27000_FLAG_CHGS)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else if (power_supply_am_i_supplied(&di->bat))
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ val->intval = status;
+
+ return 0;
+}
+
+/*
+ * Return the battery Voltage in milivolts
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_voltage(struct bq27x00_device_info *di,
+ union power_supply_propval *val)
+{
+ int volt;
+
+ volt = bq27x00_read(di, BQ27x00_REG_VOLT, false);
+ if (volt < 0)
+ return volt;
+
+ val->intval = volt * 1000;
+
+ return 0;
+}
+
+/*
+ * Return the battery Available energy in µWh
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_energy(struct bq27x00_device_info *di,
+ union power_supply_propval *val)
+{
+ int ae;
+
+ ae = bq27x00_read(di, BQ27x00_REG_AE, false);
+ if (ae < 0) {
+ dev_err(di->dev, "error reading available energy\n");
+ return ae;
+ }
+
+ if (di->chip == BQ27500)
+ ae *= 1000;
+ else
+ ae = ae * 29200 / BQ27000_RS;
+
+ val->intval = ae;
+
+ return 0;
+}
+
+
+static int bq27x00_simple_value(int value,
+ union power_supply_propval *val)
+{
+ if (value < 0)
+ return value;
+
+ val->intval = value;
+
+ return 0;
+}
+
+#define to_bq27x00_device_info(x) container_of((x), \
+ struct bq27x00_device_info, bat);
+
+static int bq27x00_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct bq27x00_device_info *di = to_bq27x00_device_info(psy);
+
+ mutex_lock(&di->lock);
+ if (time_is_before_jiffies(di->last_update + 5 * HZ)) {
+ cancel_delayed_work_sync(&di->work);
+ bq27x00_battery_poll(&di->work.work);
+ }
+ mutex_unlock(&di->lock);
+
+ if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0)
+ return -ENODEV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = bq27x00_battery_status(di, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = bq27x00_battery_voltage(di, val);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = di->cache.flags < 0 ? 0 : 1;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = bq27x00_battery_current(di, val);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = bq27x00_simple_value(di->cache.capacity, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = bq27x00_battery_temperature(di, val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ ret = bq27x00_simple_value(di->cache.time_to_empty, val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ ret = bq27x00_simple_value(di->cache.time_to_empty_avg, val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ ret = bq27x00_simple_value(di->cache.time_to_full, val);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = bq27x00_simple_value(bq27x00_battery_read_nac(di), val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = bq27x00_simple_value(di->cache.charge_full, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ ret = bq27x00_simple_value(di->charge_design_full, val);
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ ret = bq27x00_simple_value(di->cache.cycle_count, val);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ ret = bq27x00_battery_energy(di, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static void bq27x00_external_power_changed(struct power_supply *psy)
+{
+ struct bq27x00_device_info *di = to_bq27x00_device_info(psy);
+
+ cancel_delayed_work_sync(&di->work);
+ schedule_delayed_work(&di->work, 0);
+}
+
+static int bq27x00_powersupply_init(struct bq27x00_device_info *di)
+{
+ int ret;
+
+ di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->bat.properties = bq27x00_battery_props;
+ di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props);
+ di->bat.get_property = bq27x00_battery_get_property;
+ di->bat.external_power_changed = bq27x00_external_power_changed;
+
+ INIT_DELAYED_WORK(&di->work, bq27x00_battery_poll);
+ mutex_init(&di->lock);
+
+ ret = power_supply_register(di->dev, &di->bat);
+ if (ret) {
+ dev_err(di->dev, "failed to register battery: %d\n", ret);
+ return ret;
+ }
+
+ dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION);
+
+ bq27x00_update(di);
+
+ return 0;
+}
+
+static void bq27x00_powersupply_unregister(struct bq27x00_device_info *di)
+{
+ cancel_delayed_work_sync(&di->work);
+
+ power_supply_unregister(&di->bat);
+
+ mutex_destroy(&di->lock);
+}
+
+
+/* i2c specific code */
+#ifdef CONFIG_BATTERY_BQ27X00_I2C
+
+/* If the system has several batteries we need a different name for each
+ * of them...
+ */
+static DEFINE_IDR(battery_id);
+static DEFINE_MUTEX(battery_mutex);
+
+static int bq27x00_read_i2c(struct bq27x00_device_info *di, u8 reg, bool single)
+{
+ struct i2c_client *client = to_i2c_client(di->dev);
+ struct i2c_msg msg[2];
+ unsigned char data[2];
+ int ret;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = &reg;
+ msg[0].len = sizeof(reg);
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = data;
+ if (single)
+ msg[1].len = 1;
+ else
+ msg[1].len = 2;
+
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret < 0)
+ return ret;
+
+ if (!single)
+ ret = get_unaligned_le16(data);
+ else
+ ret = data[0];
+
+ return ret;
+}
+
+static int bq27x00_battery_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ char *name;
+ struct bq27x00_device_info *di;
+ int num;
+ int retval = 0;
+
+ /* Get new ID for the new battery device */
+ retval = idr_pre_get(&battery_id, GFP_KERNEL);
+ if (retval == 0)
+ return -ENOMEM;
+ mutex_lock(&battery_mutex);
+ retval = idr_get_new(&battery_id, client, &num);
+ mutex_unlock(&battery_mutex);
+ if (retval < 0)
+ return retval;
+
+ name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
+ if (!name) {
+ dev_err(&client->dev, "failed to allocate device name\n");
+ retval = -ENOMEM;
+ goto batt_failed_1;
+ }
+
+ di = kzalloc(sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ dev_err(&client->dev, "failed to allocate device info data\n");
+ retval = -ENOMEM;
+ goto batt_failed_2;
+ }
+
+ di->id = num;
+ di->dev = &client->dev;
+ di->chip = id->driver_data;
+ di->bat.name = name;
+ di->bus.read = &bq27x00_read_i2c;
+
+ if (bq27x00_powersupply_init(di))
+ goto batt_failed_3;
+
+ i2c_set_clientdata(client, di);
+
+ return 0;
+
+batt_failed_3:
+ kfree(di);
+batt_failed_2:
+ kfree(name);
+batt_failed_1:
+ mutex_lock(&battery_mutex);
+ idr_remove(&battery_id, num);
+ mutex_unlock(&battery_mutex);
+
+ return retval;
+}
+
+static int bq27x00_battery_remove(struct i2c_client *client)
+{
+ struct bq27x00_device_info *di = i2c_get_clientdata(client);
+
+ bq27x00_powersupply_unregister(di);
+
+ kfree(di->bat.name);
+
+ mutex_lock(&battery_mutex);
+ idr_remove(&battery_id, di->id);
+ mutex_unlock(&battery_mutex);
+
+ kfree(di);
+
+ return 0;
+}
+
+static const struct i2c_device_id bq27x00_id[] = {
+ { "bq27200", BQ27000 }, /* bq27200 is same as bq27000, but with i2c */
+ { "bq27500", BQ27500 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, bq27x00_id);
+
+static struct i2c_driver bq27x00_battery_driver = {
+ .driver = {
+ .name = "bq27x00-battery",
+ },
+ .probe = bq27x00_battery_probe,
+ .remove = bq27x00_battery_remove,
+ .id_table = bq27x00_id,
+};
+
+static inline int bq27x00_battery_i2c_init(void)
+{
+ int ret = i2c_add_driver(&bq27x00_battery_driver);
+ if (ret)
+ printk(KERN_ERR "Unable to register BQ27x00 i2c driver\n");
+
+ return ret;
+}
+
+static inline void bq27x00_battery_i2c_exit(void)
+{
+ i2c_del_driver(&bq27x00_battery_driver);
+}
+
+#else
+
+static inline int bq27x00_battery_i2c_init(void) { return 0; }
+static inline void bq27x00_battery_i2c_exit(void) {};
+
+#endif
+
+/* platform specific code */
+#ifdef CONFIG_BATTERY_BQ27X00_PLATFORM
+
+static int bq27000_read_platform(struct bq27x00_device_info *di, u8 reg,
+ bool single)
+{
+ struct device *dev = di->dev;
+ struct bq27000_platform_data *pdata = dev->platform_data;
+ unsigned int timeout = 3;
+ int upper, lower;
+ int temp;
+
+ if (!single) {
+ /* Make sure the value has not changed in between reading the
+ * lower and the upper part */
+ upper = pdata->read(dev, reg + 1);
+ do {
+ temp = upper;
+ if (upper < 0)
+ return upper;
+
+ lower = pdata->read(dev, reg);
+ if (lower < 0)
+ return lower;
+
+ upper = pdata->read(dev, reg + 1);
+ } while (temp != upper && --timeout);
+
+ if (timeout == 0)
+ return -EIO;
+
+ return (upper << 8) | lower;
+ }
+
+ return pdata->read(dev, reg);
+}
+
+static int __devinit bq27000_battery_probe(struct platform_device *pdev)
+{
+ struct bq27x00_device_info *di;
+ struct bq27000_platform_data *pdata = pdev->dev.platform_data;
+ int ret;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform_data supplied\n");
+ return -EINVAL;
+ }
+
+ if (!pdata->read) {
+ dev_err(&pdev->dev, "no hdq read callback supplied\n");
+ return -EINVAL;
+ }
+
+ di = kzalloc(sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ dev_err(&pdev->dev, "failed to allocate device info data\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ di->dev = &pdev->dev;
+ di->chip = BQ27000;
+
+ di->bat.name = pdata->name ?: dev_name(&pdev->dev);
+ di->bus.read = &bq27000_read_platform;
+
+ ret = bq27x00_powersupply_init(di);
+ if (ret)
+ goto err_free;
+
+ return 0;
+
+err_free:
+ platform_set_drvdata(pdev, NULL);
+ kfree(di);
+
+ return ret;
+}
+
+static int __devexit bq27000_battery_remove(struct platform_device *pdev)
+{
+ struct bq27x00_device_info *di = platform_get_drvdata(pdev);
+
+ bq27x00_powersupply_unregister(di);
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(di);
+
+ return 0;
+}
+
+static struct platform_driver bq27000_battery_driver = {
+ .probe = bq27000_battery_probe,
+ .remove = __devexit_p(bq27000_battery_remove),
+ .driver = {
+ .name = "bq27000-battery",
+ .owner = THIS_MODULE,
+ },
+};
+
+static inline int bq27x00_battery_platform_init(void)
+{
+ int ret = platform_driver_register(&bq27000_battery_driver);
+ if (ret)
+ printk(KERN_ERR "Unable to register BQ27000 platform driver\n");
+
+ return ret;
+}
+
+static inline void bq27x00_battery_platform_exit(void)
+{
+ platform_driver_unregister(&bq27000_battery_driver);
+}
+
+#else
+
+static inline int bq27x00_battery_platform_init(void) { return 0; }
+static inline void bq27x00_battery_platform_exit(void) {};
+
+#endif
+
+/*
+ * Module stuff
+ */
+
+static int __init bq27x00_battery_init(void)
+{
+ int ret;
+
+ ret = bq27x00_battery_i2c_init();
+ if (ret)
+ return ret;
+
+ ret = bq27x00_battery_platform_init();
+ if (ret)
+ bq27x00_battery_i2c_exit();
+
+ return ret;
+}
+module_init(bq27x00_battery_init);
+
+static void __exit bq27x00_battery_exit(void)
+{
+ bq27x00_battery_platform_exit();
+ bq27x00_battery_i2c_exit();
+}
+module_exit(bq27x00_battery_exit);
+
+MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
+MODULE_DESCRIPTION("BQ27x00 battery monitor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/collie_battery.c b/drivers/power/collie_battery.c
new file mode 100644
index 00000000..548d263b
--- /dev/null
+++ b/drivers/power/collie_battery.c
@@ -0,0 +1,417 @@
+/*
+ * Battery and Power Management code for the Sharp SL-5x00
+ *
+ * Copyright (C) 2009 Thomas Kunze
+ *
+ * based on tosa_battery.c
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/mfd/ucb1x00.h>
+
+#include <asm/mach/sharpsl_param.h>
+#include <asm/mach-types.h>
+#include <mach/collie.h>
+
+static DEFINE_MUTEX(bat_lock); /* protects gpio pins */
+static struct work_struct bat_work;
+static struct ucb1x00 *ucb;
+
+struct collie_bat {
+ int status;
+ struct power_supply psy;
+ int full_chrg;
+
+ struct mutex work_lock; /* protects data */
+
+ bool (*is_present)(struct collie_bat *bat);
+ int gpio_full;
+ int gpio_charge_on;
+
+ int technology;
+
+ int gpio_bat;
+ int adc_bat;
+ int adc_bat_divider;
+ int bat_max;
+ int bat_min;
+
+ int gpio_temp;
+ int adc_temp;
+ int adc_temp_divider;
+};
+
+static struct collie_bat collie_bat_main;
+
+static unsigned long collie_read_bat(struct collie_bat *bat)
+{
+ unsigned long value = 0;
+
+ if (bat->gpio_bat < 0 || bat->adc_bat < 0)
+ return 0;
+ mutex_lock(&bat_lock);
+ gpio_set_value(bat->gpio_bat, 1);
+ msleep(5);
+ ucb1x00_adc_enable(ucb);
+ value = ucb1x00_adc_read(ucb, bat->adc_bat, UCB_SYNC);
+ ucb1x00_adc_disable(ucb);
+ gpio_set_value(bat->gpio_bat, 0);
+ mutex_unlock(&bat_lock);
+ value = value * 1000000 / bat->adc_bat_divider;
+
+ return value;
+}
+
+static unsigned long collie_read_temp(struct collie_bat *bat)
+{
+ unsigned long value = 0;
+ if (bat->gpio_temp < 0 || bat->adc_temp < 0)
+ return 0;
+
+ mutex_lock(&bat_lock);
+ gpio_set_value(bat->gpio_temp, 1);
+ msleep(5);
+ ucb1x00_adc_enable(ucb);
+ value = ucb1x00_adc_read(ucb, bat->adc_temp, UCB_SYNC);
+ ucb1x00_adc_disable(ucb);
+ gpio_set_value(bat->gpio_temp, 0);
+ mutex_unlock(&bat_lock);
+
+ value = value * 10000 / bat->adc_temp_divider;
+
+ return value;
+}
+
+static int collie_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct collie_bat *bat = container_of(psy, struct collie_bat, psy);
+
+ if (bat->is_present && !bat->is_present(bat)
+ && psp != POWER_SUPPLY_PROP_PRESENT) {
+ return -ENODEV;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = bat->status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = bat->technology;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = collie_read_bat(bat);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (bat->full_chrg == -1)
+ val->intval = bat->bat_max;
+ else
+ val->intval = bat->full_chrg;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = bat->bat_max;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = bat->bat_min;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = collie_read_temp(bat);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = bat->is_present ? bat->is_present(bat) : 1;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static void collie_bat_external_power_changed(struct power_supply *psy)
+{
+ schedule_work(&bat_work);
+}
+
+static irqreturn_t collie_bat_gpio_isr(int irq, void *data)
+{
+ pr_info("collie_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq)));
+ schedule_work(&bat_work);
+ return IRQ_HANDLED;
+}
+
+static void collie_bat_update(struct collie_bat *bat)
+{
+ int old;
+ struct power_supply *psy = &bat->psy;
+
+ mutex_lock(&bat->work_lock);
+
+ old = bat->status;
+
+ if (bat->is_present && !bat->is_present(bat)) {
+ printk(KERN_NOTICE "%s not present\n", psy->name);
+ bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ bat->full_chrg = -1;
+ } else if (power_supply_am_i_supplied(psy)) {
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ gpio_set_value(bat->gpio_charge_on, 1);
+ mdelay(15);
+ }
+
+ if (gpio_get_value(bat->gpio_full)) {
+ if (old == POWER_SUPPLY_STATUS_CHARGING ||
+ bat->full_chrg == -1)
+ bat->full_chrg = collie_read_bat(bat);
+
+ gpio_set_value(bat->gpio_charge_on, 0);
+ bat->status = POWER_SUPPLY_STATUS_FULL;
+ } else {
+ gpio_set_value(bat->gpio_charge_on, 1);
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ } else {
+ gpio_set_value(bat->gpio_charge_on, 0);
+ bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ if (old != bat->status)
+ power_supply_changed(psy);
+
+ mutex_unlock(&bat->work_lock);
+}
+
+static void collie_bat_work(struct work_struct *work)
+{
+ collie_bat_update(&collie_bat_main);
+}
+
+
+static enum power_supply_property collie_bat_main_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static enum power_supply_property collie_bat_bu_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static struct collie_bat collie_bat_main = {
+ .status = POWER_SUPPLY_STATUS_DISCHARGING,
+ .full_chrg = -1,
+ .psy = {
+ .name = "main-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = collie_bat_main_props,
+ .num_properties = ARRAY_SIZE(collie_bat_main_props),
+ .get_property = collie_bat_get_property,
+ .external_power_changed = collie_bat_external_power_changed,
+ .use_for_apm = 1,
+ },
+
+ .gpio_full = COLLIE_GPIO_CO,
+ .gpio_charge_on = COLLIE_GPIO_CHARGE_ON,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
+
+ .gpio_bat = COLLIE_GPIO_MBAT_ON,
+ .adc_bat = UCB_ADC_INP_AD1,
+ .adc_bat_divider = 155,
+ .bat_max = 4310000,
+ .bat_min = 1551 * 1000000 / 414,
+
+ .gpio_temp = COLLIE_GPIO_TMP_ON,
+ .adc_temp = UCB_ADC_INP_AD0,
+ .adc_temp_divider = 10000,
+};
+
+static struct collie_bat collie_bat_bu = {
+ .status = POWER_SUPPLY_STATUS_UNKNOWN,
+ .full_chrg = -1,
+
+ .psy = {
+ .name = "backup-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = collie_bat_bu_props,
+ .num_properties = ARRAY_SIZE(collie_bat_bu_props),
+ .get_property = collie_bat_get_property,
+ .external_power_changed = collie_bat_external_power_changed,
+ },
+
+ .gpio_full = -1,
+ .gpio_charge_on = -1,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LiMn,
+
+ .gpio_bat = COLLIE_GPIO_BBAT_ON,
+ .adc_bat = UCB_ADC_INP_AD1,
+ .adc_bat_divider = 155,
+ .bat_max = 3000000,
+ .bat_min = 1900000,
+
+ .gpio_temp = -1,
+ .adc_temp = -1,
+ .adc_temp_divider = -1,
+};
+
+static struct {
+ int gpio;
+ char *name;
+ bool output;
+ int value;
+} gpios[] = {
+ { COLLIE_GPIO_CO, "main battery full", 0, 0 },
+ { COLLIE_GPIO_MAIN_BAT_LOW, "main battery low", 0, 0 },
+ { COLLIE_GPIO_CHARGE_ON, "main charge on", 1, 0 },
+ { COLLIE_GPIO_MBAT_ON, "main battery", 1, 0 },
+ { COLLIE_GPIO_TMP_ON, "main battery temp", 1, 0 },
+ { COLLIE_GPIO_BBAT_ON, "backup battery", 1, 0 },
+};
+
+#ifdef CONFIG_PM
+static int collie_bat_suspend(struct ucb1x00_dev *dev, pm_message_t state)
+{
+ /* flush all pending status updates */
+ flush_work_sync(&bat_work);
+ return 0;
+}
+
+static int collie_bat_resume(struct ucb1x00_dev *dev)
+{
+ /* things may have changed while we were away */
+ schedule_work(&bat_work);
+ return 0;
+}
+#else
+#define collie_bat_suspend NULL
+#define collie_bat_resume NULL
+#endif
+
+static int __devinit collie_bat_probe(struct ucb1x00_dev *dev)
+{
+ int ret;
+ int i;
+
+ if (!machine_is_collie())
+ return -ENODEV;
+
+ ucb = dev->ucb;
+
+ for (i = 0; i < ARRAY_SIZE(gpios); i++) {
+ ret = gpio_request(gpios[i].gpio, gpios[i].name);
+ if (ret) {
+ i--;
+ goto err_gpio;
+ }
+
+ if (gpios[i].output)
+ ret = gpio_direction_output(gpios[i].gpio,
+ gpios[i].value);
+ else
+ ret = gpio_direction_input(gpios[i].gpio);
+
+ if (ret)
+ goto err_gpio;
+ }
+
+ mutex_init(&collie_bat_main.work_lock);
+
+ INIT_WORK(&bat_work, collie_bat_work);
+
+ ret = power_supply_register(&dev->ucb->dev, &collie_bat_main.psy);
+ if (ret)
+ goto err_psy_reg_main;
+ ret = power_supply_register(&dev->ucb->dev, &collie_bat_bu.psy);
+ if (ret)
+ goto err_psy_reg_bu;
+
+ ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO),
+ collie_bat_gpio_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "main full", &collie_bat_main);
+ if (!ret) {
+ schedule_work(&bat_work);
+ return 0;
+ }
+ power_supply_unregister(&collie_bat_bu.psy);
+err_psy_reg_bu:
+ power_supply_unregister(&collie_bat_main.psy);
+err_psy_reg_main:
+
+ /* see comment in collie_bat_remove */
+ cancel_work_sync(&bat_work);
+
+ i--;
+err_gpio:
+ for (; i >= 0; i--)
+ gpio_free(gpios[i].gpio);
+
+ return ret;
+}
+
+static void __devexit collie_bat_remove(struct ucb1x00_dev *dev)
+{
+ int i;
+
+ free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main);
+
+ power_supply_unregister(&collie_bat_bu.psy);
+ power_supply_unregister(&collie_bat_main.psy);
+
+ /*
+ * Now cancel the bat_work. We won't get any more schedules,
+ * since all sources (isr and external_power_changed) are
+ * unregistered now.
+ */
+ cancel_work_sync(&bat_work);
+
+ for (i = ARRAY_SIZE(gpios) - 1; i >= 0; i--)
+ gpio_free(gpios[i].gpio);
+}
+
+static struct ucb1x00_driver collie_bat_driver = {
+ .add = collie_bat_probe,
+ .remove = __devexit_p(collie_bat_remove),
+ .suspend = collie_bat_suspend,
+ .resume = collie_bat_resume,
+};
+
+static int __init collie_bat_init(void)
+{
+ return ucb1x00_register_driver(&collie_bat_driver);
+}
+
+static void __exit collie_bat_exit(void)
+{
+ ucb1x00_unregister_driver(&collie_bat_driver);
+}
+
+module_init(collie_bat_init);
+module_exit(collie_bat_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Thomas Kunze");
+MODULE_DESCRIPTION("Collie battery driver");
diff --git a/drivers/power/da9030_battery.c b/drivers/power/da9030_battery.c
new file mode 100644
index 00000000..d2c793cf
--- /dev/null
+++ b/drivers/power/da9030_battery.c
@@ -0,0 +1,606 @@
+/*
+ * Battery charger driver for Dialog Semiconductor DA9030
+ *
+ * Copyright (C) 2008 Compulab, Ltd.
+ * Mike Rapoport <mike@compulab.co.il>
+ *
+ * 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/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/da903x.h>
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#define DA9030_FAULT_LOG 0x0a
+#define DA9030_FAULT_LOG_OVER_TEMP (1 << 7)
+#define DA9030_FAULT_LOG_VBAT_OVER (1 << 4)
+
+#define DA9030_CHARGE_CONTROL 0x28
+#define DA9030_CHRG_CHARGER_ENABLE (1 << 7)
+
+#define DA9030_ADC_MAN_CONTROL 0x30
+#define DA9030_ADC_TBATREF_ENABLE (1 << 5)
+#define DA9030_ADC_LDO_INT_ENABLE (1 << 4)
+
+#define DA9030_ADC_AUTO_CONTROL 0x31
+#define DA9030_ADC_TBAT_ENABLE (1 << 5)
+#define DA9030_ADC_VBAT_IN_TXON (1 << 4)
+#define DA9030_ADC_VCH_ENABLE (1 << 3)
+#define DA9030_ADC_ICH_ENABLE (1 << 2)
+#define DA9030_ADC_VBAT_ENABLE (1 << 1)
+#define DA9030_ADC_AUTO_SLEEP_ENABLE (1 << 0)
+
+#define DA9030_VBATMON 0x32
+#define DA9030_VBATMONTXON 0x33
+#define DA9030_TBATHIGHP 0x34
+#define DA9030_TBATHIGHN 0x35
+#define DA9030_TBATLOW 0x36
+
+#define DA9030_VBAT_RES 0x41
+#define DA9030_VBATMIN_RES 0x42
+#define DA9030_VBATMINTXON_RES 0x43
+#define DA9030_ICHMAX_RES 0x44
+#define DA9030_ICHMIN_RES 0x45
+#define DA9030_ICHAVERAGE_RES 0x46
+#define DA9030_VCHMAX_RES 0x47
+#define DA9030_VCHMIN_RES 0x48
+#define DA9030_TBAT_RES 0x49
+
+struct da9030_adc_res {
+ uint8_t vbat_res;
+ uint8_t vbatmin_res;
+ uint8_t vbatmintxon;
+ uint8_t ichmax_res;
+ uint8_t ichmin_res;
+ uint8_t ichaverage_res;
+ uint8_t vchmax_res;
+ uint8_t vchmin_res;
+ uint8_t tbat_res;
+ uint8_t adc_in4_res;
+ uint8_t adc_in5_res;
+};
+
+struct da9030_battery_thresholds {
+ int tbat_low;
+ int tbat_high;
+ int tbat_restart;
+
+ int vbat_low;
+ int vbat_crit;
+ int vbat_charge_start;
+ int vbat_charge_stop;
+ int vbat_charge_restart;
+
+ int vcharge_min;
+ int vcharge_max;
+};
+
+struct da9030_charger {
+ struct power_supply psy;
+
+ struct device *master;
+
+ struct da9030_adc_res adc;
+ struct delayed_work work;
+ unsigned int interval;
+
+ struct power_supply_info *battery_info;
+
+ struct da9030_battery_thresholds thresholds;
+
+ unsigned int charge_milliamp;
+ unsigned int charge_millivolt;
+
+ /* charger status */
+ bool chdet;
+ uint8_t fault;
+ int mA;
+ int mV;
+ bool is_on;
+
+ struct notifier_block nb;
+
+ /* platform callbacks for battery low and critical events */
+ void (*battery_low)(void);
+ void (*battery_critical)(void);
+
+ struct dentry *debug_file;
+};
+
+static inline int da9030_reg_to_mV(int reg)
+{
+ return ((reg * 2650) >> 8) + 2650;
+}
+
+static inline int da9030_millivolt_to_reg(int mV)
+{
+ return ((mV - 2650) << 8) / 2650;
+}
+
+static inline int da9030_reg_to_mA(int reg)
+{
+ return ((reg * 24000) >> 8) / 15;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int bat_debug_show(struct seq_file *s, void *data)
+{
+ struct da9030_charger *charger = s->private;
+
+ seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off");
+ if (charger->chdet) {
+ seq_printf(s, "iset = %dmA, vset = %dmV\n",
+ charger->mA, charger->mV);
+ }
+
+ seq_printf(s, "vbat_res = %d (%dmV)\n",
+ charger->adc.vbat_res,
+ da9030_reg_to_mV(charger->adc.vbat_res));
+ seq_printf(s, "vbatmin_res = %d (%dmV)\n",
+ charger->adc.vbatmin_res,
+ da9030_reg_to_mV(charger->adc.vbatmin_res));
+ seq_printf(s, "vbatmintxon = %d (%dmV)\n",
+ charger->adc.vbatmintxon,
+ da9030_reg_to_mV(charger->adc.vbatmintxon));
+ seq_printf(s, "ichmax_res = %d (%dmA)\n",
+ charger->adc.ichmax_res,
+ da9030_reg_to_mV(charger->adc.ichmax_res));
+ seq_printf(s, "ichmin_res = %d (%dmA)\n",
+ charger->adc.ichmin_res,
+ da9030_reg_to_mA(charger->adc.ichmin_res));
+ seq_printf(s, "ichaverage_res = %d (%dmA)\n",
+ charger->adc.ichaverage_res,
+ da9030_reg_to_mA(charger->adc.ichaverage_res));
+ seq_printf(s, "vchmax_res = %d (%dmV)\n",
+ charger->adc.vchmax_res,
+ da9030_reg_to_mA(charger->adc.vchmax_res));
+ seq_printf(s, "vchmin_res = %d (%dmV)\n",
+ charger->adc.vchmin_res,
+ da9030_reg_to_mV(charger->adc.vchmin_res));
+
+ return 0;
+}
+
+static int debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, bat_debug_show, inode->i_private);
+}
+
+static const struct file_operations bat_debug_fops = {
+ .open = debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger)
+{
+ charger->debug_file = debugfs_create_file("charger", 0666, 0, charger,
+ &bat_debug_fops);
+ return charger->debug_file;
+}
+
+static void da9030_bat_remove_debugfs(struct da9030_charger *charger)
+{
+ debugfs_remove(charger->debug_file);
+}
+#else
+static inline struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger)
+{
+ return NULL;
+}
+static inline void da9030_bat_remove_debugfs(struct da9030_charger *charger)
+{
+}
+#endif
+
+static inline void da9030_read_adc(struct da9030_charger *charger,
+ struct da9030_adc_res *adc)
+{
+ da903x_reads(charger->master, DA9030_VBAT_RES,
+ sizeof(*adc), (uint8_t *)adc);
+}
+
+static void da9030_charger_update_state(struct da9030_charger *charger)
+{
+ uint8_t val;
+
+ da903x_read(charger->master, DA9030_CHARGE_CONTROL, &val);
+ charger->is_on = (val & DA9030_CHRG_CHARGER_ENABLE) ? 1 : 0;
+ charger->mA = ((val >> 3) & 0xf) * 100;
+ charger->mV = (val & 0x7) * 50 + 4000;
+
+ da9030_read_adc(charger, &charger->adc);
+ da903x_read(charger->master, DA9030_FAULT_LOG, &charger->fault);
+ charger->chdet = da903x_query_status(charger->master,
+ DA9030_STATUS_CHDET);
+}
+
+static void da9030_set_charge(struct da9030_charger *charger, int on)
+{
+ uint8_t val;
+
+ if (on) {
+ val = DA9030_CHRG_CHARGER_ENABLE;
+ val |= (charger->charge_milliamp / 100) << 3;
+ val |= (charger->charge_millivolt - 4000) / 50;
+ charger->is_on = 1;
+ } else {
+ val = 0;
+ charger->is_on = 0;
+ }
+
+ da903x_write(charger->master, DA9030_CHARGE_CONTROL, val);
+
+ power_supply_changed(&charger->psy);
+}
+
+static void da9030_charger_check_state(struct da9030_charger *charger)
+{
+ da9030_charger_update_state(charger);
+
+ /* we wake or boot with external power on */
+ if (!charger->is_on) {
+ if ((charger->chdet) &&
+ (charger->adc.vbat_res <
+ charger->thresholds.vbat_charge_start)) {
+ da9030_set_charge(charger, 1);
+ }
+ } else {
+ /* Charger has been pulled out */
+ if (!charger->chdet) {
+ da9030_set_charge(charger, 0);
+ return;
+ }
+
+ if (charger->adc.vbat_res >=
+ charger->thresholds.vbat_charge_stop) {
+ da9030_set_charge(charger, 0);
+ da903x_write(charger->master, DA9030_VBATMON,
+ charger->thresholds.vbat_charge_restart);
+ } else if (charger->adc.vbat_res >
+ charger->thresholds.vbat_low) {
+ /* we are charging and passed LOW_THRESH,
+ so upate DA9030 VBAT threshold
+ */
+ da903x_write(charger->master, DA9030_VBATMON,
+ charger->thresholds.vbat_low);
+ }
+ if (charger->adc.vchmax_res > charger->thresholds.vcharge_max ||
+ charger->adc.vchmin_res < charger->thresholds.vcharge_min ||
+ /* Tempreture readings are negative */
+ charger->adc.tbat_res < charger->thresholds.tbat_high ||
+ charger->adc.tbat_res > charger->thresholds.tbat_low) {
+ /* disable charger */
+ da9030_set_charge(charger, 0);
+ }
+ }
+}
+
+static void da9030_charging_monitor(struct work_struct *work)
+{
+ struct da9030_charger *charger;
+
+ charger = container_of(work, struct da9030_charger, work.work);
+
+ da9030_charger_check_state(charger);
+
+ /* reschedule for the next time */
+ schedule_delayed_work(&charger->work, charger->interval);
+}
+
+static enum power_supply_property da9030_battery_props[] = {
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+};
+
+static void da9030_battery_check_status(struct da9030_charger *charger,
+ union power_supply_propval *val)
+{
+ if (charger->chdet) {
+ if (charger->is_on)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+}
+
+static void da9030_battery_check_health(struct da9030_charger *charger,
+ union power_supply_propval *val)
+{
+ if (charger->fault & DA9030_FAULT_LOG_OVER_TEMP)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (charger->fault & DA9030_FAULT_LOG_VBAT_OVER)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static int da9030_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct da9030_charger *charger;
+ charger = container_of(psy, struct da9030_charger, psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ da9030_battery_check_status(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ da9030_battery_check_health(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = charger->battery_info->technology;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = charger->battery_info->voltage_max_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = charger->battery_info->voltage_min_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = da9030_reg_to_mV(charger->adc.vbat_res) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval =
+ da9030_reg_to_mA(charger->adc.ichaverage_res) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = charger->battery_info->name;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void da9030_battery_vbat_event(struct da9030_charger *charger)
+{
+ da9030_read_adc(charger, &charger->adc);
+
+ if (charger->is_on)
+ return;
+
+ if (charger->adc.vbat_res < charger->thresholds.vbat_low) {
+ /* set VBAT threshold for critical */
+ da903x_write(charger->master, DA9030_VBATMON,
+ charger->thresholds.vbat_crit);
+ if (charger->battery_low)
+ charger->battery_low();
+ } else if (charger->adc.vbat_res <
+ charger->thresholds.vbat_crit) {
+ /* notify the system of battery critical */
+ if (charger->battery_critical)
+ charger->battery_critical();
+ }
+}
+
+static int da9030_battery_event(struct notifier_block *nb, unsigned long event,
+ void *data)
+{
+ struct da9030_charger *charger =
+ container_of(nb, struct da9030_charger, nb);
+
+ switch (event) {
+ case DA9030_EVENT_CHDET:
+ cancel_delayed_work_sync(&charger->work);
+ schedule_work(&charger->work.work);
+ break;
+ case DA9030_EVENT_VBATMON:
+ da9030_battery_vbat_event(charger);
+ break;
+ case DA9030_EVENT_CHIOVER:
+ case DA9030_EVENT_TBAT:
+ da9030_set_charge(charger, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static void da9030_battery_convert_thresholds(struct da9030_charger *charger,
+ struct da9030_battery_info *pdata)
+{
+ charger->thresholds.tbat_low = pdata->tbat_low;
+ charger->thresholds.tbat_high = pdata->tbat_high;
+ charger->thresholds.tbat_restart = pdata->tbat_restart;
+
+ charger->thresholds.vbat_low =
+ da9030_millivolt_to_reg(pdata->vbat_low);
+ charger->thresholds.vbat_crit =
+ da9030_millivolt_to_reg(pdata->vbat_crit);
+ charger->thresholds.vbat_charge_start =
+ da9030_millivolt_to_reg(pdata->vbat_charge_start);
+ charger->thresholds.vbat_charge_stop =
+ da9030_millivolt_to_reg(pdata->vbat_charge_stop);
+ charger->thresholds.vbat_charge_restart =
+ da9030_millivolt_to_reg(pdata->vbat_charge_restart);
+
+ charger->thresholds.vcharge_min =
+ da9030_millivolt_to_reg(pdata->vcharge_min);
+ charger->thresholds.vcharge_max =
+ da9030_millivolt_to_reg(pdata->vcharge_max);
+}
+
+static void da9030_battery_setup_psy(struct da9030_charger *charger)
+{
+ struct power_supply *psy = &charger->psy;
+ struct power_supply_info *info = charger->battery_info;
+
+ psy->name = info->name;
+ psy->use_for_apm = info->use_for_apm;
+ psy->type = POWER_SUPPLY_TYPE_BATTERY;
+ psy->get_property = da9030_battery_get_property;
+
+ psy->properties = da9030_battery_props;
+ psy->num_properties = ARRAY_SIZE(da9030_battery_props);
+};
+
+static int da9030_battery_charger_init(struct da9030_charger *charger)
+{
+ char v[5];
+ int ret;
+
+ v[0] = v[1] = charger->thresholds.vbat_low;
+ v[2] = charger->thresholds.tbat_high;
+ v[3] = charger->thresholds.tbat_restart;
+ v[4] = charger->thresholds.tbat_low;
+
+ ret = da903x_writes(charger->master, DA9030_VBATMON, 5, v);
+ if (ret)
+ return ret;
+
+ /*
+ * Enable reference voltage supply for ADC from the LDO_INTERNAL
+ * regulator. Must be set before ADC measurements can be made.
+ */
+ ret = da903x_write(charger->master, DA9030_ADC_MAN_CONTROL,
+ DA9030_ADC_LDO_INT_ENABLE |
+ DA9030_ADC_TBATREF_ENABLE);
+ if (ret)
+ return ret;
+
+ /* enable auto ADC measuremnts */
+ return da903x_write(charger->master, DA9030_ADC_AUTO_CONTROL,
+ DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON |
+ DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE |
+ DA9030_ADC_VBAT_ENABLE |
+ DA9030_ADC_AUTO_SLEEP_ENABLE);
+}
+
+static int da9030_battery_probe(struct platform_device *pdev)
+{
+ struct da9030_charger *charger;
+ struct da9030_battery_info *pdata = pdev->dev.platform_data;
+ int ret;
+
+ if (pdata == NULL)
+ return -EINVAL;
+
+ if (pdata->charge_milliamp >= 1500 ||
+ pdata->charge_millivolt < 4000 ||
+ pdata->charge_millivolt > 4350)
+ return -EINVAL;
+
+ charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+ if (charger == NULL)
+ return -ENOMEM;
+
+ charger->master = pdev->dev.parent;
+
+ /* 10 seconds between monitor runs unless platform defines other
+ interval */
+ charger->interval = msecs_to_jiffies(
+ (pdata->batmon_interval ? : 10) * 1000);
+
+ charger->charge_milliamp = pdata->charge_milliamp;
+ charger->charge_millivolt = pdata->charge_millivolt;
+ charger->battery_info = pdata->battery_info;
+ charger->battery_low = pdata->battery_low;
+ charger->battery_critical = pdata->battery_critical;
+
+ da9030_battery_convert_thresholds(charger, pdata);
+
+ ret = da9030_battery_charger_init(charger);
+ if (ret)
+ goto err_charger_init;
+
+ INIT_DELAYED_WORK(&charger->work, da9030_charging_monitor);
+ schedule_delayed_work(&charger->work, charger->interval);
+
+ charger->nb.notifier_call = da9030_battery_event;
+ ret = da903x_register_notifier(charger->master, &charger->nb,
+ DA9030_EVENT_CHDET |
+ DA9030_EVENT_VBATMON |
+ DA9030_EVENT_CHIOVER |
+ DA9030_EVENT_TBAT);
+ if (ret)
+ goto err_notifier;
+
+ da9030_battery_setup_psy(charger);
+ ret = power_supply_register(&pdev->dev, &charger->psy);
+ if (ret)
+ goto err_ps_register;
+
+ charger->debug_file = da9030_bat_create_debugfs(charger);
+ platform_set_drvdata(pdev, charger);
+ return 0;
+
+err_ps_register:
+ da903x_unregister_notifier(charger->master, &charger->nb,
+ DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON |
+ DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT);
+err_notifier:
+ cancel_delayed_work(&charger->work);
+
+err_charger_init:
+ kfree(charger);
+
+ return ret;
+}
+
+static int da9030_battery_remove(struct platform_device *dev)
+{
+ struct da9030_charger *charger = platform_get_drvdata(dev);
+
+ da9030_bat_remove_debugfs(charger);
+
+ da903x_unregister_notifier(charger->master, &charger->nb,
+ DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON |
+ DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT);
+ cancel_delayed_work_sync(&charger->work);
+ da9030_set_charge(charger, 0);
+ power_supply_unregister(&charger->psy);
+
+ kfree(charger);
+
+ return 0;
+}
+
+static struct platform_driver da903x_battery_driver = {
+ .driver = {
+ .name = "da903x-battery",
+ .owner = THIS_MODULE,
+ },
+ .probe = da9030_battery_probe,
+ .remove = da9030_battery_remove,
+};
+
+static int da903x_battery_init(void)
+{
+ return platform_driver_register(&da903x_battery_driver);
+}
+
+static void da903x_battery_exit(void)
+{
+ platform_driver_unregister(&da903x_battery_driver);
+}
+
+module_init(da903x_battery_init);
+module_exit(da903x_battery_exit);
+
+MODULE_DESCRIPTION("DA9030 battery charger driver");
+MODULE_AUTHOR("Mike Rapoport, CompuLab");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/da9052-battery.c b/drivers/power/da9052-battery.c
new file mode 100644
index 00000000..85561b46
--- /dev/null
+++ b/drivers/power/da9052-battery.c
@@ -0,0 +1,844 @@
+/*
+ * da9052-battery.c -- Batttery Driver for Dialog DA9052
+ *
+ * Copyright(c) 2009 Dialog Semiconductor Ltd.
+
+ * Author: Dialog Semiconductor Ltd <dchen@diasemi.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/uaccess.h>
+#include <linux/jiffies.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/freezer.h>
+
+#include <linux/mfd/da9052/da9052.h>
+#include <linux/mfd/da9052/reg.h>
+#include <linux/mfd/da9052/bat.h>
+#include <linux/mfd/da9052/adc.h>
+
+#define DA9052_BAT_DEVICE_NAME "da9052-bat"
+
+static const char __initdata banner[] = KERN_INFO "DA9052 BAT, (c) \
+2009 Dialog semiconductor Ltd.\n";
+
+static struct da9052_bat_hysteresis bat_hysteresis;
+static struct da9052_bat_event_registration event_status;
+
+
+static u16 array_hys_batvoltage[2];
+static u16 bat_volt_arr[3];
+static u8 hys_flag = FALSE;
+
+static int da9052_read(struct da9052 *da9052, u8 reg_address, u8 *reg_data)
+{
+ struct da9052_ssc_msg msg;
+ int ret;
+
+ msg.addr = reg_address;
+ msg.data = 0;
+
+ da9052_lock(da9052);
+ ret = da9052->read(da9052, &msg);
+ if (ret)
+ goto ssc_comm_err;
+ da9052_unlock(da9052);
+
+ *reg_data = msg.data;
+ return 0;
+ssc_comm_err:
+ da9052_unlock(da9052);
+ return ret;
+}
+
+static s32 da9052_adc_read_ich(struct da9052 *da9052, u16 *data)
+{
+ struct da9052_ssc_msg msg;
+ da9052_lock(da9052);
+ /* Read charging conversion register */
+ msg.addr = DA9052_ICHGAV_REG;
+ msg.data = 0;
+ if (da9052->read(da9052, &msg)) {
+ da9052_unlock(da9052);
+ return DA9052_SSC_FAIL;
+ }
+ da9052_unlock(da9052);
+
+ *data = (u16)msg.data;
+ DA9052_DEBUG(
+ "In function: %s, ICHGAV_REG value read (1)= 0x%X \n",
+ __func__, msg.data);
+ return SUCCESS;
+}
+
+
+static s32 da9052_adc_read_tbat(struct da9052 *da9052, u16 *data)
+{
+ s32 ret;
+ u8 reg_data;
+
+ ret = da9052_read(da9052, DA9052_TBATRES_REG, &reg_data);
+ if (ret)
+ return ret;
+ *data = (u16)reg_data;
+
+ DA9052_DEBUG("In function: %s, TBATRES_REG value read (1)= 0x%X \n",
+ __func__, msg.data);
+ return SUCCESS;
+}
+
+s32 da9052_adc_read_vbat(struct da9052 *da9052, u16 *data)
+{
+ s32 ret;
+
+ ret = da9052_manual_read(da9052, DA9052_ADC_VBAT);
+ DA9052_DEBUG("In function: %s, VBAT value read (1)= 0x%X \n",
+ __func__, temp);
+ if (ret == -EIO) {
+ *data = 0;
+ return ret;
+ } else {
+ *data = ret;
+ return 0;
+ }
+ return 0;
+}
+
+
+static u16 filter_sample(u16 *buffer)
+{
+ u8 count;
+ u16 tempvalue = 0;
+ u16 ret;
+
+ if (buffer == NULL)
+ return -EINVAL;
+
+ for (count = 0; count < DA9052_FILTER_SIZE; count++)
+ tempvalue = tempvalue + *(buffer + count);
+
+ ret = tempvalue/DA9052_FILTER_SIZE;
+ return ret;
+}
+
+static s32 da9052_bat_get_battery_temperature(struct da9052_charger_device
+ *chg_device, u16 *buffer)
+{
+
+ u8 count;
+ u16 filterqueue[DA9052_FILTER_SIZE];
+
+ /* Measure the battery temperature using ADC function.
+ Number of read equal to average filter size*/
+
+ for (count = 0; count < DA9052_FILTER_SIZE; count++)
+ if (da9052_adc_read_tbat(chg_device->da9052, &filterqueue[count]))
+ return -EIO;
+
+ /* Apply Average filter */
+ filterqueue[0] = filter_sample(filterqueue);
+
+ chg_device->bat_temp = filterqueue[0];
+ *buffer = chg_device->bat_temp;
+
+ return SUCCESS;
+}
+
+static s32 da9052_bat_get_chg_current(struct da9052_charger_device
+ *chg_device, u16 *buffer)
+{
+ if (chg_device->status == POWER_SUPPLY_STATUS_DISCHARGING)
+ return -EIO;
+
+ /* Measure the Charger current using ADC function */
+ if (da9052_adc_read_ich(chg_device->da9052, buffer))
+ return -EIO;
+
+ /* Convert the raw value in terms of mA */
+ chg_device->chg_current = ichg_reg_to_mA(*buffer);
+ *buffer = chg_device->chg_current;
+
+ return 0;
+}
+
+
+s32 da9052_bat_get_battery_voltage(struct da9052_charger_device *chg_device,
+ u16 *buffer)
+{
+ u8 count;
+ u16 filterqueue[DA9052_FILTER_SIZE];
+
+ /* Measure the battery voltage using ADC function.
+ Number of read equal to average filter size*/
+ for (count = 0; count < DA9052_FILTER_SIZE; count++)
+ if (da9052_adc_read_vbat(chg_device->da9052, &filterqueue[count]))
+ return -EIO;
+
+ /* Apply average filter */
+ filterqueue[0] = filter_sample(filterqueue);
+
+ /* Convert battery voltage raw value in terms of mV */
+ chg_device->bat_voltage = volt_reg_to_mV(filterqueue[0]);
+ *buffer = chg_device->bat_voltage;
+ return 0;
+}
+
+static void da9052_bat_status_update(struct da9052_charger_device
+ *chg_device)
+{
+ struct da9052_ssc_msg msg;
+ u16 current_value = 0;
+ u16 buffer =0;
+ u8 regvalue = 0;
+ u8 old_status = chg_device->status;
+
+ DA9052_DEBUG("FUNCTION = %s \n", __func__);
+
+ /* Read Status A register */
+ msg.addr = DA9052_STATUSA_REG;
+ msg.data = 0;
+ da9052_lock(chg_device->da9052);
+
+ if (chg_device->da9052->read(chg_device->da9052, &msg)) {
+ DA9052_DEBUG("%s : failed\n", __func__);
+ da9052_unlock(chg_device->da9052);
+ return;
+ }
+ regvalue = msg.data;
+
+ /* Read Status B register */
+ msg.addr = DA9052_STATUSB_REG;
+ msg.data = 0;
+ if (chg_device->da9052->read(chg_device->da9052, &msg)) {
+ DA9052_DEBUG("%s : failed\n", __func__);
+ da9052_unlock(chg_device->da9052);
+ return;
+ }
+ da9052_unlock(chg_device->da9052);
+
+ /* If DCINDET and DCINSEL are set then connected charger is
+ WALL Charger unit */
+ if( (regvalue & DA9052_STATUSA_DCINSEL)
+ && (regvalue & DA9052_STATUSA_DCINDET) ) {
+
+ chg_device->charger_type = DA9052_WALL_CHARGER;
+ }
+ /* If VBUS_DET and VBUSEL are set then connected charger is
+ USB Type */
+ else if((regvalue & DA9052_STATUSA_VBUSSEL)
+ && (regvalue & DA9052_STATUSA_VBUSDET)) {
+ if (regvalue & DA9052_STATUSA_VDATDET) {
+ chg_device->charger_type = DA9052_USB_CHARGER;
+ }
+ else {
+ /* Else it has to be USB Host charger */
+ chg_device->charger_type = DA9052_USB_HUB;
+ }
+ }
+ /* Battery is discharging since charging device is not present */
+ else
+ {
+ chg_device->charger_type = DA9052_NOCHARGER;
+ /* Eqv to DISCHARGING_WITHOUT_CHARGER state */
+ chg_device->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+
+ if( chg_device->charger_type != DA9052_NOCHARGER ) {
+ /* if Charging end flag is set and Charging current is greater
+ than charging end limit then battery is charging */
+ if ((msg.data & DA9052_STATUSB_CHGEND) != 0) {
+
+ if(da9052_bat_get_chg_current(chg_device,&current_value)) {
+ return;
+ }
+
+ if( current_value >= chg_device->chg_end_current ) {
+ chg_device->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ else {
+ /* Eqv to DISCHARGING_WITH_CHARGER state*/
+ chg_device->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+ }
+ /* if Charging end flag is cleared then battery is charging */
+ else {
+ chg_device->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ if( POWER_SUPPLY_STATUS_CHARGING == chg_device->status){
+ if(msg.data != DA9052_STATUSB_CHGPRE) {
+ /* Measure battery voltage. if battery voltage is greater than
+ (VCHG_BAT - VCHG_DROP) then battery is in the termintation mode.
+ */
+ if(da9052_bat_get_battery_voltage(chg_device,&buffer)) {
+ DA9052_DEBUG("%s : failed\n",__FUNCTION__);
+ return ;
+ }
+ if(buffer > (chg_device->bat_target_voltage -
+ chg_device->charger_voltage_drop) &&
+ ( chg_device->cal_capacity >= 99 ) ){
+ chg_device->status = POWER_SUPPLY_STATUS_FULL;
+ }
+
+ }
+ }
+ }
+
+ if(chg_device->illegal)
+ chg_device->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ else if (chg_device->cal_capacity < chg_device->bat_capacity_limit_low)
+ chg_device->health = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ chg_device->health = POWER_SUPPLY_HEALTH_GOOD;
+
+ if ( chg_device->status != old_status)
+ power_supply_changed(&chg_device->psy);
+
+ return;
+}
+
+static s32 da9052_bat_suspend_charging(struct da9052_charger_device *chg_device)
+{
+ struct da9052_ssc_msg msg;
+
+ if ((chg_device->status == POWER_SUPPLY_STATUS_DISCHARGING) ||
+ (chg_device->status == POWER_SUPPLY_STATUS_NOT_CHARGING))
+ return 0;
+
+ msg.addr = DA9052_INPUTCONT_REG;
+ msg.data = 0;
+ da9052_lock(chg_device->da9052);
+ /* Read Input condition register */
+ if (chg_device->da9052->read(chg_device->da9052, &msg)) {
+ da9052_unlock(chg_device->da9052);
+ return DA9052_SSC_FAIL;
+ }
+
+ /* set both Wall charger and USB charger suspend bit */
+ msg.data = set_bits(msg.data, DA9052_INPUTCONT_DCINSUSP);
+ msg.data = set_bits(msg.data, DA9052_INPUTCONT_VBUSSUSP);
+
+ /* Write to Input control register */
+ if (chg_device->da9052->write(chg_device->da9052, &msg)) {
+ da9052_unlock(chg_device->da9052);
+ DA9052_DEBUG("%s : failed\n", __func__);
+ return DA9052_SSC_FAIL;
+ }
+ da9052_unlock(chg_device->da9052);
+
+ DA9052_DEBUG("%s : Sucess\n", __func__);
+ return 0;
+}
+
+u32 interpolated(u32 vbat_lower, u32 vbat_upper, u32 level_lower,
+ u32 level_upper, u32 bat_voltage)
+{
+ s32 temp;
+ /*apply formula y= yk + (x - xk) * (yk+1 -yk)/(xk+1 -xk) */
+ temp = ((level_upper - level_lower) * 1000)/(vbat_upper - vbat_lower);
+ temp = level_lower + (((bat_voltage - vbat_lower) * temp)/1000);
+
+ return temp;
+}
+
+s32 capture_first_correct_vbat_sample(struct da9052_charger_device *chg_device,
+u16 *battery_voltage)
+{
+ static u8 count;
+ s32 ret = 0;
+ u32 temp_data = 0;
+
+ ret = da9052_bat_get_battery_voltage(chg_device,
+ &bat_volt_arr[count]);
+ if (ret)
+ return ret;
+ count++;
+
+ if (count < chg_device->vbat_first_valid_detect_iteration)
+ return FAILURE;
+ for (count = 0; count <
+ (chg_device->vbat_first_valid_detect_iteration - 1);
+ count++) {
+ temp_data = (bat_volt_arr[count] *
+ (chg_device->hysteresis_window_size))/100;
+ bat_hysteresis.upper_limit = bat_volt_arr[count] + temp_data;
+ bat_hysteresis.lower_limit = bat_volt_arr[count] - temp_data;
+
+ if ((bat_volt_arr[count + 1] < bat_hysteresis.upper_limit) &&
+ (bat_volt_arr[count + 1] >
+ bat_hysteresis.lower_limit)) {
+ *battery_voltage = (bat_volt_arr[count] +
+ bat_volt_arr[count+1]) / 2;
+ hys_flag = TRUE;
+ return 0;
+ }
+ }
+
+ for (count = 0; count <
+ (chg_device->vbat_first_valid_detect_iteration - 1);
+ count++)
+ bat_volt_arr[count] = bat_volt_arr[count + 1];
+
+ return FAILURE;
+}
+
+
+s32 check_hystersis(struct da9052_charger_device *chg_device, u16 *bat_voltage)
+{
+ u8 ret = 0;
+ u32 offset = 0;
+
+ /* Measure battery voltage using BAT internal function*/
+ if (hys_flag == FALSE) {
+ ret = capture_first_correct_vbat_sample
+ (chg_device, &array_hys_batvoltage[0]);
+ if (ret)
+ return ret;
+ }
+
+ ret = da9052_bat_get_battery_voltage
+ (chg_device, &array_hys_batvoltage[1]);
+ if (ret)
+ return ret;
+ *bat_voltage = array_hys_batvoltage[1];
+
+#if DA9052_BAT_FILTER_HYS
+ printk(KERN_CRIT "\nBAT_LOG: Previous Battery Voltage = %d mV\n",
+ array_hys_batvoltage[0]);
+ printk(KERN_CRIT "\nBAT_LOG:Battery Voltage Before Filter = %d mV\n",
+ array_hys_batvoltage[1]);
+#endif
+ /* Check if measured battery voltage value is within the hysteresis
+ window limit using measured battey votlage value */
+ if ((bat_hysteresis.upper_limit < *bat_voltage) ||
+ (bat_hysteresis.lower_limit > *bat_voltage)) {
+
+ bat_hysteresis.index++;
+ if (bat_hysteresis.index ==
+ chg_device->hysteresis_no_of_reading) {
+ /* Hysteresis Window is set to +- of
+ HYSTERESIS_WINDOW_SIZE percentage of current VBAT */
+ bat_hysteresis.index = 0;
+ offset = ((*bat_voltage) *
+ chg_device->hysteresis_window_size)/
+ 100;
+ bat_hysteresis.upper_limit = (*bat_voltage) + offset;
+ bat_hysteresis.lower_limit = (*bat_voltage) - offset;
+ } else {
+#if DA9052_BAT_FILTER_HYS
+ printk(KERN_CRIT "CheckHystersis: Failed\n");
+#endif
+ return -EIO;
+ }
+ } else {
+ bat_hysteresis.index = 0;
+ offset = ((*bat_voltage) *
+ chg_device->hysteresis_window_size)/100;
+ bat_hysteresis.upper_limit = (*bat_voltage) + offset;
+ bat_hysteresis.lower_limit = (*bat_voltage) - offset;
+ }
+
+ /* Digital C Filter, formula Yn = k Yn-1 + (1-k) Xn */
+ *bat_voltage = ((chg_device->chg_hysteresis_const *
+ array_hys_batvoltage[0])/100) +
+ (((100 - chg_device->chg_hysteresis_const) *
+ array_hys_batvoltage[1])/100);
+
+ if ((chg_device->status == POWER_SUPPLY_STATUS_DISCHARGING) &&
+ (*bat_voltage > array_hys_batvoltage[0])) {
+ *bat_voltage = array_hys_batvoltage[0];
+ }
+
+ array_hys_batvoltage[0] = *bat_voltage;
+
+#if DA9052_BAT_FILTER_HYS
+ printk(KERN_CRIT "\nBAT_LOG:Battery Voltage After Filter = %d mV\n",\
+ *bat_voltage);
+
+#endif
+ return 0;
+}
+
+u8 select_temperature(u8 temp_index, u16 bat_temperature)
+{
+ u16 temp_temperature = 0;
+ temp_temperature = (temperature_lookup_ref[temp_index] +
+ temperature_lookup_ref[temp_index+1]) / 2;
+
+ if (bat_temperature >= temp_temperature) {
+ temp_index += 1;
+ return temp_index;
+ } else
+ return temp_index;
+}
+
+s32 da9052_bat_level_update(struct da9052_charger_device *chg_device)
+{
+ u16 bat_temperature;
+ u16 bat_voltage;
+ u32 vbat_lower, vbat_upper, level_upper, level_lower, level;
+ u8 access_index = 0;
+ u8 index = 0, ret;
+ u8 flag = FALSE;
+
+ ret = 0;
+ vbat_lower = 0;
+ vbat_upper = 0;
+ level_upper = 0;
+ level_lower = 0;
+
+ ret = check_hystersis(chg_device, &bat_voltage);
+ if (ret)
+ return ret;
+
+ ret = da9052_bat_get_battery_temperature(chg_device,
+ &bat_temperature);
+ if (ret)
+ return ret;
+
+ for (index = 0; index < (DA9052_NO_OF_LOOKUP_TABLE-1); index++) {
+ if (bat_temperature <= temperature_lookup_ref[0]) {
+ access_index = 0;
+ break;
+ } else if (bat_temperature >
+ temperature_lookup_ref[DA9052_NO_OF_LOOKUP_TABLE]){
+ access_index = DA9052_NO_OF_LOOKUP_TABLE - 1;
+ break;
+ } else if ((bat_temperature >= temperature_lookup_ref[index]) &&
+ (bat_temperature >= temperature_lookup_ref[index+1])) {
+ access_index = select_temperature(index,
+ bat_temperature);
+ break;
+ }
+ }
+ if (bat_voltage >= vbat_vs_capacity_look_up[access_index][0][0]) {
+ chg_device->cal_capacity = 100;
+ return 0;
+ }
+ if (bat_voltage <= vbat_vs_capacity_look_up[access_index]
+ [DA9052_LOOK_UP_TABLE_SIZE-1][0]){
+ chg_device->cal_capacity = 0;
+ return 0;
+ }
+ flag = FALSE;
+
+ for (index = 0; index < (DA9052_LOOK_UP_TABLE_SIZE-1); index++) {
+ if ((bat_voltage <=
+ vbat_vs_capacity_look_up[access_index][index][0]) &&
+ (bat_voltage >=
+ vbat_vs_capacity_look_up[access_index][index+1][0])) {
+ vbat_upper =
+ vbat_vs_capacity_look_up[access_index][index][0];
+ vbat_lower =
+ vbat_vs_capacity_look_up[access_index][index+1][0];
+ level_upper =
+ vbat_vs_capacity_look_up[access_index][index][1];
+ level_lower =
+ vbat_vs_capacity_look_up[access_index][index+1][1];
+ flag = TRUE;
+ break;
+ }
+ }
+ if (!flag)
+ return -EIO;
+
+ level = interpolated(vbat_lower, vbat_upper, level_lower,
+ level_upper, bat_voltage);
+ chg_device->cal_capacity = level;
+ DA9052_DEBUG(" TOTAl_BAT_CAPACITY : %d\n", chg_device->cal_capacity);
+ return 0;
+}
+
+void da9052_bat_tbat_handler(struct da9052_eh_nb *eh_data, unsigned int event)
+{
+ struct da9052_charger_device *chg_device =
+ container_of(eh_data, struct da9052_charger_device, tbat_eh_data);
+
+ chg_device->health = POWER_SUPPLY_HEALTH_OVERHEAT;
+
+}
+
+static s32 da9052_bat_register_event(struct da9052_charger_device *chg_device)
+{
+ s32 ret;
+
+ if (event_status.da9052_event_tbat == FALSE) {
+ chg_device->tbat_eh_data.eve_type = TBAT_EVE;
+ chg_device->tbat_eh_data.call_back =da9052_bat_tbat_handler;
+ DA9052_DEBUG("events = %d\n",TBAT_EVE);
+ ret = chg_device->da9052->register_event_notifier
+ (chg_device->da9052, &chg_device->tbat_eh_data);
+ if (ret)
+ return -EIO;
+ event_status.da9052_event_tbat = TRUE;
+ }
+
+ return 0;
+}
+
+static s32 da9052_bat_unregister_event(struct da9052_charger_device *chg_device)
+{
+ s32 ret;
+
+ if (event_status.da9052_event_tbat) {
+ ret =
+ chg_device->da9052->unregister_event_notifier
+ (chg_device->da9052, &chg_device->tbat_eh_data);
+ if (ret)
+ return -EIO;
+ event_status.da9052_event_tbat = FALSE;
+ }
+
+ return 0;
+}
+
+static int da9052_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct da9052_charger_device *chg_device =
+ container_of(psy, struct da9052_charger_device, psy);
+
+ /* Validate battery presence */
+ if( chg_device->illegal && psp != POWER_SUPPLY_PROP_PRESENT ) {
+ return -ENODEV;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = chg_device->status;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = (chg_device->charger_type == DA9052_NOCHARGER) ? 0: 1;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = chg_device->illegal;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = chg_device->health;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = chg_device->bat_target_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = chg_device->bat_volt_cutoff * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ val->intval = chg_device->bat_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = chg_device->chg_current * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = chg_device->cal_capacity;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = bat_temp_reg_to_C(chg_device->bat_temp);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = chg_device->technology;
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ return 0;
+}
+
+
+static u8 detect_illegal_battery(struct da9052_charger_device *chg_device)
+{
+ u16 buffer = 0;
+ s32 ret = 0;
+
+ /* Measure battery temeperature */
+ ret = da9052_bat_get_battery_temperature(chg_device, &buffer);
+ if (ret) {
+ DA9052_DEBUG("%s: Battery temperature measurement failed \n",
+ __func__);
+ return ret;
+ }
+
+ if (buffer > chg_device->bat_with_no_resistor)
+ chg_device->illegal = TRUE;
+ else
+ chg_device->illegal = FALSE;
+
+
+ /* suspend charging of battery if illegal battey is detected */
+ if (chg_device->illegal)
+ da9052_bat_suspend_charging(chg_device);
+
+ return chg_device->illegal;
+}
+
+void da9052_update_bat_properties(struct da9052_charger_device *chg_device)
+{
+ /* Get Bat status and type */
+ da9052_bat_status_update(chg_device);
+ da9052_bat_level_update(chg_device);
+}
+
+static void da9052_bat_external_power_changed(struct power_supply *psy)
+{
+ struct da9052_charger_device *chg_device =
+ container_of(psy, struct da9052_charger_device, psy);
+
+ cancel_delayed_work(&chg_device->monitor_work);
+ queue_delayed_work(chg_device->monitor_wqueue, &chg_device->monitor_work, HZ/10);
+}
+
+
+static void da9052_bat_work(struct work_struct *work)
+{
+ struct da9052_charger_device *chg_device = container_of(work,
+ struct da9052_charger_device,monitor_work.work);
+
+ da9052_update_bat_properties(chg_device);
+ queue_delayed_work(chg_device->monitor_wqueue, &chg_device->monitor_work, HZ * 8);
+}
+
+static enum power_supply_property da9052_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+
+};
+
+static s32 __devinit da9052_bat_probe(struct platform_device *pdev)
+{
+ struct da9052_charger_device *chg_device;
+ u8 reg_data;
+ int ret;
+
+ chg_device = kzalloc(sizeof(*chg_device), GFP_KERNEL);
+ if (!chg_device)
+ return -ENOMEM;
+
+ chg_device->da9052 = dev_get_drvdata(pdev->dev.parent);
+ platform_set_drvdata(pdev, chg_device);
+
+ chg_device->psy.name = DA9052_BAT_DEVICE_NAME;
+ chg_device->psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ chg_device->psy.properties = da9052_bat_props;
+ chg_device->psy.num_properties = ARRAY_SIZE(da9052_bat_props);
+ chg_device->psy.get_property = da9052_bat_get_property;
+ chg_device->psy.external_power_changed = da9052_bat_external_power_changed;
+ chg_device->psy.use_for_apm = 1;
+ chg_device->charger_type = DA9052_NOCHARGER;
+ chg_device->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ chg_device->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ chg_device->technology = POWER_SUPPLY_TECHNOLOGY_LION;
+ chg_device->bat_with_no_resistor = 62;
+ chg_device->bat_capacity_limit_low = 4;
+ chg_device->bat_capacity_limit_high = 70;
+ chg_device->bat_capacity_full = 100;
+ chg_device->bat_volt_cutoff = 2800;
+ chg_device->vbat_first_valid_detect_iteration = 3;
+ chg_device->hysteresis_window_size =1;
+ chg_device->chg_hysteresis_const =89;
+ chg_device->hysteresis_reading_interval =1000;
+ chg_device->hysteresis_no_of_reading =10;
+
+ ret = da9052_read(chg_device->da9052, DA9052_CHGCONT_REG, &reg_data);
+ if (ret)
+ goto err_charger_init;
+ chg_device->charger_voltage_drop = bat_drop_reg_to_mV(reg_data &&
+ DA9052_CHGCONT_TCTR);
+ chg_device->bat_target_voltage =
+ bat_reg_to_mV(reg_data && DA9052_CHGCONT_VCHGBAT);
+
+ ret = da9052_read(chg_device->da9052, DA9052_ICHGEND_REG, &reg_data);
+ if (ret)
+ goto err_charger_init;
+ chg_device->chg_end_current = ichg_reg_to_mA(reg_data);
+
+ bat_hysteresis.upper_limit = 0;
+ bat_hysteresis.lower_limit = 0;
+ bat_hysteresis.hys_flag = 0;
+
+ chg_device->illegal = FALSE;
+ detect_illegal_battery(chg_device);
+
+ da9052_bat_register_event(chg_device);
+ if (ret)
+ goto err_charger_init;
+
+ ret = power_supply_register(&pdev->dev, &chg_device->psy);
+ if (ret)
+ goto err_charger_init;
+
+ INIT_DELAYED_WORK(&chg_device->monitor_work, da9052_bat_work);
+ chg_device->monitor_wqueue = create_singlethread_workqueue(pdev->dev.bus_id);
+ if (!chg_device->monitor_wqueue) {
+ goto err_charger_init;
+ }
+ queue_delayed_work(chg_device->monitor_wqueue, &chg_device->monitor_work, HZ * 1);
+
+ return 0;
+
+err_charger_init:
+ platform_set_drvdata(pdev, NULL);
+ kfree(chg_device);
+ return ret;
+}
+static int __devexit da9052_bat_remove(struct platform_device *dev)
+{
+ struct da9052_charger_device *chg_device = platform_get_drvdata(dev);
+
+ /* unregister the events.*/
+ da9052_bat_unregister_event(chg_device);
+
+ cancel_rearming_delayed_workqueue(chg_device->monitor_wqueue,
+ &chg_device->monitor_work);
+ destroy_workqueue(chg_device->monitor_wqueue);
+
+ power_supply_unregister(&chg_device->psy);
+
+ return 0;
+}
+
+static struct platform_driver da9052_bat_driver = {
+ .probe = da9052_bat_probe,
+ .remove = __devexit_p(da9052_bat_remove),
+ .driver.name = DA9052_BAT_DEVICE_NAME,
+ .driver.owner = THIS_MODULE,
+};
+
+static int __init da9052_bat_init(void)
+{
+ printk(banner);
+ return platform_driver_register(&da9052_bat_driver);
+}
+
+static void __exit da9052_bat_exit(void)
+{
+ // To remove printk("DA9052: Unregistering BAT device.\n");
+ platform_driver_unregister(&da9052_bat_driver);
+}
+
+module_init(da9052_bat_init);
+module_exit(da9052_bat_exit);
+
+MODULE_AUTHOR("Dialog Semiconductor Ltd");
+MODULE_DESCRIPTION("DA9052 BAT Device Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c
new file mode 100644
index 00000000..f2c9cc33
--- /dev/null
+++ b/drivers/power/ds2760_battery.c
@@ -0,0 +1,657 @@
+/*
+ * Driver for batteries with DS2760 chips inside.
+ *
+ * Copyright © 2007 Anton Vorontsov
+ * 2004-2007 Matt Reimer
+ * 2004 Szabolcs Gyurko
+ *
+ * Use consistent with the GNU GPL is permitted,
+ * provided that this copyright notice is
+ * preserved in its entirety in all copies and derived works.
+ *
+ * Author: Anton Vorontsov <cbou@mail.ru>
+ * February 2007
+ *
+ * Matt Reimer <mreimer@vpop.net>
+ * April 2004, 2005, 2007
+ *
+ * Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>
+ * September 2004
+ */
+
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include "../w1/w1.h"
+#include "../w1/slaves/w1_ds2760.h"
+
+struct ds2760_device_info {
+ struct device *dev;
+
+ /* DS2760 data, valid after calling ds2760_battery_read_status() */
+ unsigned long update_time; /* jiffies when data read */
+ char raw[DS2760_DATA_SIZE]; /* raw DS2760 data */
+ int voltage_raw; /* units of 4.88 mV */
+ int voltage_uV; /* units of µV */
+ int current_raw; /* units of 0.625 mA */
+ int current_uA; /* units of µA */
+ int accum_current_raw; /* units of 0.25 mAh */
+ int accum_current_uAh; /* units of µAh */
+ int temp_raw; /* units of 0.125 °C */
+ int temp_C; /* units of 0.1 °C */
+ int rated_capacity; /* units of µAh */
+ int rem_capacity; /* percentage */
+ int full_active_uAh; /* units of µAh */
+ int empty_uAh; /* units of µAh */
+ int life_sec; /* units of seconds */
+ int charge_status; /* POWER_SUPPLY_STATUS_* */
+
+ int full_counter;
+ struct power_supply bat;
+ struct device *w1_dev;
+ struct workqueue_struct *monitor_wqueue;
+ struct delayed_work monitor_work;
+ struct delayed_work set_charged_work;
+};
+
+static unsigned int cache_time = 1000;
+module_param(cache_time, uint, 0644);
+MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
+
+static unsigned int pmod_enabled;
+module_param(pmod_enabled, bool, 0644);
+MODULE_PARM_DESC(pmod_enabled, "PMOD enable bit");
+
+static unsigned int rated_capacity;
+module_param(rated_capacity, uint, 0644);
+MODULE_PARM_DESC(rated_capacity, "rated battery capacity, 10*mAh or index");
+
+static unsigned int current_accum;
+module_param(current_accum, uint, 0644);
+MODULE_PARM_DESC(current_accum, "current accumulator value");
+
+/* Some batteries have their rated capacity stored a N * 10 mAh, while
+ * others use an index into this table. */
+static int rated_capacities[] = {
+ 0,
+ 920, /* Samsung */
+ 920, /* BYD */
+ 920, /* Lishen */
+ 920, /* NEC */
+ 1440, /* Samsung */
+ 1440, /* BYD */
+#ifdef CONFIG_MACH_H4700
+ 1800, /* HP iPAQ hx4700 3.7V 1800mAh (359113-001) */
+#else
+ 1440, /* Lishen */
+#endif
+ 1440, /* NEC */
+ 2880, /* Samsung */
+ 2880, /* BYD */
+ 2880, /* Lishen */
+ 2880 /* NEC */
+};
+
+/* array is level at temps 0°C, 10°C, 20°C, 30°C, 40°C
+ * temp is in Celsius */
+static int battery_interpolate(int array[], int temp)
+{
+ int index, dt;
+
+ if (temp <= 0)
+ return array[0];
+ if (temp >= 40)
+ return array[4];
+
+ index = temp / 10;
+ dt = temp % 10;
+
+ return array[index] + (((array[index + 1] - array[index]) * dt) / 10);
+}
+
+static int ds2760_battery_read_status(struct ds2760_device_info *di)
+{
+ int ret, i, start, count, scale[5];
+
+ if (di->update_time && time_before(jiffies, di->update_time +
+ msecs_to_jiffies(cache_time)))
+ return 0;
+
+ /* The first time we read the entire contents of SRAM/EEPROM,
+ * but after that we just read the interesting bits that change. */
+ if (di->update_time == 0) {
+ start = 0;
+ count = DS2760_DATA_SIZE;
+ } else {
+ start = DS2760_VOLTAGE_MSB;
+ count = DS2760_TEMP_LSB - start + 1;
+ }
+
+ ret = w1_ds2760_read(di->w1_dev, di->raw + start, start, count);
+ if (ret != count) {
+ dev_warn(di->dev, "call to w1_ds2760_read failed (0x%p)\n",
+ di->w1_dev);
+ return 1;
+ }
+
+ di->update_time = jiffies;
+
+ /* DS2760 reports voltage in units of 4.88mV, but the battery class
+ * reports in units of uV, so convert by multiplying by 4880. */
+ di->voltage_raw = (di->raw[DS2760_VOLTAGE_MSB] << 3) |
+ (di->raw[DS2760_VOLTAGE_LSB] >> 5);
+ di->voltage_uV = di->voltage_raw * 4880;
+
+ /* DS2760 reports current in signed units of 0.625mA, but the battery
+ * class reports in units of µA, so convert by multiplying by 625. */
+ di->current_raw =
+ (((signed char)di->raw[DS2760_CURRENT_MSB]) << 5) |
+ (di->raw[DS2760_CURRENT_LSB] >> 3);
+ di->current_uA = di->current_raw * 625;
+
+ /* DS2760 reports accumulated current in signed units of 0.25mAh. */
+ di->accum_current_raw =
+ (((signed char)di->raw[DS2760_CURRENT_ACCUM_MSB]) << 8) |
+ di->raw[DS2760_CURRENT_ACCUM_LSB];
+ di->accum_current_uAh = di->accum_current_raw * 250;
+
+ /* DS2760 reports temperature in signed units of 0.125°C, but the
+ * battery class reports in units of 1/10 °C, so we convert by
+ * multiplying by .125 * 10 = 1.25. */
+ di->temp_raw = (((signed char)di->raw[DS2760_TEMP_MSB]) << 3) |
+ (di->raw[DS2760_TEMP_LSB] >> 5);
+ di->temp_C = di->temp_raw + (di->temp_raw / 4);
+
+ /* At least some battery monitors (e.g. HP iPAQ) store the battery's
+ * maximum rated capacity. */
+ if (di->raw[DS2760_RATED_CAPACITY] < ARRAY_SIZE(rated_capacities))
+ di->rated_capacity = rated_capacities[
+ (unsigned int)di->raw[DS2760_RATED_CAPACITY]];
+ else
+ di->rated_capacity = di->raw[DS2760_RATED_CAPACITY] * 10;
+
+ di->rated_capacity *= 1000; /* convert to µAh */
+
+ /* Calculate the full level at the present temperature. */
+ di->full_active_uAh = di->raw[DS2760_ACTIVE_FULL] << 8 |
+ di->raw[DS2760_ACTIVE_FULL + 1];
+
+ /* If the full_active_uAh value is not given, fall back to the rated
+ * capacity. This is likely to happen when chips are not part of the
+ * battery pack and is therefore not bootstrapped. */
+ if (di->full_active_uAh == 0)
+ di->full_active_uAh = di->rated_capacity / 1000L;
+
+ scale[0] = di->full_active_uAh;
+ for (i = 1; i < 5; i++)
+ scale[i] = scale[i - 1] + di->raw[DS2760_ACTIVE_FULL + 1 + i];
+
+ di->full_active_uAh = battery_interpolate(scale, di->temp_C / 10);
+ di->full_active_uAh *= 1000; /* convert to µAh */
+
+ /* Calculate the empty level at the present temperature. */
+ scale[4] = di->raw[DS2760_ACTIVE_EMPTY + 4];
+ for (i = 3; i >= 0; i--)
+ scale[i] = scale[i + 1] + di->raw[DS2760_ACTIVE_EMPTY + i];
+
+ di->empty_uAh = battery_interpolate(scale, di->temp_C / 10);
+ di->empty_uAh *= 1000; /* convert to µAh */
+
+ if (di->full_active_uAh == di->empty_uAh)
+ di->rem_capacity = 0;
+ else
+ /* From Maxim Application Note 131: remaining capacity =
+ * ((ICA - Empty Value) / (Full Value - Empty Value)) x 100% */
+ di->rem_capacity = ((di->accum_current_uAh - di->empty_uAh) * 100L) /
+ (di->full_active_uAh - di->empty_uAh);
+
+ if (di->rem_capacity < 0)
+ di->rem_capacity = 0;
+ if (di->rem_capacity > 100)
+ di->rem_capacity = 100;
+
+ if (di->current_uA < -100L)
+ di->life_sec = -((di->accum_current_uAh - di->empty_uAh) * 36L)
+ / (di->current_uA / 100L);
+ else
+ di->life_sec = 0;
+
+ return 0;
+}
+
+static void ds2760_battery_set_current_accum(struct ds2760_device_info *di,
+ unsigned int acr_val)
+{
+ unsigned char acr[2];
+
+ /* acr is in units of 0.25 mAh */
+ acr_val *= 4L;
+ acr_val /= 1000;
+
+ acr[0] = acr_val >> 8;
+ acr[1] = acr_val & 0xff;
+
+ if (w1_ds2760_write(di->w1_dev, acr, DS2760_CURRENT_ACCUM_MSB, 2) < 2)
+ dev_warn(di->dev, "ACR write failed\n");
+}
+
+static void ds2760_battery_update_status(struct ds2760_device_info *di)
+{
+ int old_charge_status = di->charge_status;
+
+ ds2760_battery_read_status(di);
+
+ if (di->charge_status == POWER_SUPPLY_STATUS_UNKNOWN)
+ di->full_counter = 0;
+
+ if (power_supply_am_i_supplied(&di->bat)) {
+ if (di->current_uA > 10000) {
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ di->full_counter = 0;
+ } else if (di->current_uA < -5000) {
+ if (di->charge_status != POWER_SUPPLY_STATUS_NOT_CHARGING)
+ dev_notice(di->dev, "not enough power to "
+ "charge\n");
+ di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ di->full_counter = 0;
+ } else if (di->current_uA < 10000 &&
+ di->charge_status != POWER_SUPPLY_STATUS_FULL) {
+
+ /* Don't consider the battery to be full unless
+ * we've seen the current < 10 mA at least two
+ * consecutive times. */
+
+ di->full_counter++;
+
+ if (di->full_counter < 2) {
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ di->charge_status = POWER_SUPPLY_STATUS_FULL;
+ ds2760_battery_set_current_accum(di,
+ di->full_active_uAh);
+ }
+ }
+ } else {
+ di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ di->full_counter = 0;
+ }
+
+ if (di->charge_status != old_charge_status)
+ power_supply_changed(&di->bat);
+}
+
+static void ds2760_battery_write_status(struct ds2760_device_info *di,
+ char status)
+{
+ if (status == di->raw[DS2760_STATUS_REG])
+ return;
+
+ w1_ds2760_write(di->w1_dev, &status, DS2760_STATUS_WRITE_REG, 1);
+ w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+}
+
+static void ds2760_battery_write_rated_capacity(struct ds2760_device_info *di,
+ unsigned char rated_capacity)
+{
+ if (rated_capacity == di->raw[DS2760_RATED_CAPACITY])
+ return;
+
+ w1_ds2760_write(di->w1_dev, &rated_capacity, DS2760_RATED_CAPACITY, 1);
+ w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+}
+
+static void ds2760_battery_write_active_full(struct ds2760_device_info *di,
+ int active_full)
+{
+ unsigned char tmp[2] = {
+ active_full >> 8,
+ active_full & 0xff
+ };
+
+ if (tmp[0] == di->raw[DS2760_ACTIVE_FULL] &&
+ tmp[1] == di->raw[DS2760_ACTIVE_FULL + 1])
+ return;
+
+ w1_ds2760_write(di->w1_dev, tmp, DS2760_ACTIVE_FULL, sizeof(tmp));
+ w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0);
+ w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0);
+
+ /* Write to the di->raw[] buffer directly - the DS2760_ACTIVE_FULL
+ * values won't be read back by ds2760_battery_read_status() */
+ di->raw[DS2760_ACTIVE_FULL] = tmp[0];
+ di->raw[DS2760_ACTIVE_FULL + 1] = tmp[1];
+}
+
+static void ds2760_battery_work(struct work_struct *work)
+{
+ struct ds2760_device_info *di = container_of(work,
+ struct ds2760_device_info, monitor_work.work);
+ const int interval = HZ * 60;
+
+ dev_dbg(di->dev, "%s\n", __func__);
+
+ ds2760_battery_update_status(di);
+ queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval);
+}
+
+#define to_ds2760_device_info(x) container_of((x), struct ds2760_device_info, \
+ bat);
+
+static void ds2760_battery_external_power_changed(struct power_supply *psy)
+{
+ struct ds2760_device_info *di = to_ds2760_device_info(psy);
+
+ dev_dbg(di->dev, "%s\n", __func__);
+
+ cancel_delayed_work(&di->monitor_work);
+ queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ/10);
+}
+
+
+static void ds2760_battery_set_charged_work(struct work_struct *work)
+{
+ char bias;
+ struct ds2760_device_info *di = container_of(work,
+ struct ds2760_device_info, set_charged_work.work);
+
+ dev_dbg(di->dev, "%s\n", __func__);
+
+ ds2760_battery_read_status(di);
+
+ /* When we get notified by external circuitry that the battery is
+ * considered fully charged now, we know that there is no current
+ * flow any more. However, the ds2760's internal current meter is
+ * too inaccurate to rely on - spec say something ~15% failure.
+ * Hence, we use the current offset bias register to compensate
+ * that error.
+ */
+
+ if (!power_supply_am_i_supplied(&di->bat))
+ return;
+
+ bias = (signed char) di->current_raw +
+ (signed char) di->raw[DS2760_CURRENT_OFFSET_BIAS];
+
+ dev_dbg(di->dev, "%s: bias = %d\n", __func__, bias);
+
+ w1_ds2760_write(di->w1_dev, &bias, DS2760_CURRENT_OFFSET_BIAS, 1);
+ w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+
+ /* Write to the di->raw[] buffer directly - the CURRENT_OFFSET_BIAS
+ * value won't be read back by ds2760_battery_read_status() */
+ di->raw[DS2760_CURRENT_OFFSET_BIAS] = bias;
+}
+
+static void ds2760_battery_set_charged(struct power_supply *psy)
+{
+ struct ds2760_device_info *di = to_ds2760_device_info(psy);
+
+ /* postpone the actual work by 20 secs. This is for debouncing GPIO
+ * signals and to let the current value settle. See AN4188. */
+ cancel_delayed_work(&di->set_charged_work);
+ queue_delayed_work(di->monitor_wqueue, &di->set_charged_work, HZ * 20);
+}
+
+static int ds2760_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ds2760_device_info *di = to_ds2760_device_info(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = di->charge_status;
+ return 0;
+ default:
+ break;
+ }
+
+ ds2760_battery_read_status(di);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = di->voltage_uV;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = di->current_uA;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = di->rated_capacity;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = di->full_active_uAh;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_EMPTY:
+ val->intval = di->empty_uAh;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = di->accum_current_uAh;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = di->temp_C;
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ val->intval = di->life_sec;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = di->rem_capacity;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ds2760_battery_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct ds2760_device_info *di = to_ds2760_device_info(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ /* the interface counts in uAh, convert the value */
+ ds2760_battery_write_active_full(di, val->intval / 1000L);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ /* ds2760_battery_set_current_accum() does the conversion */
+ ds2760_battery_set_current_accum(di, val->intval);
+ break;
+
+ default:
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+static int ds2760_battery_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ return 1;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property ds2760_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_EMPTY,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int ds2760_battery_probe(struct platform_device *pdev)
+{
+ char status;
+ int retval = 0;
+ struct ds2760_device_info *di;
+
+ di = kzalloc(sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ retval = -ENOMEM;
+ goto di_alloc_failed;
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ di->dev = &pdev->dev;
+ di->w1_dev = pdev->dev.parent;
+ di->bat.name = dev_name(&pdev->dev);
+ di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->bat.properties = ds2760_battery_props;
+ di->bat.num_properties = ARRAY_SIZE(ds2760_battery_props);
+ di->bat.get_property = ds2760_battery_get_property;
+ di->bat.set_property = ds2760_battery_set_property;
+ di->bat.property_is_writeable =
+ ds2760_battery_property_is_writeable;
+ di->bat.set_charged = ds2760_battery_set_charged;
+ di->bat.external_power_changed =
+ ds2760_battery_external_power_changed;
+
+ di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ /* enable sleep mode feature */
+ ds2760_battery_read_status(di);
+ status = di->raw[DS2760_STATUS_REG];
+ if (pmod_enabled)
+ status |= DS2760_STATUS_PMOD;
+ else
+ status &= ~DS2760_STATUS_PMOD;
+
+ ds2760_battery_write_status(di, status);
+
+ /* set rated capacity from module param */
+ if (rated_capacity)
+ ds2760_battery_write_rated_capacity(di, rated_capacity);
+
+ /* set current accumulator if given as parameter.
+ * this should only be done for bootstrapping the value */
+ if (current_accum)
+ ds2760_battery_set_current_accum(di, current_accum);
+
+ retval = power_supply_register(&pdev->dev, &di->bat);
+ if (retval) {
+ dev_err(di->dev, "failed to register battery\n");
+ goto batt_failed;
+ }
+
+ INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work);
+ INIT_DELAYED_WORK(&di->set_charged_work,
+ ds2760_battery_set_charged_work);
+ di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev));
+ if (!di->monitor_wqueue) {
+ retval = -ESRCH;
+ goto workqueue_failed;
+ }
+ queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 1);
+
+ goto success;
+
+workqueue_failed:
+ power_supply_unregister(&di->bat);
+batt_failed:
+ kfree(di);
+di_alloc_failed:
+success:
+ return retval;
+}
+
+static int ds2760_battery_remove(struct platform_device *pdev)
+{
+ struct ds2760_device_info *di = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&di->monitor_work);
+ cancel_delayed_work_sync(&di->set_charged_work);
+ destroy_workqueue(di->monitor_wqueue);
+ power_supply_unregister(&di->bat);
+ kfree(di);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int ds2760_battery_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ds2760_device_info *di = platform_get_drvdata(pdev);
+
+ di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ return 0;
+}
+
+static int ds2760_battery_resume(struct platform_device *pdev)
+{
+ struct ds2760_device_info *di = platform_get_drvdata(pdev);
+
+ di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ power_supply_changed(&di->bat);
+
+ cancel_delayed_work(&di->monitor_work);
+ queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ);
+
+ return 0;
+}
+
+#else
+
+#define ds2760_battery_suspend NULL
+#define ds2760_battery_resume NULL
+
+#endif /* CONFIG_PM */
+
+MODULE_ALIAS("platform:ds2760-battery");
+
+static struct platform_driver ds2760_battery_driver = {
+ .driver = {
+ .name = "ds2760-battery",
+ },
+ .probe = ds2760_battery_probe,
+ .remove = ds2760_battery_remove,
+ .suspend = ds2760_battery_suspend,
+ .resume = ds2760_battery_resume,
+};
+
+static int __init ds2760_battery_init(void)
+{
+ return platform_driver_register(&ds2760_battery_driver);
+}
+
+static void __exit ds2760_battery_exit(void)
+{
+ platform_driver_unregister(&ds2760_battery_driver);
+}
+
+module_init(ds2760_battery_init);
+module_exit(ds2760_battery_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, "
+ "Matt Reimer <mreimer@vpop.net>, "
+ "Anton Vorontsov <cbou@mail.ru>");
+MODULE_DESCRIPTION("ds2760 battery driver");
diff --git a/drivers/power/ds2780_battery.c b/drivers/power/ds2780_battery.c
new file mode 100644
index 00000000..91a783d7
--- /dev/null
+++ b/drivers/power/ds2780_battery.c
@@ -0,0 +1,869 @@
+/*
+ * 1-wire client/driver for the Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC
+ *
+ * Copyright (C) 2010 Indesign, LLC
+ *
+ * Author: Clifton Barnes <cabarnes@indesign-llc.com>
+ *
+ * Based on ds2760_battery and ds2782_battery drivers
+ *
+ * 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/module.h>
+#include <linux/slab.h>
+#include <linux/param.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+
+#include "../w1/w1.h"
+#include "../w1/slaves/w1_ds2780.h"
+
+/* Current unit measurement in uA for a 1 milli-ohm sense resistor */
+#define DS2780_CURRENT_UNITS 1563
+/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */
+#define DS2780_CHARGE_UNITS 6250
+/* Number of bytes in user EEPROM space */
+#define DS2780_USER_EEPROM_SIZE (DS2780_EEPROM_BLOCK0_END - \
+ DS2780_EEPROM_BLOCK0_START + 1)
+/* Number of bytes in parameter EEPROM space */
+#define DS2780_PARAM_EEPROM_SIZE (DS2780_EEPROM_BLOCK1_END - \
+ DS2780_EEPROM_BLOCK1_START + 1)
+
+struct ds2780_device_info {
+ struct device *dev;
+ struct power_supply bat;
+ struct device *w1_dev;
+ struct task_struct *mutex_holder;
+};
+
+enum current_types {
+ CURRENT_NOW,
+ CURRENT_AVG,
+};
+
+static const char model[] = "DS2780";
+static const char manufacturer[] = "Maxim/Dallas";
+
+static inline struct ds2780_device_info *
+to_ds2780_device_info(struct power_supply *psy)
+{
+ return container_of(psy, struct ds2780_device_info, bat);
+}
+
+static inline struct power_supply *to_power_supply(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static inline int ds2780_battery_io(struct ds2780_device_info *dev_info,
+ char *buf, int addr, size_t count, int io)
+{
+ if (dev_info->mutex_holder == current)
+ return w1_ds2780_io_nolock(dev_info->w1_dev, buf, addr, count, io);
+ else
+ return w1_ds2780_io(dev_info->w1_dev, buf, addr, count, io);
+}
+
+static inline int ds2780_read8(struct ds2780_device_info *dev_info, u8 *val,
+ int addr)
+{
+ return ds2780_battery_io(dev_info, val, addr, sizeof(u8), 0);
+}
+
+static int ds2780_read16(struct ds2780_device_info *dev_info, s16 *val,
+ int addr)
+{
+ int ret;
+ u8 raw[2];
+
+ ret = ds2780_battery_io(dev_info, raw, addr, sizeof(raw), 0);
+ if (ret < 0)
+ return ret;
+
+ *val = (raw[0] << 8) | raw[1];
+
+ return 0;
+}
+
+static inline int ds2780_read_block(struct ds2780_device_info *dev_info,
+ u8 *val, int addr, size_t count)
+{
+ return ds2780_battery_io(dev_info, val, addr, count, 0);
+}
+
+static inline int ds2780_write(struct ds2780_device_info *dev_info, u8 *val,
+ int addr, size_t count)
+{
+ return ds2780_battery_io(dev_info, val, addr, count, 1);
+}
+
+static inline int ds2780_store_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_COPY_DATA);
+}
+
+static inline int ds2780_recall_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_RECALL_DATA);
+}
+
+static int ds2780_save_eeprom(struct ds2780_device_info *dev_info, int reg)
+{
+ int ret;
+
+ ret = ds2780_store_eeprom(dev_info->w1_dev, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_recall_eeprom(dev_info->w1_dev, reg);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* Set sense resistor value in mhos */
+static int ds2780_set_sense_register(struct ds2780_device_info *dev_info,
+ u8 conductance)
+{
+ int ret;
+
+ ret = ds2780_write(dev_info, &conductance,
+ DS2780_RSNSP_REG, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return ds2780_save_eeprom(dev_info, DS2780_RSNSP_REG);
+}
+
+/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */
+static int ds2780_get_rsgain_register(struct ds2780_device_info *dev_info,
+ u16 *rsgain)
+{
+ return ds2780_read16(dev_info, rsgain, DS2780_RSGAIN_MSB_REG);
+}
+
+/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */
+static int ds2780_set_rsgain_register(struct ds2780_device_info *dev_info,
+ u16 rsgain)
+{
+ int ret;
+ u8 raw[] = {rsgain >> 8, rsgain & 0xFF};
+
+ ret = ds2780_write(dev_info, raw,
+ DS2780_RSGAIN_MSB_REG, sizeof(raw));
+ if (ret < 0)
+ return ret;
+
+ return ds2780_save_eeprom(dev_info, DS2780_RSGAIN_MSB_REG);
+}
+
+static int ds2780_get_voltage(struct ds2780_device_info *dev_info,
+ int *voltage_uV)
+{
+ int ret;
+ s16 voltage_raw;
+
+ /*
+ * The voltage value is located in 10 bits across the voltage MSB
+ * and LSB registers in two's compliment form
+ * Sign bit of the voltage value is in bit 7 of the voltage MSB register
+ * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the
+ * voltage MSB register
+ * Bits 2 - 0 of the voltage value are in bits 7 - 5 of the
+ * voltage LSB register
+ */
+ ret = ds2780_read16(dev_info, &voltage_raw,
+ DS2780_VOLT_MSB_REG);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * DS2780 reports voltage in units of 4.88mV, but the battery class
+ * reports in units of uV, so convert by multiplying by 4880.
+ */
+ *voltage_uV = (voltage_raw / 32) * 4880;
+ return 0;
+}
+
+static int ds2780_get_temperature(struct ds2780_device_info *dev_info,
+ int *temperature)
+{
+ int ret;
+ s16 temperature_raw;
+
+ /*
+ * The temperature value is located in 10 bits across the temperature
+ * MSB and LSB registers in two's compliment form
+ * Sign bit of the temperature value is in bit 7 of the temperature
+ * MSB register
+ * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the
+ * temperature MSB register
+ * Bits 2 - 0 of the temperature value are in bits 7 - 5 of the
+ * temperature LSB register
+ */
+ ret = ds2780_read16(dev_info, &temperature_raw,
+ DS2780_TEMP_MSB_REG);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Temperature is measured in units of 0.125 degrees celcius, the
+ * power_supply class measures temperature in tenths of degrees
+ * celsius. The temperature value is stored as a 10 bit number, plus
+ * sign in the upper bits of a 16 bit register.
+ */
+ *temperature = ((temperature_raw / 32) * 125) / 100;
+ return 0;
+}
+
+static int ds2780_get_current(struct ds2780_device_info *dev_info,
+ enum current_types type, int *current_uA)
+{
+ int ret, sense_res;
+ s16 current_raw;
+ u8 sense_res_raw, reg_msb;
+
+ /*
+ * The units of measurement for current are dependent on the value of
+ * the sense resistor.
+ */
+ ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG);
+ if (ret < 0)
+ return ret;
+
+ if (sense_res_raw == 0) {
+ dev_err(dev_info->dev, "sense resistor value is 0\n");
+ return -EINVAL;
+ }
+ sense_res = 1000 / sense_res_raw;
+
+ if (type == CURRENT_NOW)
+ reg_msb = DS2780_CURRENT_MSB_REG;
+ else if (type == CURRENT_AVG)
+ reg_msb = DS2780_IAVG_MSB_REG;
+ else
+ return -EINVAL;
+
+ /*
+ * The current value is located in 16 bits across the current MSB
+ * and LSB registers in two's compliment form
+ * Sign bit of the current value is in bit 7 of the current MSB register
+ * Bits 14 - 8 of the current value are in bits 6 - 0 of the current
+ * MSB register
+ * Bits 7 - 0 of the current value are in bits 7 - 0 of the current
+ * LSB register
+ */
+ ret = ds2780_read16(dev_info, &current_raw, reg_msb);
+ if (ret < 0)
+ return ret;
+
+ *current_uA = current_raw * (DS2780_CURRENT_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2780_get_accumulated_current(struct ds2780_device_info *dev_info,
+ int *accumulated_current)
+{
+ int ret, sense_res;
+ s16 current_raw;
+ u8 sense_res_raw;
+
+ /*
+ * The units of measurement for accumulated current are dependent on
+ * the value of the sense resistor.
+ */
+ ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG);
+ if (ret < 0)
+ return ret;
+
+ if (sense_res_raw == 0) {
+ dev_err(dev_info->dev, "sense resistor value is 0\n");
+ return -ENXIO;
+ }
+ sense_res = 1000 / sense_res_raw;
+
+ /*
+ * The ACR value is located in 16 bits across the ACR MSB and
+ * LSB registers
+ * Bits 15 - 8 of the ACR value are in bits 7 - 0 of the ACR
+ * MSB register
+ * Bits 7 - 0 of the ACR value are in bits 7 - 0 of the ACR
+ * LSB register
+ */
+ ret = ds2780_read16(dev_info, &current_raw, DS2780_ACR_MSB_REG);
+ if (ret < 0)
+ return ret;
+
+ *accumulated_current = current_raw * (DS2780_CHARGE_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2780_get_capacity(struct ds2780_device_info *dev_info,
+ int *capacity)
+{
+ int ret;
+ u8 raw;
+
+ ret = ds2780_read8(dev_info, &raw, DS2780_RARC_REG);
+ if (ret < 0)
+ return ret;
+
+ *capacity = raw;
+ return raw;
+}
+
+static int ds2780_get_status(struct ds2780_device_info *dev_info, int *status)
+{
+ int ret, current_uA, capacity;
+
+ ret = ds2780_get_current(dev_info, CURRENT_NOW, &current_uA);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_get_capacity(dev_info, &capacity);
+ if (ret < 0)
+ return ret;
+
+ if (capacity == 100)
+ *status = POWER_SUPPLY_STATUS_FULL;
+ else if (current_uA == 0)
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (current_uA < 0)
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+
+ return 0;
+}
+
+static int ds2780_get_charge_now(struct ds2780_device_info *dev_info,
+ int *charge_now)
+{
+ int ret;
+ u16 charge_raw;
+
+ /*
+ * The RAAC value is located in 16 bits across the RAAC MSB and
+ * LSB registers
+ * Bits 15 - 8 of the RAAC value are in bits 7 - 0 of the RAAC
+ * MSB register
+ * Bits 7 - 0 of the RAAC value are in bits 7 - 0 of the RAAC
+ * LSB register
+ */
+ ret = ds2780_read16(dev_info, &charge_raw, DS2780_RAAC_MSB_REG);
+ if (ret < 0)
+ return ret;
+
+ *charge_now = charge_raw * 1600;
+ return 0;
+}
+
+static int ds2780_get_control_register(struct ds2780_device_info *dev_info,
+ u8 *control_reg)
+{
+ return ds2780_read8(dev_info, control_reg, DS2780_CONTROL_REG);
+}
+
+static int ds2780_set_control_register(struct ds2780_device_info *dev_info,
+ u8 control_reg)
+{
+ int ret;
+
+ ret = ds2780_write(dev_info, &control_reg,
+ DS2780_CONTROL_REG, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return ds2780_save_eeprom(dev_info, DS2780_CONTROL_REG);
+}
+
+static int ds2780_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = ds2780_get_voltage(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = ds2780_get_temperature(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = model;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = manufacturer;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = ds2780_get_current(dev_info, CURRENT_NOW, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = ds2780_get_current(dev_info, CURRENT_AVG, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = ds2780_get_status(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = ds2780_get_capacity(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = ds2780_get_accumulated_current(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = ds2780_get_charge_now(dev_info, &val->intval);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property ds2780_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+};
+
+static ssize_t ds2780_get_pmod_enabled(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 control_reg;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ /* Get power mode */
+ ret = ds2780_get_control_register(dev_info, &control_reg);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n",
+ !!(control_reg & DS2780_CONTROL_REG_PMOD));
+}
+
+static ssize_t ds2780_set_pmod_enabled(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 control_reg, new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ /* Set power mode */
+ ret = ds2780_get_control_register(dev_info, &control_reg);
+ if (ret < 0)
+ return ret;
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ if ((new_setting != 0) && (new_setting != 1)) {
+ dev_err(dev_info->dev, "Invalid pmod setting (0 or 1)\n");
+ return -EINVAL;
+ }
+
+ if (new_setting)
+ control_reg |= DS2780_CONTROL_REG_PMOD;
+ else
+ control_reg &= ~DS2780_CONTROL_REG_PMOD;
+
+ ret = ds2780_set_control_register(dev_info, control_reg);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2780_get_sense_resistor_value(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 sense_resistor;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = ds2780_read8(dev_info, &sense_resistor, DS2780_RSNSP_REG);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(buf, "%d\n", sense_resistor);
+ return ret;
+}
+
+static ssize_t ds2780_set_sense_resistor_value(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_set_sense_register(dev_info, new_setting);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2780_get_rsgain_setting(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u16 rsgain;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = ds2780_get_rsgain_register(dev_info, &rsgain);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", rsgain);
+}
+
+static ssize_t ds2780_set_rsgain_setting(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u16 new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = kstrtou16(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ /* Gain can only be from 0 to 1.999 in steps of .001 */
+ if (new_setting > 1999) {
+ dev_err(dev_info->dev, "Invalid rsgain setting (0 - 1999)\n");
+ return -EINVAL;
+ }
+
+ ret = ds2780_set_rsgain_register(dev_info, new_setting);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2780_get_pio_pin(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 sfr;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = ds2780_read8(dev_info, &sfr, DS2780_SFR_REG);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(buf, "%d\n", sfr & DS2780_SFR_REG_PIOSC);
+ return ret;
+}
+
+static ssize_t ds2780_set_pio_pin(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ if ((new_setting != 0) && (new_setting != 1)) {
+ dev_err(dev_info->dev, "Invalid pio_pin setting (0 or 1)\n");
+ return -EINVAL;
+ }
+
+ ret = ds2780_write(dev_info, &new_setting,
+ DS2780_SFR_REG, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2780_read_param_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ count = min_t(loff_t, count,
+ DS2780_EEPROM_BLOCK1_END -
+ DS2780_EEPROM_BLOCK1_START + 1 - off);
+
+ return ds2780_read_block(dev_info, buf,
+ DS2780_EEPROM_BLOCK1_START + off, count);
+}
+
+static ssize_t ds2780_write_param_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+ int ret;
+
+ count = min_t(loff_t, count,
+ DS2780_EEPROM_BLOCK1_END -
+ DS2780_EEPROM_BLOCK1_START + 1 - off);
+
+ ret = ds2780_write(dev_info, buf,
+ DS2780_EEPROM_BLOCK1_START + off, count);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK1_START);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static struct bin_attribute ds2780_param_eeprom_bin_attr = {
+ .attr = {
+ .name = "param_eeprom",
+ .mode = S_IRUGO | S_IWUSR,
+ },
+ .size = DS2780_EEPROM_BLOCK1_END - DS2780_EEPROM_BLOCK1_START + 1,
+ .read = ds2780_read_param_eeprom_bin,
+ .write = ds2780_write_param_eeprom_bin,
+};
+
+static ssize_t ds2780_read_user_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ count = min_t(loff_t, count,
+ DS2780_EEPROM_BLOCK0_END -
+ DS2780_EEPROM_BLOCK0_START + 1 - off);
+
+ return ds2780_read_block(dev_info, buf,
+ DS2780_EEPROM_BLOCK0_START + off, count);
+}
+
+static ssize_t ds2780_write_user_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+ int ret;
+
+ count = min_t(loff_t, count,
+ DS2780_EEPROM_BLOCK0_END -
+ DS2780_EEPROM_BLOCK0_START + 1 - off);
+
+ ret = ds2780_write(dev_info, buf,
+ DS2780_EEPROM_BLOCK0_START + off, count);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK0_START);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static struct bin_attribute ds2780_user_eeprom_bin_attr = {
+ .attr = {
+ .name = "user_eeprom",
+ .mode = S_IRUGO | S_IWUSR,
+ },
+ .size = DS2780_EEPROM_BLOCK0_END - DS2780_EEPROM_BLOCK0_START + 1,
+ .read = ds2780_read_user_eeprom_bin,
+ .write = ds2780_write_user_eeprom_bin,
+};
+
+static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2780_get_pmod_enabled,
+ ds2780_set_pmod_enabled);
+static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR,
+ ds2780_get_sense_resistor_value, ds2780_set_sense_resistor_value);
+static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2780_get_rsgain_setting,
+ ds2780_set_rsgain_setting);
+static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2780_get_pio_pin,
+ ds2780_set_pio_pin);
+
+
+static struct attribute *ds2780_attributes[] = {
+ &dev_attr_pmod_enabled.attr,
+ &dev_attr_sense_resistor_value.attr,
+ &dev_attr_rsgain_setting.attr,
+ &dev_attr_pio_pin.attr,
+ NULL
+};
+
+static const struct attribute_group ds2780_attr_group = {
+ .attrs = ds2780_attributes,
+};
+
+static int __devinit ds2780_battery_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct ds2780_device_info *dev_info;
+
+ dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL);
+ if (!dev_info) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ platform_set_drvdata(pdev, dev_info);
+
+ dev_info->dev = &pdev->dev;
+ dev_info->w1_dev = pdev->dev.parent;
+ dev_info->bat.name = dev_name(&pdev->dev);
+ dev_info->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+ dev_info->bat.properties = ds2780_battery_props;
+ dev_info->bat.num_properties = ARRAY_SIZE(ds2780_battery_props);
+ dev_info->bat.get_property = ds2780_battery_get_property;
+ dev_info->mutex_holder = current;
+
+ ret = power_supply_register(&pdev->dev, &dev_info->bat);
+ if (ret) {
+ dev_err(dev_info->dev, "failed to register battery\n");
+ goto fail_free_info;
+ }
+
+ ret = sysfs_create_group(&dev_info->bat.dev->kobj, &ds2780_attr_group);
+ if (ret) {
+ dev_err(dev_info->dev, "failed to create sysfs group\n");
+ goto fail_unregister;
+ }
+
+ ret = sysfs_create_bin_file(&dev_info->bat.dev->kobj,
+ &ds2780_param_eeprom_bin_attr);
+ if (ret) {
+ dev_err(dev_info->dev,
+ "failed to create param eeprom bin file");
+ goto fail_remove_group;
+ }
+
+ ret = sysfs_create_bin_file(&dev_info->bat.dev->kobj,
+ &ds2780_user_eeprom_bin_attr);
+ if (ret) {
+ dev_err(dev_info->dev,
+ "failed to create user eeprom bin file");
+ goto fail_remove_bin_file;
+ }
+
+ dev_info->mutex_holder = NULL;
+
+ return 0;
+
+fail_remove_bin_file:
+ sysfs_remove_bin_file(&dev_info->bat.dev->kobj,
+ &ds2780_param_eeprom_bin_attr);
+fail_remove_group:
+ sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group);
+fail_unregister:
+ power_supply_unregister(&dev_info->bat);
+fail_free_info:
+ kfree(dev_info);
+fail:
+ return ret;
+}
+
+static int __devexit ds2780_battery_remove(struct platform_device *pdev)
+{
+ struct ds2780_device_info *dev_info = platform_get_drvdata(pdev);
+
+ dev_info->mutex_holder = current;
+
+ /* remove attributes */
+ sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group);
+
+ power_supply_unregister(&dev_info->bat);
+
+ kfree(dev_info);
+ return 0;
+}
+
+MODULE_ALIAS("platform:ds2780-battery");
+
+static struct platform_driver ds2780_battery_driver = {
+ .driver = {
+ .name = "ds2780-battery",
+ },
+ .probe = ds2780_battery_probe,
+ .remove = ds2780_battery_remove,
+};
+
+static int __init ds2780_battery_init(void)
+{
+ return platform_driver_register(&ds2780_battery_driver);
+}
+
+static void __exit ds2780_battery_exit(void)
+{
+ platform_driver_unregister(&ds2780_battery_driver);
+}
+
+module_init(ds2780_battery_init);
+module_exit(ds2780_battery_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Clifton Barnes <cabarnes@indesign-llc.com>");
+MODULE_DESCRIPTION("Maxim/Dallas DS2780 Stand-Alone Fuel Gauage IC driver");
diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c
new file mode 100644
index 00000000..4d2dc4fa
--- /dev/null
+++ b/drivers/power/ds2782_battery.c
@@ -0,0 +1,421 @@
+/*
+ * I2C client/driver for the Maxim/Dallas DS2782 Stand-Alone Fuel Gauge IC
+ *
+ * Copyright (C) 2009 Bluewater Systems Ltd
+ *
+ * Author: Ryan Mallon <ryan@bluewatersys.com>
+ *
+ * DS2786 added by Yulia Vilensky <vilensky@compulab.co.il>
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/swab.h>
+#include <linux/i2c.h>
+#include <linux/idr.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/ds2782_battery.h>
+
+#define DS2782_REG_RARC 0x06 /* Remaining active relative capacity */
+
+#define DS278x_REG_VOLT_MSB 0x0c
+#define DS278x_REG_TEMP_MSB 0x0a
+#define DS278x_REG_CURRENT_MSB 0x0e
+
+/* EEPROM Block */
+#define DS2782_REG_RSNSP 0x69 /* Sense resistor value */
+
+/* Current unit measurement in uA for a 1 milli-ohm sense resistor */
+#define DS2782_CURRENT_UNITS 1563
+
+#define DS2786_REG_RARC 0x02 /* Remaining active relative capacity */
+
+#define DS2786_CURRENT_UNITS 25
+
+struct ds278x_info;
+
+struct ds278x_battery_ops {
+ int (*get_battery_current)(struct ds278x_info *info, int *current_uA);
+ int (*get_battery_voltage)(struct ds278x_info *info, int *voltage_uV);
+ int (*get_battery_capacity)(struct ds278x_info *info, int *capacity);
+};
+
+#define to_ds278x_info(x) container_of(x, struct ds278x_info, battery)
+
+struct ds278x_info {
+ struct i2c_client *client;
+ struct power_supply battery;
+ struct ds278x_battery_ops *ops;
+ int id;
+ int rsns;
+};
+
+static DEFINE_IDR(battery_id);
+static DEFINE_MUTEX(battery_lock);
+
+static inline int ds278x_read_reg(struct ds278x_info *info, int reg, u8 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(info->client, reg);
+ if (ret < 0) {
+ dev_err(&info->client->dev, "register read failed\n");
+ return ret;
+ }
+
+ *val = ret;
+ return 0;
+}
+
+static inline int ds278x_read_reg16(struct ds278x_info *info, int reg_msb,
+ s16 *val)
+{
+ int ret;
+
+ ret = swab16(i2c_smbus_read_word_data(info->client, reg_msb));
+ if (ret < 0) {
+ dev_err(&info->client->dev, "register read failed\n");
+ return ret;
+ }
+
+ *val = ret;
+ return 0;
+}
+
+static int ds278x_get_temp(struct ds278x_info *info, int *temp)
+{
+ s16 raw;
+ int err;
+
+ /*
+ * Temperature is measured in units of 0.125 degrees celcius, the
+ * power_supply class measures temperature in tenths of degrees
+ * celsius. The temperature value is stored as a 10 bit number, plus
+ * sign in the upper bits of a 16 bit register.
+ */
+ err = ds278x_read_reg16(info, DS278x_REG_TEMP_MSB, &raw);
+ if (err)
+ return err;
+ *temp = ((raw / 32) * 125) / 100;
+ return 0;
+}
+
+static int ds2782_get_current(struct ds278x_info *info, int *current_uA)
+{
+ int sense_res;
+ int err;
+ u8 sense_res_raw;
+ s16 raw;
+
+ /*
+ * The units of measurement for current are dependent on the value of
+ * the sense resistor.
+ */
+ err = ds278x_read_reg(info, DS2782_REG_RSNSP, &sense_res_raw);
+ if (err)
+ return err;
+ if (sense_res_raw == 0) {
+ dev_err(&info->client->dev, "sense resistor value is 0\n");
+ return -ENXIO;
+ }
+ sense_res = 1000 / sense_res_raw;
+
+ dev_dbg(&info->client->dev, "sense resistor = %d milli-ohms\n",
+ sense_res);
+ err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw);
+ if (err)
+ return err;
+ *current_uA = raw * (DS2782_CURRENT_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2782_get_voltage(struct ds278x_info *info, int *voltage_uV)
+{
+ s16 raw;
+ int err;
+
+ /*
+ * Voltage is measured in units of 4.88mV. The voltage is stored as
+ * a 10-bit number plus sign, in the upper bits of a 16-bit register
+ */
+ err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw);
+ if (err)
+ return err;
+ *voltage_uV = (raw / 32) * 4800;
+ return 0;
+}
+
+static int ds2782_get_capacity(struct ds278x_info *info, int *capacity)
+{
+ int err;
+ u8 raw;
+
+ err = ds278x_read_reg(info, DS2782_REG_RARC, &raw);
+ if (err)
+ return err;
+ *capacity = raw;
+ return 0;
+}
+
+static int ds2786_get_current(struct ds278x_info *info, int *current_uA)
+{
+ int err;
+ s16 raw;
+
+ err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw);
+ if (err)
+ return err;
+ *current_uA = (raw / 16) * (DS2786_CURRENT_UNITS / info->rsns);
+ return 0;
+}
+
+static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV)
+{
+ s16 raw;
+ int err;
+
+ /*
+ * Voltage is measured in units of 1.22mV. The voltage is stored as
+ * a 10-bit number plus sign, in the upper bits of a 16-bit register
+ */
+ err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw);
+ if (err)
+ return err;
+ *voltage_uV = (raw / 8) * 1220;
+ return 0;
+}
+
+static int ds2786_get_capacity(struct ds278x_info *info, int *capacity)
+{
+ int err;
+ u8 raw;
+
+ err = ds278x_read_reg(info, DS2786_REG_RARC, &raw);
+ if (err)
+ return err;
+ /* Relative capacity is displayed with resolution 0.5 % */
+ *capacity = raw/2 ;
+ return 0;
+}
+
+static int ds278x_get_status(struct ds278x_info *info, int *status)
+{
+ int err;
+ int current_uA;
+ int capacity;
+
+ err = info->ops->get_battery_current(info, &current_uA);
+ if (err)
+ return err;
+
+ err = info->ops->get_battery_capacity(info, &capacity);
+ if (err)
+ return err;
+
+ if (capacity == 100)
+ *status = POWER_SUPPLY_STATUS_FULL;
+ else if (current_uA == 0)
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (current_uA < 0)
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+
+ return 0;
+}
+
+static int ds278x_battery_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct ds278x_info *info = to_ds278x_info(psy);
+ int ret;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = ds278x_get_status(info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = info->ops->get_battery_capacity(info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = info->ops->get_battery_voltage(info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = info->ops->get_battery_current(info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = ds278x_get_temp(info, &val->intval);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property ds278x_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static void ds278x_power_supply_init(struct power_supply *battery)
+{
+ battery->type = POWER_SUPPLY_TYPE_BATTERY;
+ battery->properties = ds278x_battery_props;
+ battery->num_properties = ARRAY_SIZE(ds278x_battery_props);
+ battery->get_property = ds278x_battery_get_property;
+ battery->external_power_changed = NULL;
+}
+
+static int ds278x_battery_remove(struct i2c_client *client)
+{
+ struct ds278x_info *info = i2c_get_clientdata(client);
+
+ power_supply_unregister(&info->battery);
+ kfree(info->battery.name);
+
+ mutex_lock(&battery_lock);
+ idr_remove(&battery_id, info->id);
+ mutex_unlock(&battery_lock);
+
+ kfree(info);
+ return 0;
+}
+
+enum ds278x_num_id {
+ DS2782 = 0,
+ DS2786,
+};
+
+static struct ds278x_battery_ops ds278x_ops[] = {
+ [DS2782] = {
+ .get_battery_current = ds2782_get_current,
+ .get_battery_voltage = ds2782_get_voltage,
+ .get_battery_capacity = ds2782_get_capacity,
+ },
+ [DS2786] = {
+ .get_battery_current = ds2786_get_current,
+ .get_battery_voltage = ds2786_get_voltage,
+ .get_battery_capacity = ds2786_get_capacity,
+ }
+};
+
+static int ds278x_battery_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ds278x_platform_data *pdata = client->dev.platform_data;
+ struct ds278x_info *info;
+ int ret;
+ int num;
+
+ /*
+ * ds2786 should have the sense resistor value set
+ * in the platform data
+ */
+ if (id->driver_data == DS2786 && !pdata) {
+ dev_err(&client->dev, "missing platform data for ds2786\n");
+ return -EINVAL;
+ }
+
+ /* Get an ID for this battery */
+ ret = idr_pre_get(&battery_id, GFP_KERNEL);
+ if (ret == 0) {
+ ret = -ENOMEM;
+ goto fail_id;
+ }
+
+ mutex_lock(&battery_lock);
+ ret = idr_get_new(&battery_id, client, &num);
+ mutex_unlock(&battery_lock);
+ if (ret < 0)
+ goto fail_id;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto fail_info;
+ }
+
+ info->battery.name = kasprintf(GFP_KERNEL, "%s-%d", client->name, num);
+ if (!info->battery.name) {
+ ret = -ENOMEM;
+ goto fail_name;
+ }
+
+ if (id->driver_data == DS2786)
+ info->rsns = pdata->rsns;
+
+ i2c_set_clientdata(client, info);
+ info->client = client;
+ info->id = num;
+ info->ops = &ds278x_ops[id->driver_data];
+ ds278x_power_supply_init(&info->battery);
+
+ ret = power_supply_register(&client->dev, &info->battery);
+ if (ret) {
+ dev_err(&client->dev, "failed to register battery\n");
+ goto fail_register;
+ }
+
+ return 0;
+
+fail_register:
+ kfree(info->battery.name);
+fail_name:
+ kfree(info);
+fail_info:
+ mutex_lock(&battery_lock);
+ idr_remove(&battery_id, num);
+ mutex_unlock(&battery_lock);
+fail_id:
+ return ret;
+}
+
+static const struct i2c_device_id ds278x_id[] = {
+ {"ds2782", DS2782},
+ {"ds2786", DS2786},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, ds278x_id);
+
+static struct i2c_driver ds278x_battery_driver = {
+ .driver = {
+ .name = "ds2782-battery",
+ },
+ .probe = ds278x_battery_probe,
+ .remove = ds278x_battery_remove,
+ .id_table = ds278x_id,
+};
+
+static int __init ds278x_init(void)
+{
+ return i2c_add_driver(&ds278x_battery_driver);
+}
+module_init(ds278x_init);
+
+static void __exit ds278x_exit(void)
+{
+ i2c_del_driver(&ds278x_battery_driver);
+}
+module_exit(ds278x_exit);
+
+MODULE_AUTHOR("Ryan Mallon <ryan@bluewatersys.com>");
+MODULE_DESCRIPTION("Maxim/Dallas DS2782 Stand-Alone Fuel Gauage IC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c
new file mode 100644
index 00000000..718f2c53
--- /dev/null
+++ b/drivers/power/gpio-charger.c
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ * Driver for chargers which report their online status through a GPIO pin
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include <linux/power/gpio-charger.h>
+
+struct gpio_charger {
+ const struct gpio_charger_platform_data *pdata;
+ unsigned int irq;
+
+ struct power_supply charger;
+};
+
+static irqreturn_t gpio_charger_irq(int irq, void *devid)
+{
+ struct power_supply *charger = devid;
+
+ power_supply_changed(charger);
+
+ return IRQ_HANDLED;
+}
+
+static inline struct gpio_charger *psy_to_gpio_charger(struct power_supply *psy)
+{
+ return container_of(psy, struct gpio_charger, charger);
+}
+
+static int gpio_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct gpio_charger *gpio_charger = psy_to_gpio_charger(psy);
+ const struct gpio_charger_platform_data *pdata = gpio_charger->pdata;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = gpio_get_value(pdata->gpio);
+ val->intval ^= pdata->gpio_active_low;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property gpio_charger_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int __devinit gpio_charger_probe(struct platform_device *pdev)
+{
+ const struct gpio_charger_platform_data *pdata = pdev->dev.platform_data;
+ struct gpio_charger *gpio_charger;
+ struct power_supply *charger;
+ int ret;
+ int irq;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform data\n");
+ return -EINVAL;
+ }
+
+ if (!gpio_is_valid(pdata->gpio)) {
+ dev_err(&pdev->dev, "Invalid gpio pin\n");
+ return -EINVAL;
+ }
+
+ gpio_charger = kzalloc(sizeof(*gpio_charger), GFP_KERNEL);
+ if (!gpio_charger) {
+ dev_err(&pdev->dev, "Failed to alloc driver structure\n");
+ return -ENOMEM;
+ }
+
+ charger = &gpio_charger->charger;
+
+ charger->name = pdata->name ? pdata->name : "gpio-charger";
+ charger->type = pdata->type;
+ charger->properties = gpio_charger_properties;
+ charger->num_properties = ARRAY_SIZE(gpio_charger_properties);
+ charger->get_property = gpio_charger_get_property;
+ charger->supplied_to = pdata->supplied_to;
+ charger->num_supplicants = pdata->num_supplicants;
+
+ ret = gpio_request(pdata->gpio, dev_name(&pdev->dev));
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request gpio pin: %d\n", ret);
+ goto err_free;
+ }
+ ret = gpio_direction_input(pdata->gpio);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to set gpio to input: %d\n", ret);
+ goto err_gpio_free;
+ }
+
+ gpio_charger->pdata = pdata;
+
+ ret = power_supply_register(&pdev->dev, charger);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register power supply: %d\n",
+ ret);
+ goto err_gpio_free;
+ }
+
+ irq = gpio_to_irq(pdata->gpio);
+ if (irq > 0) {
+ ret = request_any_context_irq(irq, gpio_charger_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(&pdev->dev), charger);
+ if (ret)
+ dev_warn(&pdev->dev, "Failed to request irq: %d\n", ret);
+ else
+ gpio_charger->irq = irq;
+ }
+
+ platform_set_drvdata(pdev, gpio_charger);
+
+ return 0;
+
+err_gpio_free:
+ gpio_free(pdata->gpio);
+err_free:
+ kfree(gpio_charger);
+ return ret;
+}
+
+static int __devexit gpio_charger_remove(struct platform_device *pdev)
+{
+ struct gpio_charger *gpio_charger = platform_get_drvdata(pdev);
+
+ if (gpio_charger->irq)
+ free_irq(gpio_charger->irq, &gpio_charger->charger);
+
+ power_supply_unregister(&gpio_charger->charger);
+
+ gpio_free(gpio_charger->pdata->gpio);
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(gpio_charger);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int gpio_charger_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct gpio_charger *gpio_charger = platform_get_drvdata(pdev);
+
+ power_supply_changed(&gpio_charger->charger);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, NULL, gpio_charger_resume);
+
+static struct platform_driver gpio_charger_driver = {
+ .probe = gpio_charger_probe,
+ .remove = __devexit_p(gpio_charger_remove),
+ .driver = {
+ .name = "gpio-charger",
+ .owner = THIS_MODULE,
+ .pm = &gpio_charger_pm_ops,
+ },
+};
+
+static int __init gpio_charger_init(void)
+{
+ return platform_driver_register(&gpio_charger_driver);
+}
+module_init(gpio_charger_init);
+
+static void __exit gpio_charger_exit(void)
+{
+ platform_driver_unregister(&gpio_charger_driver);
+}
+module_exit(gpio_charger_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("Driver for chargers which report their online status through a GPIO");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-charger");
diff --git a/drivers/power/intel_mid_battery.c b/drivers/power/intel_mid_battery.c
new file mode 100644
index 00000000..cffcb7c0
--- /dev/null
+++ b/drivers/power/intel_mid_battery.c
@@ -0,0 +1,797 @@
+/*
+ * intel_mid_battery.c - Intel MID PMIC Battery Driver
+ *
+ * Copyright (C) 2009 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Author: Nithish Mahalingam <nithish.mahalingam@intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/param.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include <asm/intel_scu_ipc.h>
+
+#define DRIVER_NAME "pmic_battery"
+
+/*********************************************************************
+ * Generic defines
+ *********************************************************************/
+
+static int debug;
+module_param(debug, int, 0444);
+MODULE_PARM_DESC(debug, "Flag to enable PMIC Battery debug messages.");
+
+#define PMIC_BATT_DRV_INFO_UPDATED 1
+#define PMIC_BATT_PRESENT 1
+#define PMIC_BATT_NOT_PRESENT 0
+#define PMIC_USB_PRESENT PMIC_BATT_PRESENT
+#define PMIC_USB_NOT_PRESENT PMIC_BATT_NOT_PRESENT
+
+/* pmic battery register related */
+#define PMIC_BATT_CHR_SCHRGINT_ADDR 0xD2
+#define PMIC_BATT_CHR_SBATOVP_MASK (1 << 1)
+#define PMIC_BATT_CHR_STEMP_MASK (1 << 2)
+#define PMIC_BATT_CHR_SCOMP_MASK (1 << 3)
+#define PMIC_BATT_CHR_SUSBDET_MASK (1 << 4)
+#define PMIC_BATT_CHR_SBATDET_MASK (1 << 5)
+#define PMIC_BATT_CHR_SDCLMT_MASK (1 << 6)
+#define PMIC_BATT_CHR_SUSBOVP_MASK (1 << 7)
+#define PMIC_BATT_CHR_EXCPT_MASK 0xC6
+#define PMIC_BATT_ADC_ACCCHRG_MASK (1 << 31)
+#define PMIC_BATT_ADC_ACCCHRGVAL_MASK 0x7FFFFFFF
+
+/* pmic ipc related */
+#define PMIC_BATT_CHR_IPC_FCHRG_SUBID 0x4
+#define PMIC_BATT_CHR_IPC_TCHRG_SUBID 0x6
+
+/* types of battery charging */
+enum batt_charge_type {
+ BATT_USBOTG_500MA_CHARGE,
+ BATT_USBOTG_TRICKLE_CHARGE,
+};
+
+/* valid battery events */
+enum batt_event {
+ BATT_EVENT_BATOVP_EXCPT,
+ BATT_EVENT_USBOVP_EXCPT,
+ BATT_EVENT_TEMP_EXCPT,
+ BATT_EVENT_DCLMT_EXCPT,
+ BATT_EVENT_EXCPT
+};
+
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+
+/*
+ * pmic battery info
+ */
+struct pmic_power_module_info {
+ bool is_dev_info_updated;
+ struct device *dev;
+ /* pmic battery data */
+ unsigned long update_time; /* jiffies when data read */
+ unsigned int usb_is_present;
+ unsigned int batt_is_present;
+ unsigned int batt_health;
+ unsigned int usb_health;
+ unsigned int batt_status;
+ unsigned int batt_charge_now; /* in mAS */
+ unsigned int batt_prev_charge_full; /* in mAS */
+ unsigned int batt_charge_rate; /* in units per second */
+
+ struct power_supply usb;
+ struct power_supply batt;
+ int irq; /* GPE_ID or IRQ# */
+ struct workqueue_struct *monitor_wqueue;
+ struct delayed_work monitor_battery;
+ struct work_struct handler;
+};
+
+static unsigned int delay_time = 2000; /* in ms */
+
+/*
+ * pmic ac properties
+ */
+static enum power_supply_property pmic_usb_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+/*
+ * pmic battery properties
+ */
+static enum power_supply_property pmic_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+};
+
+
+/*
+ * Glue functions for talking to the IPC
+ */
+
+struct battery_property {
+ u32 capacity; /* Charger capacity */
+ u8 crnt; /* Quick charge current value*/
+ u8 volt; /* Fine adjustment of constant charge voltage */
+ u8 prot; /* CHRGPROT register value */
+ u8 prot2; /* CHRGPROT1 register value */
+ u8 timer; /* Charging timer */
+};
+
+#define IPCMSG_BATTERY 0xEF
+
+/* Battery coulomb counter accumulator commands */
+#define IPC_CMD_CC_WR 0 /* Update coulomb counter value */
+#define IPC_CMD_CC_RD 1 /* Read coulomb counter value */
+#define IPC_CMD_BATTERY_PROPERTY 2 /* Read Battery property */
+
+/**
+ * pmic_scu_ipc_battery_cc_read - read battery cc
+ * @value: battery coulomb counter read
+ *
+ * Reads the battery couloumb counter value, returns 0 on success, or
+ * an error code
+ *
+ * This function may sleep. Locking for SCU accesses is handled for
+ * the caller.
+ */
+static int pmic_scu_ipc_battery_cc_read(u32 *value)
+{
+ return intel_scu_ipc_command(IPCMSG_BATTERY, IPC_CMD_CC_RD,
+ NULL, 0, value, 1);
+}
+
+/**
+ * pmic_scu_ipc_battery_property_get - fetch properties
+ * @prop: battery properties
+ *
+ * Retrieve the battery properties from the power management
+ *
+ * This function may sleep. Locking for SCU accesses is handled for
+ * the caller.
+ */
+static int pmic_scu_ipc_battery_property_get(struct battery_property *prop)
+{
+ u32 data[3];
+ u8 *p = (u8 *)&data[1];
+ int err = intel_scu_ipc_command(IPCMSG_BATTERY,
+ IPC_CMD_BATTERY_PROPERTY, NULL, 0, data, 3);
+
+ prop->capacity = data[0];
+ prop->crnt = *p++;
+ prop->volt = *p++;
+ prop->prot = *p++;
+ prop->prot2 = *p++;
+ prop->timer = *p++;
+
+ return err;
+}
+
+/**
+ * pmic_scu_ipc_set_charger - set charger
+ * @charger: charger to select
+ *
+ * Switch the charging mode for the SCU
+ */
+
+static int pmic_scu_ipc_set_charger(int charger)
+{
+ return intel_scu_ipc_simple_command(IPCMSG_BATTERY, charger);
+}
+
+/**
+ * pmic_battery_log_event - log battery events
+ * @event: battery event to be logged
+ * Context: can sleep
+ *
+ * There are multiple battery events which may be of interest to users;
+ * this battery function logs the different battery events onto the
+ * kernel log messages.
+ */
+static void pmic_battery_log_event(enum batt_event event)
+{
+ printk(KERN_WARNING "pmic-battery: ");
+ switch (event) {
+ case BATT_EVENT_BATOVP_EXCPT:
+ printk(KERN_CONT "battery overvoltage condition\n");
+ break;
+ case BATT_EVENT_USBOVP_EXCPT:
+ printk(KERN_CONT "usb charger overvoltage condition\n");
+ break;
+ case BATT_EVENT_TEMP_EXCPT:
+ printk(KERN_CONT "high battery temperature condition\n");
+ break;
+ case BATT_EVENT_DCLMT_EXCPT:
+ printk(KERN_CONT "over battery charge current condition\n");
+ break;
+ default:
+ printk(KERN_CONT "charger/battery exception %d\n", event);
+ break;
+ }
+}
+
+/**
+ * pmic_battery_read_status - read battery status information
+ * @pbi: device info structure to update the read information
+ * Context: can sleep
+ *
+ * PMIC power source information need to be updated based on the data read
+ * from the PMIC battery registers.
+ *
+ */
+static void pmic_battery_read_status(struct pmic_power_module_info *pbi)
+{
+ unsigned int update_time_intrvl;
+ unsigned int chrg_val;
+ u32 ccval;
+ u8 r8;
+ struct battery_property batt_prop;
+ int batt_present = 0;
+ int usb_present = 0;
+ int batt_exception = 0;
+
+ /* make sure the last batt_status read happened delay_time before */
+ if (pbi->update_time && time_before(jiffies, pbi->update_time +
+ msecs_to_jiffies(delay_time)))
+ return;
+
+ update_time_intrvl = jiffies_to_msecs(jiffies - pbi->update_time);
+ pbi->update_time = jiffies;
+
+ /* read coulomb counter registers and schrgint register */
+ if (pmic_scu_ipc_battery_cc_read(&ccval)) {
+ dev_warn(pbi->dev, "%s(): ipc config cmd failed\n",
+ __func__);
+ return;
+ }
+
+ if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) {
+ dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
+ __func__);
+ return;
+ }
+
+ /*
+ * set pmic_power_module_info members based on pmic register values
+ * read.
+ */
+
+ /* set batt_is_present */
+ if (r8 & PMIC_BATT_CHR_SBATDET_MASK) {
+ pbi->batt_is_present = PMIC_BATT_PRESENT;
+ batt_present = 1;
+ } else {
+ pbi->batt_is_present = PMIC_BATT_NOT_PRESENT;
+ pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ /* set batt_health */
+ if (batt_present) {
+ if (r8 & PMIC_BATT_CHR_SBATOVP_MASK) {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ pmic_battery_log_event(BATT_EVENT_BATOVP_EXCPT);
+ batt_exception = 1;
+ } else if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT);
+ batt_exception = 1;
+ } else if (r8 & PMIC_BATT_CHR_STEMP_MASK) {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ pmic_battery_log_event(BATT_EVENT_TEMP_EXCPT);
+ batt_exception = 1;
+ } else {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ }
+ }
+
+ /* set usb_is_present */
+ if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) {
+ pbi->usb_is_present = PMIC_USB_PRESENT;
+ usb_present = 1;
+ } else {
+ pbi->usb_is_present = PMIC_USB_NOT_PRESENT;
+ pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ if (usb_present) {
+ if (r8 & PMIC_BATT_CHR_SUSBOVP_MASK) {
+ pbi->usb_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ pmic_battery_log_event(BATT_EVENT_USBOVP_EXCPT);
+ } else {
+ pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD;
+ }
+ }
+
+ chrg_val = ccval & PMIC_BATT_ADC_ACCCHRGVAL_MASK;
+
+ /* set batt_prev_charge_full to battery capacity the first time */
+ if (!pbi->is_dev_info_updated) {
+ if (pmic_scu_ipc_battery_property_get(&batt_prop)) {
+ dev_warn(pbi->dev, "%s(): ipc config cmd failed\n",
+ __func__);
+ return;
+ }
+ pbi->batt_prev_charge_full = batt_prop.capacity;
+ }
+
+ /* set batt_status */
+ if (batt_present && !batt_exception) {
+ if (r8 & PMIC_BATT_CHR_SCOMP_MASK) {
+ pbi->batt_status = POWER_SUPPLY_STATUS_FULL;
+ pbi->batt_prev_charge_full = chrg_val;
+ } else if (ccval & PMIC_BATT_ADC_ACCCHRG_MASK) {
+ pbi->batt_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else {
+ pbi->batt_status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ }
+
+ /* set batt_charge_rate */
+ if (pbi->is_dev_info_updated && batt_present && !batt_exception) {
+ if (pbi->batt_status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ if (pbi->batt_charge_now - chrg_val) {
+ pbi->batt_charge_rate = ((pbi->batt_charge_now -
+ chrg_val) * 1000 * 60) /
+ update_time_intrvl;
+ }
+ } else if (pbi->batt_status == POWER_SUPPLY_STATUS_CHARGING) {
+ if (chrg_val - pbi->batt_charge_now) {
+ pbi->batt_charge_rate = ((chrg_val -
+ pbi->batt_charge_now) * 1000 * 60) /
+ update_time_intrvl;
+ }
+ } else
+ pbi->batt_charge_rate = 0;
+ } else {
+ pbi->batt_charge_rate = -1;
+ }
+
+ /* batt_charge_now */
+ if (batt_present && !batt_exception)
+ pbi->batt_charge_now = chrg_val;
+ else
+ pbi->batt_charge_now = -1;
+
+ pbi->is_dev_info_updated = PMIC_BATT_DRV_INFO_UPDATED;
+}
+
+/**
+ * pmic_usb_get_property - usb power source get property
+ * @psy: usb power supply context
+ * @psp: usb power source property
+ * @val: usb power source property value
+ * Context: can sleep
+ *
+ * PMIC usb power source property needs to be provided to power_supply
+ * subsytem for it to provide the information to users.
+ */
+static int pmic_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pmic_power_module_info *pbi = container_of(psy,
+ struct pmic_power_module_info, usb);
+
+ /* update pmic_power_module_info members */
+ pmic_battery_read_status(pbi);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = pbi->usb_is_present;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = pbi->usb_health;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static inline unsigned long mAStouAh(unsigned long v)
+{
+ /* seconds to hours, mA to µA */
+ return (v * 1000) / 3600;
+}
+
+/**
+ * pmic_battery_get_property - battery power source get property
+ * @psy: battery power supply context
+ * @psp: battery power source property
+ * @val: battery power source property value
+ * Context: can sleep
+ *
+ * PMIC battery power source property needs to be provided to power_supply
+ * subsytem for it to provide the information to users.
+ */
+static int pmic_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pmic_power_module_info *pbi = container_of(psy,
+ struct pmic_power_module_info, batt);
+
+ /* update pmic_power_module_info members */
+ pmic_battery_read_status(pbi);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = pbi->batt_status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = pbi->batt_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = pbi->batt_is_present;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = mAStouAh(pbi->batt_charge_now);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = mAStouAh(pbi->batt_prev_charge_full);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * pmic_battery_monitor - monitor battery status
+ * @work: work structure
+ * Context: can sleep
+ *
+ * PMIC battery status needs to be monitored for any change
+ * and information needs to be frequently updated.
+ */
+static void pmic_battery_monitor(struct work_struct *work)
+{
+ struct pmic_power_module_info *pbi = container_of(work,
+ struct pmic_power_module_info, monitor_battery.work);
+
+ /* update pmic_power_module_info members */
+ pmic_battery_read_status(pbi);
+ queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 10);
+}
+
+/**
+ * pmic_battery_set_charger - set battery charger
+ * @pbi: device info structure
+ * @chrg: charge mode to set battery charger in
+ * Context: can sleep
+ *
+ * PMIC battery charger needs to be enabled based on the usb charge
+ * capabilities connected to the platform.
+ */
+static int pmic_battery_set_charger(struct pmic_power_module_info *pbi,
+ enum batt_charge_type chrg)
+{
+ int retval;
+
+ /* set usblmt bits and chrgcntl register bits appropriately */
+ switch (chrg) {
+ case BATT_USBOTG_500MA_CHARGE:
+ retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_FCHRG_SUBID);
+ break;
+ case BATT_USBOTG_TRICKLE_CHARGE:
+ retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_TCHRG_SUBID);
+ break;
+ default:
+ dev_warn(pbi->dev, "%s(): out of range usb charger "
+ "charge detected\n", __func__);
+ return -EINVAL;
+ }
+
+ if (retval) {
+ dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
+ __func__);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * pmic_battery_interrupt_handler - pmic battery interrupt handler
+ * Context: interrupt context
+ *
+ * PMIC battery interrupt handler which will be called with either
+ * battery full condition occurs or usb otg & battery connect
+ * condition occurs.
+ */
+static irqreturn_t pmic_battery_interrupt_handler(int id, void *dev)
+{
+ struct pmic_power_module_info *pbi = dev;
+
+ schedule_work(&pbi->handler);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * pmic_battery_handle_intrpt - pmic battery service interrupt
+ * @work: work structure
+ * Context: can sleep
+ *
+ * PMIC battery needs to either update the battery status as full
+ * if it detects battery full condition caused the interrupt or needs
+ * to enable battery charger if it detects usb and battery detect
+ * caused the source of interrupt.
+ */
+static void pmic_battery_handle_intrpt(struct work_struct *work)
+{
+ struct pmic_power_module_info *pbi = container_of(work,
+ struct pmic_power_module_info, handler);
+ enum batt_charge_type chrg;
+ u8 r8;
+
+ if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) {
+ dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
+ __func__);
+ return;
+ }
+ /* find the cause of the interrupt */
+ if (r8 & PMIC_BATT_CHR_SBATDET_MASK) {
+ pbi->batt_is_present = PMIC_BATT_PRESENT;
+ } else {
+ pbi->batt_is_present = PMIC_BATT_NOT_PRESENT;
+ pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ return;
+ }
+
+ if (r8 & PMIC_BATT_CHR_EXCPT_MASK) {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ pmic_battery_log_event(BATT_EVENT_EXCPT);
+ return;
+ } else {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ if (r8 & PMIC_BATT_CHR_SCOMP_MASK) {
+ u32 ccval;
+ pbi->batt_status = POWER_SUPPLY_STATUS_FULL;
+
+ if (pmic_scu_ipc_battery_cc_read(&ccval)) {
+ dev_warn(pbi->dev, "%s(): ipc config cmd "
+ "failed\n", __func__);
+ return;
+ }
+ pbi->batt_prev_charge_full = ccval &
+ PMIC_BATT_ADC_ACCCHRGVAL_MASK;
+ return;
+ }
+
+ if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) {
+ pbi->usb_is_present = PMIC_USB_PRESENT;
+ } else {
+ pbi->usb_is_present = PMIC_USB_NOT_PRESENT;
+ pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ return;
+ }
+
+ /* setup battery charging */
+
+#if 0
+ /* check usb otg power capability and set charger accordingly */
+ retval = langwell_udc_maxpower(&power);
+ if (retval) {
+ dev_warn(pbi->dev,
+ "%s(): usb otg power query failed with error code %d\n",
+ __func__, retval);
+ return;
+ }
+
+ if (power >= 500)
+ chrg = BATT_USBOTG_500MA_CHARGE;
+ else
+#endif
+ chrg = BATT_USBOTG_TRICKLE_CHARGE;
+
+ /* enable battery charging */
+ if (pmic_battery_set_charger(pbi, chrg)) {
+ dev_warn(pbi->dev,
+ "%s(): failed to set up battery charging\n", __func__);
+ return;
+ }
+
+ dev_dbg(pbi->dev,
+ "pmic-battery: %s() - setting up battery charger successful\n",
+ __func__);
+}
+
+/**
+ * pmic_battery_probe - pmic battery initialize
+ * @irq: pmic battery device irq
+ * @dev: pmic battery device structure
+ * Context: can sleep
+ *
+ * PMIC battery initializes its internal data structue and other
+ * infrastructure components for it to work as expected.
+ */
+static __devinit int probe(int irq, struct device *dev)
+{
+ int retval = 0;
+ struct pmic_power_module_info *pbi;
+
+ dev_dbg(dev, "pmic-battery: found pmic battery device\n");
+
+ pbi = kzalloc(sizeof(*pbi), GFP_KERNEL);
+ if (!pbi) {
+ dev_err(dev, "%s(): memory allocation failed\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ pbi->dev = dev;
+ pbi->irq = irq;
+ dev_set_drvdata(dev, pbi);
+
+ /* initialize all required framework before enabling interrupts */
+ INIT_WORK(&pbi->handler, pmic_battery_handle_intrpt);
+ INIT_DELAYED_WORK(&pbi->monitor_battery, pmic_battery_monitor);
+ pbi->monitor_wqueue =
+ create_singlethread_workqueue(dev_name(dev));
+ if (!pbi->monitor_wqueue) {
+ dev_err(dev, "%s(): wqueue init failed\n", __func__);
+ retval = -ESRCH;
+ goto wqueue_failed;
+ }
+
+ /* register interrupt */
+ retval = request_irq(pbi->irq, pmic_battery_interrupt_handler,
+ 0, DRIVER_NAME, pbi);
+ if (retval) {
+ dev_err(dev, "%s(): cannot get IRQ\n", __func__);
+ goto requestirq_failed;
+ }
+
+ /* register pmic-batt with power supply subsystem */
+ pbi->batt.name = "pmic-batt";
+ pbi->batt.type = POWER_SUPPLY_TYPE_BATTERY;
+ pbi->batt.properties = pmic_battery_props;
+ pbi->batt.num_properties = ARRAY_SIZE(pmic_battery_props);
+ pbi->batt.get_property = pmic_battery_get_property;
+ retval = power_supply_register(dev, &pbi->batt);
+ if (retval) {
+ dev_err(dev,
+ "%s(): failed to register pmic battery device with power supply subsystem\n",
+ __func__);
+ goto power_reg_failed;
+ }
+
+ dev_dbg(dev, "pmic-battery: %s() - pmic battery device "
+ "registration with power supply subsystem successful\n",
+ __func__);
+
+ queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 1);
+
+ /* register pmic-usb with power supply subsystem */
+ pbi->usb.name = "pmic-usb";
+ pbi->usb.type = POWER_SUPPLY_TYPE_USB;
+ pbi->usb.properties = pmic_usb_props;
+ pbi->usb.num_properties = ARRAY_SIZE(pmic_usb_props);
+ pbi->usb.get_property = pmic_usb_get_property;
+ retval = power_supply_register(dev, &pbi->usb);
+ if (retval) {
+ dev_err(dev,
+ "%s(): failed to register pmic usb device with power supply subsystem\n",
+ __func__);
+ goto power_reg_failed_1;
+ }
+
+ if (debug)
+ printk(KERN_INFO "pmic-battery: %s() - pmic usb device "
+ "registration with power supply subsystem successful\n",
+ __func__);
+
+ return retval;
+
+power_reg_failed_1:
+ power_supply_unregister(&pbi->batt);
+power_reg_failed:
+ cancel_delayed_work_sync(&pbi->monitor_battery);
+requestirq_failed:
+ destroy_workqueue(pbi->monitor_wqueue);
+wqueue_failed:
+ kfree(pbi);
+
+ return retval;
+}
+
+static int __devinit platform_pmic_battery_probe(struct platform_device *pdev)
+{
+ return probe(pdev->id, &pdev->dev);
+}
+
+/**
+ * pmic_battery_remove - pmic battery finalize
+ * @dev: pmic battery device structure
+ * Context: can sleep
+ *
+ * PMIC battery finalizes its internal data structue and other
+ * infrastructure components that it initialized in
+ * pmic_battery_probe.
+ */
+
+static int __devexit platform_pmic_battery_remove(struct platform_device *pdev)
+{
+ struct pmic_power_module_info *pbi = dev_get_drvdata(&pdev->dev);
+
+ free_irq(pbi->irq, pbi);
+ cancel_delayed_work_sync(&pbi->monitor_battery);
+ destroy_workqueue(pbi->monitor_wqueue);
+
+ power_supply_unregister(&pbi->usb);
+ power_supply_unregister(&pbi->batt);
+
+ cancel_work_sync(&pbi->handler);
+ kfree(pbi);
+ return 0;
+}
+
+static struct platform_driver platform_pmic_battery_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = platform_pmic_battery_probe,
+ .remove = __devexit_p(platform_pmic_battery_remove),
+};
+
+static int __init platform_pmic_battery_module_init(void)
+{
+ return platform_driver_register(&platform_pmic_battery_driver);
+}
+
+static void __exit platform_pmic_battery_module_exit(void)
+{
+ platform_driver_unregister(&platform_pmic_battery_driver);
+}
+
+module_init(platform_pmic_battery_module_init);
+module_exit(platform_pmic_battery_module_exit);
+
+MODULE_AUTHOR("Nithish Mahalingam <nithish.mahalingam@intel.com>");
+MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c
new file mode 100644
index 00000000..f6d72b40
--- /dev/null
+++ b/drivers/power/isp1704_charger.c
@@ -0,0 +1,512 @@
+/*
+ * ISP1704 USB Charger Detection driver
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/delay.h>
+
+#include <linux/usb/otg.h>
+#include <linux/usb/ulpi.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/power/isp1704_charger.h>
+
+/* Vendor specific Power Control register */
+#define ISP1704_PWR_CTRL 0x3d
+#define ISP1704_PWR_CTRL_SWCTRL (1 << 0)
+#define ISP1704_PWR_CTRL_DET_COMP (1 << 1)
+#define ISP1704_PWR_CTRL_BVALID_RISE (1 << 2)
+#define ISP1704_PWR_CTRL_BVALID_FALL (1 << 3)
+#define ISP1704_PWR_CTRL_DP_WKPU_EN (1 << 4)
+#define ISP1704_PWR_CTRL_VDAT_DET (1 << 5)
+#define ISP1704_PWR_CTRL_DPVSRC_EN (1 << 6)
+#define ISP1704_PWR_CTRL_HWDETECT (1 << 7)
+
+#define NXP_VENDOR_ID 0x04cc
+
+static u16 isp170x_id[] = {
+ 0x1704,
+ 0x1707,
+};
+
+struct isp1704_charger {
+ struct device *dev;
+ struct power_supply psy;
+ struct otg_transceiver *otg;
+ struct notifier_block nb;
+ struct work_struct work;
+
+ /* properties */
+ char model[8];
+ unsigned present:1;
+ unsigned online:1;
+ unsigned current_max;
+
+ /* temp storage variables */
+ unsigned long event;
+ unsigned max_power;
+};
+
+/*
+ * Disable/enable the power from the isp1704 if a function for it
+ * has been provided with platform data.
+ */
+static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on)
+{
+ struct isp1704_charger_data *board = isp->dev->platform_data;
+
+ if (board->set_power)
+ board->set_power(on);
+}
+
+/*
+ * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB
+ * chargers).
+ *
+ * REVISIT: The method is defined in Battery Charging Specification and is
+ * applicable to any ULPI transceiver. Nothing isp170x specific here.
+ */
+static inline int isp1704_charger_type(struct isp1704_charger *isp)
+{
+ u8 reg;
+ u8 func_ctrl;
+ u8 otg_ctrl;
+ int type = POWER_SUPPLY_TYPE_USB_DCP;
+
+ func_ctrl = otg_io_read(isp->otg, ULPI_FUNC_CTRL);
+ otg_ctrl = otg_io_read(isp->otg, ULPI_OTG_CTRL);
+
+ /* disable pulldowns */
+ reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN;
+ otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), reg);
+
+ /* full speed */
+ otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_XCVRSEL_MASK);
+ otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_FULL_SPEED);
+
+ /* Enable strong pull-up on DP (1.5K) and reset */
+ reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
+ otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), reg);
+ usleep_range(1000, 2000);
+
+ reg = otg_io_read(isp->otg, ULPI_DEBUG);
+ if ((reg & 3) != 3)
+ type = POWER_SUPPLY_TYPE_USB_CDP;
+
+ /* recover original state */
+ otg_io_write(isp->otg, ULPI_FUNC_CTRL, func_ctrl);
+ otg_io_write(isp->otg, ULPI_OTG_CTRL, otg_ctrl);
+
+ return type;
+}
+
+/*
+ * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger
+ * is actually a dedicated charger, the following steps need to be taken.
+ */
+static inline int isp1704_charger_verify(struct isp1704_charger *isp)
+{
+ int ret = 0;
+ u8 r;
+
+ /* Reset the transceiver */
+ r = otg_io_read(isp->otg, ULPI_FUNC_CTRL);
+ r |= ULPI_FUNC_CTRL_RESET;
+ otg_io_write(isp->otg, ULPI_FUNC_CTRL, r);
+ usleep_range(1000, 2000);
+
+ /* Set normal mode */
+ r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK);
+ otg_io_write(isp->otg, ULPI_FUNC_CTRL, r);
+
+ /* Clear the DP and DM pull-down bits */
+ r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN;
+ otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), r);
+
+ /* Enable strong pull-up on DP (1.5K) and reset */
+ r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
+ otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), r);
+ usleep_range(1000, 2000);
+
+ /* Read the line state */
+ if (!otg_io_read(isp->otg, ULPI_DEBUG)) {
+ /* Disable strong pull-up on DP (1.5K) */
+ otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_TERMSELECT);
+ return 1;
+ }
+
+ /* Is it a charger or PS/2 connection */
+
+ /* Enable weak pull-up resistor on DP */
+ otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL),
+ ISP1704_PWR_CTRL_DP_WKPU_EN);
+
+ /* Disable strong pull-up on DP (1.5K) */
+ otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_TERMSELECT);
+
+ /* Enable weak pull-down resistor on DM */
+ otg_io_write(isp->otg, ULPI_SET(ULPI_OTG_CTRL),
+ ULPI_OTG_CTRL_DM_PULLDOWN);
+
+ /* It's a charger if the line states are clear */
+ if (!(otg_io_read(isp->otg, ULPI_DEBUG)))
+ ret = 1;
+
+ /* Disable weak pull-up resistor on DP */
+ otg_io_write(isp->otg, ULPI_CLR(ISP1704_PWR_CTRL),
+ ISP1704_PWR_CTRL_DP_WKPU_EN);
+
+ return ret;
+}
+
+static inline int isp1704_charger_detect(struct isp1704_charger *isp)
+{
+ unsigned long timeout;
+ u8 pwr_ctrl;
+ int ret = 0;
+
+ pwr_ctrl = otg_io_read(isp->otg, ISP1704_PWR_CTRL);
+
+ /* set SW control bit in PWR_CTRL register */
+ otg_io_write(isp->otg, ISP1704_PWR_CTRL,
+ ISP1704_PWR_CTRL_SWCTRL);
+
+ /* enable manual charger detection */
+ otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL),
+ ISP1704_PWR_CTRL_SWCTRL
+ | ISP1704_PWR_CTRL_DPVSRC_EN);
+ usleep_range(1000, 2000);
+
+ timeout = jiffies + msecs_to_jiffies(300);
+ do {
+ /* Check if there is a charger */
+ if (otg_io_read(isp->otg, ISP1704_PWR_CTRL)
+ & ISP1704_PWR_CTRL_VDAT_DET) {
+ ret = isp1704_charger_verify(isp);
+ break;
+ }
+ } while (!time_after(jiffies, timeout) && isp->online);
+
+ /* recover original state */
+ otg_io_write(isp->otg, ISP1704_PWR_CTRL, pwr_ctrl);
+
+ return ret;
+}
+
+static void isp1704_charger_work(struct work_struct *data)
+{
+ int detect;
+ unsigned long event;
+ unsigned power;
+ struct isp1704_charger *isp =
+ container_of(data, struct isp1704_charger, work);
+ static DEFINE_MUTEX(lock);
+
+ event = isp->event;
+ power = isp->max_power;
+
+ mutex_lock(&lock);
+
+ if (event != USB_EVENT_NONE)
+ isp1704_charger_set_power(isp, 1);
+
+ switch (event) {
+ case USB_EVENT_VBUS:
+ isp->online = true;
+
+ /* detect charger */
+ detect = isp1704_charger_detect(isp);
+
+ if (detect) {
+ isp->present = detect;
+ isp->psy.type = isp1704_charger_type(isp);
+ }
+
+ switch (isp->psy.type) {
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ isp->current_max = 1800;
+ break;
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ /*
+ * Only 500mA here or high speed chirp
+ * handshaking may break
+ */
+ isp->current_max = 500;
+ /* FALLTHROUGH */
+ case POWER_SUPPLY_TYPE_USB:
+ default:
+ /* enable data pullups */
+ if (isp->otg->gadget)
+ usb_gadget_connect(isp->otg->gadget);
+ }
+ break;
+ case USB_EVENT_NONE:
+ isp->online = false;
+ isp->current_max = 0;
+ isp->present = 0;
+ isp->current_max = 0;
+ isp->psy.type = POWER_SUPPLY_TYPE_USB;
+
+ /*
+ * Disable data pullups. We need to prevent the controller from
+ * enumerating.
+ *
+ * FIXME: This is here to allow charger detection with Host/HUB
+ * chargers. The pullups may be enabled elsewhere, so this can
+ * not be the final solution.
+ */
+ if (isp->otg->gadget)
+ usb_gadget_disconnect(isp->otg->gadget);
+
+ isp1704_charger_set_power(isp, 0);
+ break;
+ case USB_EVENT_ENUMERATED:
+ if (isp->present)
+ isp->current_max = 1800;
+ else
+ isp->current_max = power;
+ break;
+ default:
+ goto out;
+ }
+
+ power_supply_changed(&isp->psy);
+out:
+ mutex_unlock(&lock);
+}
+
+static int isp1704_notifier_call(struct notifier_block *nb,
+ unsigned long event, void *power)
+{
+ struct isp1704_charger *isp =
+ container_of(nb, struct isp1704_charger, nb);
+
+ isp->event = event;
+
+ if (power)
+ isp->max_power = *((unsigned *)power);
+
+ schedule_work(&isp->work);
+
+ return NOTIFY_OK;
+}
+
+static int isp1704_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct isp1704_charger *isp =
+ container_of(psy, struct isp1704_charger, psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = isp->present;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = isp->online;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = isp->current_max;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = isp->model;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "NXP";
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property power_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static inline int isp1704_test_ulpi(struct isp1704_charger *isp)
+{
+ int vendor;
+ int product;
+ int i;
+ int ret = -ENODEV;
+
+ /* Test ULPI interface */
+ ret = otg_io_write(isp->otg, ULPI_SCRATCH, 0xaa);
+ if (ret < 0)
+ return ret;
+
+ ret = otg_io_read(isp->otg, ULPI_SCRATCH);
+ if (ret < 0)
+ return ret;
+
+ if (ret != 0xaa)
+ return -ENODEV;
+
+ /* Verify the product and vendor id matches */
+ vendor = otg_io_read(isp->otg, ULPI_VENDOR_ID_LOW);
+ vendor |= otg_io_read(isp->otg, ULPI_VENDOR_ID_HIGH) << 8;
+ if (vendor != NXP_VENDOR_ID)
+ return -ENODEV;
+
+ product = otg_io_read(isp->otg, ULPI_PRODUCT_ID_LOW);
+ product |= otg_io_read(isp->otg, ULPI_PRODUCT_ID_HIGH) << 8;
+
+ for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) {
+ if (product == isp170x_id[i]) {
+ sprintf(isp->model, "isp%x", product);
+ return product;
+ }
+ }
+
+ dev_err(isp->dev, "product id %x not matching known ids", product);
+
+ return -ENODEV;
+}
+
+static int __devinit isp1704_charger_probe(struct platform_device *pdev)
+{
+ struct isp1704_charger *isp;
+ int ret = -ENODEV;
+
+ isp = kzalloc(sizeof *isp, GFP_KERNEL);
+ if (!isp)
+ return -ENOMEM;
+
+ isp->otg = otg_get_transceiver();
+ if (!isp->otg)
+ goto fail0;
+
+ isp->dev = &pdev->dev;
+ platform_set_drvdata(pdev, isp);
+
+ isp1704_charger_set_power(isp, 1);
+
+ ret = isp1704_test_ulpi(isp);
+ if (ret < 0)
+ goto fail1;
+
+ isp->psy.name = "isp1704";
+ isp->psy.type = POWER_SUPPLY_TYPE_USB;
+ isp->psy.properties = power_props;
+ isp->psy.num_properties = ARRAY_SIZE(power_props);
+ isp->psy.get_property = isp1704_charger_get_property;
+
+ ret = power_supply_register(isp->dev, &isp->psy);
+ if (ret)
+ goto fail1;
+
+ /*
+ * REVISIT: using work in order to allow the otg notifications to be
+ * made atomically in the future.
+ */
+ INIT_WORK(&isp->work, isp1704_charger_work);
+
+ isp->nb.notifier_call = isp1704_notifier_call;
+
+ ret = otg_register_notifier(isp->otg, &isp->nb);
+ if (ret)
+ goto fail2;
+
+ dev_info(isp->dev, "registered with product id %s\n", isp->model);
+
+ /*
+ * Taking over the D+ pullup.
+ *
+ * FIXME: The device will be disconnected if it was already
+ * enumerated. The charger driver should be always loaded before any
+ * gadget is loaded.
+ */
+ if (isp->otg->gadget)
+ usb_gadget_disconnect(isp->otg->gadget);
+
+ /* Detect charger if VBUS is valid (the cable was already plugged). */
+ ret = otg_io_read(isp->otg, ULPI_USB_INT_STS);
+ isp1704_charger_set_power(isp, 0);
+ if ((ret & ULPI_INT_VBUS_VALID) && !isp->otg->default_a) {
+ isp->event = USB_EVENT_VBUS;
+ schedule_work(&isp->work);
+ }
+
+ return 0;
+fail2:
+ power_supply_unregister(&isp->psy);
+fail1:
+ otg_put_transceiver(isp->otg);
+fail0:
+ kfree(isp);
+
+ dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
+
+ return ret;
+}
+
+static int __devexit isp1704_charger_remove(struct platform_device *pdev)
+{
+ struct isp1704_charger *isp = platform_get_drvdata(pdev);
+
+ otg_unregister_notifier(isp->otg, &isp->nb);
+ power_supply_unregister(&isp->psy);
+ otg_put_transceiver(isp->otg);
+ isp1704_charger_set_power(isp, 0);
+ kfree(isp);
+
+ return 0;
+}
+
+static struct platform_driver isp1704_charger_driver = {
+ .driver = {
+ .name = "isp1704_charger",
+ },
+ .probe = isp1704_charger_probe,
+ .remove = __devexit_p(isp1704_charger_remove),
+};
+
+static int __init isp1704_charger_init(void)
+{
+ return platform_driver_register(&isp1704_charger_driver);
+}
+module_init(isp1704_charger_init);
+
+static void __exit isp1704_charger_exit(void)
+{
+ platform_driver_unregister(&isp1704_charger_driver);
+}
+module_exit(isp1704_charger_exit);
+
+MODULE_ALIAS("platform:isp1704_charger");
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_DESCRIPTION("ISP170x USB Charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c
new file mode 100644
index 00000000..763f894e
--- /dev/null
+++ b/drivers/power/jz4740-battery.c
@@ -0,0 +1,459 @@
+/*
+ * Battery measurement code for Ingenic JZ SOC.
+ *
+ * Copyright (C) 2009 Jiejing Zhang <kzjeef@gmail.com>
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * based on tosa_battery.c
+ *
+ * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.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/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/mfd/core.h>
+#include <linux/power_supply.h>
+
+#include <linux/power/jz4740-battery.h>
+#include <linux/jz4740-adc.h>
+
+struct jz_battery {
+ struct jz_battery_platform_data *pdata;
+ struct platform_device *pdev;
+
+ struct resource *mem;
+ void __iomem *base;
+
+ int irq;
+ int charge_irq;
+
+ const struct mfd_cell *cell;
+
+ int status;
+ long voltage;
+
+ struct completion read_completion;
+
+ struct power_supply battery;
+ struct delayed_work work;
+
+ struct mutex lock;
+};
+
+static inline struct jz_battery *psy_to_jz_battery(struct power_supply *psy)
+{
+ return container_of(psy, struct jz_battery, battery);
+}
+
+static irqreturn_t jz_battery_irq_handler(int irq, void *devid)
+{
+ struct jz_battery *battery = devid;
+
+ complete(&battery->read_completion);
+ return IRQ_HANDLED;
+}
+
+static long jz_battery_read_voltage(struct jz_battery *battery)
+{
+ unsigned long t;
+ unsigned long val;
+ long voltage;
+
+ mutex_lock(&battery->lock);
+
+ INIT_COMPLETION(battery->read_completion);
+
+ enable_irq(battery->irq);
+ battery->cell->enable(battery->pdev);
+
+ t = wait_for_completion_interruptible_timeout(&battery->read_completion,
+ HZ);
+
+ if (t > 0) {
+ val = readw(battery->base) & 0xfff;
+
+ if (battery->pdata->info.voltage_max_design <= 2500000)
+ val = (val * 78125UL) >> 7UL;
+ else
+ val = ((val * 924375UL) >> 9UL) + 33000;
+ voltage = (long)val;
+ } else {
+ voltage = t ? t : -ETIMEDOUT;
+ }
+
+ battery->cell->disable(battery->pdev);
+ disable_irq(battery->irq);
+
+ mutex_unlock(&battery->lock);
+
+ return voltage;
+}
+
+static int jz_battery_get_capacity(struct power_supply *psy)
+{
+ struct jz_battery *jz_battery = psy_to_jz_battery(psy);
+ struct power_supply_info *info = &jz_battery->pdata->info;
+ long voltage;
+ int ret;
+ int voltage_span;
+
+ voltage = jz_battery_read_voltage(jz_battery);
+
+ if (voltage < 0)
+ return voltage;
+
+ voltage_span = info->voltage_max_design - info->voltage_min_design;
+ ret = ((voltage - info->voltage_min_design) * 100) / voltage_span;
+
+ if (ret > 100)
+ ret = 100;
+ else if (ret < 0)
+ ret = 0;
+
+ return ret;
+}
+
+static int jz_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct jz_battery *jz_battery = psy_to_jz_battery(psy);
+ struct power_supply_info *info = &jz_battery->pdata->info;
+ long voltage;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = jz_battery->status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = jz_battery->pdata->info.technology;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ voltage = jz_battery_read_voltage(jz_battery);
+ if (voltage < info->voltage_min_design)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = jz_battery_get_capacity(psy);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = jz_battery_read_voltage(jz_battery);
+ if (val->intval < 0)
+ return val->intval;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = info->voltage_max_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = info->voltage_min_design;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void jz_battery_external_power_changed(struct power_supply *psy)
+{
+ struct jz_battery *jz_battery = psy_to_jz_battery(psy);
+
+ cancel_delayed_work(&jz_battery->work);
+ schedule_delayed_work(&jz_battery->work, 0);
+}
+
+static irqreturn_t jz_battery_charge_irq(int irq, void *data)
+{
+ struct jz_battery *jz_battery = data;
+
+ cancel_delayed_work(&jz_battery->work);
+ schedule_delayed_work(&jz_battery->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static void jz_battery_update(struct jz_battery *jz_battery)
+{
+ int status;
+ long voltage;
+ bool has_changed = false;
+ int is_charging;
+
+ if (gpio_is_valid(jz_battery->pdata->gpio_charge)) {
+ is_charging = gpio_get_value(jz_battery->pdata->gpio_charge);
+ is_charging ^= jz_battery->pdata->gpio_charge_active_low;
+ if (is_charging)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ if (status != jz_battery->status) {
+ jz_battery->status = status;
+ has_changed = true;
+ }
+ }
+
+ voltage = jz_battery_read_voltage(jz_battery);
+ if (abs(voltage - jz_battery->voltage) < 50000) {
+ jz_battery->voltage = voltage;
+ has_changed = true;
+ }
+
+ if (has_changed)
+ power_supply_changed(&jz_battery->battery);
+}
+
+static enum power_supply_property jz_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static void jz_battery_work(struct work_struct *work)
+{
+ /* Too small interval will increase system workload */
+ const int interval = HZ * 30;
+ struct jz_battery *jz_battery = container_of(work, struct jz_battery,
+ work.work);
+
+ jz_battery_update(jz_battery);
+ schedule_delayed_work(&jz_battery->work, interval);
+}
+
+static int __devinit jz_battery_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data;
+ struct jz_battery *jz_battery;
+ struct power_supply *battery;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform_data supplied\n");
+ return -ENXIO;
+ }
+
+ jz_battery = kzalloc(sizeof(*jz_battery), GFP_KERNEL);
+ if (!jz_battery) {
+ dev_err(&pdev->dev, "Failed to allocate driver structure\n");
+ return -ENOMEM;
+ }
+
+ jz_battery->cell = mfd_get_cell(pdev);
+
+ jz_battery->irq = platform_get_irq(pdev, 0);
+ if (jz_battery->irq < 0) {
+ ret = jz_battery->irq;
+ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+ goto err_free;
+ }
+
+ jz_battery->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!jz_battery->mem) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
+ goto err_free;
+ }
+
+ jz_battery->mem = request_mem_region(jz_battery->mem->start,
+ resource_size(jz_battery->mem), pdev->name);
+ if (!jz_battery->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ goto err_free;
+ }
+
+ jz_battery->base = ioremap_nocache(jz_battery->mem->start,
+ resource_size(jz_battery->mem));
+ if (!jz_battery->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ goto err_release_mem_region;
+ }
+
+ battery = &jz_battery->battery;
+ battery->name = pdata->info.name;
+ battery->type = POWER_SUPPLY_TYPE_BATTERY;
+ battery->properties = jz_battery_properties;
+ battery->num_properties = ARRAY_SIZE(jz_battery_properties);
+ battery->get_property = jz_battery_get_property;
+ battery->external_power_changed = jz_battery_external_power_changed;
+ battery->use_for_apm = 1;
+
+ jz_battery->pdata = pdata;
+ jz_battery->pdev = pdev;
+
+ init_completion(&jz_battery->read_completion);
+ mutex_init(&jz_battery->lock);
+
+ INIT_DELAYED_WORK(&jz_battery->work, jz_battery_work);
+
+ ret = request_irq(jz_battery->irq, jz_battery_irq_handler, 0, pdev->name,
+ jz_battery);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq %d\n", ret);
+ goto err_iounmap;
+ }
+ disable_irq(jz_battery->irq);
+
+ if (gpio_is_valid(pdata->gpio_charge)) {
+ ret = gpio_request(pdata->gpio_charge, dev_name(&pdev->dev));
+ if (ret) {
+ dev_err(&pdev->dev, "charger state gpio request failed.\n");
+ goto err_free_irq;
+ }
+ ret = gpio_direction_input(pdata->gpio_charge);
+ if (ret) {
+ dev_err(&pdev->dev, "charger state gpio set direction failed.\n");
+ goto err_free_gpio;
+ }
+
+ jz_battery->charge_irq = gpio_to_irq(pdata->gpio_charge);
+
+ if (jz_battery->charge_irq >= 0) {
+ ret = request_irq(jz_battery->charge_irq,
+ jz_battery_charge_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(&pdev->dev), jz_battery);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request charge irq: %d\n", ret);
+ goto err_free_gpio;
+ }
+ }
+ } else {
+ jz_battery->charge_irq = -1;
+ }
+
+ if (jz_battery->pdata->info.voltage_max_design <= 2500000)
+ jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB,
+ JZ_ADC_CONFIG_BAT_MB);
+ else
+ jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, 0);
+
+ ret = power_supply_register(&pdev->dev, &jz_battery->battery);
+ if (ret) {
+ dev_err(&pdev->dev, "power supply battery register failed.\n");
+ goto err_free_charge_irq;
+ }
+
+ platform_set_drvdata(pdev, jz_battery);
+ schedule_delayed_work(&jz_battery->work, 0);
+
+ return 0;
+
+err_free_charge_irq:
+ if (jz_battery->charge_irq >= 0)
+ free_irq(jz_battery->charge_irq, jz_battery);
+err_free_gpio:
+ if (gpio_is_valid(pdata->gpio_charge))
+ gpio_free(jz_battery->pdata->gpio_charge);
+err_free_irq:
+ free_irq(jz_battery->irq, jz_battery);
+err_iounmap:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(jz_battery->base);
+err_release_mem_region:
+ release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem));
+err_free:
+ kfree(jz_battery);
+ return ret;
+}
+
+static int __devexit jz_battery_remove(struct platform_device *pdev)
+{
+ struct jz_battery *jz_battery = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&jz_battery->work);
+
+ if (gpio_is_valid(jz_battery->pdata->gpio_charge)) {
+ if (jz_battery->charge_irq >= 0)
+ free_irq(jz_battery->charge_irq, jz_battery);
+ gpio_free(jz_battery->pdata->gpio_charge);
+ }
+
+ power_supply_unregister(&jz_battery->battery);
+
+ free_irq(jz_battery->irq, jz_battery);
+
+ iounmap(jz_battery->base);
+ release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem));
+ kfree(jz_battery);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int jz_battery_suspend(struct device *dev)
+{
+ struct jz_battery *jz_battery = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&jz_battery->work);
+ jz_battery->status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ return 0;
+}
+
+static int jz_battery_resume(struct device *dev)
+{
+ struct jz_battery *jz_battery = dev_get_drvdata(dev);
+
+ schedule_delayed_work(&jz_battery->work, 0);
+
+ return 0;
+}
+
+static const struct dev_pm_ops jz_battery_pm_ops = {
+ .suspend = jz_battery_suspend,
+ .resume = jz_battery_resume,
+};
+
+#define JZ_BATTERY_PM_OPS (&jz_battery_pm_ops)
+#else
+#define JZ_BATTERY_PM_OPS NULL
+#endif
+
+static struct platform_driver jz_battery_driver = {
+ .probe = jz_battery_probe,
+ .remove = __devexit_p(jz_battery_remove),
+ .driver = {
+ .name = "jz4740-battery",
+ .owner = THIS_MODULE,
+ .pm = JZ_BATTERY_PM_OPS,
+ },
+};
+
+static int __init jz_battery_init(void)
+{
+ return platform_driver_register(&jz_battery_driver);
+}
+module_init(jz_battery_init);
+
+static void __exit jz_battery_exit(void)
+{
+ platform_driver_unregister(&jz_battery_driver);
+}
+module_exit(jz_battery_exit);
+
+MODULE_ALIAS("platform:jz4740-battery");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("JZ4740 SoC battery driver");
diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c
new file mode 100644
index 00000000..2f2f9a6f
--- /dev/null
+++ b/drivers/power/max17040_battery.c
@@ -0,0 +1,308 @@
+/*
+ * max17040_battery.c
+ * fuel-gauge systems for lithium-ion (Li+) batteries
+ *
+ * Copyright (C) 2009 Samsung Electronics
+ * Minkyu Kang <mk7.kang@samsung.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/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/max17040_battery.h>
+#include <linux/slab.h>
+
+#define MAX17040_VCELL_MSB 0x02
+#define MAX17040_VCELL_LSB 0x03
+#define MAX17040_SOC_MSB 0x04
+#define MAX17040_SOC_LSB 0x05
+#define MAX17040_MODE_MSB 0x06
+#define MAX17040_MODE_LSB 0x07
+#define MAX17040_VER_MSB 0x08
+#define MAX17040_VER_LSB 0x09
+#define MAX17040_RCOMP_MSB 0x0C
+#define MAX17040_RCOMP_LSB 0x0D
+#define MAX17040_CMD_MSB 0xFE
+#define MAX17040_CMD_LSB 0xFF
+
+#define MAX17040_DELAY 1000
+#define MAX17040_BATTERY_FULL 95
+
+struct max17040_chip {
+ struct i2c_client *client;
+ struct delayed_work work;
+ struct power_supply battery;
+ struct max17040_platform_data *pdata;
+
+ /* State Of Connect */
+ int online;
+ /* battery voltage */
+ int vcell;
+ /* battery capacity */
+ int soc;
+ /* State Of Charge */
+ int status;
+};
+
+static int max17040_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max17040_chip *chip = container_of(psy,
+ struct max17040_chip, battery);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = chip->status;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = chip->online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = chip->vcell;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = chip->soc;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max17040_write_reg(struct i2c_client *client, int reg, u8 value)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, reg, value);
+
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int max17040_read_reg(struct i2c_client *client, int reg)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ return ret;
+}
+
+static void max17040_reset(struct i2c_client *client)
+{
+ max17040_write_reg(client, MAX17040_CMD_MSB, 0x54);
+ max17040_write_reg(client, MAX17040_CMD_LSB, 0x00);
+}
+
+static void max17040_get_vcell(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+ u8 msb;
+ u8 lsb;
+
+ msb = max17040_read_reg(client, MAX17040_VCELL_MSB);
+ lsb = max17040_read_reg(client, MAX17040_VCELL_LSB);
+
+ chip->vcell = (msb << 4) + (lsb >> 4);
+}
+
+static void max17040_get_soc(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+ u8 msb;
+ u8 lsb;
+
+ msb = max17040_read_reg(client, MAX17040_SOC_MSB);
+ lsb = max17040_read_reg(client, MAX17040_SOC_LSB);
+
+ chip->soc = msb;
+}
+
+static void max17040_get_version(struct i2c_client *client)
+{
+ u8 msb;
+ u8 lsb;
+
+ msb = max17040_read_reg(client, MAX17040_VER_MSB);
+ lsb = max17040_read_reg(client, MAX17040_VER_LSB);
+
+ dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d%d\n", msb, lsb);
+}
+
+static void max17040_get_online(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ if (chip->pdata->battery_online)
+ chip->online = chip->pdata->battery_online();
+ else
+ chip->online = 1;
+}
+
+static void max17040_get_status(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ if (!chip->pdata->charger_online || !chip->pdata->charger_enable) {
+ chip->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ return;
+ }
+
+ if (chip->pdata->charger_online()) {
+ if (chip->pdata->charger_enable())
+ chip->status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ chip->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ if (chip->soc > MAX17040_BATTERY_FULL)
+ chip->status = POWER_SUPPLY_STATUS_FULL;
+}
+
+static void max17040_work(struct work_struct *work)
+{
+ struct max17040_chip *chip;
+
+ chip = container_of(work, struct max17040_chip, work.work);
+
+ max17040_get_vcell(chip->client);
+ max17040_get_soc(chip->client);
+ max17040_get_online(chip->client);
+ max17040_get_status(chip->client);
+
+ schedule_delayed_work(&chip->work, MAX17040_DELAY);
+}
+
+static enum power_supply_property max17040_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int __devinit max17040_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct max17040_chip *chip;
+ int ret;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->pdata = client->dev.platform_data;
+
+ i2c_set_clientdata(client, chip);
+
+ chip->battery.name = "battery";
+ chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->battery.get_property = max17040_get_property;
+ chip->battery.properties = max17040_battery_props;
+ chip->battery.num_properties = ARRAY_SIZE(max17040_battery_props);
+
+ ret = power_supply_register(&client->dev, &chip->battery);
+ if (ret) {
+ dev_err(&client->dev, "failed: power supply register\n");
+ kfree(chip);
+ return ret;
+ }
+
+ max17040_reset(client);
+ max17040_get_version(client);
+
+ INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work);
+ schedule_delayed_work(&chip->work, MAX17040_DELAY);
+
+ return 0;
+}
+
+static int __devexit max17040_remove(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ power_supply_unregister(&chip->battery);
+ cancel_delayed_work(&chip->work);
+ kfree(chip);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int max17040_suspend(struct i2c_client *client,
+ pm_message_t state)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ cancel_delayed_work(&chip->work);
+ return 0;
+}
+
+static int max17040_resume(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ schedule_delayed_work(&chip->work, MAX17040_DELAY);
+ return 0;
+}
+
+#else
+
+#define max17040_suspend NULL
+#define max17040_resume NULL
+
+#endif /* CONFIG_PM */
+
+static const struct i2c_device_id max17040_id[] = {
+ { "max17040", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max17040_id);
+
+static struct i2c_driver max17040_i2c_driver = {
+ .driver = {
+ .name = "max17040",
+ },
+ .probe = max17040_probe,
+ .remove = __devexit_p(max17040_remove),
+ .suspend = max17040_suspend,
+ .resume = max17040_resume,
+ .id_table = max17040_id,
+};
+
+static int __init max17040_init(void)
+{
+ return i2c_add_driver(&max17040_i2c_driver);
+}
+module_init(max17040_init);
+
+static void __exit max17040_exit(void)
+{
+ i2c_del_driver(&max17040_i2c_driver);
+}
+module_exit(max17040_exit);
+
+MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>");
+MODULE_DESCRIPTION("MAX17040 Fuel Gauge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c
new file mode 100644
index 00000000..c5c88051
--- /dev/null
+++ b/drivers/power/max17042_battery.c
@@ -0,0 +1,239 @@
+/*
+ * Fuel gauge driver for Maxim 17042 / 8966 / 8997
+ * Note that Maxim 8966 and 8997 are mfd and this is its subdevice.
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This driver is based on max17040_battery.c
+ */
+
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/power_supply.h>
+#include <linux/power/max17042_battery.h>
+
+enum max17042_register {
+ MAX17042_STATUS = 0x00,
+ MAX17042_VALRT_Th = 0x01,
+ MAX17042_TALRT_Th = 0x02,
+ MAX17042_SALRT_Th = 0x03,
+ MAX17042_AtRate = 0x04,
+ MAX17042_RepCap = 0x05,
+ MAX17042_RepSOC = 0x06,
+ MAX17042_Age = 0x07,
+ MAX17042_TEMP = 0x08,
+ MAX17042_VCELL = 0x09,
+ MAX17042_Current = 0x0A,
+ MAX17042_AvgCurrent = 0x0B,
+ MAX17042_Qresidual = 0x0C,
+ MAX17042_SOC = 0x0D,
+ MAX17042_AvSOC = 0x0E,
+ MAX17042_RemCap = 0x0F,
+ MAX17402_FullCAP = 0x10,
+ MAX17042_TTE = 0x11,
+ MAX17042_V_empty = 0x12,
+
+ MAX17042_RSLOW = 0x14,
+
+ MAX17042_AvgTA = 0x16,
+ MAX17042_Cycles = 0x17,
+ MAX17042_DesignCap = 0x18,
+ MAX17042_AvgVCELL = 0x19,
+ MAX17042_MinMaxTemp = 0x1A,
+ MAX17042_MinMaxVolt = 0x1B,
+ MAX17042_MinMaxCurr = 0x1C,
+ MAX17042_CONFIG = 0x1D,
+ MAX17042_ICHGTerm = 0x1E,
+ MAX17042_AvCap = 0x1F,
+ MAX17042_ManName = 0x20,
+ MAX17042_DevName = 0x21,
+ MAX17042_DevChem = 0x22,
+
+ MAX17042_TempNom = 0x24,
+ MAX17042_TempCold = 0x25,
+ MAX17042_TempHot = 0x26,
+ MAX17042_AIN = 0x27,
+ MAX17042_LearnCFG = 0x28,
+ MAX17042_SHFTCFG = 0x29,
+ MAX17042_RelaxCFG = 0x2A,
+ MAX17042_MiscCFG = 0x2B,
+ MAX17042_TGAIN = 0x2C,
+ MAx17042_TOFF = 0x2D,
+ MAX17042_CGAIN = 0x2E,
+ MAX17042_COFF = 0x2F,
+
+ MAX17042_Q_empty = 0x33,
+ MAX17042_T_empty = 0x34,
+
+ MAX17042_RCOMP0 = 0x38,
+ MAX17042_TempCo = 0x39,
+ MAX17042_Rx = 0x3A,
+ MAX17042_T_empty0 = 0x3B,
+ MAX17042_TaskPeriod = 0x3C,
+ MAX17042_FSTAT = 0x3D,
+
+ MAX17042_SHDNTIMER = 0x3F,
+
+ MAX17042_VFRemCap = 0x4A,
+
+ MAX17042_QH = 0x4D,
+ MAX17042_QL = 0x4E,
+};
+
+struct max17042_chip {
+ struct i2c_client *client;
+ struct power_supply battery;
+ struct max17042_platform_data *pdata;
+};
+
+static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value)
+{
+ int ret = i2c_smbus_write_word_data(client, reg, value);
+
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int max17042_read_reg(struct i2c_client *client, u8 reg)
+{
+ int ret = i2c_smbus_read_word_data(client, reg);
+
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ return ret;
+}
+
+static enum power_supply_property max17042_battery_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int max17042_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max17042_chip *chip = container_of(psy,
+ struct max17042_chip, battery);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = max17042_read_reg(chip->client,
+ MAX17042_VCELL) * 83; /* 1000 / 12 = 83 */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ val->intval = max17042_read_reg(chip->client,
+ MAX17042_AvgVCELL) * 83;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = max17042_read_reg(chip->client,
+ MAX17042_SOC) / 256;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int __devinit max17042_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct max17042_chip *chip;
+ int ret;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return -EIO;
+
+ chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->pdata = client->dev.platform_data;
+
+ i2c_set_clientdata(client, chip);
+
+ chip->battery.name = "max17042_battery";
+ chip->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->battery.get_property = max17042_get_property;
+ chip->battery.properties = max17042_battery_props;
+ chip->battery.num_properties = ARRAY_SIZE(max17042_battery_props);
+
+ ret = power_supply_register(&client->dev, &chip->battery);
+ if (ret) {
+ dev_err(&client->dev, "failed: power supply register\n");
+ i2c_set_clientdata(client, NULL);
+ kfree(chip);
+ return ret;
+ }
+
+ if (!chip->pdata->enable_current_sense) {
+ max17042_write_reg(client, MAX17042_CGAIN, 0x0000);
+ max17042_write_reg(client, MAX17042_MiscCFG, 0x0003);
+ max17042_write_reg(client, MAX17042_LearnCFG, 0x0007);
+ }
+
+ return 0;
+}
+
+static int __devexit max17042_remove(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ power_supply_unregister(&chip->battery);
+ i2c_set_clientdata(client, NULL);
+ kfree(chip);
+ return 0;
+}
+
+static const struct i2c_device_id max17042_id[] = {
+ { "max17042", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max17042_id);
+
+static struct i2c_driver max17042_i2c_driver = {
+ .driver = {
+ .name = "max17042",
+ },
+ .probe = max17042_probe,
+ .remove = __devexit_p(max17042_remove),
+ .id_table = max17042_id,
+};
+
+static int __init max17042_init(void)
+{
+ return i2c_add_driver(&max17042_i2c_driver);
+}
+module_init(max17042_init);
+
+static void __exit max17042_exit(void)
+{
+ i2c_del_driver(&max17042_i2c_driver);
+}
+module_exit(max17042_exit);
+
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_DESCRIPTION("MAX17042 Fuel Gauge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max8903_battery.c b/drivers/power/max8903_battery.c
new file mode 100755
index 00000000..84b5373c
--- /dev/null
+++ b/drivers/power/max8903_battery.c
@@ -0,0 +1,748 @@
+/*
+ * max8903_battery.c - Maxim 8903 USB/Adapter Charger Driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Copyright (C) 2011-2012 Freescale Semiconductor, Inc.
+ * Based on max8903_charger.c
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/power/max8903_battery.h>
+#include <linux/sort.h>
+
+
+#define BATTERY_UPDATE_INTERVAL 5 /*seconds*/
+#define LOW_VOLT_THRESHOLD 2800000
+#define HIGH_VOLT_THRESHOLD 4200000
+#define ADC_SAMPLE_COUNT 4
+
+struct max8903_data {
+ struct max8903_pdata *pdata;
+ struct device *dev;
+ struct power_supply psy;
+ bool fault;
+ bool usb_in;
+ bool ta_in;
+ bool chg_state;
+ struct delayed_work work;
+ unsigned int interval;
+ unsigned short thermal_raw;
+ int voltage_uV;
+ int current_uA;
+ int battery_status;
+ int charger_online;
+ int charger_voltage_uV;
+ int real_capacity;
+ int percent;
+ int old_percent;
+ struct power_supply bat;
+ struct mutex work_lock;
+};
+
+typedef struct {
+ u32 voltage;
+ u32 percent;
+} battery_capacity , *pbattery_capacity;
+
+static bool capacity_changed_flag;
+
+static battery_capacity chargingTable[] = {
+ {4148, 100},
+ {4126, 99},
+ {4123, 98},
+ {4120, 97},
+ {4105, 96},
+ {4090, 96},
+ {4075, 95},
+ {4060, 94},
+ {4045, 93},
+ {4030, 92},
+ {4015, 91},
+ {4000, 90},
+ {3900, 85},
+ {3790, 80},
+ {3760, 75},
+ {3730, 70},
+ {3700, 65},
+ {3680, 60},
+ {3660, 55},
+ {3640, 50},
+ {3600, 45},
+ {3550, 40},
+ {3510, 35},
+ {3450, 30},
+ {3310, 25},
+ {3240, 20},
+ {3180, 15},
+ {3030, 10},
+ {2820, 5},
+ {2800, 0},
+ {0, 0}
+};
+static battery_capacity dischargingTable[] = {
+ {4100, 100},
+ {4090, 99},
+ {4080, 98},
+ {4060, 97},
+ {4040, 96},
+ {3920, 96},
+ {3900, 95},
+ {3970, 94},
+ {3940, 93},
+ {3910, 92},
+ {3890, 91},
+ {3860, 90},
+ {3790, 85},
+ {3690, 80},
+ {3660, 75},
+ {3630, 70},
+ {3600, 65},
+ {3580, 60},
+ {3560, 55},
+ {3540, 50},
+ {3500, 45},
+ {3450, 40},
+ {3410, 35},
+ {3350, 30},
+ {3310, 25},
+ {3240, 20},
+ {3180, 15},
+ {3030, 10},
+ {2820, 5},
+ {2800, 0},
+ {0, 0}
+};
+
+u32 calibrate_battery_capability_percent(struct max8903_data *data)
+{
+ u8 i;
+ pbattery_capacity pTable;
+ u32 tableSize;
+ if (data->battery_status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ pTable = dischargingTable;
+ tableSize = sizeof(dischargingTable)/sizeof(dischargingTable[0]);
+ } else {
+ pTable = chargingTable;
+ tableSize = sizeof(chargingTable)/sizeof(chargingTable[0]);
+ }
+ for (i = 0; i < tableSize; i++) {
+ if (data->voltage_uV >= pTable[i].voltage)
+ return pTable[i].percent;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property max8903_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+static enum power_supply_property max8903_battery_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+};
+
+extern u32 max11801_read_adc(void);
+
+static void max8903_charger_update_status(struct max8903_data *data)
+{
+ if (data->usb_in || data->ta_in) {
+ data->charger_online = 1;
+ } else {
+ data->charger_online = 0;
+ }
+ if (data->charger_online == 0) {
+ data->battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else {
+ if (data->pdata->chg) {
+ if (gpio_get_value(data->pdata->chg) == 0) {
+ data->battery_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else if ((data->usb_in || data->ta_in) &&
+ gpio_get_value(data->pdata->chg) == 1) {
+ data->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+ }
+ }
+ pr_debug("chg: %d \n", gpio_get_value(data->pdata->chg));
+}
+static int cmp_func(const void *_a, const void *_b)
+{
+ const int *a = _a, *b = _b;
+
+ if (*a > *b)
+ return 1;
+ if (*a < *b)
+ return -1;
+ return 0;
+}
+u32 calibration_voltage(struct max8903_data *data)
+{
+ int volt[ADC_SAMPLE_COUNT];
+ u32 voltage_data;
+ int i;
+ for (i = 0; i < ADC_SAMPLE_COUNT; i++) {
+ if (data->charger_online == 0) {
+ /* ADC offset when battery is discharger*/
+ volt[i] = max11801_read_adc()-1880;
+ } else {
+ volt[i] = max11801_read_adc()-1960;
+ }
+ }
+ sort(volt, i, 4, cmp_func, NULL);
+ for (i = 0; i < ADC_SAMPLE_COUNT; i++)
+ pr_debug("volt_sorted[%2d]: %d\n", i, volt[i]);
+ /* get the average of second max/min of remained. */
+ voltage_data = (volt[1] + volt[i - 2]) / 2;
+ return voltage_data;
+}
+
+static void max8903_battery_update_status(struct max8903_data *data)
+{
+ static int counter;
+#if 0
+ data->voltage_uV = max11801_read_adc();
+#endif
+ mutex_lock(&data->work_lock);
+ data->voltage_uV = calibration_voltage(data);
+ data->percent = calibrate_battery_capability_percent(data);
+ if (data->percent != data->old_percent) {
+ data->old_percent = data->percent;
+ capacity_changed_flag = true;
+ }
+ if ((capacity_changed_flag == true) && (data->charger_online)) {
+ counter++;
+ if (counter > 2) {
+ counter = 0;
+ capacity_changed_flag = false;
+ power_supply_changed(&data->bat);
+ }
+ }
+ mutex_unlock(&data->work_lock);
+}
+
+static int max8903_battery_get_property(struct power_supply *bat,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *di = container_of(bat,
+ struct max8903_data, bat);
+ static unsigned long last;
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ if (di->pdata->chg) {
+ if (gpio_get_value(di->pdata->chg) == 0) {
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ } else if ((di->usb_in || di->ta_in) && gpio_get_value(di->pdata->chg) == 1) {
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ }
+ di->battery_status = val->intval;
+ return 0;
+ default:
+ break;
+ }
+ if (!last || time_after(jiffies, last + HZ / 2)) {
+ last = jiffies;
+ max8903_charger_update_status(di);
+ max8903_battery_update_status(di);
+ }
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = di->voltage_uV;
+ break;
+#if 0
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = di->current_uA;
+ break;
+#endif
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = HIGH_VOLT_THRESHOLD;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = LOW_VOLT_THRESHOLD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = di->percent < 0 ? 0 :
+ (di->percent > 100 ? 100 : di->percent);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (di->usb_in || di->ta_in)
+ val->intval = 1;
+ di->charger_online = val->intval;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+static int max8903_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *data = container_of(psy,
+ struct max8903_data, psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ if (data->pdata->chg) {
+ if (gpio_get_value(data->pdata->chg) == 0) {
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ else if ((data->usb_in || data->ta_in) &&
+ gpio_get_value(data->pdata->chg) == 1) {
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ }
+ data->battery_status = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (data->usb_in || data->ta_in)
+ val->intval = 1;
+ data->charger_online = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ if (data->fault)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static irqreturn_t max8903_dcin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool ta_in;
+ enum power_supply_type old_type;
+
+ ta_in = gpio_get_value(pdata->dok) ? false : true;
+
+ if (ta_in == data->ta_in)
+ return IRQ_HANDLED;
+
+ data->ta_in = ta_in;
+#if 0
+ /* Set Current-Limit-Mode 1:DC 0:USB */
+ if (pdata->dcm)
+ gpio_set_value(pdata->dcm, ta_in ? 1 : 0);
+ /* Charger Enable / Disable (cen is negated) */
+ if (pdata->cen)
+ gpio_set_value(pdata->cen, ta_in ? 0 :
+ (data->usb_in ? 0 : 1));
+#endif
+ pr_info("TA(DC-IN) Charger %s.\n", ta_in ?
+ "Connected" : "Disconnected");
+
+ old_type = data->psy.type;
+
+ if (data->ta_in)
+ data->psy.type = POWER_SUPPLY_TYPE_MAINS;
+ else if (data->usb_in)
+ data->psy.type = POWER_SUPPLY_TYPE_USB;
+ else
+ data->psy.type = POWER_SUPPLY_TYPE_BATTERY;
+
+ if (old_type != data->psy.type) {
+ power_supply_changed(&data->psy);
+ power_supply_changed(&data->bat);
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_usbin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool usb_in;
+ enum power_supply_type old_type;
+
+ usb_in = gpio_get_value(pdata->uok) ? false : true;
+
+ if (usb_in == data->usb_in)
+ return IRQ_HANDLED;
+
+ data->usb_in = usb_in;
+
+#if 0
+ /* Do not touch Current-Limit-Mode */
+
+ /* Charger Enable / Disable (cen is negated) */
+ if (pdata->cen)
+ gpio_set_value(pdata->cen, usb_in ? 0 :
+ (data->ta_in ? 0 : 1));
+#endif
+ pr_info("USB Charger %s.\n", usb_in ?
+ "Connected" : "Disconnected");
+
+ old_type = data->psy.type;
+
+ if (data->ta_in)
+ data->psy.type = POWER_SUPPLY_TYPE_MAINS;
+ else if (data->usb_in)
+ data->psy.type = POWER_SUPPLY_TYPE_USB;
+ else
+ data->psy.type = POWER_SUPPLY_TYPE_BATTERY;
+
+ if (old_type != data->psy.type) {
+ power_supply_changed(&data->psy);
+ power_supply_changed(&data->bat);
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_fault(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool fault;
+
+ fault = gpio_get_value(pdata->flt) ? false : true;
+
+ if (fault == data->fault)
+ return IRQ_HANDLED;
+
+ data->fault = fault;
+
+ if (fault)
+ dev_err(data->dev, "Charger suffers a fault and stops.\n");
+ else
+ dev_err(data->dev, "Charger recovered from a fault.\n");
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_chg(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ int chg_state;
+
+ chg_state = gpio_get_value(pdata->chg) ? false : true;
+
+ if (chg_state == data->chg_state)
+ return IRQ_HANDLED;
+
+ data->chg_state = chg_state;
+#if 0
+ if (chg_state)
+ pr_info("Charger stop.\n ");
+ else
+ pr_info("Charger start.\n ");
+#endif
+ return IRQ_HANDLED;
+}
+
+static void max8903_battery_work(struct work_struct *work)
+{
+ struct max8903_data *data;
+ data = container_of(work, struct max8903_data, work.work);
+ data->interval = HZ * BATTERY_UPDATE_INTERVAL;
+
+ max8903_charger_update_status(data);
+ max8903_battery_update_status(data);
+
+ pr_debug("battery voltage: %4d mV\n" , data->voltage_uV);
+ pr_debug("charger online status: %d\n" , data->charger_online);
+ pr_debug("battery status : %d\n" , data->battery_status);
+ pr_debug("battery capacity percent: %3d\n" , data->percent);
+ pr_debug("data->usb_in: %x , data->ta_in: %x \n" , data->usb_in, data->ta_in);
+ /* reschedule for the next time */
+ schedule_delayed_work(&data->work, data->interval);
+}
+static __devinit int max8903_probe(struct platform_device *pdev)
+{
+ struct max8903_data *data;
+ struct device *dev = &pdev->dev;
+ struct max8903_pdata *pdata = pdev->dev.platform_data;
+ int ret = 0;
+ int gpio = 0;
+ int ta_in = 0;
+ int usb_in = 0;
+ int retval;
+
+ data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+ data->pdata = pdata;
+ data->dev = dev;
+
+ platform_set_drvdata(pdev, data);
+ capacity_changed_flag = false;
+ data->usb_in = 0;
+ data->ta_in = 0;
+
+ if (pdata->dc_valid == false && pdata->usb_valid == false) {
+ dev_err(dev, "No valid power sources.\n");
+ printk(KERN_INFO "No valid power sources.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ if (pdata->dc_valid) {
+#if 0
+ if (pdata->dok && gpio_is_valid(pdata->dok) &&
+ pdata->dcm && gpio_is_valid(pdata->dcm)) {
+#endif
+ if (pdata->dok && gpio_is_valid(pdata->dok)) {
+ gpio = pdata->dok; /* PULL_UPed Interrupt */
+ ta_in = gpio_get_value(gpio) ? 0 : 1;
+#if 0
+ gpio = pdata->dcm; /* Output */
+ gpio_set_value(gpio, ta_in);
+#endif
+ } else if (pdata->dok && gpio_is_valid(pdata->dok) && pdata->dcm_always_high) {
+ ta_in = pdata->dok; /* PULL_UPed Interrupt */
+ ta_in = gpio_get_value(gpio) ? 0 : 1;
+ } else {
+ dev_err(dev, "When DC is wired, DOK and DCM should"
+ " be wired as well."
+ " or set dcm always high\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+ if (pdata->usb_valid) {
+ if (pdata->uok && gpio_is_valid(pdata->uok)) {
+ gpio = pdata->uok;
+ usb_in = gpio_get_value(gpio) ? 0 : 1;
+ } else {
+ dev_err(dev, "When USB is wired, UOK should be wired."
+ "as well.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+ if (pdata->chg) {
+ if (!gpio_is_valid(pdata->chg)) {
+ dev_err(dev, "Invalid pin: chg.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ if (pdata->flt) {
+ if (!gpio_is_valid(pdata->flt)) {
+ dev_err(dev, "Invalid pin: flt.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ if (pdata->usus) {
+ if (!gpio_is_valid(pdata->usus)) {
+ dev_err(dev, "Invalid pin: usus.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+ mutex_init(&data->work_lock);
+ data->fault = false;
+ data->ta_in = ta_in;
+ data->usb_in = usb_in;
+ data->psy.name = "max8903-ac";
+ data->psy.type = (ta_in) ? POWER_SUPPLY_TYPE_MAINS :
+ ((usb_in) ? POWER_SUPPLY_TYPE_USB :
+ POWER_SUPPLY_TYPE_BATTERY);
+ data->psy.get_property = max8903_get_property;
+ data->psy.properties = max8903_charger_props;
+ data->psy.num_properties = ARRAY_SIZE(max8903_charger_props);
+ ret = power_supply_register(dev, &data->psy);
+ if (ret) {
+ dev_err(dev, "failed: power supply register.\n");
+ goto err_psy;
+ }
+ data->bat.name = "max8903-charger";
+ data->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+ data->bat.properties = max8903_battery_props;
+ data->bat.num_properties = ARRAY_SIZE(max8903_battery_props);
+ data->bat.get_property = max8903_battery_get_property;
+ data->bat.use_for_apm = 1;
+ retval = power_supply_register(&pdev->dev, &data->bat);
+ if (retval) {
+ dev_err(data->dev, "failed to register battery\n");
+ goto battery_failed;
+ }
+ INIT_DELAYED_WORK(&data->work, max8903_battery_work);
+ schedule_delayed_work(&data->work, data->interval);
+ if (pdata->dc_valid) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->dok),
+ NULL, max8903_dcin,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 DC IN", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for DC (%d)\n",
+ gpio_to_irq(pdata->dok), ret);
+ goto err_usb_irq;
+ }
+ }
+
+ if (pdata->usb_valid) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->uok),
+ NULL, max8903_usbin,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 USB IN", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for USB (%d)\n",
+ gpio_to_irq(pdata->uok), ret);
+ goto err_dc_irq;
+ }
+ }
+
+ if (pdata->flt) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->flt),
+ NULL, max8903_fault,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 Fault", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
+ gpio_to_irq(pdata->flt), ret);
+ goto err_flt_irq;
+ }
+ }
+
+ if (pdata->chg) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->chg),
+ NULL, max8903_chg,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 Fault", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
+ gpio_to_irq(pdata->flt), ret);
+ goto err_chg_irq;
+ }
+ }
+ return 0;
+err_psy:
+ power_supply_unregister(&data->psy);
+battery_failed:
+ power_supply_unregister(&data->bat);
+err_usb_irq:
+ if (pdata->usb_valid)
+ free_irq(gpio_to_irq(pdata->uok), data);
+ cancel_delayed_work(&data->work);
+err_dc_irq:
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->dok), data);
+ cancel_delayed_work(&data->work);
+err_flt_irq:
+ if (pdata->usb_valid)
+ free_irq(gpio_to_irq(pdata->uok), data);
+ cancel_delayed_work(&data->work);
+err_chg_irq:
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->dok), data);
+ cancel_delayed_work(&data->work);
+err:
+ kfree(data);
+ return ret;
+}
+
+static __devexit int max8903_remove(struct platform_device *pdev)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+ if (data) {
+ struct max8903_pdata *pdata = data->pdata;
+ if (pdata->flt)
+ free_irq(gpio_to_irq(pdata->flt), data);
+ if (pdata->usb_valid)
+ free_irq(gpio_to_irq(pdata->uok), data);
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->dok), data);
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->chg), data);
+ cancel_delayed_work_sync(&data->work);
+ power_supply_unregister(&data->psy);
+ power_supply_unregister(&data->bat);
+ kfree(data);
+ }
+ return 0;
+}
+
+static int max8903_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+
+ cancel_delayed_work(&data->work);
+ return 0;
+}
+
+static int max8903_resume(struct platform_device *pdev)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+
+ schedule_delayed_work(&data->work, BATTERY_UPDATE_INTERVAL);
+ return 0;
+
+}
+
+static struct platform_driver max8903_driver = {
+ .probe = max8903_probe,
+ .remove = __devexit_p(max8903_remove),
+ .suspend = max8903_suspend,
+ .resume = max8903_resume,
+ .driver = {
+ .name = "max8903-charger",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init max8903_init(void)
+{
+ return platform_driver_register(&max8903_driver);
+}
+module_init(max8903_init);
+
+static void __exit max8903_exit(void)
+{
+ platform_driver_unregister(&max8903_driver);
+}
+module_exit(max8903_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MAX8903 Battery Driver");
+MODULE_ALIAS("max8903_battery");
diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c
new file mode 100644
index 00000000..fee81917
--- /dev/null
+++ b/drivers/power/max8903_charger.c
@@ -0,0 +1,506 @@
+/*
+ * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/power/max8903_charger.h>
+
+#define MAX8903_DELAY (50 * HZ)
+struct max8903_data {
+ struct max8903_pdata *pdata;
+ struct device *dev;
+ struct power_supply psy;
+ struct power_supply acpsy;
+ bool fault;
+ bool usb_in;
+ bool ta_in;
+ int cap;
+ struct delayed_work work;
+};
+
+static enum power_supply_property max8903_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS, /* Charger status output */
+ POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static enum power_supply_property max8903_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE, /* External power source */
+};
+
+/* fake capacity */
+static inline void get_cap(struct max8903_data *data)
+{
+ data->cap = 90;
+ power_supply_changed(&data->psy);
+}
+
+static int max8903_get_ac_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *data = container_of(psy,
+ struct max8903_data, acpsy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (data->usb_in || data->ta_in)
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max8903_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *data = container_of(psy,
+ struct max8903_data, psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ if (data->pdata->chg) {
+ if (gpio_get_value(data->pdata->chg) == 0)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (data->usb_in || data->ta_in) {
+ if (data->cap == 100)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ if (data->fault)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = data->cap; /* fake capacity */
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (data->usb_in || data->ta_in)
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void max8903_work(struct work_struct *work)
+{
+ struct max8903_data *data = container_of(work,
+ struct max8903_data, work.work);
+ get_cap(data);
+ schedule_delayed_work(&data->work, MAX8903_DELAY);
+}
+
+static irqreturn_t max8903_dcin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool ta_in;
+
+ ta_in = gpio_get_value(pdata->dok) ? false : true;
+
+ if (ta_in == data->ta_in)
+ return IRQ_HANDLED;
+
+ data->ta_in = ta_in;
+
+ /* Set Current-Limit-Mode 1:DC 0:USB */
+ if (pdata->dcm)
+ gpio_set_value(pdata->dcm, ta_in ? 1 : 0);
+
+ /* Charger Enable / Disable (cen is negated) */
+ if (pdata->cen)
+ gpio_set_value(pdata->cen, ta_in ? 0 :
+ (data->usb_in ? 0 : 1));
+
+ dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?
+ "Connected" : "Disconnected");
+
+ power_supply_changed(&data->psy);
+ power_supply_changed(&data->acpsy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_usbin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool usb_in;
+
+ usb_in = gpio_get_value(pdata->uok) ? false : true;
+
+ if (usb_in == data->usb_in)
+ return IRQ_HANDLED;
+
+ data->usb_in = usb_in;
+
+ /* Do not touch Current-Limit-Mode */
+
+ /* Charger Enable / Disable (cen is negated) */
+ if (pdata->cen)
+ gpio_set_value(pdata->cen, usb_in ? 0 :
+ (data->ta_in ? 0 : 1));
+
+ dev_dbg(data->dev, "USB Charger %s.\n", usb_in ?
+ "Connected" : "Disconnected");
+
+ power_supply_changed(&data->psy);
+ power_supply_changed(&data->acpsy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_fault(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool fault;
+
+ fault = gpio_get_value(pdata->flt) ? false : true;
+
+ if (fault == data->fault)
+ return IRQ_HANDLED;
+
+ data->fault = fault;
+
+ if (fault)
+ dev_err(data->dev, "Charger suffers a fault and stops.\n");
+ else
+ dev_err(data->dev, "Charger recovered from a fault.\n");
+
+ return IRQ_HANDLED;
+}
+
+static __devinit int max8903_probe(struct platform_device *pdev)
+{
+ struct max8903_data *data;
+ struct max8903_data *ac_data;
+ struct device *dev = &pdev->dev;
+ struct max8903_pdata *pdata = pdev->dev.platform_data;
+ int ret = 0;
+ int gpio = 0;
+ int ta_in = 0;
+ int usb_in = 0;
+ int error;
+
+ data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+ data->pdata = pdata;
+ data->dev = dev;
+ platform_set_drvdata(pdev, data);
+
+ if (pdata->dc_valid == false && pdata->usb_valid == false) {
+ dev_err(dev, "No valid power sources.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (pdata->dc_valid) {
+ if (pdata->dok && gpio_is_valid(pdata->dok) &&
+ pdata->dcm && gpio_is_valid(pdata->dcm)) {
+ gpio = pdata->dok; /* PULL_UPed Interrupt */
+ ta_in = gpio_get_value(gpio) ? 0 : 1;
+
+ gpio = pdata->dcm; /* Output */
+ gpio_set_value(gpio, ta_in);
+ } else if (pdata->dok && gpio_is_valid(pdata->dok) &&
+ pdata->dcm_always_high) {
+ gpio = pdata->dok; /* PULL_UPed Interrupt */
+
+ error = gpio_request(gpio, "chg_dc");
+ if (error < 0) {
+ dev_err(dev, "failed to configure"
+ " request/direction for GPIO %d, error %d\n",
+ gpio, error);
+ goto err;
+ }
+ gpio_direction_input(gpio);
+
+ ta_in = gpio_get_value(gpio) ? 0 : 1;
+
+ if (ta_in)
+ data->ta_in = true;
+ else
+ data->ta_in = false;
+ } else {
+ dev_err(dev, "When DC is wired, DOK and DCM should"
+ " be wired as well."
+ " or set dcm always high\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ } else {
+ if (pdata->dcm) {
+ if (gpio_is_valid(pdata->dcm))
+ gpio_set_value(pdata->dcm, 0);
+ else {
+ dev_err(dev, "Invalid pin: dcm.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+ }
+
+ if (pdata->usb_valid) {
+ if (pdata->uok && gpio_is_valid(pdata->uok)) {
+ gpio = pdata->uok;
+ error = gpio_request(gpio, "chg_usb");
+ if (error < 0) {
+ dev_err(dev, "failed to configure"
+ " request/direction for GPIO %d, error %d\n",
+ gpio, error);
+ goto err;
+ }
+
+ gpio_direction_input(gpio);
+ usb_in = gpio_get_value(gpio) ? 0 : 1;
+ if (usb_in)
+ data->usb_in = true;
+ else
+ data->usb_in = false;
+ } else {
+ dev_err(dev, "When USB is wired, UOK should be wired."
+ "as well.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ if (pdata->cen) {
+ if (gpio_is_valid(pdata->cen)) {
+ gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1);
+ } else {
+ dev_err(dev, "Invalid pin: cen.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ if (pdata->chg) {
+ if (!gpio_is_valid(pdata->chg)) {
+ dev_err(dev, "Invalid pin: chg.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ error = gpio_request(pdata->chg, "chg_status");
+ if (error < 0) {
+ dev_err(dev, "failed to configure"
+ " request/direction for GPIO %d, error %d\n",
+ pdata->chg, error);
+ goto err;
+ }
+ error = gpio_direction_input(pdata->chg);
+ }
+
+ if (pdata->flt) {
+ if (!gpio_is_valid(pdata->flt)) {
+ dev_err(dev, "Invalid pin: flt.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ error = gpio_request(pdata->flt, "chg_fault");
+ if (error < 0) {
+ dev_err(dev, "failed to configure"
+ " request/direction for GPIO %d, error %d\n",
+ pdata->flt, error);
+ goto err;
+ }
+ error = gpio_direction_input(pdata->flt);
+ }
+
+ if (pdata->usus) {
+ if (!gpio_is_valid(pdata->usus)) {
+ dev_err(dev, "Invalid pin: usus.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ data->fault = false;
+ data->ta_in = ta_in;
+ data->usb_in = usb_in;
+
+ data->acpsy.name = "max8903-ac";
+ data->acpsy.type = POWER_SUPPLY_TYPE_MAINS;
+ data->acpsy.get_property = max8903_get_ac_property;
+ data->acpsy.properties = max8903_ac_props;
+ data->acpsy.num_properties = ARRAY_SIZE(max8903_ac_props);
+
+ ret = power_supply_register(dev, &data->acpsy);
+ if (ret) {
+ dev_err(dev, "failed: power supply register.\n");
+ goto err;
+ }
+
+ data->psy.name = "max8903-charger";
+ data->psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ data->psy.get_property = max8903_get_property;
+ data->psy.properties = max8903_charger_props;
+ data->psy.num_properties = ARRAY_SIZE(max8903_charger_props);
+
+ ret = power_supply_register(dev, &data->psy);
+ if (ret) {
+ dev_err(dev, "failed: power supply register.\n");
+ goto err;
+ }
+
+ if (pdata->dc_valid) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->dok),
+ NULL, max8903_dcin,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 DC IN", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for DC (%d)\n",
+ gpio_to_irq(pdata->dok), ret);
+ goto err_psy;
+ }
+ }
+
+ if (pdata->usb_valid) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->uok),
+ NULL, max8903_usbin,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 USB IN", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for USB (%d)\n",
+ gpio_to_irq(pdata->uok), ret);
+ goto err_dc_irq;
+ }
+ }
+
+ if (pdata->flt) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->flt),
+ NULL, max8903_fault,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 Fault", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
+ gpio_to_irq(pdata->flt), ret);
+ goto err_usb_irq;
+ }
+ }
+ /* should remove this if capacity is supported */
+ data->cap = 90;
+
+ INIT_DELAYED_WORK_DEFERRABLE(&data->work, max8903_work);
+
+ power_supply_changed(&data->psy);
+ power_supply_changed(&data->acpsy);
+ schedule_delayed_work(&data->work, MAX8903_DELAY);
+
+ return 0;
+
+err_usb_irq:
+ if (pdata->usb_valid)
+ free_irq(gpio_to_irq(pdata->uok), data);
+err_dc_irq:
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->dok), data);
+err_psy:
+ power_supply_unregister(&data->psy);
+ power_supply_unregister(&data->acpsy);
+err:
+ if (pdata->uok)
+ gpio_free(pdata->uok);
+ if (pdata->dok)
+ gpio_free(pdata->dok);
+ if (pdata->flt)
+ gpio_free(pdata->flt);
+ if (pdata->chg)
+ gpio_free(pdata->chg);
+ kfree(data);
+ return ret;
+}
+
+static __devexit int max8903_remove(struct platform_device *pdev)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+
+ if (data) {
+ struct max8903_pdata *pdata = data->pdata;
+
+ if (pdata->flt) {
+ free_irq(gpio_to_irq(pdata->flt), data);
+ gpio_free(pdata->flt);
+ }
+ if (pdata->usb_valid) {
+ free_irq(gpio_to_irq(pdata->uok), data);
+ gpio_free(pdata->uok);
+ }
+ if (pdata->dc_valid) {
+ free_irq(gpio_to_irq(pdata->dok), data);
+ gpio_free(pdata->dok);
+ }
+ power_supply_unregister(&data->psy);
+ power_supply_unregister(&data->acpsy);
+ if (pdata->chg)
+ gpio_free(pdata->chg);
+ kfree(data);
+ }
+
+ return 0;
+}
+
+static struct platform_driver max8903_driver = {
+ .probe = max8903_probe,
+ .remove = __devexit_p(max8903_remove),
+ .driver = {
+ .name = "max8903-charger",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init max8903_init(void)
+{
+ return platform_driver_register(&max8903_driver);
+}
+module_init(max8903_init);
+
+static void __exit max8903_exit(void)
+{
+ platform_driver_unregister(&max8903_driver);
+}
+module_exit(max8903_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MAX8903 Charger Driver");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_ALIAS("max8903-charger");
diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c
new file mode 100644
index 00000000..a70e16d3
--- /dev/null
+++ b/drivers/power/max8925_power.c
@@ -0,0 +1,529 @@
+/*
+ * Battery driver for Maxim MAX8925
+ *
+ * Copyright (c) 2009-2010 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.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/module.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max8925.h>
+
+/* registers in GPM */
+#define MAX8925_OUT5VEN 0x54
+#define MAX8925_OUT3VEN 0x58
+#define MAX8925_CHG_CNTL1 0x7c
+
+/* bits definition */
+#define MAX8925_CHG_STAT_VSYSLOW (1 << 0)
+#define MAX8925_CHG_STAT_MODE_MASK (3 << 2)
+#define MAX8925_CHG_STAT_EN_MASK (1 << 4)
+#define MAX8925_CHG_MBDET (1 << 1)
+#define MAX8925_CHG_AC_RANGE_MASK (3 << 6)
+
+/* registers in ADC */
+#define MAX8925_ADC_RES_CNFG1 0x06
+#define MAX8925_ADC_AVG_CNFG1 0x07
+#define MAX8925_ADC_ACQ_CNFG1 0x08
+#define MAX8925_ADC_ACQ_CNFG2 0x09
+/* 2 bytes registers in below. MSB is 1st, LSB is 2nd. */
+#define MAX8925_ADC_AUX2 0x62
+#define MAX8925_ADC_VCHG 0x64
+#define MAX8925_ADC_VBBATT 0x66
+#define MAX8925_ADC_VMBATT 0x68
+#define MAX8925_ADC_ISNS 0x6a
+#define MAX8925_ADC_THM 0x6c
+#define MAX8925_ADC_TDIE 0x6e
+#define MAX8925_CMD_AUX2 0xc8
+#define MAX8925_CMD_VCHG 0xd0
+#define MAX8925_CMD_VBBATT 0xd8
+#define MAX8925_CMD_VMBATT 0xe0
+#define MAX8925_CMD_ISNS 0xe8
+#define MAX8925_CMD_THM 0xf0
+#define MAX8925_CMD_TDIE 0xf8
+
+enum {
+ MEASURE_AUX2,
+ MEASURE_VCHG,
+ MEASURE_VBBATT,
+ MEASURE_VMBATT,
+ MEASURE_ISNS,
+ MEASURE_THM,
+ MEASURE_TDIE,
+ MEASURE_MAX,
+};
+
+struct max8925_power_info {
+ struct max8925_chip *chip;
+ struct i2c_client *gpm;
+ struct i2c_client *adc;
+
+ struct power_supply ac;
+ struct power_supply usb;
+ struct power_supply battery;
+ int irq_base;
+ unsigned ac_online:1;
+ unsigned usb_online:1;
+ unsigned bat_online:1;
+ unsigned chg_mode:2;
+ unsigned batt_detect:1; /* detecing MB by ID pin */
+ unsigned topoff_threshold:2;
+ unsigned fast_charge:3;
+
+ int (*set_charger) (int);
+};
+
+static int __set_charger(struct max8925_power_info *info, int enable)
+{
+ struct max8925_chip *chip = info->chip;
+ if (enable) {
+ /* enable charger in platform */
+ if (info->set_charger)
+ info->set_charger(1);
+ /* enable charger */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 0);
+ } else {
+ /* disable charge */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7);
+ if (info->set_charger)
+ info->set_charger(0);
+ }
+ dev_dbg(chip->dev, "%s\n", (enable) ? "Enable charger"
+ : "Disable charger");
+ return 0;
+}
+
+static irqreturn_t max8925_charger_handler(int irq, void *data)
+{
+ struct max8925_power_info *info = (struct max8925_power_info *)data;
+ struct max8925_chip *chip = info->chip;
+
+ switch (irq - chip->irq_base) {
+ case MAX8925_IRQ_VCHG_DC_R:
+ info->ac_online = 1;
+ __set_charger(info, 1);
+ dev_dbg(chip->dev, "Adapter inserted\n");
+ break;
+ case MAX8925_IRQ_VCHG_DC_F:
+ info->ac_online = 0;
+ __set_charger(info, 0);
+ dev_dbg(chip->dev, "Adapter is removal\n");
+ break;
+ case MAX8925_IRQ_VCHG_USB_R:
+ info->usb_online = 1;
+ __set_charger(info, 1);
+ dev_dbg(chip->dev, "USB inserted\n");
+ break;
+ case MAX8925_IRQ_VCHG_USB_F:
+ info->usb_online = 0;
+ __set_charger(info, 0);
+ dev_dbg(chip->dev, "USB is removal\n");
+ break;
+ case MAX8925_IRQ_VCHG_THM_OK_F:
+ /* Battery is not ready yet */
+ dev_dbg(chip->dev, "Battery temperature is out of range\n");
+ case MAX8925_IRQ_VCHG_DC_OVP:
+ dev_dbg(chip->dev, "Error detection\n");
+ __set_charger(info, 0);
+ break;
+ case MAX8925_IRQ_VCHG_THM_OK_R:
+ /* Battery is ready now */
+ dev_dbg(chip->dev, "Battery temperature is in range\n");
+ break;
+ case MAX8925_IRQ_VCHG_SYSLOW_R:
+ /* VSYS is low */
+ dev_info(chip->dev, "Sys power is too low\n");
+ break;
+ case MAX8925_IRQ_VCHG_SYSLOW_F:
+ dev_dbg(chip->dev, "Sys power is above low threshold\n");
+ break;
+ case MAX8925_IRQ_VCHG_DONE:
+ __set_charger(info, 0);
+ dev_dbg(chip->dev, "Charging is done\n");
+ break;
+ case MAX8925_IRQ_VCHG_TOPOFF:
+ dev_dbg(chip->dev, "Charging in top-off mode\n");
+ break;
+ case MAX8925_IRQ_VCHG_TMR_FAULT:
+ __set_charger(info, 0);
+ dev_dbg(chip->dev, "Safe timer is expired\n");
+ break;
+ case MAX8925_IRQ_VCHG_RST:
+ __set_charger(info, 0);
+ dev_dbg(chip->dev, "Charger is reset\n");
+ break;
+ }
+ return IRQ_HANDLED;
+}
+
+static int start_measure(struct max8925_power_info *info, int type)
+{
+ unsigned char buf[2] = {0, 0};
+ int meas_reg = 0, ret;
+
+ switch (type) {
+ case MEASURE_VCHG:
+ meas_reg = MAX8925_ADC_VCHG;
+ break;
+ case MEASURE_VBBATT:
+ meas_reg = MAX8925_ADC_VBBATT;
+ break;
+ case MEASURE_VMBATT:
+ meas_reg = MAX8925_ADC_VMBATT;
+ break;
+ case MEASURE_ISNS:
+ meas_reg = MAX8925_ADC_ISNS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ max8925_bulk_read(info->adc, meas_reg, 2, buf);
+ ret = (buf[0] << 4) | (buf[1] >> 4);
+
+ return ret;
+}
+
+static int max8925_ac_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = info->ac_online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (info->ac_online) {
+ ret = start_measure(info, MEASURE_VCHG);
+ if (ret >= 0) {
+ val->intval = ret << 1; /* unit is mV */
+ goto out;
+ }
+ }
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -ENODEV;
+ break;
+ }
+out:
+ return ret;
+}
+
+static enum power_supply_property max8925_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static int max8925_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = info->usb_online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (info->usb_online) {
+ ret = start_measure(info, MEASURE_VCHG);
+ if (ret >= 0) {
+ val->intval = ret << 1; /* unit is mV */
+ goto out;
+ }
+ }
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -ENODEV;
+ break;
+ }
+out:
+ return ret;
+}
+
+static enum power_supply_property max8925_usb_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static int max8925_bat_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8925_power_info *info = dev_get_drvdata(psy->dev->parent);
+ long long int tmp = 0;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = info->bat_online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (info->bat_online) {
+ ret = start_measure(info, MEASURE_VMBATT);
+ if (ret >= 0) {
+ val->intval = ret << 1; /* unit is mV */
+ ret = 0;
+ break;
+ }
+ }
+ ret = -ENODATA;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (info->bat_online) {
+ ret = start_measure(info, MEASURE_ISNS);
+ if (ret >= 0) {
+ tmp = (long long int)ret * 6250 / 4096 - 3125;
+ ret = (int)tmp;
+ val->intval = 0;
+ if (ret > 0)
+ val->intval = ret; /* unit is mA */
+ ret = 0;
+ break;
+ }
+ }
+ ret = -ENODATA;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (!info->bat_online) {
+ ret = -ENODATA;
+ break;
+ }
+ ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
+ ret = (ret & MAX8925_CHG_STAT_MODE_MASK) >> 2;
+ switch (ret) {
+ case 1:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case 0:
+ case 2:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case 3:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ }
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!info->bat_online) {
+ ret = -ENODATA;
+ break;
+ }
+ ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
+ if (info->usb_online || info->ac_online) {
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ if (ret & MAX8925_CHG_STAT_EN_MASK)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ } else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret = 0;
+ break;
+ default:
+ ret = -ENODEV;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property max8925_battery_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
+#define REQUEST_IRQ(_irq, _name) \
+do { \
+ ret = request_threaded_irq(chip->irq_base + _irq, NULL, \
+ max8925_charger_handler, \
+ IRQF_ONESHOT, _name, info); \
+ if (ret) \
+ dev_err(chip->dev, "Failed to request IRQ #%d: %d\n", \
+ _irq, ret); \
+} while (0)
+
+static __devinit int max8925_init_charger(struct max8925_chip *chip,
+ struct max8925_power_info *info)
+{
+ int ret;
+
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_OVP, "usb-ovp");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_F, "usb-remove");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_USB_R, "usb-insert");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DONE, "charger-done");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire");
+
+ info->ac_online = 0;
+ info->usb_online = 0;
+ info->bat_online = 0;
+ ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
+ if (ret >= 0) {
+ /*
+ * If battery detection is enabled, ID pin of battery is
+ * connected to MBDET pin of MAX8925. It could be used to
+ * detect battery presence.
+ * Otherwise, we have to assume that battery is always on.
+ */
+ if (info->batt_detect)
+ info->bat_online = (ret & MAX8925_CHG_MBDET) ? 0 : 1;
+ else
+ info->bat_online = 1;
+ if (ret & MAX8925_CHG_AC_RANGE_MASK)
+ info->ac_online = 1;
+ else
+ info->ac_online = 0;
+ }
+ /* disable charge */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7);
+ /* set charging current in charge topoff mode */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 3 << 5,
+ info->topoff_threshold << 5);
+ /* set charing current in fast charge mode */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 7, info->fast_charge);
+
+ return 0;
+}
+
+static __devexit int max8925_deinit_charger(struct max8925_power_info *info)
+{
+ struct max8925_chip *chip = info->chip;
+ int irq;
+
+ irq = chip->irq_base + MAX8925_IRQ_VCHG_DC_OVP;
+ for (; irq <= chip->irq_base + MAX8925_IRQ_VCHG_TMR_FAULT; irq++)
+ free_irq(irq, info);
+
+ return 0;
+}
+
+static __devinit int max8925_power_probe(struct platform_device *pdev)
+{
+ struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct max8925_power_pdata *pdata = NULL;
+ struct max8925_power_info *info;
+ int ret;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata) {
+ dev_err(&pdev->dev, "platform data isn't assigned to "
+ "power supply\n");
+ return -EINVAL;
+ }
+
+ info = kzalloc(sizeof(struct max8925_power_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ info->chip = chip;
+ info->gpm = chip->i2c;
+ info->adc = chip->adc;
+ platform_set_drvdata(pdev, info);
+
+ info->ac.name = "max8925-ac";
+ info->ac.type = POWER_SUPPLY_TYPE_MAINS;
+ info->ac.properties = max8925_ac_props;
+ info->ac.num_properties = ARRAY_SIZE(max8925_ac_props);
+ info->ac.get_property = max8925_ac_get_prop;
+ ret = power_supply_register(&pdev->dev, &info->ac);
+ if (ret)
+ goto out;
+ info->ac.dev->parent = &pdev->dev;
+
+ info->usb.name = "max8925-usb";
+ info->usb.type = POWER_SUPPLY_TYPE_USB;
+ info->usb.properties = max8925_usb_props;
+ info->usb.num_properties = ARRAY_SIZE(max8925_usb_props);
+ info->usb.get_property = max8925_usb_get_prop;
+ ret = power_supply_register(&pdev->dev, &info->usb);
+ if (ret)
+ goto out_usb;
+ info->usb.dev->parent = &pdev->dev;
+
+ info->battery.name = "max8925-battery";
+ info->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ info->battery.properties = max8925_battery_props;
+ info->battery.num_properties = ARRAY_SIZE(max8925_battery_props);
+ info->battery.get_property = max8925_bat_get_prop;
+ ret = power_supply_register(&pdev->dev, &info->battery);
+ if (ret)
+ goto out_battery;
+ info->battery.dev->parent = &pdev->dev;
+
+ info->batt_detect = pdata->batt_detect;
+ info->topoff_threshold = pdata->topoff_threshold;
+ info->fast_charge = pdata->fast_charge;
+ info->set_charger = pdata->set_charger;
+
+ max8925_init_charger(chip, info);
+ return 0;
+out_battery:
+ power_supply_unregister(&info->battery);
+out_usb:
+ power_supply_unregister(&info->ac);
+out:
+ kfree(info);
+ return ret;
+}
+
+static __devexit int max8925_power_remove(struct platform_device *pdev)
+{
+ struct max8925_power_info *info = platform_get_drvdata(pdev);
+
+ if (info) {
+ power_supply_unregister(&info->ac);
+ power_supply_unregister(&info->usb);
+ power_supply_unregister(&info->battery);
+ max8925_deinit_charger(info);
+ kfree(info);
+ }
+ return 0;
+}
+
+static struct platform_driver max8925_power_driver = {
+ .probe = max8925_power_probe,
+ .remove = __devexit_p(max8925_power_remove),
+ .driver = {
+ .name = "max8925-power",
+ },
+};
+
+static int __init max8925_power_init(void)
+{
+ return platform_driver_register(&max8925_power_driver);
+}
+module_init(max8925_power_init);
+
+static void __exit max8925_power_exit(void)
+{
+ platform_driver_unregister(&max8925_power_driver);
+}
+module_exit(max8925_power_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Power supply driver for MAX8925");
+MODULE_ALIAS("platform:max8925-power");
diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c
new file mode 100644
index 00000000..0b0ff3a9
--- /dev/null
+++ b/drivers/power/olpc_battery.c
@@ -0,0 +1,618 @@
+/*
+ * Battery driver for One Laptop Per Child board.
+ *
+ * Copyright © 2006-2010 David Woodhouse <dwmw2@infradead.org>
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <asm/olpc.h>
+
+
+#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */
+#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */
+#define EC_BAT_ACR 0x12 /* int16_t, *6250/15, µAh */
+#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */
+#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */
+#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */
+#define EC_BAT_SOC 0x16 /* uint8_t, percentage */
+#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */
+#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */
+#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */
+
+#define BAT_STAT_PRESENT 0x01
+#define BAT_STAT_FULL 0x02
+#define BAT_STAT_LOW 0x04
+#define BAT_STAT_DESTROY 0x08
+#define BAT_STAT_AC 0x10
+#define BAT_STAT_CHARGING 0x20
+#define BAT_STAT_DISCHARGING 0x40
+#define BAT_STAT_TRICKLE 0x80
+
+#define BAT_ERR_INFOFAIL 0x02
+#define BAT_ERR_OVERVOLTAGE 0x04
+#define BAT_ERR_OVERTEMP 0x05
+#define BAT_ERR_GAUGESTOP 0x06
+#define BAT_ERR_OUT_OF_CONTROL 0x07
+#define BAT_ERR_ID_FAIL 0x09
+#define BAT_ERR_ACR_FAIL 0x10
+
+#define BAT_ADDR_MFR_TYPE 0x5F
+
+/*********************************************************************
+ * Power
+ *********************************************************************/
+
+static int olpc_ac_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ uint8_t status;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
+ if (ret)
+ return ret;
+
+ val->intval = !!(status & BAT_STAT_AC);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property olpc_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static struct power_supply olpc_ac = {
+ .name = "olpc-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = olpc_ac_props,
+ .num_properties = ARRAY_SIZE(olpc_ac_props),
+ .get_property = olpc_ac_get_prop,
+};
+
+static char bat_serial[17]; /* Ick */
+
+static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte)
+{
+ if (olpc_platform_info.ecver > 0x44) {
+ if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (ec_byte & BAT_STAT_DISCHARGING)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (ec_byte & BAT_STAT_FULL)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else /* er,... */
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ /* Older EC didn't report charge/discharge bits */
+ if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (ec_byte & BAT_STAT_FULL)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else /* Not _necessarily_ true but EC doesn't tell all yet */
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ return 0;
+}
+
+static int olpc_bat_get_health(union power_supply_propval *val)
+{
+ uint8_t ec_byte;
+ int ret;
+
+ ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ switch (ec_byte) {
+ case 0:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+
+ case BAT_ERR_OVERTEMP:
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+
+ case BAT_ERR_OVERVOLTAGE:
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+
+ case BAT_ERR_INFOFAIL:
+ case BAT_ERR_OUT_OF_CONTROL:
+ case BAT_ERR_ID_FAIL:
+ case BAT_ERR_ACR_FAIL:
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+
+ default:
+ /* Eep. We don't know this failure code */
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static int olpc_bat_get_mfr(union power_supply_propval *val)
+{
+ uint8_t ec_byte;
+ int ret;
+
+ ec_byte = BAT_ADDR_MFR_TYPE;
+ ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ switch (ec_byte >> 4) {
+ case 1:
+ val->strval = "Gold Peak";
+ break;
+ case 2:
+ val->strval = "BYD";
+ break;
+ default:
+ val->strval = "Unknown";
+ break;
+ }
+
+ return ret;
+}
+
+static int olpc_bat_get_tech(union power_supply_propval *val)
+{
+ uint8_t ec_byte;
+ int ret;
+
+ ec_byte = BAT_ADDR_MFR_TYPE;
+ ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ switch (ec_byte & 0xf) {
+ case 1:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
+ break;
+ case 2:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+ break;
+ }
+
+ return ret;
+}
+
+static int olpc_bat_get_charge_full_design(union power_supply_propval *val)
+{
+ uint8_t ec_byte;
+ union power_supply_propval tech;
+ int ret, mfr;
+
+ ret = olpc_bat_get_tech(&tech);
+ if (ret)
+ return ret;
+
+ ec_byte = BAT_ADDR_MFR_TYPE;
+ ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ mfr = ec_byte >> 4;
+
+ switch (tech.intval) {
+ case POWER_SUPPLY_TECHNOLOGY_NiMH:
+ switch (mfr) {
+ case 1: /* Gold Peak */
+ val->intval = 3000000*.8;
+ break;
+ default:
+ return -EIO;
+ }
+ break;
+
+ case POWER_SUPPLY_TECHNOLOGY_LiFe:
+ switch (mfr) {
+ case 1: /* Gold Peak */
+ val->intval = 2800000;
+ break;
+ case 2: /* BYD */
+ val->intval = 3100000;
+ break;
+ default:
+ return -EIO;
+ }
+ break;
+
+ default:
+ return -EIO;
+ }
+
+ return ret;
+}
+
+static int olpc_bat_get_charge_now(union power_supply_propval *val)
+{
+ uint8_t soc;
+ union power_supply_propval full;
+ int ret;
+
+ ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &soc, 1);
+ if (ret)
+ return ret;
+
+ ret = olpc_bat_get_charge_full_design(&full);
+ if (ret)
+ return ret;
+
+ val->intval = soc * (full.intval / 100);
+ return 0;
+}
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+static int olpc_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ __be16 ec_word;
+ uint8_t ec_byte;
+ __be64 ser_buf;
+
+ ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ /* Theoretically there's a race here -- the battery could be
+ removed immediately after we check whether it's present, and
+ then we query for some other property of the now-absent battery.
+ It doesn't matter though -- the EC will return the last-known
+ information, and it's as if we just ran that _little_ bit faster
+ and managed to read it out before the battery went away. */
+ if (!(ec_byte & (BAT_STAT_PRESENT | BAT_STAT_TRICKLE)) &&
+ psp != POWER_SUPPLY_PROP_PRESENT)
+ return -ENODEV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = olpc_bat_get_status(val, ec_byte);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (ec_byte & BAT_STAT_TRICKLE)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ else if (ec_byte & BAT_STAT_CHARGING)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(ec_byte & (BAT_STAT_PRESENT |
+ BAT_STAT_TRICKLE));
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (ec_byte & BAT_STAT_DESTROY)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else {
+ ret = olpc_bat_get_health(val);
+ if (ret)
+ return ret;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ ret = olpc_bat_get_mfr(val);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ ret = olpc_bat_get_tech(val);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2);
+ if (ret)
+ return ret;
+
+ val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2);
+ if (ret)
+ return ret;
+
+ val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1);
+ if (ret)
+ return ret;
+ val->intval = ec_byte;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ if (ec_byte & BAT_STAT_FULL)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (ec_byte & BAT_STAT_LOW)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ ret = olpc_bat_get_charge_full_design(val);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = olpc_bat_get_charge_now(val);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2);
+ if (ret)
+ return ret;
+
+ val->intval = (s16)be16_to_cpu(ec_word) * 100 / 256;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+ ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2);
+ if (ret)
+ return ret;
+
+ val->intval = (int)be16_to_cpu(ec_word) * 100 / 256;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2);
+ if (ret)
+ return ret;
+
+ val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15;
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8);
+ if (ret)
+ return ret;
+
+ sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf));
+ val->strval = bat_serial;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property olpc_xo1_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_AMBIENT,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+};
+
+/* XO-1.5 does not have ambient temperature property */
+static enum power_supply_property olpc_xo15_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+};
+
+/* EEPROM reading goes completely around the power_supply API, sadly */
+
+#define EEPROM_START 0x20
+#define EEPROM_END 0x80
+#define EEPROM_SIZE (EEPROM_END - EEPROM_START)
+
+static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf, loff_t off, size_t count)
+{
+ uint8_t ec_byte;
+ int ret;
+ int i;
+
+ if (off >= EEPROM_SIZE)
+ return 0;
+ if (off + count > EEPROM_SIZE)
+ count = EEPROM_SIZE - off;
+
+ for (i = 0; i < count; i++) {
+ ec_byte = EEPROM_START + off + i;
+ ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1);
+ if (ret) {
+ pr_err("olpc-battery: "
+ "EC_BAT_EEPROM cmd @ 0x%x failed - %d!\n",
+ ec_byte, ret);
+ return -EIO;
+ }
+ }
+
+ return count;
+}
+
+static struct bin_attribute olpc_bat_eeprom = {
+ .attr = {
+ .name = "eeprom",
+ .mode = S_IRUGO,
+ },
+ .size = 0,
+ .read = olpc_bat_eeprom_read,
+};
+
+/* Allow userspace to see the specific error value pulled from the EC */
+
+static ssize_t olpc_bat_error_read(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ uint8_t ec_byte;
+ ssize_t ret;
+
+ ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", ec_byte);
+}
+
+static struct device_attribute olpc_bat_error = {
+ .attr = {
+ .name = "error",
+ .mode = S_IRUGO,
+ },
+ .show = olpc_bat_error_read,
+};
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static struct platform_device *bat_pdev;
+
+static struct power_supply olpc_bat = {
+ .get_property = olpc_bat_get_property,
+ .use_for_apm = 1,
+};
+
+void olpc_battery_trigger_uevent(unsigned long cause)
+{
+ if (cause & EC_SCI_SRC_ACPWR)
+ kobject_uevent(&olpc_ac.dev->kobj, KOBJ_CHANGE);
+ if (cause & (EC_SCI_SRC_BATERR|EC_SCI_SRC_BATSOC|EC_SCI_SRC_BATTERY))
+ kobject_uevent(&olpc_bat.dev->kobj, KOBJ_CHANGE);
+}
+
+static int __init olpc_bat_init(void)
+{
+ int ret = 0;
+ uint8_t status;
+
+ if (!olpc_platform_info.ecver)
+ return -ENXIO;
+
+ /*
+ * We've seen a number of EC protocol changes; this driver requires
+ * the latest EC protocol, supported by 0x44 and above.
+ */
+ if (olpc_platform_info.ecver < 0x44) {
+ printk(KERN_NOTICE "OLPC EC version 0x%02x too old for "
+ "battery driver.\n", olpc_platform_info.ecver);
+ return -ENXIO;
+ }
+
+ ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
+ if (ret)
+ return ret;
+
+ /* Ignore the status. It doesn't actually matter */
+
+ bat_pdev = platform_device_register_simple("olpc-battery", 0, NULL, 0);
+ if (IS_ERR(bat_pdev))
+ return PTR_ERR(bat_pdev);
+
+ ret = power_supply_register(&bat_pdev->dev, &olpc_ac);
+ if (ret)
+ goto ac_failed;
+
+ olpc_bat.name = bat_pdev->name;
+ if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */
+ olpc_bat.properties = olpc_xo15_bat_props;
+ olpc_bat.num_properties = ARRAY_SIZE(olpc_xo15_bat_props);
+ } else { /* XO-1 */
+ olpc_bat.properties = olpc_xo1_bat_props;
+ olpc_bat.num_properties = ARRAY_SIZE(olpc_xo1_bat_props);
+ }
+
+ ret = power_supply_register(&bat_pdev->dev, &olpc_bat);
+ if (ret)
+ goto battery_failed;
+
+ ret = device_create_bin_file(olpc_bat.dev, &olpc_bat_eeprom);
+ if (ret)
+ goto eeprom_failed;
+
+ ret = device_create_file(olpc_bat.dev, &olpc_bat_error);
+ if (ret)
+ goto error_failed;
+
+ goto success;
+
+error_failed:
+ device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom);
+eeprom_failed:
+ power_supply_unregister(&olpc_bat);
+battery_failed:
+ power_supply_unregister(&olpc_ac);
+ac_failed:
+ platform_device_unregister(bat_pdev);
+success:
+ return ret;
+}
+
+static void __exit olpc_bat_exit(void)
+{
+ device_remove_file(olpc_bat.dev, &olpc_bat_error);
+ device_remove_bin_file(olpc_bat.dev, &olpc_bat_eeprom);
+ power_supply_unregister(&olpc_bat);
+ power_supply_unregister(&olpc_ac);
+ platform_device_unregister(bat_pdev);
+}
+
+module_init(olpc_bat_init);
+module_exit(olpc_bat_exit);
+
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine");
diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c
new file mode 100644
index 00000000..4fa52e17
--- /dev/null
+++ b/drivers/power/pcf50633-charger.c
@@ -0,0 +1,492 @@
+/* NXP PCF50633 Main Battery Charger Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50633 driver mainly by
+ * Harald Welte, Andy Green and Werner Almesberger
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include <linux/mfd/pcf50633/core.h>
+#include <linux/mfd/pcf50633/mbc.h>
+
+struct pcf50633_mbc {
+ struct pcf50633 *pcf;
+
+ int adapter_online;
+ int usb_online;
+
+ struct power_supply usb;
+ struct power_supply adapter;
+ struct power_supply ac;
+};
+
+int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma)
+{
+ struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
+ int ret = 0;
+ u8 bits;
+ int charging_start = 1;
+ u8 mbcs2, chgmod;
+ unsigned int mbcc5;
+
+ if (ma >= 1000) {
+ bits = PCF50633_MBCC7_USB_1000mA;
+ ma = 1000;
+ } else if (ma >= 500) {
+ bits = PCF50633_MBCC7_USB_500mA;
+ ma = 500;
+ } else if (ma >= 100) {
+ bits = PCF50633_MBCC7_USB_100mA;
+ ma = 100;
+ } else {
+ bits = PCF50633_MBCC7_USB_SUSPEND;
+ charging_start = 0;
+ ma = 0;
+ }
+
+ ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7,
+ PCF50633_MBCC7_USB_MASK, bits);
+ if (ret)
+ dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma);
+ else
+ dev_info(pcf->dev, "usb curlim to %d mA\n", ma);
+
+ /*
+ * We limit the charging current to be the USB current limit.
+ * The reason is that on pcf50633, when it enters PMU Standby mode,
+ * which it does when the device goes "off", the USB current limit
+ * reverts to the variant default. In at least one common case, that
+ * default is 500mA. By setting the charging current to be the same
+ * as the USB limit we set here before PMU standby, we enforce it only
+ * using the correct amount of current even when the USB current limit
+ * gets reset to the wrong thing
+ */
+
+ if (mbc->pcf->pdata->charger_reference_current_ma) {
+ mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma;
+ if (mbcc5 > 255)
+ mbcc5 = 255;
+ pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5);
+ }
+
+ mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2);
+ chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK);
+
+ /* If chgmod == BATFULL, setting chgena has no effect.
+ * Datasheet says we need to set resume instead but when autoresume is
+ * used resume doesn't work. Clear and set chgena instead.
+ */
+ if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL)
+ pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
+ PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA);
+ else {
+ pcf50633_reg_clear_bits(pcf, PCF50633_REG_MBCC1,
+ PCF50633_MBCC1_CHGENA);
+ pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
+ PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA);
+ }
+
+ power_supply_changed(&mbc->usb);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set);
+
+int pcf50633_mbc_get_status(struct pcf50633 *pcf)
+{
+ struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
+ int status = 0;
+ u8 chgmod;
+
+ if (!mbc)
+ return 0;
+
+ chgmod = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2)
+ & PCF50633_MBCS2_MBC_MASK;
+
+ if (mbc->usb_online)
+ status |= PCF50633_MBC_USB_ONLINE;
+ if (chgmod == PCF50633_MBCS2_MBC_USB_PRE ||
+ chgmod == PCF50633_MBCS2_MBC_USB_PRE_WAIT ||
+ chgmod == PCF50633_MBCS2_MBC_USB_FAST ||
+ chgmod == PCF50633_MBCS2_MBC_USB_FAST_WAIT)
+ status |= PCF50633_MBC_USB_ACTIVE;
+ if (mbc->adapter_online)
+ status |= PCF50633_MBC_ADAPTER_ONLINE;
+ if (chgmod == PCF50633_MBCS2_MBC_ADP_PRE ||
+ chgmod == PCF50633_MBCS2_MBC_ADP_PRE_WAIT ||
+ chgmod == PCF50633_MBCS2_MBC_ADP_FAST ||
+ chgmod == PCF50633_MBCS2_MBC_ADP_FAST_WAIT)
+ status |= PCF50633_MBC_ADAPTER_ACTIVE;
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status);
+
+int pcf50633_mbc_get_usb_online_status(struct pcf50633 *pcf)
+{
+ struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
+
+ if (!mbc)
+ return 0;
+
+ return mbc->usb_online;
+}
+EXPORT_SYMBOL_GPL(pcf50633_mbc_get_usb_online_status);
+
+static ssize_t
+show_chgmode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+
+ u8 mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2);
+ u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK);
+
+ return sprintf(buf, "%d\n", chgmod);
+}
+static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL);
+
+static ssize_t
+show_usblim(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+ u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
+ PCF50633_MBCC7_USB_MASK;
+ unsigned int ma;
+
+ if (usblim == PCF50633_MBCC7_USB_1000mA)
+ ma = 1000;
+ else if (usblim == PCF50633_MBCC7_USB_500mA)
+ ma = 500;
+ else if (usblim == PCF50633_MBCC7_USB_100mA)
+ ma = 100;
+ else
+ ma = 0;
+
+ return sprintf(buf, "%u\n", ma);
+}
+
+static ssize_t set_usblim(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+ unsigned long ma;
+ int ret;
+
+ ret = strict_strtoul(buf, 10, &ma);
+ if (ret)
+ return -EINVAL;
+
+ pcf50633_mbc_usb_curlim_set(mbc->pcf, ma);
+
+ return count;
+}
+
+static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim);
+
+static ssize_t
+show_chglim(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+ u8 mbcc5 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC5);
+ unsigned int ma;
+
+ if (!mbc->pcf->pdata->charger_reference_current_ma)
+ return -ENODEV;
+
+ ma = (mbc->pcf->pdata->charger_reference_current_ma * mbcc5) >> 8;
+
+ return sprintf(buf, "%u\n", ma);
+}
+
+static ssize_t set_chglim(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+ unsigned long ma;
+ unsigned int mbcc5;
+ int ret;
+
+ if (!mbc->pcf->pdata->charger_reference_current_ma)
+ return -ENODEV;
+
+ ret = strict_strtoul(buf, 10, &ma);
+ if (ret)
+ return -EINVAL;
+
+ mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma;
+ if (mbcc5 > 255)
+ mbcc5 = 255;
+ pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5);
+
+ return count;
+}
+
+/*
+ * This attribute allows to change MBC charging limit on the fly
+ * independently of usb current limit. It also gets set automatically every
+ * time usb current limit is changed.
+ */
+static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim);
+
+static struct attribute *pcf50633_mbc_sysfs_entries[] = {
+ &dev_attr_chgmode.attr,
+ &dev_attr_usb_curlim.attr,
+ &dev_attr_chg_curlim.attr,
+ NULL,
+};
+
+static struct attribute_group mbc_attr_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = pcf50633_mbc_sysfs_entries,
+};
+
+static void
+pcf50633_mbc_irq_handler(int irq, void *data)
+{
+ struct pcf50633_mbc *mbc = data;
+
+ /* USB */
+ if (irq == PCF50633_IRQ_USBINS) {
+ mbc->usb_online = 1;
+ } else if (irq == PCF50633_IRQ_USBREM) {
+ mbc->usb_online = 0;
+ pcf50633_mbc_usb_curlim_set(mbc->pcf, 0);
+ }
+
+ /* Adapter */
+ if (irq == PCF50633_IRQ_ADPINS)
+ mbc->adapter_online = 1;
+ else if (irq == PCF50633_IRQ_ADPREM)
+ mbc->adapter_online = 0;
+
+ power_supply_changed(&mbc->ac);
+ power_supply_changed(&mbc->usb);
+ power_supply_changed(&mbc->adapter);
+
+ if (mbc->pcf->pdata->mbc_event_callback)
+ mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq);
+}
+
+static int adapter_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pcf50633_mbc *mbc = container_of(psy,
+ struct pcf50633_mbc, adapter);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = mbc->adapter_online;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, usb);
+ int ret = 0;
+ u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
+ PCF50633_MBCC7_USB_MASK;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = mbc->usb_online &&
+ (usblim <= PCF50633_MBCC7_USB_500mA);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pcf50633_mbc *mbc = container_of(psy, struct pcf50633_mbc, ac);
+ int ret = 0;
+ u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
+ PCF50633_MBCC7_USB_MASK;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = mbc->usb_online &&
+ (usblim == PCF50633_MBCC7_USB_1000mA);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const u8 mbc_irq_handlers[] = {
+ PCF50633_IRQ_ADPINS,
+ PCF50633_IRQ_ADPREM,
+ PCF50633_IRQ_USBINS,
+ PCF50633_IRQ_USBREM,
+ PCF50633_IRQ_BATFULL,
+ PCF50633_IRQ_CHGHALT,
+ PCF50633_IRQ_THLIMON,
+ PCF50633_IRQ_THLIMOFF,
+ PCF50633_IRQ_USBLIMON,
+ PCF50633_IRQ_USBLIMOFF,
+ PCF50633_IRQ_LOWSYS,
+ PCF50633_IRQ_LOWBAT,
+};
+
+static int __devinit pcf50633_mbc_probe(struct platform_device *pdev)
+{
+ struct pcf50633_mbc *mbc;
+ int ret;
+ int i;
+ u8 mbcs1;
+
+ mbc = kzalloc(sizeof(*mbc), GFP_KERNEL);
+ if (!mbc)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, mbc);
+ mbc->pcf = dev_to_pcf50633(pdev->dev.parent);
+
+ /* Set up IRQ handlers */
+ for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
+ pcf50633_register_irq(mbc->pcf, mbc_irq_handlers[i],
+ pcf50633_mbc_irq_handler, mbc);
+
+ /* Create power supplies */
+ mbc->adapter.name = "adapter";
+ mbc->adapter.type = POWER_SUPPLY_TYPE_MAINS;
+ mbc->adapter.properties = power_props;
+ mbc->adapter.num_properties = ARRAY_SIZE(power_props);
+ mbc->adapter.get_property = &adapter_get_property;
+ mbc->adapter.supplied_to = mbc->pcf->pdata->batteries;
+ mbc->adapter.num_supplicants = mbc->pcf->pdata->num_batteries;
+
+ mbc->usb.name = "usb";
+ mbc->usb.type = POWER_SUPPLY_TYPE_USB;
+ mbc->usb.properties = power_props;
+ mbc->usb.num_properties = ARRAY_SIZE(power_props);
+ mbc->usb.get_property = usb_get_property;
+ mbc->usb.supplied_to = mbc->pcf->pdata->batteries;
+ mbc->usb.num_supplicants = mbc->pcf->pdata->num_batteries;
+
+ mbc->ac.name = "ac";
+ mbc->ac.type = POWER_SUPPLY_TYPE_MAINS;
+ mbc->ac.properties = power_props;
+ mbc->ac.num_properties = ARRAY_SIZE(power_props);
+ mbc->ac.get_property = ac_get_property;
+ mbc->ac.supplied_to = mbc->pcf->pdata->batteries;
+ mbc->ac.num_supplicants = mbc->pcf->pdata->num_batteries;
+
+ ret = power_supply_register(&pdev->dev, &mbc->adapter);
+ if (ret) {
+ dev_err(mbc->pcf->dev, "failed to register adapter\n");
+ kfree(mbc);
+ return ret;
+ }
+
+ ret = power_supply_register(&pdev->dev, &mbc->usb);
+ if (ret) {
+ dev_err(mbc->pcf->dev, "failed to register usb\n");
+ power_supply_unregister(&mbc->adapter);
+ kfree(mbc);
+ return ret;
+ }
+
+ ret = power_supply_register(&pdev->dev, &mbc->ac);
+ if (ret) {
+ dev_err(mbc->pcf->dev, "failed to register ac\n");
+ power_supply_unregister(&mbc->adapter);
+ power_supply_unregister(&mbc->usb);
+ kfree(mbc);
+ return ret;
+ }
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group);
+ if (ret)
+ dev_err(mbc->pcf->dev, "failed to create sysfs entries\n");
+
+ mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1);
+ if (mbcs1 & PCF50633_MBCS1_USBPRES)
+ pcf50633_mbc_irq_handler(PCF50633_IRQ_USBINS, mbc);
+ if (mbcs1 & PCF50633_MBCS1_ADAPTPRES)
+ pcf50633_mbc_irq_handler(PCF50633_IRQ_ADPINS, mbc);
+
+ return 0;
+}
+
+static int __devexit pcf50633_mbc_remove(struct platform_device *pdev)
+{
+ struct pcf50633_mbc *mbc = platform_get_drvdata(pdev);
+ int i;
+
+ /* Remove IRQ handlers */
+ for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
+ pcf50633_free_irq(mbc->pcf, mbc_irq_handlers[i]);
+
+ sysfs_remove_group(&pdev->dev.kobj, &mbc_attr_group);
+ power_supply_unregister(&mbc->usb);
+ power_supply_unregister(&mbc->adapter);
+ power_supply_unregister(&mbc->ac);
+
+ kfree(mbc);
+
+ return 0;
+}
+
+static struct platform_driver pcf50633_mbc_driver = {
+ .driver = {
+ .name = "pcf50633-mbc",
+ },
+ .probe = pcf50633_mbc_probe,
+ .remove = __devexit_p(pcf50633_mbc_remove),
+};
+
+static int __init pcf50633_mbc_init(void)
+{
+ return platform_driver_register(&pcf50633_mbc_driver);
+}
+module_init(pcf50633_mbc_init);
+
+static void __exit pcf50633_mbc_exit(void)
+{
+ platform_driver_unregister(&pcf50633_mbc_driver);
+}
+module_exit(pcf50633_mbc_exit);
+
+MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
+MODULE_DESCRIPTION("PCF50633 mbc driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50633-mbc");
diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c
new file mode 100644
index 00000000..81b72010
--- /dev/null
+++ b/drivers/power/pda_power.c
@@ -0,0 +1,515 @@
+/*
+ * Common power driver for PDAs and phones with one or two external
+ * power supplies (AC/USB) connected to main and backup batteries,
+ * and optional builtin charger.
+ *
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ *
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/power_supply.h>
+#include <linux/pda_power.h>
+#include <linux/regulator/consumer.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/usb/otg.h>
+
+static inline unsigned int get_irq_flags(struct resource *res)
+{
+ unsigned int flags = IRQF_SAMPLE_RANDOM | IRQF_SHARED;
+
+ flags |= res->flags & IRQF_TRIGGER_MASK;
+
+ return flags;
+}
+
+static struct device *dev;
+static struct pda_power_pdata *pdata;
+static struct resource *ac_irq, *usb_irq;
+static struct timer_list charger_timer;
+static struct timer_list supply_timer;
+static struct timer_list polling_timer;
+static int polling;
+
+static struct otg_transceiver *transceiver;
+static struct notifier_block otg_nb;
+static struct regulator *ac_draw;
+
+enum {
+ PDA_PSY_OFFLINE = 0,
+ PDA_PSY_ONLINE = 1,
+ PDA_PSY_TO_CHANGE,
+};
+static int new_ac_status = -1;
+static int new_usb_status = -1;
+static int ac_status = -1;
+static int usb_status = -1;
+
+static int pda_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (psy->type == POWER_SUPPLY_TYPE_MAINS)
+ val->intval = pdata->is_ac_online ?
+ pdata->is_ac_online() : 0;
+ else
+ val->intval = pdata->is_usb_online ?
+ pdata->is_usb_online() : 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property pda_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *pda_power_supplied_to[] = {
+ "main-battery",
+ "backup-battery",
+};
+
+static struct power_supply pda_psy_ac = {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .supplied_to = pda_power_supplied_to,
+ .num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
+ .properties = pda_power_props,
+ .num_properties = ARRAY_SIZE(pda_power_props),
+ .get_property = pda_power_get_property,
+};
+
+static struct power_supply pda_psy_usb = {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .supplied_to = pda_power_supplied_to,
+ .num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
+ .properties = pda_power_props,
+ .num_properties = ARRAY_SIZE(pda_power_props),
+ .get_property = pda_power_get_property,
+};
+
+static void update_status(void)
+{
+ if (pdata->is_ac_online)
+ new_ac_status = !!pdata->is_ac_online();
+
+ if (pdata->is_usb_online)
+ new_usb_status = !!pdata->is_usb_online();
+}
+
+static void update_charger(void)
+{
+ static int regulator_enabled;
+ int max_uA = pdata->ac_max_uA;
+
+ if (pdata->set_charge) {
+ if (new_ac_status > 0) {
+ dev_dbg(dev, "charger on (AC)\n");
+ pdata->set_charge(PDA_POWER_CHARGE_AC);
+ } else if (new_usb_status > 0) {
+ dev_dbg(dev, "charger on (USB)\n");
+ pdata->set_charge(PDA_POWER_CHARGE_USB);
+ } else {
+ dev_dbg(dev, "charger off\n");
+ pdata->set_charge(0);
+ }
+ } else if (ac_draw) {
+ if (new_ac_status > 0) {
+ regulator_set_current_limit(ac_draw, max_uA, max_uA);
+ if (!regulator_enabled) {
+ dev_dbg(dev, "charger on (AC)\n");
+ regulator_enable(ac_draw);
+ regulator_enabled = 1;
+ }
+ } else {
+ if (regulator_enabled) {
+ dev_dbg(dev, "charger off\n");
+ regulator_disable(ac_draw);
+ regulator_enabled = 0;
+ }
+ }
+ }
+}
+
+static void supply_timer_func(unsigned long unused)
+{
+ if (ac_status == PDA_PSY_TO_CHANGE) {
+ ac_status = new_ac_status;
+ power_supply_changed(&pda_psy_ac);
+ }
+
+ if (usb_status == PDA_PSY_TO_CHANGE) {
+ usb_status = new_usb_status;
+ power_supply_changed(&pda_psy_usb);
+ }
+}
+
+static void psy_changed(void)
+{
+ update_charger();
+
+ /*
+ * Okay, charger set. Now wait a bit before notifying supplicants,
+ * charge power should stabilize.
+ */
+ mod_timer(&supply_timer,
+ jiffies + msecs_to_jiffies(pdata->wait_for_charger));
+}
+
+static void charger_timer_func(unsigned long unused)
+{
+ update_status();
+ psy_changed();
+}
+
+static irqreturn_t power_changed_isr(int irq, void *power_supply)
+{
+ if (power_supply == &pda_psy_ac)
+ ac_status = PDA_PSY_TO_CHANGE;
+ else if (power_supply == &pda_psy_usb)
+ usb_status = PDA_PSY_TO_CHANGE;
+ else
+ return IRQ_NONE;
+
+ /*
+ * Wait a bit before reading ac/usb line status and setting charger,
+ * because ac/usb status readings may lag from irq.
+ */
+ mod_timer(&charger_timer,
+ jiffies + msecs_to_jiffies(pdata->wait_for_status));
+
+ return IRQ_HANDLED;
+}
+
+static void polling_timer_func(unsigned long unused)
+{
+ int changed = 0;
+
+ dev_dbg(dev, "polling...\n");
+
+ update_status();
+
+ if (!ac_irq && new_ac_status != ac_status) {
+ ac_status = PDA_PSY_TO_CHANGE;
+ changed = 1;
+ }
+
+ if (!usb_irq && new_usb_status != usb_status) {
+ usb_status = PDA_PSY_TO_CHANGE;
+ changed = 1;
+ }
+
+ if (changed)
+ psy_changed();
+
+ mod_timer(&polling_timer,
+ jiffies + msecs_to_jiffies(pdata->polling_interval));
+}
+
+#ifdef CONFIG_USB_OTG_UTILS
+static int otg_is_usb_online(void)
+{
+ return (transceiver->last_event == USB_EVENT_VBUS ||
+ transceiver->last_event == USB_EVENT_ENUMERATED);
+}
+
+static int otg_is_ac_online(void)
+{
+ return (transceiver->last_event == USB_EVENT_CHARGER);
+}
+
+static int otg_handle_notification(struct notifier_block *nb,
+ unsigned long event, void *unused)
+{
+ switch (event) {
+ case USB_EVENT_CHARGER:
+ ac_status = PDA_PSY_TO_CHANGE;
+ break;
+ case USB_EVENT_VBUS:
+ case USB_EVENT_ENUMERATED:
+ usb_status = PDA_PSY_TO_CHANGE;
+ break;
+ case USB_EVENT_NONE:
+ ac_status = PDA_PSY_TO_CHANGE;
+ usb_status = PDA_PSY_TO_CHANGE;
+ break;
+ default:
+ return NOTIFY_OK;
+ }
+
+ /*
+ * Wait a bit before reading ac/usb line status and setting charger,
+ * because ac/usb status readings may lag from irq.
+ */
+ mod_timer(&charger_timer,
+ jiffies + msecs_to_jiffies(pdata->wait_for_status));
+
+ return NOTIFY_OK;
+}
+#endif
+
+static int pda_power_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ dev = &pdev->dev;
+
+ if (pdev->id != -1) {
+ dev_err(dev, "it's meaningless to register several "
+ "pda_powers; use id = -1\n");
+ ret = -EINVAL;
+ goto wrongid;
+ }
+
+ pdata = pdev->dev.platform_data;
+
+ if (pdata->init) {
+ ret = pdata->init(dev);
+ if (ret < 0)
+ goto init_failed;
+ }
+
+ update_status();
+ update_charger();
+
+ if (!pdata->wait_for_status)
+ pdata->wait_for_status = 500;
+
+ if (!pdata->wait_for_charger)
+ pdata->wait_for_charger = 500;
+
+ if (!pdata->polling_interval)
+ pdata->polling_interval = 2000;
+
+ if (!pdata->ac_max_uA)
+ pdata->ac_max_uA = 500000;
+
+ setup_timer(&charger_timer, charger_timer_func, 0);
+ setup_timer(&supply_timer, supply_timer_func, 0);
+
+ ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac");
+ usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb");
+
+ if (pdata->supplied_to) {
+ pda_psy_ac.supplied_to = pdata->supplied_to;
+ pda_psy_ac.num_supplicants = pdata->num_supplicants;
+ pda_psy_usb.supplied_to = pdata->supplied_to;
+ pda_psy_usb.num_supplicants = pdata->num_supplicants;
+ }
+
+ ac_draw = regulator_get(dev, "ac_draw");
+ if (IS_ERR(ac_draw)) {
+ dev_dbg(dev, "couldn't get ac_draw regulator\n");
+ ac_draw = NULL;
+ ret = PTR_ERR(ac_draw);
+ }
+
+ transceiver = otg_get_transceiver();
+ if (transceiver && !pdata->is_usb_online) {
+ pdata->is_usb_online = otg_is_usb_online;
+ }
+ if (transceiver && !pdata->is_ac_online) {
+ pdata->is_ac_online = otg_is_ac_online;
+ }
+
+ if (pdata->is_ac_online) {
+ ret = power_supply_register(&pdev->dev, &pda_psy_ac);
+ if (ret) {
+ dev_err(dev, "failed to register %s power supply\n",
+ pda_psy_ac.name);
+ goto ac_supply_failed;
+ }
+
+ if (ac_irq) {
+ ret = request_irq(ac_irq->start, power_changed_isr,
+ get_irq_flags(ac_irq), ac_irq->name,
+ &pda_psy_ac);
+ if (ret) {
+ dev_err(dev, "request ac irq failed\n");
+ goto ac_irq_failed;
+ }
+ } else {
+ polling = 1;
+ }
+ }
+
+ if (pdata->is_usb_online) {
+ ret = power_supply_register(&pdev->dev, &pda_psy_usb);
+ if (ret) {
+ dev_err(dev, "failed to register %s power supply\n",
+ pda_psy_usb.name);
+ goto usb_supply_failed;
+ }
+
+ if (usb_irq) {
+ ret = request_irq(usb_irq->start, power_changed_isr,
+ get_irq_flags(usb_irq),
+ usb_irq->name, &pda_psy_usb);
+ if (ret) {
+ dev_err(dev, "request usb irq failed\n");
+ goto usb_irq_failed;
+ }
+ } else {
+ polling = 1;
+ }
+ }
+
+ if (transceiver && pdata->use_otg_notifier) {
+ otg_nb.notifier_call = otg_handle_notification;
+ ret = otg_register_notifier(transceiver, &otg_nb);
+ if (ret) {
+ dev_err(dev, "failure to register otg notifier\n");
+ goto otg_reg_notifier_failed;
+ }
+ polling = 0;
+ }
+
+ if (polling) {
+ dev_dbg(dev, "will poll for status\n");
+ setup_timer(&polling_timer, polling_timer_func, 0);
+ mod_timer(&polling_timer,
+ jiffies + msecs_to_jiffies(pdata->polling_interval));
+ }
+
+ if (ac_irq || usb_irq)
+ device_init_wakeup(&pdev->dev, 1);
+
+ return 0;
+
+otg_reg_notifier_failed:
+ if (pdata->is_usb_online && usb_irq)
+ free_irq(usb_irq->start, &pda_psy_usb);
+usb_irq_failed:
+ if (pdata->is_usb_online)
+ power_supply_unregister(&pda_psy_usb);
+usb_supply_failed:
+ if (pdata->is_ac_online && ac_irq)
+ free_irq(ac_irq->start, &pda_psy_ac);
+ if (transceiver)
+ otg_put_transceiver(transceiver);
+ac_irq_failed:
+ if (pdata->is_ac_online)
+ power_supply_unregister(&pda_psy_ac);
+ac_supply_failed:
+ if (ac_draw) {
+ regulator_put(ac_draw);
+ ac_draw = NULL;
+ }
+ if (pdata->exit)
+ pdata->exit(dev);
+init_failed:
+wrongid:
+ return ret;
+}
+
+static int pda_power_remove(struct platform_device *pdev)
+{
+ if (pdata->is_usb_online && usb_irq)
+ free_irq(usb_irq->start, &pda_psy_usb);
+ if (pdata->is_ac_online && ac_irq)
+ free_irq(ac_irq->start, &pda_psy_ac);
+
+ if (polling)
+ del_timer_sync(&polling_timer);
+ del_timer_sync(&charger_timer);
+ del_timer_sync(&supply_timer);
+
+ if (pdata->is_usb_online)
+ power_supply_unregister(&pda_psy_usb);
+ if (pdata->is_ac_online)
+ power_supply_unregister(&pda_psy_ac);
+#ifdef CONFIG_USB_OTG_UTILS
+ if (transceiver)
+ otg_put_transceiver(transceiver);
+#endif
+ if (ac_draw) {
+ regulator_put(ac_draw);
+ ac_draw = NULL;
+ }
+ if (pdata->exit)
+ pdata->exit(dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ac_wakeup_enabled;
+static int usb_wakeup_enabled;
+
+static int pda_power_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ if (pdata->suspend) {
+ int ret = pdata->suspend(state);
+
+ if (ret)
+ return ret;
+ }
+
+ if (device_may_wakeup(&pdev->dev)) {
+ if (ac_irq)
+ ac_wakeup_enabled = !enable_irq_wake(ac_irq->start);
+ if (usb_irq)
+ usb_wakeup_enabled = !enable_irq_wake(usb_irq->start);
+ }
+
+ return 0;
+}
+
+static int pda_power_resume(struct platform_device *pdev)
+{
+ if (device_may_wakeup(&pdev->dev)) {
+ if (usb_irq && usb_wakeup_enabled)
+ disable_irq_wake(usb_irq->start);
+ if (ac_irq && ac_wakeup_enabled)
+ disable_irq_wake(ac_irq->start);
+ }
+
+ if (pdata->resume)
+ return pdata->resume();
+
+ return 0;
+}
+#else
+#define pda_power_suspend NULL
+#define pda_power_resume NULL
+#endif /* CONFIG_PM */
+
+MODULE_ALIAS("platform:pda-power");
+
+static struct platform_driver pda_power_pdrv = {
+ .driver = {
+ .name = "pda-power",
+ },
+ .probe = pda_power_probe,
+ .remove = pda_power_remove,
+ .suspend = pda_power_suspend,
+ .resume = pda_power_resume,
+};
+
+static int __init pda_power_init(void)
+{
+ return platform_driver_register(&pda_power_pdrv);
+}
+
+static void __exit pda_power_exit(void)
+{
+ platform_driver_unregister(&pda_power_pdrv);
+}
+
+module_init(pda_power_init);
+module_exit(pda_power_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>");
diff --git a/drivers/power/pmu_battery.c b/drivers/power/pmu_battery.c
new file mode 100644
index 00000000..023d2499
--- /dev/null
+++ b/drivers/power/pmu_battery.c
@@ -0,0 +1,216 @@
+/*
+ * Battery class driver for Apple PMU
+ *
+ * Copyright © 2006 David Woodhouse <dwmw2@infradead.org>
+ *
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/power_supply.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+#include <linux/slab.h>
+
+static struct pmu_battery_dev {
+ struct power_supply bat;
+ struct pmu_battery_info *pbi;
+ char name[16];
+ int propval;
+} *pbats[PMU_MAX_BATTERIES];
+
+#define to_pmu_battery_dev(x) container_of(x, struct pmu_battery_dev, bat)
+
+/*********************************************************************
+ * Power
+ *********************************************************************/
+
+static int pmu_get_ac_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = (!!(pmu_power_flags & PMU_PWR_AC_PRESENT)) ||
+ (pmu_battery_count == 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property pmu_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static struct power_supply pmu_ac = {
+ .name = "pmu-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = pmu_ac_props,
+ .num_properties = ARRAY_SIZE(pmu_ac_props),
+ .get_property = pmu_get_ac_prop,
+};
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+
+static char *pmu_batt_types[] = {
+ "Smart", "Comet", "Hooper", "Unknown"
+};
+
+static char *pmu_bat_get_model_name(struct pmu_battery_info *pbi)
+{
+ switch (pbi->flags & PMU_BATT_TYPE_MASK) {
+ case PMU_BATT_TYPE_SMART:
+ return pmu_batt_types[0];
+ case PMU_BATT_TYPE_COMET:
+ return pmu_batt_types[1];
+ case PMU_BATT_TYPE_HOOPER:
+ return pmu_batt_types[2];
+ default: break;
+ }
+ return pmu_batt_types[3];
+}
+
+static int pmu_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pmu_battery_dev *pbat = to_pmu_battery_dev(psy);
+ struct pmu_battery_info *pbi = pbat->pbi;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (pbi->flags & PMU_BATT_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (pmu_power_flags & PMU_PWR_AC_PRESENT)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(pbi->flags & PMU_BATT_PRESENT);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = pmu_bat_get_model_name(pbi);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_AVG:
+ val->intval = pbi->charge * 1000; /* mWh -> µWh */
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ val->intval = pbi->max_charge * 1000; /* mWh -> µWh */
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = pbi->amperage * 1000; /* mA -> µA */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ val->intval = pbi->voltage * 1000; /* mV -> µV */
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ val->intval = pbi->time_remaining;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property pmu_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_ENERGY_AVG,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+};
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static struct platform_device *bat_pdev;
+
+static int __init pmu_bat_init(void)
+{
+ int ret;
+ int i;
+
+ bat_pdev = platform_device_register_simple("pmu-battery",
+ 0, NULL, 0);
+ if (IS_ERR(bat_pdev)) {
+ ret = PTR_ERR(bat_pdev);
+ goto pdev_register_failed;
+ }
+
+ ret = power_supply_register(&bat_pdev->dev, &pmu_ac);
+ if (ret)
+ goto ac_register_failed;
+
+ for (i = 0; i < pmu_battery_count; i++) {
+ struct pmu_battery_dev *pbat = kzalloc(sizeof(*pbat),
+ GFP_KERNEL);
+ if (!pbat)
+ break;
+
+ sprintf(pbat->name, "PMU_battery_%d", i);
+ pbat->bat.name = pbat->name;
+ pbat->bat.properties = pmu_bat_props;
+ pbat->bat.num_properties = ARRAY_SIZE(pmu_bat_props);
+ pbat->bat.get_property = pmu_bat_get_property;
+ pbat->pbi = &pmu_batteries[i];
+
+ ret = power_supply_register(&bat_pdev->dev, &pbat->bat);
+ if (ret) {
+ kfree(pbat);
+ goto battery_register_failed;
+ }
+ pbats[i] = pbat;
+ }
+
+ goto success;
+
+battery_register_failed:
+ while (i--) {
+ if (!pbats[i])
+ continue;
+ power_supply_unregister(&pbats[i]->bat);
+ kfree(pbats[i]);
+ }
+ power_supply_unregister(&pmu_ac);
+ac_register_failed:
+ platform_device_unregister(bat_pdev);
+pdev_register_failed:
+success:
+ return ret;
+}
+
+static void __exit pmu_bat_exit(void)
+{
+ int i;
+
+ for (i = 0; i < PMU_MAX_BATTERIES; i++) {
+ if (!pbats[i])
+ continue;
+ power_supply_unregister(&pbats[i]->bat);
+ kfree(pbats[i]);
+ }
+ power_supply_unregister(&pmu_ac);
+ platform_device_unregister(bat_pdev);
+}
+
+module_init(pmu_bat_init);
+module_exit(pmu_bat_exit);
+
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("PMU battery driver");
diff --git a/drivers/power/power_supply.h b/drivers/power/power_supply.h
new file mode 100644
index 00000000..018de2b2
--- /dev/null
+++ b/drivers/power/power_supply.h
@@ -0,0 +1,38 @@
+/*
+ * Functions private to power supply class
+ *
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2004 Szabolcs Gyurko
+ * Copyright © 2003 Ian Molton <spyro@f2s.com>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ */
+
+#ifdef CONFIG_SYSFS
+
+extern void power_supply_init_attrs(struct device_type *dev_type);
+extern int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env);
+
+#else
+
+static inline void power_supply_init_attrs(struct device_type *dev_type) {}
+#define power_supply_uevent NULL
+
+#endif /* CONFIG_SYSFS */
+
+#ifdef CONFIG_LEDS_TRIGGERS
+
+extern void power_supply_update_leds(struct power_supply *psy);
+extern int power_supply_create_triggers(struct power_supply *psy);
+extern void power_supply_remove_triggers(struct power_supply *psy);
+
+#else
+
+static inline void power_supply_update_leds(struct power_supply *psy) {}
+static inline int power_supply_create_triggers(struct power_supply *psy)
+{ return 0; }
+static inline void power_supply_remove_triggers(struct power_supply *psy) {}
+
+#endif /* CONFIG_LEDS_TRIGGERS */
diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c
new file mode 100644
index 00000000..6e80146a
--- /dev/null
+++ b/drivers/power/power_supply_core.c
@@ -0,0 +1,288 @@
+/*
+ * Universal power supply monitor class
+ *
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2004 Szabolcs Gyurko
+ * Copyright © 2003 Ian Molton <spyro@f2s.com>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/power_supply.h>
+#include "power_supply.h"
+
+/* exported for the APM Power driver, APM emulation */
+struct class *power_supply_class;
+EXPORT_SYMBOL_GPL(power_supply_class);
+
+static struct device_type power_supply_dev_type;
+
+static int __power_supply_changed_work(struct device *dev, void *data)
+{
+ struct power_supply *psy = (struct power_supply *)data;
+ struct power_supply *pst = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < psy->num_supplicants; i++)
+ if (!strcmp(psy->supplied_to[i], pst->name)) {
+ if (pst->external_power_changed)
+ pst->external_power_changed(pst);
+ }
+ return 0;
+}
+
+static void power_supply_changed_work(struct work_struct *work)
+{
+ unsigned long flags;
+ struct power_supply *psy = container_of(work, struct power_supply,
+ changed_work);
+
+ dev_dbg(psy->dev, "%s\n", __func__);
+
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ if (psy->changed) {
+ psy->changed = false;
+ spin_unlock_irqrestore(&psy->changed_lock, flags);
+
+ class_for_each_device(power_supply_class, NULL, psy,
+ __power_supply_changed_work);
+
+ power_supply_update_leds(psy);
+
+ kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ }
+ if (!psy->changed)
+ wake_unlock(&psy->work_wake_lock);
+ spin_unlock_irqrestore(&psy->changed_lock, flags);
+}
+
+void power_supply_changed(struct power_supply *psy)
+{
+ unsigned long flags;
+
+ dev_dbg(psy->dev, "%s\n", __func__);
+
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ psy->changed = true;
+ wake_lock(&psy->work_wake_lock);
+ spin_unlock_irqrestore(&psy->changed_lock, flags);
+ schedule_work(&psy->changed_work);
+}
+EXPORT_SYMBOL_GPL(power_supply_changed);
+
+static int __power_supply_am_i_supplied(struct device *dev, void *data)
+{
+ union power_supply_propval ret = {0,};
+ struct power_supply *psy = (struct power_supply *)data;
+ struct power_supply *epsy = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < epsy->num_supplicants; i++) {
+ if (!strcmp(epsy->supplied_to[i], psy->name)) {
+ if (epsy->get_property(epsy,
+ POWER_SUPPLY_PROP_ONLINE, &ret))
+ continue;
+ if (ret.intval)
+ return ret.intval;
+ }
+ }
+ return 0;
+}
+
+int power_supply_am_i_supplied(struct power_supply *psy)
+{
+ int error;
+
+ error = class_for_each_device(power_supply_class, NULL, psy,
+ __power_supply_am_i_supplied);
+
+ dev_dbg(psy->dev, "%s %d\n", __func__, error);
+
+ return error;
+}
+EXPORT_SYMBOL_GPL(power_supply_am_i_supplied);
+
+static int power_supply_find_supplier(struct device *dev, void *data)
+{
+ struct power_supply *psy = (struct power_supply *)data;
+ struct power_supply *epsy = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < epsy->num_supplicants; i++)
+ if (!strcmp(epsy->supplied_to[i], psy->name))
+ return 1;
+ return 0;
+}
+
+int power_supply_get_supplier_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct power_supply *epsy;
+ struct device *dev;
+
+ dev = class_find_device(power_supply_class, NULL, psy,
+ power_supply_find_supplier);
+ if (!dev)
+ return 1;
+
+ epsy = dev_get_drvdata(dev);
+ put_device(dev);
+
+ return epsy->get_property(epsy, psp, val);
+}
+EXPORT_SYMBOL_GPL(power_supply_get_supplier_property);
+
+static int __power_supply_is_system_supplied(struct device *dev, void *data)
+{
+ union power_supply_propval ret = {0,};
+ struct power_supply *psy = dev_get_drvdata(dev);
+
+ if (psy->type != POWER_SUPPLY_TYPE_BATTERY) {
+ if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret))
+ return 0;
+ if (ret.intval)
+ return ret.intval;
+ }
+ return 0;
+}
+
+int power_supply_is_system_supplied(void)
+{
+ int error;
+
+ error = class_for_each_device(power_supply_class, NULL, NULL,
+ __power_supply_is_system_supplied);
+
+ return error;
+}
+EXPORT_SYMBOL_GPL(power_supply_is_system_supplied);
+
+int power_supply_set_battery_charged(struct power_supply *psy)
+{
+ if (psy->type == POWER_SUPPLY_TYPE_BATTERY && psy->set_charged) {
+ psy->set_charged(psy);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(power_supply_set_battery_charged);
+
+static int power_supply_match_device_by_name(struct device *dev, void *data)
+{
+ const char *name = data;
+ struct power_supply *psy = dev_get_drvdata(dev);
+
+ return strcmp(psy->name, name) == 0;
+}
+
+struct power_supply *power_supply_get_by_name(char *name)
+{
+ struct device *dev = class_find_device(power_supply_class, NULL, name,
+ power_supply_match_device_by_name);
+
+ return dev ? dev_get_drvdata(dev) : NULL;
+}
+EXPORT_SYMBOL_GPL(power_supply_get_by_name);
+
+static void power_supply_dev_release(struct device *dev)
+{
+ pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
+ kfree(dev);
+}
+
+int power_supply_register(struct device *parent, struct power_supply *psy)
+{
+ struct device *dev;
+ int rc;
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ device_initialize(dev);
+
+ dev->class = power_supply_class;
+ dev->type = &power_supply_dev_type;
+ dev->parent = parent;
+ dev->release = power_supply_dev_release;
+ dev_set_drvdata(dev, psy);
+ psy->dev = dev;
+
+ INIT_WORK(&psy->changed_work, power_supply_changed_work);
+
+ rc = kobject_set_name(&dev->kobj, "%s", psy->name);
+ if (rc)
+ goto kobject_set_name_failed;
+
+ spin_lock_init(&psy->changed_lock);
+ wake_lock_init(&psy->work_wake_lock, WAKE_LOCK_SUSPEND, "power-supply");
+
+ rc = device_add(dev);
+ if (rc)
+ goto device_add_failed;
+
+ rc = power_supply_create_triggers(psy);
+ if (rc)
+ goto create_triggers_failed;
+
+ power_supply_changed(psy);
+
+ goto success;
+
+create_triggers_failed:
+ wake_lock_destroy(&psy->work_wake_lock);
+ device_del(dev);
+kobject_set_name_failed:
+device_add_failed:
+ put_device(dev);
+success:
+ return rc;
+}
+EXPORT_SYMBOL_GPL(power_supply_register);
+
+void power_supply_unregister(struct power_supply *psy)
+{
+ cancel_work_sync(&psy->changed_work);
+ power_supply_remove_triggers(psy);
+ wake_lock_destroy(&psy->work_wake_lock);
+ device_unregister(psy->dev);
+}
+EXPORT_SYMBOL_GPL(power_supply_unregister);
+
+static int __init power_supply_class_init(void)
+{
+ power_supply_class = class_create(THIS_MODULE, "power_supply");
+
+ if (IS_ERR(power_supply_class))
+ return PTR_ERR(power_supply_class);
+
+ power_supply_class->dev_uevent = power_supply_uevent;
+ power_supply_init_attrs(&power_supply_dev_type);
+
+ return 0;
+}
+
+static void __exit power_supply_class_exit(void)
+{
+ class_destroy(power_supply_class);
+}
+
+subsys_initcall(power_supply_class_init);
+module_exit(power_supply_class_exit);
+
+MODULE_DESCRIPTION("Universal power supply monitor class");
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>, "
+ "Szabolcs Gyurko, "
+ "Anton Vorontsov <cbou@mail.ru>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c
new file mode 100644
index 00000000..da25eb94
--- /dev/null
+++ b/drivers/power/power_supply_leds.c
@@ -0,0 +1,179 @@
+/*
+ * LEDs triggers for power supply class
+ *
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2004 Szabolcs Gyurko
+ * Copyright © 2003 Ian Molton <spyro@f2s.com>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ */
+
+#include <linux/kernel.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include "power_supply.h"
+
+/* Battery specific LEDs triggers. */
+
+static void power_supply_update_bat_leds(struct power_supply *psy)
+{
+ union power_supply_propval status;
+ unsigned long delay_on = 0;
+ unsigned long delay_off = 0;
+
+ if (psy->get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
+ return;
+
+ dev_dbg(psy->dev, "%s %d\n", __func__, status.intval);
+
+ switch (status.intval) {
+ case POWER_SUPPLY_STATUS_FULL:
+ led_trigger_event(psy->charging_full_trig, LED_FULL);
+ led_trigger_event(psy->charging_trig, LED_OFF);
+ led_trigger_event(psy->full_trig, LED_FULL);
+ led_trigger_event(psy->charging_blink_full_solid_trig,
+ LED_FULL);
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ led_trigger_event(psy->charging_full_trig, LED_FULL);
+ led_trigger_event(psy->charging_trig, LED_FULL);
+ led_trigger_event(psy->full_trig, LED_OFF);
+ led_trigger_blink(psy->charging_blink_full_solid_trig,
+ &delay_on, &delay_off);
+ break;
+ default:
+ led_trigger_event(psy->charging_full_trig, LED_OFF);
+ led_trigger_event(psy->charging_trig, LED_OFF);
+ led_trigger_event(psy->full_trig, LED_OFF);
+ led_trigger_event(psy->charging_blink_full_solid_trig,
+ LED_OFF);
+ break;
+ }
+}
+
+static int power_supply_create_bat_triggers(struct power_supply *psy)
+{
+ int rc = 0;
+
+ psy->charging_full_trig_name = kasprintf(GFP_KERNEL,
+ "%s-charging-or-full", psy->name);
+ if (!psy->charging_full_trig_name)
+ goto charging_full_failed;
+
+ psy->charging_trig_name = kasprintf(GFP_KERNEL,
+ "%s-charging", psy->name);
+ if (!psy->charging_trig_name)
+ goto charging_failed;
+
+ psy->full_trig_name = kasprintf(GFP_KERNEL, "%s-full", psy->name);
+ if (!psy->full_trig_name)
+ goto full_failed;
+
+ psy->charging_blink_full_solid_trig_name = kasprintf(GFP_KERNEL,
+ "%s-charging-blink-full-solid", psy->name);
+ if (!psy->charging_blink_full_solid_trig_name)
+ goto charging_blink_full_solid_failed;
+
+ led_trigger_register_simple(psy->charging_full_trig_name,
+ &psy->charging_full_trig);
+ led_trigger_register_simple(psy->charging_trig_name,
+ &psy->charging_trig);
+ led_trigger_register_simple(psy->full_trig_name,
+ &psy->full_trig);
+ led_trigger_register_simple(psy->charging_blink_full_solid_trig_name,
+ &psy->charging_blink_full_solid_trig);
+
+ goto success;
+
+charging_blink_full_solid_failed:
+ kfree(psy->full_trig_name);
+full_failed:
+ kfree(psy->charging_trig_name);
+charging_failed:
+ kfree(psy->charging_full_trig_name);
+charging_full_failed:
+ rc = -ENOMEM;
+success:
+ return rc;
+}
+
+static void power_supply_remove_bat_triggers(struct power_supply *psy)
+{
+ led_trigger_unregister_simple(psy->charging_full_trig);
+ led_trigger_unregister_simple(psy->charging_trig);
+ led_trigger_unregister_simple(psy->full_trig);
+ led_trigger_unregister_simple(psy->charging_blink_full_solid_trig);
+ kfree(psy->charging_blink_full_solid_trig_name);
+ kfree(psy->full_trig_name);
+ kfree(psy->charging_trig_name);
+ kfree(psy->charging_full_trig_name);
+}
+
+/* Generated power specific LEDs triggers. */
+
+static void power_supply_update_gen_leds(struct power_supply *psy)
+{
+ union power_supply_propval online;
+
+ if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online))
+ return;
+
+ dev_dbg(psy->dev, "%s %d\n", __func__, online.intval);
+
+ if (online.intval)
+ led_trigger_event(psy->online_trig, LED_FULL);
+ else
+ led_trigger_event(psy->online_trig, LED_OFF);
+}
+
+static int power_supply_create_gen_triggers(struct power_supply *psy)
+{
+ int rc = 0;
+
+ psy->online_trig_name = kasprintf(GFP_KERNEL, "%s-online", psy->name);
+ if (!psy->online_trig_name)
+ goto online_failed;
+
+ led_trigger_register_simple(psy->online_trig_name, &psy->online_trig);
+
+ goto success;
+
+online_failed:
+ rc = -ENOMEM;
+success:
+ return rc;
+}
+
+static void power_supply_remove_gen_triggers(struct power_supply *psy)
+{
+ led_trigger_unregister_simple(psy->online_trig);
+ kfree(psy->online_trig_name);
+}
+
+/* Choice what triggers to create&update. */
+
+void power_supply_update_leds(struct power_supply *psy)
+{
+ if (psy->type == POWER_SUPPLY_TYPE_BATTERY)
+ power_supply_update_bat_leds(psy);
+ else
+ power_supply_update_gen_leds(psy);
+}
+
+int power_supply_create_triggers(struct power_supply *psy)
+{
+ if (psy->type == POWER_SUPPLY_TYPE_BATTERY)
+ return power_supply_create_bat_triggers(psy);
+ return power_supply_create_gen_triggers(psy);
+}
+
+void power_supply_remove_triggers(struct power_supply *psy)
+{
+ if (psy->type == POWER_SUPPLY_TYPE_BATTERY)
+ power_supply_remove_bat_triggers(psy);
+ else
+ power_supply_remove_gen_triggers(psy);
+}
diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c
new file mode 100644
index 00000000..605514af
--- /dev/null
+++ b/drivers/power/power_supply_sysfs.c
@@ -0,0 +1,305 @@
+/*
+ * Sysfs interface for the universal power supply monitor class
+ *
+ * Copyright © 2007 David Woodhouse <dwmw2@infradead.org>
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2004 Szabolcs Gyurko
+ * Copyright © 2003 Ian Molton <spyro@f2s.com>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ */
+
+#include <linux/ctype.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include "power_supply.h"
+
+/*
+ * This is because the name "current" breaks the device attr macro.
+ * The "current" word resolves to "(get_current())" so instead of
+ * "current" "(get_current())" appears in the sysfs.
+ *
+ * The source of this definition is the device.h which calls __ATTR
+ * macro in sysfs.h which calls the __stringify macro.
+ *
+ * Only modification that the name is not tried to be resolved
+ * (as a macro let's say).
+ */
+
+#define POWER_SUPPLY_ATTR(_name) \
+{ \
+ .attr = { .name = #_name }, \
+ .show = power_supply_show_property, \
+ .store = power_supply_store_property, \
+}
+
+static struct device_attribute power_supply_attrs[];
+
+static ssize_t power_supply_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf) {
+ static char *type_text[] = {
+ "Battery", "UPS", "Mains", "USB",
+ "USB_DCP", "USB_CDP", "USB_ACA"
+ };
+ static char *status_text[] = {
+ "Unknown", "Charging", "Discharging", "Not charging", "Full"
+ };
+ static char *charge_type[] = {
+ "Unknown", "N/A", "Trickle", "Fast"
+ };
+ static char *health_text[] = {
+ "Unknown", "Good", "Overheat", "Dead", "Over voltage",
+ "Unspecified failure", "Cold",
+ };
+ static char *technology_text[] = {
+ "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
+ "LiMn"
+ };
+ static char *capacity_level_text[] = {
+ "Unknown", "Critical", "Low", "Normal", "High", "Full"
+ };
+ ssize_t ret = 0;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ const ptrdiff_t off = attr - power_supply_attrs;
+ union power_supply_propval value;
+
+ if (off == POWER_SUPPLY_PROP_TYPE)
+ value.intval = psy->type;
+ else
+ ret = psy->get_property(psy, off, &value);
+
+ if (ret < 0) {
+ if (ret == -ENODATA)
+ dev_dbg(dev, "driver has no data for `%s' property\n",
+ attr->attr.name);
+ else if (ret != -ENODEV)
+ dev_err(dev, "driver failed to report `%s' property\n",
+ attr->attr.name);
+ return ret;
+ }
+
+ if (off == POWER_SUPPLY_PROP_STATUS)
+ return sprintf(buf, "%s\n", status_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE)
+ return sprintf(buf, "%s\n", charge_type[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_HEALTH)
+ return sprintf(buf, "%s\n", health_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_TECHNOLOGY)
+ return sprintf(buf, "%s\n", technology_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL)
+ return sprintf(buf, "%s\n", capacity_level_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_TYPE)
+ return sprintf(buf, "%s\n", type_text[value.intval]);
+ else if (off >= POWER_SUPPLY_PROP_MODEL_NAME)
+ return sprintf(buf, "%s\n", value.strval);
+
+ return sprintf(buf, "%d\n", value.intval);
+}
+
+static ssize_t power_supply_store_property(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count) {
+ ssize_t ret;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ const ptrdiff_t off = attr - power_supply_attrs;
+ union power_supply_propval value;
+ long long_val;
+
+ /* TODO: support other types than int */
+ ret = strict_strtol(buf, 10, &long_val);
+ if (ret < 0)
+ return ret;
+
+ value.intval = long_val;
+
+ ret = psy->set_property(psy, off, &value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+/* Must be in the same order as POWER_SUPPLY_PROP_* */
+static struct device_attribute power_supply_attrs[] = {
+ /* Properties of type `int' */
+ POWER_SUPPLY_ATTR(status),
+ POWER_SUPPLY_ATTR(charge_type),
+ POWER_SUPPLY_ATTR(health),
+ POWER_SUPPLY_ATTR(present),
+ POWER_SUPPLY_ATTR(online),
+ POWER_SUPPLY_ATTR(technology),
+ POWER_SUPPLY_ATTR(cycle_count),
+ POWER_SUPPLY_ATTR(voltage_max),
+ POWER_SUPPLY_ATTR(voltage_min),
+ POWER_SUPPLY_ATTR(voltage_max_design),
+ POWER_SUPPLY_ATTR(voltage_min_design),
+ POWER_SUPPLY_ATTR(voltage_now),
+ POWER_SUPPLY_ATTR(voltage_avg),
+ POWER_SUPPLY_ATTR(current_max),
+ POWER_SUPPLY_ATTR(current_now),
+ POWER_SUPPLY_ATTR(current_avg),
+ POWER_SUPPLY_ATTR(power_now),
+ POWER_SUPPLY_ATTR(power_avg),
+ POWER_SUPPLY_ATTR(charge_full_design),
+ POWER_SUPPLY_ATTR(charge_empty_design),
+ POWER_SUPPLY_ATTR(charge_full),
+ POWER_SUPPLY_ATTR(charge_empty),
+ POWER_SUPPLY_ATTR(charge_now),
+ POWER_SUPPLY_ATTR(charge_avg),
+ POWER_SUPPLY_ATTR(charge_counter),
+ POWER_SUPPLY_ATTR(energy_full_design),
+ POWER_SUPPLY_ATTR(energy_empty_design),
+ POWER_SUPPLY_ATTR(energy_full),
+ POWER_SUPPLY_ATTR(energy_empty),
+ POWER_SUPPLY_ATTR(energy_now),
+ POWER_SUPPLY_ATTR(energy_avg),
+ POWER_SUPPLY_ATTR(capacity),
+ POWER_SUPPLY_ATTR(capacity_level),
+ POWER_SUPPLY_ATTR(temp),
+ POWER_SUPPLY_ATTR(temp_ambient),
+ POWER_SUPPLY_ATTR(time_to_empty_now),
+ POWER_SUPPLY_ATTR(time_to_empty_avg),
+ POWER_SUPPLY_ATTR(time_to_full_now),
+ POWER_SUPPLY_ATTR(time_to_full_avg),
+ POWER_SUPPLY_ATTR(type),
+ /* Properties of type `const char *' */
+ POWER_SUPPLY_ATTR(model_name),
+ POWER_SUPPLY_ATTR(manufacturer),
+ POWER_SUPPLY_ATTR(serial_number),
+};
+
+static struct attribute *
+__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1];
+
+static mode_t power_supply_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int attrno)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = dev_get_drvdata(dev);
+ mode_t mode = S_IRUSR | S_IRGRP | S_IROTH;
+ int i;
+
+ if (attrno == POWER_SUPPLY_PROP_TYPE)
+ return mode;
+
+ for (i = 0; i < psy->num_properties; i++) {
+ int property = psy->properties[i];
+
+ if (property == attrno) {
+ if (psy->property_is_writeable &&
+ psy->property_is_writeable(psy, property) > 0)
+ mode |= S_IWUSR;
+
+ return mode;
+ }
+ }
+
+ return 0;
+}
+
+static struct attribute_group power_supply_attr_group = {
+ .attrs = __power_supply_attrs,
+ .is_visible = power_supply_attr_is_visible,
+};
+
+static const struct attribute_group *power_supply_attr_groups[] = {
+ &power_supply_attr_group,
+ NULL,
+};
+
+void power_supply_init_attrs(struct device_type *dev_type)
+{
+ int i;
+
+ dev_type->groups = power_supply_attr_groups;
+
+ for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++)
+ __power_supply_attrs[i] = &power_supply_attrs[i].attr;
+}
+
+static char *kstruprdup(const char *str, gfp_t gfp)
+{
+ char *ret, *ustr;
+
+ ustr = ret = kmalloc(strlen(str) + 1, gfp);
+
+ if (!ret)
+ return NULL;
+
+ while (*str)
+ *ustr++ = toupper(*str++);
+
+ *ustr = 0;
+
+ return ret;
+}
+
+int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ int ret = 0, j;
+ char *prop_buf;
+ char *attrname;
+
+ dev_dbg(dev, "uevent\n");
+
+ if (!psy || !psy->dev) {
+ dev_dbg(dev, "No power supply yet\n");
+ return ret;
+ }
+
+ dev_dbg(dev, "POWER_SUPPLY_NAME=%s\n", psy->name);
+
+ ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->name);
+ if (ret)
+ return ret;
+
+ prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
+ if (!prop_buf)
+ return -ENOMEM;
+
+ for (j = 0; j < psy->num_properties; j++) {
+ struct device_attribute *attr;
+ char *line;
+
+ attr = &power_supply_attrs[psy->properties[j]];
+
+ ret = power_supply_show_property(dev, attr, prop_buf);
+ if (ret == -ENODEV || ret == -ENODATA) {
+ /* When a battery is absent, we expect -ENODEV. Don't abort;
+ send the uevent with at least the the PRESENT=0 property */
+ ret = 0;
+ continue;
+ }
+
+ if (ret < 0)
+ goto out;
+
+ line = strchr(prop_buf, '\n');
+ if (line)
+ *line = 0;
+
+ attrname = kstruprdup(attr->attr.name, GFP_KERNEL);
+ if (!attrname) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf);
+
+ ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf);
+ kfree(attrname);
+ if (ret)
+ goto out;
+ }
+
+out:
+ free_page((unsigned long)prop_buf);
+
+ return ret;
+}
diff --git a/drivers/power/ricoh619-battery.c b/drivers/power/ricoh619-battery.c
new file mode 100755
index 00000000..0185771e
--- /dev/null
+++ b/drivers/power/ricoh619-battery.c
@@ -0,0 +1,6361 @@
+/*
+ * drivers/power/ricoh619-battery.c
+ *
+ * Charger driver for RICOH R5T619 power management chip.
+ *
+ * Copyright (C) 2012-2014 RICOH COMPANY,LTD
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#define RICOH61x_BATTERY_VERSION "RICOH61x_BATTERY_VERSION: 2014.02.21 V3.1.0.0-Solution1 2015/02/09"
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/ricoh619.h>
+#include <linux/power/ricoh619_battery.h>
+#include <linux/power/ricoh61x_battery_init.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/rtc.h>
+#include <asm-generic/rtc.h>
+
+#include <linux/power/ricoh619_standby.h>
+
+#include "../../../arch/arm/mach-mx6/ntx_hwconfig.h"
+extern volatile NTX_HWCONFIG *gptHWCFG;
+
+/* define for function */
+#define ENABLE_FUEL_GAUGE_FUNCTION
+#define ENABLE_LOW_BATTERY_DETECTION
+#define ENABLE_FACTORY_MODE
+#define DISABLE_CHARGER_TIMER
+/* #define ENABLE_FG_KEEP_ON_MODE */
+#define ENABLE_OCV_TABLE_CALIB
+/* #define ENABLE_MASKING_INTERRUPT_IN_SLEEP */
+
+#define ENABLE_BATTERY_TEMP_DETECTION
+#define LOW_BATTERY_TEMP_VOL 1824 // 0 degree 269.96K, 2.5V * 270K / (270K+100K)
+#define HIGH_BATTERY_TEMP_VOL 577 // 60 degree 30.546K, 2.5V * 30K / (30K+100K)
+
+#define _RICOH619_DEBUG_
+#define LTS_DEBUG
+//#define STANDBY_MODE_DEBUG
+//#define CHANGE_FL_MODE_DEBUG
+
+/* FG setting */
+#define RICOH61x_REL1_SEL_VALUE 64
+#define RICOH61x_REL2_SEL_VALUE 0
+
+enum int_type {
+ SYS_INT = 0x01,
+ DCDC_INT = 0x02,
+ ADC_INT = 0x08,
+ GPIO_INT = 0x10,
+ CHG_INT = 0x40,
+};
+
+#ifdef ENABLE_FUEL_GAUGE_FUNCTION
+/* define for FG delayed time */
+#define RICOH61x_MONITOR_START_TIME 15
+#define RICOH61x_FG_RESET_TIME 6
+#define RICOH61x_MAIN_START_TIME 2
+#define RICOH61x_FG_STABLE_TIME 120
+#define RICOH61x_DISP_CHG_UPDATE_TIME 10
+#define RICOH61x_DISPLAY_UPDATE_TIME 29
+#define RICOH61x_LOW_VOL_DOWN_TIME 60 //10
+#define RICOH61x_CHARGE_MONITOR_TIME 19
+#define RICOH61x_CHARGE_RESUME_TIME 1
+#define RICOH61x_CHARGE_CALC_TIME 1
+#define RICOH61x_JEITA_UPDATE_TIME 60
+#define RICOH61x_DELAY_TIME 40 /* 120 */
+/* define for FG parameter */
+#define RICOH61x_MAX_RESET_SOC_DIFF 5
+#define RICOH61x_GET_CHARGE_NUM 10
+#define RICOH61x_UPDATE_COUNT_DISP 4
+#define RICOH61x_UPDATE_COUNT_FULL 4
+#define RICOH61x_UPDATE_COUNT_FULL_RESET 4
+#define RICOH61x_CHARGE_UPDATE_TIME 3
+#define RE_CAP_GO_DOWN 10 /* 40 */
+#define RICOH61x_ENTER_LOW_VOL 70
+#define RICOH61x_TAH_SEL2 5
+#define RICOH61x_TAL_SEL2 6
+
+#define RICOH61x_OCV_OFFSET_BOUND 3
+#define RICOH61x_OCV_OFFSET_RATIO 2
+
+#define RICOH61x_ENTER_FULL_STATE_OCV 9
+#define RICOH61x_ENTER_FULL_STATE_DSOC 85 /* 90 */
+
+#define RICOH61x_FL_LEVEL_DEF 70 // 70%
+#define RICOH61x_FL_CURRENT_DEF 29593 // 29.593mA(70%)
+#define RICOH61x_IDLE_CURRENT_DEF 20000 // 20mA
+#define RICOH61x_SUS_CURRENT_DEF 3000 // 3mA
+#define RICOH61x_SUS_CURRENT_THRESH 20000 // 20mA
+#define RICOH61x_HIBER_CURRENT_DEF 800 // 0.8mA
+#define RICOH61x_FL_CURRENT_LIMIT 150000 // 150mA
+#define RICOH61x_SLEEP_CURRENT_LIMIT 50000 // 50mA
+
+#define ORIGINAL 0
+#define USING 1
+
+
+/* define for FG status */
+enum {
+ RICOH61x_SOCA_START,
+ RICOH61x_SOCA_UNSTABLE,
+ RICOH61x_SOCA_FG_RESET,
+ RICOH61x_SOCA_DISP,
+ RICOH61x_SOCA_STABLE,
+ RICOH61x_SOCA_ZERO,
+ RICOH61x_SOCA_FULL,
+ RICOH61x_SOCA_LOW_VOL,
+};
+
+/* table of dividing charge current */
+#define RICOH61x_IBAT_TABLE_NUM 16
+static int ibat_table[RICOH61x_IBAT_TABLE_NUM] /* 85% - 100% */
+ = {370, 348, 326, 304, 282, 260, 238, 216, 194, 172, 150, 128, 107, 87, 68, 50};
+
+#endif
+
+#ifdef ENABLE_LOW_BATTERY_DETECTION
+#define LOW_BATTERY_DETECTION_TIME 0 //10
+#endif
+
+struct ricoh61x_soca_info {
+ int Rbat;
+ int n_cap;
+ int ocv_table_def[11];
+ int ocv_table[11];
+ int ocv_table_low[11];
+ uint8_t battery_init_para_original[32];
+ int soc; /* Latest FG SOC value */
+ int displayed_soc;
+ int suspend_soc;
+ int suspend_cc;
+ int suspend_rsoc;
+ bool suspend_full_flg;
+ int status; /* SOCA status 0: Not initial; 5: Finished */
+ int stable_count;
+ int chg_status; /* chg_status */
+ int soc_delta; /* soc delta for status3(DISP) */
+ int cc_delta;
+ int cc_cap_offset;
+ long sus_cc_cap_offset;
+ int last_soc;
+ int last_displayed_soc;
+ int last_cc_rrf0;
+ int last_cc_delta_cap;
+ long last_cc_delta_cap_mas;
+ long temp_cc_delta_cap_mas;
+ int temp_cc_delta_cap;
+ int ready_fg;
+ int reset_count;
+ int reset_soc[3];
+ int dischg_state;
+ int Vbat[RICOH61x_GET_CHARGE_NUM];
+ int Vsys[RICOH61x_GET_CHARGE_NUM];
+ int Ibat[RICOH61x_GET_CHARGE_NUM];
+ int Vbat_ave;
+ int Vbat_old;
+ int Vsys_ave;
+ int Ibat_ave;
+ int chg_count;
+ int full_reset_count;
+ int soc_full;
+ int fc_cap;
+ /* for LOW VOL state */
+ int hurry_up_flg;
+ int zero_flg;
+ int re_cap_old;
+
+ int cutoff_ocv;
+ int Rsys;
+ int target_vsys;
+ int target_ibat;
+ int jt_limit;
+ int OCV100_min;
+ int OCV100_max;
+ int R_low;
+ int rsoc_ready_flag;
+ int init_pswr;
+ int last_soc_full;
+ int rsoc_limit;
+ int critical_low_flag;
+
+ int store_fl_current;
+ int store_slp_state;
+ int store_sus_current;
+ int store_hiber_current;
+};
+
+static int critical_low_flag = 0;
+
+struct ricoh61x_battery_info {
+ struct device *dev;
+ struct power_supply battery;
+ struct delayed_work monitor_work;
+ struct delayed_work displayed_work;
+ struct delayed_work charge_stable_work;
+ struct delayed_work changed_work;
+#ifdef ENABLE_LOW_BATTERY_DETECTION
+ struct delayed_work low_battery_work;
+#endif
+#ifdef ENABLE_BATTERY_TEMP_DETECTION
+ struct delayed_work battery_temp_work;
+#endif
+ struct delayed_work charge_monitor_work;
+ struct delayed_work get_charge_work;
+ struct delayed_work jeita_work;
+
+ struct work_struct irq_work; /* for Charging & VADP/VUSB */
+
+ struct workqueue_struct *monitor_wqueue;
+ struct workqueue_struct *workqueue; /* for Charging & VUSB/VADP */
+
+#ifdef ENABLE_FACTORY_MODE
+ struct delayed_work factory_mode_work;
+ struct workqueue_struct *factory_mode_wqueue;
+#endif
+
+ struct mutex lock;
+ unsigned long monitor_time;
+ int adc_vdd_mv;
+ int multiple;
+ int alarm_vol_mv;
+ int status;
+ int min_voltage;
+ int max_voltage;
+ int cur_voltage;
+ int capacity;
+ int battery_temp;
+ int time_to_empty;
+ int time_to_full;
+ int chg_ctr;
+ int chg_stat1;
+ unsigned present:1;
+ u16 delay;
+ struct ricoh61x_soca_info *soca;
+ int first_pwon;
+ bool entry_factory_mode;
+ int ch_vfchg;
+ int ch_vrchg;
+ int ch_vbatovset;
+ int ch_ichg;
+ int ch_ilim_adp;
+ int ch_ilim_usb;
+ int ch_icchg;
+ int fg_target_vsys;
+ int fg_target_ibat;
+ int fg_poff_vbat;
+ int jt_en;
+ int jt_hw_sw;
+ int jt_temp_h;
+ int jt_temp_l;
+ int jt_vfchg_h;
+ int jt_vfchg_l;
+ int jt_ichg_h;
+ int jt_ichg_l;
+ bool suspend_state;
+ bool stop_disp;
+ unsigned long sleepEntryTime;
+ unsigned long sleepExitTime;
+
+ int num;
+ };
+
+int g_full_flag;
+int charger_irq;
+int g_soc;
+int g_fg_on_mode;
+
+#ifdef STANDBY_MODE_DEBUG
+int multiple_sleep_mode;
+#endif
+
+/*This is for full state*/
+static int BatteryTableFlageDef=0;
+static int BatteryTypeDef=0;
+static int Battery_Type(void)
+{
+ return BatteryTypeDef;
+}
+
+static int Battery_Table(void)
+{
+ return BatteryTableFlageDef;
+}
+
+static void ricoh61x_battery_work(struct work_struct *work)
+{
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, monitor_work.work);
+
+// printk(KERN_INFO "PMU: %s\n", __func__);
+ power_supply_changed(&info->battery);
+ queue_delayed_work(info->monitor_wqueue, &info->monitor_work,
+ info->monitor_time);
+}
+
+#define RTC_SEC_REG 0xA0
+static void get_current_time(struct ricoh61x_battery_info *info,
+ unsigned long *seconds)
+{
+ struct rtc_time tm;
+ u8 buff[7];
+ int err;
+ int cent_flag;
+
+ /* get_rtc_time(&tm); */
+ err = ricoh61x_bulk_reads(info->dev->parent, RTC_SEC_REG, sizeof(buff), buff);
+ if (err < 0) {
+ dev_err(info->dev, "PMU: %s *** failed to read time *****\n", __func__);
+ return;
+ }
+ if (buff[5] & 0x80)
+ cent_flag = 1;
+ else
+ cent_flag = 0;
+
+ tm.tm_sec = bcd2bin(buff[0] & 0x7f);
+ tm.tm_min = bcd2bin(buff[1] & 0x7f);
+ tm.tm_hour = bcd2bin(buff[2] & 0x3f); /* 24h */
+ tm.tm_wday = bcd2bin(buff[3] & 0x07);
+ tm.tm_mday = bcd2bin(buff[4] & 0x3f);
+ tm.tm_mon = bcd2bin(buff[5] & 0x1f) - 1;/* back to system 0-11 */
+ tm.tm_year = bcd2bin(buff[6]) + 100 * cent_flag;
+
+ dev_dbg(info->dev, "rtc-time : Mon/ Day/ Year H:M:S\n");
+ dev_dbg(info->dev, " : %d/%d/%d %d:%d:%d\n",
+ (tm.tm_mon+1), tm.tm_mday, (tm.tm_year + 1900),
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+ rtc_tm_to_time(&tm, seconds);
+}
+
+/**
+* Enable test register of Bank1
+*
+* info : battery info
+*
+* return value :
+* true : Removing Protect correctly
+* false : not Removing protect
+*/
+static bool Enable_Test_Register(struct ricoh61x_battery_info *info){
+ int ret;
+ uint8_t val = 0x01;
+ uint8_t val_backUp;
+ uint8_t val2;
+
+ //Remove protect of test register
+ ret = ricoh61x_write_bank1(info->dev->parent, BAT_TEST_EN_REG, 0xaa);
+ ret += ricoh61x_write_bank1(info->dev->parent, BAT_TEST_EN_REG, 0x55);
+ ret += ricoh61x_write_bank1(info->dev->parent, BAT_TEST_EN_REG, 0xaa);
+ ret += ricoh61x_write_bank1(info->dev->parent, BAT_TEST_EN_REG, 0x55);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing BAT_TEST_EN_REG\n");
+ return false;
+ }
+
+ //Check protect is removed or not
+ ret = ricoh61x_read_bank1(info->dev->parent, BAT_ADD1B2_REG, &val_backUp);
+ ret += ricoh61x_write_bank1(info->dev->parent, BAT_ADD1B2_REG, val);
+ ret += ricoh61x_read_bank1(info->dev->parent, BAT_ADD1B2_REG, &val2);
+ ret += ricoh61x_write_bank1(info->dev->parent, BAT_ADD1B2_REG, val_backUp);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing BAT_ADD1B2_REG\n");
+ return false;
+ }
+
+ if(val == val2){
+ return true;
+ } else {
+ return false;
+ }
+
+ return false;
+}
+
+/**
+* check can write correctly or not
+*
+* regAddr : register address
+* targetValue : target value for write
+* bank_num : bank number
+*
+* return : ture or false
+*/
+static bool write_and_check_read_back(struct ricoh61x_battery_info *info, u8 regAddr, uint8_t targetValue, int bank_num)
+{
+ int ret;
+ uint8_t val;
+
+ //Check protect is removed or not
+ if(bank_num == 0){
+ ret = ricoh61x_write(info->dev->parent, regAddr, targetValue);
+ ret += ricoh61x_read(info->dev->parent, regAddr, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing in 0x%d\n",regAddr);
+ return false;
+ }
+ } else {
+ ret = ricoh61x_write_bank1(info->dev->parent, regAddr, targetValue);
+ ret += ricoh61x_read_bank1(info->dev->parent, regAddr, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing in 0x%d\n",regAddr);
+ return false;
+ }
+ }
+
+ if(targetValue == val){
+ return true;
+ } else {
+ return false;
+ }
+}
+/**
+* get stored time from register
+* 0xB2 : bit 0 ~ 7
+* 0xB3 : bit 8 ~ 15
+* 0xDD : bit 16 ~ 23
+*
+* info : battery info
+*
+* return sored time unit is hour
+*/
+static unsigned long get_storedTime_from_register(struct ricoh61x_battery_info *info)
+{
+ unsigned long hour = 0;
+ uint8_t val;
+ int ret;
+
+ ret = ricoh61x_read_bank1(info->dev->parent, BAT_ADD1B2_REG, &val);
+ hour += val;
+ ret = ricoh61x_read_bank1(info->dev->parent, BAT_ADD1B3_REG, &val);
+ hour += val << 8;
+ ret = ricoh61x_read_bank1(info->dev->parent, BAT_ADD1DD_REG, &val);
+ hour += val << 16;
+
+
+ return hour;
+}
+
+
+/**
+* Set current RTC time to Register. unit is hour
+* 0xB2 : bit 0 ~ 7
+* 0xB3 : bit 8 ~ 15
+* 0xDD : bit 16 ~ 23
+*
+* info : battery info
+*
+* return
+*/
+static void set_current_time2register(struct ricoh61x_battery_info *info)
+{
+ unsigned long hour;
+ unsigned long seconds;
+ int loop_counter = 0;
+ bool canWriteFlag = true;
+ uint8_t val;
+
+ //
+ get_current_time(info, &seconds);
+ hour = seconds / 3600;
+ printk("PMU : %s : second is %lu, hour is %lu\n",__func__, seconds, hour);
+
+ do{
+ val = hour & 0xff;
+ canWriteFlag &= write_and_check_read_back(info, BAT_ADD1B2_REG, val, 1);
+ val = (hour >> 8) & 0xff;
+ canWriteFlag &= write_and_check_read_back(info, BAT_ADD1B3_REG, val, 1);
+ val = (hour >> 16) & 0xff;
+ canWriteFlag &= write_and_check_read_back(info, BAT_ADD1DD_REG, val, 1);
+
+ if(canWriteFlag != true){
+ Enable_Test_Register(info);
+ loop_counter++;
+ }
+
+ //read back
+ if(loop_counter > 5){
+ canWriteFlag = true;
+ }
+ }while(canWriteFlag == false);
+
+ return;
+}
+
+#ifdef ENABLE_FUEL_GAUGE_FUNCTION
+static int measure_vbatt_FG(struct ricoh61x_battery_info *info, int *data);
+static int measure_Ibatt_FG(struct ricoh61x_battery_info *info, int *data);
+static int calc_capacity(struct ricoh61x_battery_info *info);
+static int calc_capacity_2(struct ricoh61x_battery_info *info);
+static int get_OCV_init_Data(struct ricoh61x_battery_info *info, int index, int table_num);
+static int get_OCV_voltage(struct ricoh61x_battery_info *info, int index, int table_num);
+static int get_check_fuel_gauge_reg(struct ricoh61x_battery_info *info,
+ int Reg_h, int Reg_l, int enable_bit);
+static int calc_capacity_in_period(struct ricoh61x_battery_info *info,
+ int *cc_cap, long *cc_cap_mas, bool *is_charging, int cc_rst);
+static int get_charge_priority(struct ricoh61x_battery_info *info, bool *data);
+static int set_charge_priority(struct ricoh61x_battery_info *info, bool *data);
+static int get_power_supply_status(struct ricoh61x_battery_info *info);
+static int get_power_supply_Android_status(struct ricoh61x_battery_info *info);
+static int measure_vsys_ADC(struct ricoh61x_battery_info *info, int *data);
+static int Calc_Linear_Interpolation(int x0, int y0, int x1, int y1, int y);
+static int get_battery_temp(struct ricoh61x_battery_info *info);
+static int get_battery_temp_2(struct ricoh61x_battery_info *info);
+static int check_jeita_status(struct ricoh61x_battery_info *info, bool *is_jeita_updated);
+static void ricoh61x_scaling_OCV_table(struct ricoh61x_battery_info *info, int cutoff_vol, int full_vol, int *start_per, int *end_per);
+static int ricoh61x_Check_OCV_Offset(struct ricoh61x_battery_info *info);
+static void mainFlowOfLowVoltage(struct ricoh61x_battery_info *info);
+static void initSettingOfLowVoltage(struct ricoh61x_battery_info *info);
+static int getCapFromOriTable(struct ricoh61x_battery_info *info, int voltage, int currentvalue, int resvalue);
+static int getCapFromOriTable_U10per(struct ricoh61x_battery_info *info, int voltage, int currentvalue, int resvalue);
+
+static int calc_ocv(struct ricoh61x_battery_info *info)
+{
+ int Vbat = 0;
+ int Ibat = 0;
+ int ret;
+ int ocv;
+
+ ret = measure_vbatt_FG(info, &Vbat);
+ ret = measure_Ibatt_FG(info, &Ibat);
+
+ ocv = Vbat - Ibat * info->soca->Rbat;
+
+ return ocv;
+}
+
+static int calc_soc_on_ocv(struct ricoh61x_battery_info *info, int Ocv)
+{
+ int i;
+ int capacity=0;
+
+ /* capacity is 0.01% unit */
+ if (info->soca->ocv_table[0] >= Ocv) {
+ capacity = 1 * 100;
+ } else if (info->soca->ocv_table[10] <= Ocv) {
+ capacity = 100 * 100;
+ } else {
+ for (i = 1; i < 11; i++) {
+ if (info->soca->ocv_table[i] >= Ocv) {
+ /* unit is 0.01% */
+ capacity = Calc_Linear_Interpolation(
+ (i-1)*10 * 100, info->soca->ocv_table[i-1], i*10 * 100,
+ info->soca->ocv_table[i], Ocv);
+ if(capacity < 100){
+ capacity = 100;
+ }
+ break;
+ }
+ }
+ }
+
+ printk(KERN_INFO "PMU: %s capacity(%d)\n",
+ __func__, capacity);
+
+ return capacity;
+}
+
+static int set_Rlow(struct ricoh61x_battery_info *info)
+{
+ int err;
+ int Rbat_low_max;
+ uint8_t val;
+ int Vocv;
+ int temp;
+
+ if (info->soca->Rbat == 0)
+ info->soca->Rbat = get_OCV_init_Data(info, 12, USING) * 1000 / 512
+ * 5000 / 4095;
+
+ Vocv = calc_ocv(info);
+ Rbat_low_max = info->soca->Rbat * 1.5;
+
+ if (Vocv < get_OCV_voltage(info,3, USING))
+ {
+ info->soca->R_low = Calc_Linear_Interpolation(info->soca->Rbat,get_OCV_voltage(info,3,USING),
+ Rbat_low_max, get_OCV_voltage(info,0,USING), Vocv);
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU: Modify RBAT from %d to %d ", info->soca->Rbat, info->soca->R_low);
+#endif
+ temp = info->soca->R_low *4095/5000*512/1000;
+
+ val = temp >> 8;
+ err = ricoh61x_write_bank1(info->dev->parent, 0xD4, val);
+ if (err < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ return err;
+ }
+
+ val = info->soca->R_low & 0xff;
+ err = ricoh61x_write_bank1(info->dev->parent, 0xD5, val);
+ if (err < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ return err;
+ }
+ }
+ else info->soca->R_low = 0;
+
+
+ return err;
+}
+
+static int Set_back_ocv_table(struct ricoh61x_battery_info *info)
+{
+ int err;
+ uint8_t val;
+ int temp;
+ int i;
+ uint8_t debug_disp[22];
+
+ /* Modify back ocv table */
+
+ if (0 != info->soca->ocv_table_low[0])
+ {
+ for (i = 0 ; i < 11; i++){
+ battery_init_para[info->num][i*2 + 1] = info->soca->ocv_table_low[i];
+ battery_init_para[info->num][i*2] = info->soca->ocv_table_low[i] >> 8;
+ }
+ err = ricoh61x_clr_bits(info->dev->parent, FG_CTRL_REG, 0x01);
+
+ err = ricoh61x_bulk_writes_bank1(info->dev->parent,
+ BAT_INIT_TOP_REG, 22, battery_init_para[info->num]);
+
+ err = ricoh61x_set_bits(info->dev->parent, FG_CTRL_REG, 0x01);
+
+ /* debug comment start*/
+ err = ricoh61x_bulk_reads_bank1(info->dev->parent,
+ BAT_INIT_TOP_REG, 22, debug_disp);
+ for (i = 0; i < 11; i++){
+ printk("PMU : %s : after OCV table %d 0x%x\n",__func__, i * 10, (debug_disp[i*2] << 8 | debug_disp[i*2+1]));
+ }
+ /* end */
+ /* clear table*/
+ for(i = 0; i < 11; i++)
+ {
+ info->soca->ocv_table_low[i] = 0;
+ }
+ }
+
+ /* Modify back Rbat */
+ if (0!=info->soca->R_low)
+ {
+ printk("PMU: Modify back RBAT from %d to %d ", info->soca->R_low,info->soca->Rbat);
+ temp = info->soca->Rbat*4095/5000*512/1000;
+
+ val = temp >> 8;
+ err = ricoh61x_write_bank1(info->dev->parent, 0xD4, val);
+ if (err < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ return err;
+ }
+
+ val = info->soca->R_low & 0xff;
+ err = ricoh61x_write_bank1(info->dev->parent, 0xD5, val);
+ if (err < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ return err;
+ }
+
+ info->soca->R_low = 0;
+ }
+ return 0;
+}
+
+/**
+**/
+static int ricoh61x_Check_OCV_Offset(struct ricoh61x_battery_info *info)
+{
+ int ocv_table[11]; // HEX value
+ int i;
+ int temp;
+ int ret;
+ uint8_t debug_disp[22];
+ uint8_t val = 0;
+
+ printk("PMU : %s : calc ocv %d get OCV %d\n",__func__,calc_ocv(info),get_OCV_voltage(info, RICOH61x_OCV_OFFSET_BOUND,USING));
+
+ /* check adp/usb status */
+ ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ return 0;
+ }
+
+ val = (val & 0xC0) >> 6;
+
+ if (val != 0){ /* connect adp or usb */
+ if (calc_ocv(info) < get_OCV_voltage(info, RICOH61x_OCV_OFFSET_BOUND,USING) )
+ {
+ if(0 == info->soca->ocv_table_low[0]){
+ for (i = 0 ; i < 11; i++){
+ ocv_table[i] = (battery_init_para[info->num][i*2]<<8) | (battery_init_para[info->num][i*2+1]);
+ printk("PMU : %s : OCV table %d 0x%x\n",__func__,i * 10, ocv_table[i]);
+ info->soca->ocv_table_low[i] = ocv_table[i];
+ }
+
+ for (i = 0 ; i < 11; i++){
+ temp = ocv_table[i] * (100 + RICOH61x_OCV_OFFSET_RATIO) / 100;
+
+ battery_init_para[info->num][i*2 + 1] = temp;
+ battery_init_para[info->num][i*2] = temp >> 8;
+ }
+ ret = ricoh61x_clr_bits(info->dev->parent, FG_CTRL_REG, 0x01);
+
+ ret = ricoh61x_bulk_writes_bank1(info->dev->parent,
+ BAT_INIT_TOP_REG, 22, battery_init_para[info->num]);
+
+ ret = ricoh61x_set_bits(info->dev->parent, FG_CTRL_REG, 0x01);
+
+ /* debug comment start*/
+ ret = ricoh61x_bulk_reads_bank1(info->dev->parent,
+ BAT_INIT_TOP_REG, 22, debug_disp);
+ for (i = 0; i < 11; i++){
+ printk("PMU : %s : after OCV table %d 0x%x\n",__func__, i * 10, (debug_disp[i*2] << 8 | debug_disp[i*2+1]));
+ }
+ /* end */
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int reset_FG_process(struct ricoh61x_battery_info *info)
+{
+ int err;
+
+ //err = set_Rlow(info);
+ //err = ricoh61x_Check_OCV_Offset(info);
+ err = ricoh61x_write(info->dev->parent,
+ FG_CTRL_REG, 0x51);
+ info->soca->ready_fg = 0;
+ info->soca->rsoc_ready_flag = 1;
+
+ return err;
+}
+
+
+static int check_charge_status_2(struct ricoh61x_battery_info *info, int displayed_soc_temp)
+{
+ if (displayed_soc_temp < 0)
+ displayed_soc_temp = 0;
+
+ get_power_supply_status(info);
+ info->soca->soc = calc_capacity(info) * 100;
+
+ if (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) {
+ if ((info->first_pwon == 1)
+ && (RICOH61x_SOCA_START == info->soca->status)) {
+ g_full_flag = 1;
+ info->soca->soc_full = info->soca->soc;
+ info->soca->displayed_soc = 100*100;
+ info->soca->full_reset_count = 0;
+ } else {
+ if (calc_ocv(info) > get_OCV_voltage(info, 9,USING)){
+ g_full_flag = 1;
+ info->soca->soc_full = info->soca->soc;
+ info->soca->displayed_soc = 100*100;
+ info->soca->full_reset_count = 0;
+ } else {
+ g_full_flag = 0;
+ info->soca->displayed_soc = displayed_soc_temp;
+ printk(KERN_INFO "PMU: %s Charge Complete but OCV is low\n", __func__);
+ }
+
+ }
+ }
+ if (info->soca->Ibat_ave >= 0) {
+ if (g_full_flag == 1) {
+ info->soca->displayed_soc = 100*100;
+ } else {
+ info->soca->displayed_soc = min(9949, displayed_soc_temp);
+ }
+ }
+ if (info->soca->Ibat_ave < 0) {
+ if (g_full_flag == 1) {
+ if (calc_ocv(info) < get_OCV_voltage(info, 9, USING) + (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))*7/10) {
+ g_full_flag = 0;
+ //info->soca->displayed_soc = 100*100;
+ info->soca->displayed_soc = displayed_soc_temp;
+ printk(KERN_INFO "PMU: %s g_full_flag=1 but OCV is low\n", __func__);
+ } else {
+ info->soca->displayed_soc = 100*100;
+ }
+ } else {
+ info->soca->displayed_soc = min(9949, displayed_soc_temp);
+ g_full_flag = 0;
+ }
+ }
+ if (RICOH61x_SOCA_START == info->soca->status) {
+ if ((g_full_flag == 1) && (calc_ocv(info) > get_OCV_voltage(info, 9,USING))){
+ info->soca->soc_full = info->soca->soc;
+ info->soca->displayed_soc = 100*100;
+ info->soca->full_reset_count = 0;
+ printk(KERN_INFO "PMU:%s Charge Complete in PowerOff\n", __func__);
+ } else if ((info->first_pwon == 0)
+ && !g_fg_on_mode) {
+ printk(KERN_INFO "PMU:%s 2nd P-On init_pswr(%d), cc(%d)\n",
+ __func__, info->soca->init_pswr, info->soca->cc_delta);
+ if ((info->soca->init_pswr == 100)
+ && (info->soca->cc_delta > -100)) {
+ printk(KERN_INFO "PMU:%s Set 100%%\n", __func__);
+ g_full_flag = 1;
+ info->soca->soc_full = info->soca->soc;
+ info->soca->displayed_soc = 100*100;
+ info->soca->full_reset_count = 0;
+ }
+ }
+ } else {
+ printk(KERN_INFO "PMU:%s Resume Sus_soc(%d), cc(%d)\n",
+ __func__, info->soca->suspend_soc, info->soca->cc_delta);
+ if ((info->soca->suspend_soc == 10000)
+ && (info->soca->cc_delta > -100)) {
+ printk(KERN_INFO "PMU:%s Set 100%%\n", __func__);
+ info->soca->displayed_soc = 100*100;
+ }
+ }
+
+ return info->soca->displayed_soc;
+}
+
+/**
+* Calculate Capacity in a period
+* - read CC_SUM & FA_CAP from Coulom Counter
+* - and calculate Capacity.
+* @cc_cap: capacity in a period, unit 0.01%
+* @cc_cap_mas : capacity in a period, unit 1mAs
+* @is_charging: Flag of charging current direction
+* TRUE : charging (plus)
+* FALSE: discharging (minus)
+* @cc_rst: reset CC_SUM or not
+* 0 : not reset
+* 1 : reset
+* 2 : half reset (Leave under 1% of FACAP)
+**/
+static int calc_capacity_in_period(struct ricoh61x_battery_info *info,
+ int *cc_cap, long *cc_cap_mas, bool *is_charging, int cc_rst)
+{
+ int err;
+ uint8_t cc_sum_reg[4];
+ uint8_t cc_clr[4] = {0, 0, 0, 0};
+ uint8_t fa_cap_reg[2];
+ uint16_t fa_cap;
+ uint32_t cc_sum;
+ int cc_stop_flag;
+ uint8_t status;
+ uint8_t charge_state;
+ int Ocv;
+ uint32_t cc_cap_temp;
+ uint32_t cc_cap_min;
+ int cc_cap_res;
+ int fa_cap_int;
+ long cc_sum_int;
+ long cc_sum_dec;
+
+ *is_charging = true; /* currrent state initialize -> charging */
+
+ if (info->entry_factory_mode)
+ return 0;
+
+ /* Read FA_CAP */
+ err = ricoh61x_bulk_reads(info->dev->parent,
+ FA_CAP_H_REG, 2, fa_cap_reg);
+ if (err < 0)
+ goto out;
+
+ /* fa_cap = *(uint16_t*)fa_cap_reg & 0x7fff; */
+ fa_cap = (fa_cap_reg[0] << 8 | fa_cap_reg[1]) & 0x7fff;
+
+
+ /* get power supply status */
+ err = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &status);
+ if (err < 0)
+ goto out;
+ charge_state = (status & 0x1F);
+ Ocv = calc_ocv(info);
+ if (charge_state == CHG_STATE_CHG_COMPLETE) {
+ /* Check CHG status is complete or not */
+ cc_stop_flag = 0;
+// } else if (calc_capacity(info) == 100) {
+// /* Check HW soc is 100 or not */
+// cc_stop_flag = 0;
+ } else if (Ocv < get_OCV_voltage(info, 9, USING)) {
+ /* Check VBAT is high level or not */
+ cc_stop_flag = 0;
+ } else {
+ cc_stop_flag = 1;
+ }
+
+ if (cc_stop_flag == 1)
+ {
+ /* Disable Charging/Completion Interrupt */
+ err = ricoh61x_set_bits(info->dev->parent,
+ RICOH61x_INT_MSK_CHGSTS1, 0x01);
+ if (err < 0)
+ goto out;
+
+ /* disable charging */
+ err = ricoh61x_clr_bits(info->dev->parent, RICOH61x_CHG_CTL1, 0x03);
+ if (err < 0)
+ goto out;
+ }
+
+ /* Read CC_SUM */
+ err = ricoh61x_bulk_reads(info->dev->parent,
+ CC_SUMREG3_REG, 4, cc_sum_reg);
+ if (err < 0)
+ goto out;
+
+ /* cc_sum = *(uint32_t*)cc_sum_reg; */
+ cc_sum = cc_sum_reg[0] << 24 | cc_sum_reg[1] << 16 |
+ cc_sum_reg[2] << 8 | cc_sum_reg[3];
+
+ /* calculation two's complement of CC_SUM */
+ if (cc_sum & 0x80000000) {
+ cc_sum = (cc_sum^0xffffffff)+0x01;
+ *is_charging = false; /* discharge */
+ }
+
+ if (cc_rst == 1) {
+ /* CC_pause enter */
+ err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0x01);
+ if (err < 0)
+ goto out;
+
+ /* CC_SUM <- 0 */
+ err = ricoh61x_bulk_writes(info->dev->parent,
+ CC_SUMREG3_REG, 4, cc_clr);
+ if (err < 0)
+ goto out;
+ } else if (cc_rst == 2) {
+ /* Check 1%[mAs] of FA_CAP (FA_CAP * 3600 /100) */
+ fa_cap_int = fa_cap * 36;
+ cc_sum_int = cc_sum / fa_cap_int;
+ cc_sum_dec = cc_sum % fa_cap_int;
+
+ if (*is_charging == false) {
+ cc_sum_dec = (cc_sum_dec^0xffffffff) + 1;
+ }
+ printk(KERN_INFO "PMU %s 1%%FACAP(%d)[mAs], cc_sum(%d)[mAs], cc_sum_dec(%d)\n",
+ __func__, fa_cap_int, cc_sum, cc_sum_dec);
+
+ if (cc_sum_int != 0) {
+ cc_clr[0] = (uint8_t)(cc_sum_dec >> 24) & 0xff;
+ cc_clr[1] = (uint8_t)(cc_sum_dec >> 16) & 0xff;
+ cc_clr[2] = (uint8_t)(cc_sum_dec >> 8) & 0xff;
+ cc_clr[3] = (uint8_t)cc_sum_dec & 0xff;
+
+ /* CC_pause enter */
+ err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0x01);
+ if (err < 0)
+ goto out;
+
+ /* CC_SUM <- 0 */
+ err = ricoh61x_bulk_writes(info->dev->parent,
+ CC_SUMREG3_REG, 4, cc_clr);
+ if (err < 0)
+ goto out;
+ printk(KERN_INFO "PMU %s Half-Clear CC, cc_sum is over 1%%\n",
+ __func__);
+ }
+ }
+
+ /* CC_pause exist */
+ err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0);
+ if (err < 0)
+ goto out;
+ if (cc_stop_flag == 1)
+ {
+
+ /* Enable charging */
+ err = ricoh61x_set_bits(info->dev->parent, RICOH61x_CHG_CTL1, 0x03);
+ if (err < 0)
+ goto out;
+
+ udelay(1000);
+
+ /* Clear Charging Interrupt status */
+ err = ricoh61x_clr_bits(info->dev->parent,
+ RICOH61x_INT_IR_CHGSTS1, 0x01);
+ if (err < 0)
+ goto out;
+
+ /* ricoh61x_read(info->dev->parent, RICOH61x_INT_IR_CHGSTS1, &val);
+// printk("INT_IR_CHGSTS1 = 0x%x\n",val); */
+
+ /* Enable Charging Interrupt */
+ err = ricoh61x_clr_bits(info->dev->parent,
+ RICOH61x_INT_MSK_CHGSTS1, 0x01);
+ if (err < 0)
+ goto out;
+ }
+
+ /* (CC_SUM x 10000)/3600/FA_CAP */
+
+ if(fa_cap == 0)
+ goto out;
+ else
+ *cc_cap = cc_sum*25/9/fa_cap; /* unit is 0.01% */
+
+ *cc_cap_mas = cc_sum;
+
+ //printk("PMU: cc_sum = %d: cc_cap= %d: cc_cap_mas = %d\n", cc_sum, *cc_cap, *cc_cap_mas);
+
+ if (cc_rst == 1) {
+ cc_cap_min = fa_cap*3600/100/100/100; /* Unit is 0.0001% */
+
+ if(cc_cap_min == 0)
+ goto out;
+ else
+ cc_cap_temp = cc_sum / cc_cap_min;
+
+ cc_cap_res = cc_cap_temp % 100;
+
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU: cc_sum = %d: cc_cap_res= %d: cc_cap_mas = %d\n", cc_sum, cc_cap_res, cc_cap_mas);
+#endif
+
+ if(*is_charging) {
+ info->soca->cc_cap_offset += cc_cap_res;
+ if (info->soca->cc_cap_offset >= 100) {
+ *cc_cap += 1;
+ info->soca->cc_cap_offset %= 100;
+ }
+ } else {
+ info->soca->cc_cap_offset -= cc_cap_res;
+ if (info->soca->cc_cap_offset <= -100) {
+ *cc_cap += 1;
+ info->soca->cc_cap_offset %= 100;
+ }
+ }
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU: cc_cap_offset= %d: \n", info->soca->cc_cap_offset);
+#endif
+ } else {
+ info->soca->cc_cap_offset = 0;
+ }
+
+ //////////////////////////////////////////////////////////////////
+ return 0;
+out:
+ /* CC_pause exist */
+ err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0);
+
+ dev_err(info->dev, "Error !!-----\n");
+ return err;
+}
+
+/**
+* Initial setting of Low voltage.
+**/
+static void initSettingOfLowVoltage(struct ricoh61x_battery_info *info)
+{
+ int err;
+ int cc_cap;
+ long cc_cap_mas;
+ bool is_charging = true;
+
+
+ if(info->soca->rsoc_ready_flag ==1) {
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1);
+ info->soca->last_cc_delta_cap = 0;
+ } else {
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0);
+ info->soca->last_cc_delta_cap = (is_charging == true) ? cc_cap : -cc_cap;
+ }
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ return;
+}
+
+/**
+* Low voltage main flow.
+**/
+static void mainFlowOfLowVoltage(struct ricoh61x_battery_info *info)
+{
+ int ret = 0;
+ int cc_cap = 0;
+ long cc_cap_mas = 0;
+ bool is_charging = true;
+ int cc_delta_cap;
+ int cc_delta_cap_temp;
+ int cc_delta_cap_mas_temp;
+ int cc_delta_cap_now;
+ int cc_delta_cap_debug; //for debug value
+ int capacity_now; //Unit is 0.01 %
+ int capacity_zero; //Unit is 0.01 %
+ int capacity_remain; //Unit is 0.01 %
+ int low_rate; //Unit is 0.01 times
+ int target_equal_soc; //unit is 0.01 %
+ int temp_cc_delta_cap; //unit is 0.01 %
+ int fa_cap; //unit is mAh
+
+ if(info->soca->rsoc_ready_flag ==1) {
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1);
+ } else {
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0);
+ }
+ if (ret < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ if(is_charging == true) {
+ cc_delta_cap_now = cc_cap;
+ //cc_cap_mas;
+ } else {
+ cc_delta_cap_now = -cc_cap;
+ cc_cap_mas = -cc_cap_mas;
+ }
+
+ fa_cap = (battery_init_para[info->num][22]<<8)
+ | (battery_init_para[info->num][23]);
+
+ if(fa_cap != 0) {
+ //( cc(mas) * 10000 ) / 3600 / fa_cap
+ temp_cc_delta_cap = info->soca->temp_cc_delta_cap_mas * 25 / 9 / fa_cap;
+ } else {
+ temp_cc_delta_cap = 0;
+ }
+
+ cc_delta_cap = (cc_delta_cap_now - info->soca->last_cc_delta_cap) + temp_cc_delta_cap;
+
+// info->soca->temp_cc_delta_cap_mas = info->soca->temp_cc_delta_cap_mas - ( (fa_cap * 3600) * (temp_cc_delta_cap / 10000)) ;
+ info->soca->temp_cc_delta_cap_mas = info->soca->temp_cc_delta_cap_mas - ( ((fa_cap * 9) / 25) * temp_cc_delta_cap);
+
+ printk(KERN_DEBUG "PMU: %s : Noxx : cc_delta_cap is %d, cc_delta_cap_now is %d, last_cc_delta_cap is %d\n"
+ , __func__, cc_delta_cap, cc_delta_cap_now, info->soca->last_cc_delta_cap);
+ printk(KERN_DEBUG "PMU: %s : Noxx : temp_cc_delta_cap is %d, after temp_cc_delta_cap_mas is %ld, cc_cap_mas %ld\n"
+ , __func__, temp_cc_delta_cap ,info->soca->temp_cc_delta_cap_mas, cc_cap_mas);
+
+ if(info->soca->rsoc_ready_flag ==1) {
+ info->soca->last_cc_delta_cap = 0;
+ info->soca->last_cc_delta_cap_mas = 0;
+ } else {
+ info->soca->last_cc_delta_cap = cc_delta_cap_now;
+ info->soca->last_cc_delta_cap_mas = cc_cap_mas;
+ }
+
+ cc_delta_cap_debug = cc_delta_cap;
+
+ // check charging or not, if charging -> move to Disp state
+ if ((cc_delta_cap > 0) ||
+ (info->soca->Ibat_ave >= 0)){//chekc discharging or not
+ info->soca->soc = calc_capacity(info) * 100;
+ info->soca->status = RICOH61x_SOCA_DISP;
+ info->soca->last_soc = info->soca->soc;
+ info->soca->soc_delta = 0;
+ info->soca->hurry_up_flg = 0;
+ info->soca->temp_cc_delta_cap_mas = 0;
+ return;
+ }
+
+ //check Vbat and POff_vbat
+ if(info->soca->Vbat_ave <= (info->fg_poff_vbat * 1000)) {
+ info->soca->displayed_soc = info->soca->displayed_soc - 100;
+ info->soca->displayed_soc = max(0, info->soca->displayed_soc);
+ info->soca->hurry_up_flg = 1;
+ return;
+ }
+
+ //calc current recap value
+ capacity_now = getCapFromOriTable(info,info->soca->Vbat_ave,info->soca->Ibat_ave,info->soca->Rbat);
+
+ //calc recap value when soc is 0%
+ if(info->fg_poff_vbat != 0){
+ //enable poff vbat
+ capacity_zero = getCapFromOriTable(info,(info->fg_poff_vbat * 1000),info->soca->Ibat_ave,info->soca->Rbat);
+ } else if(info->fg_target_vsys != 0){
+ //enable target vsys
+ capacity_zero = getCapFromOriTable(info,(info->fg_target_vsys * 1000),info->soca->Ibat_ave,info->soca->Rsys);
+ } else {
+ //disable poff vbat and target vsys
+ capacity_zero = 0;
+ }
+
+ capacity_remain = (capacity_now - capacity_zero) + 50;
+
+ if (capacity_remain <= 50){
+ printk(KERN_INFO "PMU: %s : No6 :Hurry up!!! \n", __func__);
+ info->soca->displayed_soc = info->soca->displayed_soc - 100;
+ info->soca->displayed_soc = max(0, info->soca->displayed_soc);
+ info->soca->hurry_up_flg = 1;
+ return;
+ }
+ else {
+ info->soca->hurry_up_flg = 0;
+
+ if (info->soca->displayed_soc < 1000) { //low DSOC case
+ if(capacity_remain > info->soca->displayed_soc){
+ target_equal_soc = info->soca->displayed_soc * 95 / 100;
+ } else {
+ target_equal_soc = 50;
+ }
+ } else {// normal case
+ if(capacity_remain > info->soca->displayed_soc){
+ target_equal_soc = info->soca->displayed_soc - 1000;
+ } else {
+ target_equal_soc = capacity_remain - 1000;
+ }
+ }
+
+ target_equal_soc = max(50, target_equal_soc);
+
+ low_rate = (info->soca->displayed_soc - target_equal_soc) * 100 / (capacity_remain - target_equal_soc);
+
+ low_rate = max(1, low_rate);
+ low_rate = min(300, low_rate);
+
+ cc_delta_cap_temp = cc_delta_cap * 100 * low_rate / 100; //unit is 0.0001%
+
+ if(cc_delta_cap_temp < 0){
+ //Unit 0.0001 -> 0.01
+ cc_delta_cap = cc_delta_cap_temp / 100;
+
+ cc_delta_cap_temp = cc_delta_cap_temp - cc_delta_cap * 100;
+ //transform 0.0001% -> mAs
+ //mAs = 0.0001 % * (fa_cap(mAh)*60*60)
+ //mAs = cc_delta_cap_temp * (fa_cap * 60 * 60) / (100 * 100 * 100)
+ cc_delta_cap_mas_temp = cc_delta_cap_temp * fa_cap * 9 / 2500;
+ info->soca->temp_cc_delta_cap_mas += cc_delta_cap_mas_temp;
+ }else{
+ cc_delta_cap = 0;
+ }
+
+ info->soca->displayed_soc = info->soca->displayed_soc + cc_delta_cap;
+ info->soca->displayed_soc = max(100, info->soca->displayed_soc); //Set Under limit DSOC is 1%
+ printk(KERN_DEBUG "PMU: %s : No9 :Cap is %d , low_rate is %d, dsoc is %d, capnow is %d, capzero is %d, delta cc is %d, delta cc ori is %d\n"
+ , __func__, capacity_remain, low_rate, info->soca->displayed_soc, capacity_now, capacity_zero, cc_delta_cap, cc_delta_cap_debug);
+ printk(KERN_DEBUG "PMU: %s : No10 :temp_mas is %d, offset_mas is %d, value is %d, final value is %d\n"
+ , __func__, info->soca->temp_cc_delta_cap_mas, cc_delta_cap_mas_temp,(cc_delta_cap_temp + cc_delta_cap * 100), cc_delta_cap);
+ }
+ return;
+}
+
+/**
+* get capacity from Original OCV Table. this value is calculted by ocv
+* info : battery info
+* voltage : unit is 1mV
+* current : unit is 1mA
+* resvalue: unit is 1mohm
+*
+* return value : capcaity, unit is 0.01%
+*/
+static int getCapFromOriTable(struct ricoh61x_battery_info *info, int voltage, int currentvalue, int resvalue)
+{
+ int ocv = 0;
+ int i =0;
+ int capacity=0;
+
+ int ocv_table[11];
+
+ ocv = voltage - (currentvalue * resvalue);
+
+ //get ocv table from header file
+ for(i = 0; i < 11; i++){
+ ocv_table[i] = get_OCV_voltage(info, i, ORIGINAL);
+ }
+
+ /* capacity is 0.01% unit */
+ if (ocv_table[10] <= ocv) {
+ capacity = 100 * 100;
+ } else {
+ for (i = 1; i < 11; i++) {
+ if (ocv_table[i] >= ocv) {
+ if(i == 1){//Under 10 %
+ capacity = getCapFromOriTable_U10per(info, voltage, currentvalue, resvalue);
+ }else{
+ /* unit is 0.01% */
+ capacity = Calc_Linear_Interpolation(
+ (i-1)*10 * 100, ocv_table[i-1], i*10 * 100,
+ ocv_table[i], ocv);
+ if(capacity < 100){
+ capacity = 100;
+ }
+ }
+ break;
+ }
+ }
+ }
+ return capacity;
+}
+
+/**
+* get capacity from special OCV Table(10%-0%). this value is calculted by ocv
+* info : battery info
+* voltage : unit is 1mV
+* current : unit is 1mA
+* resvalue: unit is 1mohm
+*
+* return value : capcaity, unit is 0.01%
+*/
+static int getCapFromOriTable_U10per(struct ricoh61x_battery_info *info, int voltage, int currentvalue, int resvalue)
+{
+ int ocv = 0;
+ int i =0;
+ int capacity=0;
+
+ int ocv_table[11] = { 3468207,
+ 3554926,
+ 3605932,
+ 3627745,
+ 3639093,
+ 3646930,
+ 3655757,
+ 3665738,
+ 3672731,
+ 3680469,
+ 3687400};
+
+ ocv = voltage - (currentvalue * resvalue);
+
+ /* capacity is 0.01% unit */
+ if (ocv_table[0] >= ocv) {
+ capacity = 0;
+ } else if (ocv_table[10] <= ocv) {
+ capacity = 10 * 100;
+ } else {
+ for (i = 1; i < 11; i++) {
+ if (ocv_table[i] >= ocv) {
+ /* unit is 0.01% */
+ capacity = Calc_Linear_Interpolation(
+ (i-1) * 100, ocv_table[i-1], i * 100,
+ ocv_table[i], ocv);
+ if(capacity < 0){
+ capacity = 0;
+ }
+ break;
+ }
+ }
+ }
+ return capacity;
+
+}
+
+/**
+* ReWrite extra CC Value to CC_SUM(register)
+* info : battery info
+* extraValue : Under 1% value. unit is 0.01%
+*
+* return value : delta soc, unit is "minus" 0.01%
+*/
+
+static void write_extra_value_to_ccsum(struct ricoh61x_battery_info *info, int extraValue)
+{
+ int err;
+ uint8_t cc_clr[4] = {0, 0, 0, 0}; //temporary box
+ uint8_t fa_cap_reg[2]; //reg value
+ int fa_cap; //Unit is mAh
+ int cc_sum_dec; //unit is mAs
+ bool is_charging = 0;
+
+ //check dicharging or not
+ if(extraValue < 0){
+ extraValue = extraValue * -1;
+ is_charging = false;
+ } else {
+ is_charging = true;
+ }
+
+ /* Read FA_CAP */
+ err = ricoh61x_bulk_reads(info->dev->parent,
+ FA_CAP_H_REG, 2, fa_cap_reg);
+ if (err < 0)
+ dev_err(info->dev, "Read fa_cap Error !!-----\n");
+
+ /* fa_cap = *(uint16_t*)fa_cap_reg & 0x7fff; */
+ fa_cap = (fa_cap_reg[0] << 8 | fa_cap_reg[1]) & 0x7fff;
+
+ //convertion extraValue(0.01%) -> mAs
+ //cc_sum_dec = (extraValue * fa_cap * 3600) / (100 * 100)
+ cc_sum_dec = (extraValue * fa_cap * 9) / 25;
+
+ // Add 0.005%
+ if (extraValue < 100) {
+ cc_sum_dec += (1 * fa_cap * 9) / 25;
+ }
+
+ if (is_charging == false) {
+ cc_sum_dec = (cc_sum_dec^0xffffffff) + 1;
+ }
+
+ cc_clr[0] = (uint8_t)(cc_sum_dec >> 24) & 0xff;
+ cc_clr[1] = (uint8_t)(cc_sum_dec >> 16) & 0xff;
+ cc_clr[2] = (uint8_t)(cc_sum_dec >> 8) & 0xff;
+ cc_clr[3] = (uint8_t)cc_sum_dec & 0xff;
+
+ /* CC_pause enter */
+ err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0x01);
+ if (err < 0)
+ dev_err(info->dev, "Write cc_CTRL Error !!-----\n");
+
+ /* CC_SUM <- 0 */
+ err = ricoh61x_bulk_writes(info->dev->parent,
+ CC_SUMREG3_REG, 4, cc_clr);
+ if (err < 0)
+ dev_err(info->dev, "Write cc_Sum Error !!-----\n");
+
+ /* CC_pause exit */
+ err = ricoh61x_write(info->dev->parent, CC_CTRL_REG, 0);
+ if (err < 0)
+ dev_err(info->dev, "Write cc_CTRL Error !!-----\n");
+
+ return;
+}
+
+
+#ifdef ENABLE_OCV_TABLE_CALIB
+/**
+* Calibration OCV Table
+* - Update the value of VBAT on 100% in OCV table
+* if battery is Full charged.
+* - int vbat_ocv <- unit is uV
+**/
+static int calib_ocvTable(struct ricoh61x_battery_info *info, int vbat_ocv)
+{
+ int ret;
+ int cutoff_ocv;
+ int i;
+ int ocv100_new;
+ int start_per = 0;
+ int end_per = 0;
+
+ if (info->soca->Ibat_ave > RICOH61x_REL1_SEL_VALUE) {
+ printk("PMU: %s IBAT > 64mA -- Not Calibration --\n", __func__);
+ return 0;
+ }
+
+ if (vbat_ocv < info->soca->OCV100_max) {
+ if (vbat_ocv < info->soca->OCV100_min)
+ ocv100_new = info->soca->OCV100_min;
+ else
+ ocv100_new = vbat_ocv;
+ } else {
+ ocv100_new = info->soca->OCV100_max;
+ }
+ printk("PMU : %s :max %d min %d current %d\n",__func__,info->soca->OCV100_max,info->soca->OCV100_min,vbat_ocv);
+ printk("PMU : %s : New OCV 100 = 0x%x\n",__func__,ocv100_new);
+
+ /* FG_En Off */
+ ret = ricoh61x_clr_bits(info->dev->parent, FG_CTRL_REG, 0x01);
+ if (ret < 0) {
+ dev_err(info->dev,"Error in FG_En OFF\n");
+ goto err;
+ }
+
+
+ //cutoff_ocv = (battery_init_para[info->num][0]<<8) | (battery_init_para[info->num][1]);
+ cutoff_ocv = get_OCV_voltage(info, 0, USING);
+
+ info->soca->ocv_table_def[10] = info->soca->OCV100_max;
+
+ ricoh61x_scaling_OCV_table(info, cutoff_ocv/1000, ocv100_new/1000, &start_per, &end_per);
+
+ ret = ricoh61x_bulk_writes_bank1(info->dev->parent,
+ BAT_INIT_TOP_REG, 22, battery_init_para[info->num]);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ goto err;
+ }
+
+ for (i = 0; i <= 10; i = i+1) {
+ info->soca->ocv_table[i] = get_OCV_voltage(info, i, USING);
+ printk("PMU: %s : * %d0%% voltage = %d uV\n",
+ __func__, i, info->soca->ocv_table[i]);
+ }
+
+ /* FG_En on & Reset*/
+ ret = reset_FG_process(info);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in FG_En On & Reset %d\n", ret);
+ goto err;
+ }
+
+ printk("PMU: %s Exit \n", __func__);
+ return 0;
+err:
+ return ret;
+
+}
+
+#endif
+
+/**
+* get SOC value during period of Suspend/Hibernate with voltage method
+* info : battery info
+*
+* return value : soc, unit is 0.01%
+*/
+
+static int calc_soc_by_voltageMethod(struct ricoh61x_battery_info *info)
+{
+ int soc;
+ int ret;
+
+ ret = measure_vbatt_FG(info, &info->soca->Vbat_ave);
+
+ if(info->soca->Vbat_ave > 4100000) {
+ soc = 10000;
+ } else if(info->soca->Vbat_ave < 3500000) {
+ soc = 0;
+ } else {
+ soc = 10000 - ((4100000 - info->soca->Vbat_ave) / 60);
+ }
+
+ get_power_supply_status(info);
+ if (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) {
+ soc = 10000;
+ } else {
+ soc = min(soc, 9900);
+ }
+
+ // Cutoff under 1% on Voltage Method
+ soc = (soc / 100) * 100;
+
+ printk("PMU : %s : VBAT is %d [uV], soc is %d [0.01%%] ----------\n"
+ ,__func__, info->soca->Vbat_ave, soc);
+
+ // soc range is 0~10000
+ return soc;
+}
+
+/**
+* update RSOC and related parameters after using voltage method
+* info : battery info
+* soc_voltage : soc by using voltage method
+*
+*/
+
+static void update_rsoc_on_voltageMethod(struct ricoh61x_battery_info *info, int soc_voltage)
+{
+ info->soca->init_pswr = soc_voltage / 100;
+ write_extra_value_to_ccsum(info, (soc_voltage % 100));
+ info->soca->status = RICOH61x_SOCA_STABLE;
+ info->soca->last_soc = soc_voltage;
+ info->soca->rsoc_ready_flag = 0;
+
+ printk(KERN_INFO "PMU: %s : Voltage Method. state(%d), dsoc(%d), rsoc(%d), init_pswr(%d), cc_delta(%d) ----------\n",
+ __func__, info->soca->status, soc_voltage, soc_voltage, info->soca->init_pswr, soc_voltage%100);
+
+ return;
+}
+
+/**
+* update RSOC and related parameters after using current method
+* Only resume function can call this one.
+* info : battery info
+* soc_current : soc by using current method
+*
+*/
+
+static void update_rsoc_on_currentMethod(struct ricoh61x_battery_info *info, int soc_current)
+{
+ int resume_rsoc;
+
+ if (RICOH61x_SOCA_START == info->soca->status
+ || RICOH61x_SOCA_UNSTABLE == info->soca->status
+ || RICOH61x_SOCA_STABLE == info->soca->status) {
+ resume_rsoc = soc_current;
+ } else {
+ resume_rsoc = info->soca->suspend_rsoc + info->soca->cc_delta;
+ }
+ resume_rsoc = max(0, min(10000, resume_rsoc)); // Apply upper&lower limit
+ info->soca->init_pswr = resume_rsoc / 100;
+ write_extra_value_to_ccsum(info, (resume_rsoc % 100));
+ info->soca->rsoc_ready_flag = 0;
+ printk(KERN_INFO "PMU: %s : Current Method. state(%d), dsoc(%d), rsoc(%d), init_pswr(%d), cc_delta(%d) ----------\n",
+ __func__, info->soca->status, soc_current, resume_rsoc, info->soca->init_pswr, resume_rsoc%100);
+
+ return;
+}
+
+
+static void ricoh61x_displayed_work(struct work_struct *work)
+{
+ int err;
+ uint8_t val;
+ uint8_t val_pswr;
+ uint8_t val2;
+ int soc_round;
+ int last_soc_round;
+ int last_disp_round;
+ int displayed_soc_temp;
+ int disp_dec;
+ int cc_cap = 0;
+ long cc_cap_mas = 0;
+ bool is_charging = true;
+ int re_cap,fa_cap,use_cap;
+ bool is_jeita_updated;
+ uint8_t reg_val;
+ int delay_flag = 0;
+ int Vbat = 0;
+ int Ibat = 0;
+ int Vsys = 0;
+ int temp_ocv;
+ int current_soc_full;
+ int fc_delta = 0;
+ int temp_soc;
+ int current_cc_sum;
+ int calculated_ocv;
+ long full_rate = 0;
+ long full_rate_org;
+ long full_rate_max;
+ long full_rate_min;
+ int temp_cc_delta_cap;
+ int ibat_soc = 0;
+ int ibat_soc_base;
+ int dsoc_var;
+ int dsoc_var_org;
+ int cc_delta;
+ int i;
+ int last_dsoc;
+
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, displayed_work.work);
+
+ if (info->entry_factory_mode) {
+ info->soca->status = RICOH61x_SOCA_STABLE;
+ info->soca->displayed_soc = -EINVAL;
+ info->soca->ready_fg = 0;
+ return;
+ }
+
+ if (info->stop_disp) {
+ printk(KERN_INFO "PMU: Finish displayed_work func\n",
+ __func__);
+ return;
+ }
+
+ mutex_lock(&info->lock);
+
+ is_jeita_updated = false;
+
+ if ((RICOH61x_SOCA_START == info->soca->status)
+ || (RICOH61x_SOCA_STABLE == info->soca->status)
+ || (RICOH61x_SOCA_FULL == info->soca->status))
+ {
+ info->soca->ready_fg = 1;
+ }
+ //if (RICOH61x_SOCA_FG_RESET != info->soca->status)
+ // Set_back_ocv_table(info);
+
+ if (bat_alert_req_flg == 1) {
+ // Use Voltage method if difference is large
+ info->soca->displayed_soc = calc_soc_by_voltageMethod(info);
+ update_rsoc_on_voltageMethod(info, info->soca->displayed_soc);
+ bat_alert_req_flg = 0;
+
+ goto end_flow;
+ }
+
+ /* judge Full state or Moni Vsys state */
+ calculated_ocv = calc_ocv(info);
+ if ((RICOH61x_SOCA_DISP == info->soca->status)
+ || (RICOH61x_SOCA_STABLE == info->soca->status)) {
+ /* caluc 95% ocv */
+ temp_ocv = get_OCV_voltage(info, 10, USING) -
+ (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))/2;
+
+ if(g_full_flag == 1){ /* for issue 1 solution start*/
+ info->soca->status = RICOH61x_SOCA_FULL;
+ info->soca->last_soc_full = 0;
+ } else if ((POWER_SUPPLY_STATUS_FULL == info->soca->chg_status)
+ && (calculated_ocv > temp_ocv)) {
+ info->soca->status = RICOH61x_SOCA_FULL;
+ g_full_flag = 0;
+ info->soca->last_soc_full = 0;
+ } else if (info->soca->Ibat_ave >= -12) {
+ /* for issue1 solution end */
+ /* check Full state or not*/
+ if ((calculated_ocv > get_OCV_voltage(info, RICOH61x_ENTER_FULL_STATE_OCV, USING))
+ || (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status)
+ || (info->soca->displayed_soc > RICOH61x_ENTER_FULL_STATE_DSOC * 100)) {
+ info->soca->status = RICOH61x_SOCA_FULL;
+ g_full_flag = 0;
+ info->soca->last_soc_full = 0;
+ } else if ((calculated_ocv > get_OCV_voltage(info, 9, USING))
+ && (info->soca->Ibat_ave < 300)) {
+ info->soca->status = RICOH61x_SOCA_FULL;
+ g_full_flag = 0;
+ info->soca->last_soc_full = 0;
+ }
+ } else { /* dis-charging */
+// if (info->soca->displayed_soc/100 < RICOH61x_ENTER_LOW_VOL) {
+ initSettingOfLowVoltage(info);
+ info->soca->status = RICOH61x_SOCA_LOW_VOL;
+// }
+ }
+ }
+
+ if (RICOH61x_SOCA_STABLE == info->soca->status) {
+ info->soca->soc = calc_capacity_2(info);
+ info->soca->soc_delta = info->soca->soc - info->soca->last_soc;
+
+ if (info->soca->soc_delta >= -100 && info->soca->soc_delta <= 100) {
+ info->soca->displayed_soc = info->soca->soc;
+ } else {
+ info->soca->status = RICOH61x_SOCA_DISP;
+ }
+ info->soca->last_soc = info->soca->soc;
+ info->soca->soc_delta = 0;
+ } else if (RICOH61x_SOCA_FULL == info->soca->status) {
+ err = check_jeita_status(info, &is_jeita_updated);
+ if (err < 0) {
+ dev_err(info->dev, "Error in updating JEITA %d\n", err);
+ goto end_flow;
+ }
+ info->soca->soc = calc_capacity(info) * 100;
+ info->soca->last_soc = calc_capacity_2(info); /* for DISP */
+ last_dsoc = info->soca->displayed_soc;
+
+ if (info->soca->Ibat_ave >= -12) { /* charging */
+ if (0 == info->soca->jt_limit) {
+ if (g_full_flag == 1) {
+
+ if (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status) {
+ if(info->soca->full_reset_count < RICOH61x_UPDATE_COUNT_FULL_RESET) {
+ info->soca->full_reset_count++;
+ } else if (info->soca->full_reset_count < (RICOH61x_UPDATE_COUNT_FULL_RESET + 1)) {
+ err = reset_FG_process(info);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+ info->soca->full_reset_count++;
+ info->soca->rsoc_ready_flag =1;
+ goto end_flow;
+ } else if(info->soca->full_reset_count < (RICOH61x_UPDATE_COUNT_FULL_RESET + 2)) {
+ info->soca->full_reset_count++;
+ info->soca->fc_cap = 0;
+ info->soca->soc_full = info->soca->soc;
+ }
+ } else {
+ if(info->soca->fc_cap < -1 * 200) {
+ g_full_flag = 0;
+ info->soca->displayed_soc = 99 * 100;
+ }
+ info->soca->full_reset_count = 0;
+ }
+
+
+ if(info->soca->rsoc_ready_flag ==1) {
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1);
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ fc_delta = (is_charging == true) ? cc_cap : -cc_cap;
+
+ info->soca->fc_cap = info->soca->fc_cap + fc_delta;
+ }
+
+ if (g_full_flag == 1){
+ info->soca->displayed_soc = 100*100;
+ }
+ } else {
+ if ((calculated_ocv < get_OCV_voltage(info, (RICOH61x_ENTER_FULL_STATE_OCV - 1), USING))
+ && (info->soca->displayed_soc < (RICOH61x_ENTER_FULL_STATE_DSOC - 10) * 100)) { /* fail safe*/
+ g_full_flag = 0;
+ info->soca->status = RICOH61x_SOCA_DISP;
+ info->soca->soc_delta = 0;
+ info->soca->full_reset_count = 0;
+ info->soca->last_soc = info->soca->soc;
+ info->soca->temp_cc_delta_cap = 0;
+ } else if ((POWER_SUPPLY_STATUS_FULL == info->soca->chg_status)
+ && (info->soca->displayed_soc >= 9890)){
+ info->soca->displayed_soc = 100*100;
+ g_full_flag = 1;
+ info->soca->full_reset_count = 0;
+ info->soca->soc_full = info->soca->soc;
+ info->soca->fc_cap = 0;
+ info->soca->last_soc_full = 0;
+#ifdef ENABLE_OCV_TABLE_CALIB
+ err = calib_ocvTable(info,calculated_ocv);
+ if (err < 0)
+ dev_err(info->dev, "Calibration OCV Error !!\n");
+#endif
+ } else {
+ fa_cap = get_check_fuel_gauge_reg(info, FA_CAP_H_REG, FA_CAP_L_REG,
+ 0x7fff);
+
+ if (info->soca->displayed_soc >= 9950) {
+ if((info->soca->soc_full - info->soca->soc) < 200) {
+ goto end_flow;
+ }
+ }
+
+ /* Calculate CC Delta */
+ if(info->soca->rsoc_ready_flag ==1) {
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1);
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+ info->soca->cc_delta
+ = (is_charging == true) ? cc_cap : -cc_cap;
+ } else {
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0);
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+ cc_delta = (is_charging == true) ? cc_cap : -cc_cap;
+ current_soc_full = info->soca->init_pswr * 100 + cc_delta;
+
+ if (info->soca->last_soc_full == 0) { /* initial setting of last cc sum */
+ info->soca->cc_delta = 0;
+ info->soca->rsoc_limit = 0;
+ printk(KERN_INFO "PMU: %s 1st last_soc_full(%d), cc_delta=0\n",
+ __func__, info->soca->last_soc_full);
+ } else if (info->soca->rsoc_limit == 1) {
+ info->soca->cc_delta = 100 + current_soc_full - info->soca->last_soc_full;
+ } else {
+ info->soca->cc_delta = current_soc_full - info->soca->last_soc_full;
+ }
+ info->soca->last_soc_full = current_soc_full;
+
+ if ((info->soca->init_pswr == 100) && (cc_delta >= 100)) {
+ info->soca->rsoc_limit = 1;
+ } else {
+ info->soca->rsoc_limit = 0;
+ }
+ }
+
+ printk(KERN_INFO "PMU: %s rrf= %d: cc_delta= %d: current_soc= %d: rsoc_limit= %d: cc_delta_temp = %d:\n",
+ __func__, info->soca->rsoc_ready_flag, info->soca->cc_delta, current_soc_full, info->soca->rsoc_limit,info->soca->temp_cc_delta_cap);
+
+ info->soca->temp_cc_delta_cap = min(800, info->soca->temp_cc_delta_cap);
+
+ info->soca->cc_delta += info->soca->temp_cc_delta_cap;
+ info->soca->temp_cc_delta_cap = 0;
+
+
+#ifdef LTS_DEBUG
+ printk(KERN_INFO "PMU: %s rrf= %d: cc_delta= %d: current_soc= %d: rsoc_limit= %d:\n",
+ __func__, info->soca->rsoc_ready_flag, info->soca->cc_delta, current_soc_full, info->soca->rsoc_limit);
+#endif
+
+ if(POWER_SUPPLY_STATUS_FULL == info->soca->chg_status)
+ {
+ info->soca->displayed_soc += 13 * 3000 / fa_cap;
+ } else {
+ ibat_soc_base = 10000 - (RICOH61x_IBAT_TABLE_NUM - 1) * 100 - 50;
+ if (ibat_table[0] < info->soca->Ibat_ave) {
+ if (ibat_soc_base < info->soca->displayed_soc){
+ ibat_soc = ibat_soc_base;
+ } else {
+ ibat_soc = info->soca->displayed_soc;
+ }
+ printk(KERN_INFO "PMU: %s IBAT= %d: ibat_table[%d%%]= %d: ibat_soc= %d ************\n",
+ __func__, info->soca->Ibat_ave, (100 - RICOH61x_IBAT_TABLE_NUM + 1), ibat_table[0], ibat_soc);
+ } else if (ibat_table[RICOH61x_IBAT_TABLE_NUM-1] >= info->soca->Ibat_ave) {
+ ibat_soc = 9950;
+ printk(KERN_INFO "PMU: %s IBAT= %d: ibat_table[100%%]= %d: ibat_soc= %d ************\n",
+ __func__, info->soca->Ibat_ave, ibat_table[RICOH61x_IBAT_TABLE_NUM-1], ibat_soc);
+ } else {
+ for (i = 1; i <= (RICOH61x_IBAT_TABLE_NUM-1); i++) {
+ if(ibat_table[i] <= info->soca->Ibat_ave) {
+ ibat_soc = Calc_Linear_Interpolation(
+ (i-1) * 100, ibat_table[i-1], i * 100,
+ ibat_table[i], info->soca->Ibat_ave);
+ ibat_soc += ibat_soc_base;
+
+#ifdef LTS_DEBUG
+ printk(KERN_INFO "PMU: %s IBAT= %d: ibat_table[%d%%]= %d, ibat_table[%d%%]= %d: ibat_soc= %d: ************\n",
+ __func__, info->soca->Ibat_ave, (100 - RICOH61x_IBAT_TABLE_NUM + i), ibat_table[i-1],
+ (100 - RICOH61x_IBAT_TABLE_NUM + 1 + i), ibat_table[i], ibat_soc);
+#endif
+ break;
+ }
+ }
+ }
+
+ // full_rate = 100 * (100 - DSOC) * (100 - DSOC) / ((100 - IBAT_SOC) * (100 - IBAT_SOC))
+ full_rate = (long)(100 * (10000 - info->soca->displayed_soc)) / (10000 - ibat_soc);
+ full_rate = full_rate * (10000 - info->soca->displayed_soc) / (10000 - ibat_soc);
+
+ /* Adjust parameters */
+ full_rate_org = full_rate;
+ full_rate_max = 140;
+ full_rate_min = 30;
+
+ if (ibat_soc >= 9450) {
+ full_rate_max = 140 + (ibat_soc - 9450) / 2;
+ }
+
+ full_rate = min(full_rate_max, max(full_rate_min, full_rate));
+
+ dsoc_var = info->soca->cc_delta * (int)full_rate / 100;
+ dsoc_var_org = dsoc_var;
+ if (info->soca->cc_delta <= 0) {
+ dsoc_var = 0;
+ } else {
+ dsoc_var = max(3, dsoc_var);
+ }
+
+#ifdef LTS_DEBUG
+ printk(KERN_INFO "PMU: cc_delta= %d: ibat_soc= %d: full_rate= %ld: %ld: dsoc_var= %d: %d: IBAT= %d: DSOC= %d: RSOC= %d:\n",
+ info->soca->cc_delta, ibat_soc, full_rate_org, full_rate, dsoc_var_org, dsoc_var,
+ info->soca->Ibat_ave, (info->soca->displayed_soc + dsoc_var), info->soca->last_soc);
+#endif
+
+ info->soca->displayed_soc
+ = info->soca->displayed_soc + dsoc_var;
+ }
+ info->soca->displayed_soc
+ = min(10000, info->soca->displayed_soc);
+ info->soca->displayed_soc = max(0, info->soca->displayed_soc);
+
+ if (info->soca->displayed_soc >= 9890) {
+ info->soca->displayed_soc = 99 * 100;
+ }
+ }
+ }
+ } else {
+ info->soca->full_reset_count = 0;
+ }
+ } else { /* discharging */
+ if (info->soca->displayed_soc >= 9950) {
+ if (info->soca->Ibat_ave <= -1 * RICOH61x_REL1_SEL_VALUE) {
+ if ((calculated_ocv < (get_OCV_voltage(info, 9, USING) + (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))*3/10))
+ || ((info->soca->soc_full - info->soca->soc) > 200)) {
+
+ g_full_flag = 0;
+ info->soca->full_reset_count = 0;
+ info->soca->displayed_soc = 100 * 100;
+ info->soca->status = RICOH61x_SOCA_DISP;
+ info->soca->last_soc = info->soca->soc;
+ info->soca->soc_delta = 0;
+ info->soca->temp_cc_delta_cap = 0;
+ } else {
+ info->soca->displayed_soc = 100 * 100;
+ }
+ } else { /* into relaxation state */
+ ricoh61x_read(info->dev->parent, CHGSTATE_REG, &reg_val);
+ if (reg_val & 0xc0) {
+ info->soca->displayed_soc = 100 * 100;
+ } else {
+ g_full_flag = 0;
+ info->soca->full_reset_count = 0;
+ info->soca->displayed_soc = 100 * 100;
+ info->soca->status = RICOH61x_SOCA_DISP;
+ info->soca->last_soc = info->soca->soc;
+ info->soca->soc_delta = 0;
+ info->soca->temp_cc_delta_cap = 0;
+ }
+ }
+ } else {
+ g_full_flag = 0;
+ info->soca->status = RICOH61x_SOCA_DISP;
+ info->soca->soc_delta = 0;
+ info->soca->full_reset_count = 0;
+ info->soca->last_soc = info->soca->soc;
+ info->soca->temp_cc_delta_cap = 0;
+ }
+ }
+ } else if (RICOH61x_SOCA_LOW_VOL == info->soca->status) {
+
+ mainFlowOfLowVoltage(info);
+ }
+
+ if (RICOH61x_SOCA_DISP == info->soca->status) {
+
+ info->soca->soc = calc_capacity_2(info);
+
+ soc_round = (info->soca->soc + 50) / 100;
+ last_soc_round = (info->soca->last_soc + 50) / 100;
+ last_disp_round = (info->soca->displayed_soc + 50) / 100;
+
+ info->soca->soc_delta =
+ info->soca->soc_delta + (info->soca->soc - info->soca->last_soc);
+
+ info->soca->last_soc = info->soca->soc;
+ /* six case */
+ if (last_disp_round == soc_round) {
+ /* if SOC == DISPLAY move to stable */
+ info->soca->displayed_soc = info->soca->soc ;
+ info->soca->status = RICOH61x_SOCA_STABLE;
+ delay_flag = 1;
+ } else if (info->soca->Ibat_ave > 0) {
+ if ((0 == info->soca->jt_limit) ||
+ (POWER_SUPPLY_STATUS_FULL != info->soca->chg_status)) {
+ /* Charge */
+ if (last_disp_round < soc_round) {
+ /* Case 1 : Charge, Display < SOC */
+ if (info->soca->soc_delta >= 100) {
+ info->soca->displayed_soc
+ = last_disp_round * 100 + 50;
+ info->soca->soc_delta -= 100;
+ if (info->soca->soc_delta >= 100)
+ delay_flag = 1;
+ } else {
+ info->soca->displayed_soc += 25;
+ disp_dec = info->soca->displayed_soc % 100;
+ if ((50 <= disp_dec) && (disp_dec <= 74))
+ info->soca->soc_delta = 0;
+ }
+ if ((info->soca->displayed_soc + 50)/100
+ >= soc_round) {
+ info->soca->displayed_soc
+ = info->soca->soc ;
+ info->soca->status
+ = RICOH61x_SOCA_STABLE;
+ delay_flag = 1;
+ }
+ } else if (last_disp_round > soc_round) {
+ /* Case 2 : Charge, Display > SOC */
+ if (info->soca->soc_delta >= 300) {
+ info->soca->displayed_soc += 100;
+ info->soca->soc_delta -= 300;
+ }
+ if ((info->soca->displayed_soc + 50)/100
+ <= soc_round) {
+ info->soca->displayed_soc
+ = info->soca->soc ;
+ info->soca->status
+ = RICOH61x_SOCA_STABLE;
+ delay_flag = 1;
+ }
+ }
+ } else {
+ info->soca->soc_delta = 0;
+ }
+ } else {
+ /* Dis-Charge */
+ if (last_disp_round > soc_round) {
+ /* Case 3 : Dis-Charge, Display > SOC */
+ if (info->soca->soc_delta <= -100) {
+ info->soca->displayed_soc
+ = last_disp_round * 100 - 75;
+ info->soca->soc_delta += 100;
+ if (info->soca->soc_delta <= -100)
+ delay_flag = 1;
+ } else {
+ info->soca->displayed_soc -= 25;
+ disp_dec = info->soca->displayed_soc % 100;
+ if ((25 <= disp_dec) && (disp_dec <= 49))
+ info->soca->soc_delta = 0;
+ }
+ if ((info->soca->displayed_soc + 50)/100
+ <= soc_round) {
+ info->soca->displayed_soc
+ = info->soca->soc ;
+ info->soca->status
+ = RICOH61x_SOCA_STABLE;
+ delay_flag = 1;
+ }
+ } else if (last_disp_round < soc_round) {
+ /* Case 4 : Dis-Charge, Display < SOC */
+ if (info->soca->soc_delta <= -300) {
+ info->soca->displayed_soc -= 100;
+ info->soca->soc_delta += 300;
+ }
+ if ((info->soca->displayed_soc + 50)/100
+ >= soc_round) {
+ info->soca->displayed_soc
+ = info->soca->soc ;
+ info->soca->status
+ = RICOH61x_SOCA_STABLE;
+ delay_flag = 1;
+ }
+ }
+ }
+ } else if (RICOH61x_SOCA_UNSTABLE == info->soca->status) {
+ /* caluc 95% ocv */
+ temp_ocv = get_OCV_voltage(info, 10, USING) -
+ (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))/2;
+
+ if(g_full_flag == 1){ /* for issue 1 solution start*/
+ info->soca->status = RICOH61x_SOCA_FULL;
+ info->soca->last_soc_full = 0;
+ err = reset_FG_process(info);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+
+ goto end_flow;
+ }else if ((POWER_SUPPLY_STATUS_FULL == info->soca->chg_status)
+ && (calculated_ocv > temp_ocv)) {
+ info->soca->status = RICOH61x_SOCA_FULL;
+ g_full_flag = 0;
+ info->soca->last_soc_full = 0;
+ err = reset_FG_process(info);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto end_flow;
+ } else if (info->soca->Ibat_ave >= -12) {
+ /* for issue1 solution end */
+ /* check Full state or not*/
+ if ((calculated_ocv > (get_OCV_voltage(info, 9, USING) + (get_OCV_voltage(info, 10, USING) - get_OCV_voltage(info, 9, USING))*7/10))
+ || (POWER_SUPPLY_STATUS_FULL == info->soca->chg_status)
+ || (info->soca->displayed_soc > 9850))
+ {
+ info->soca->status = RICOH61x_SOCA_FULL;
+ g_full_flag = 0;
+ info->soca->last_soc_full = 0;
+ err = reset_FG_process(info);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto end_flow;
+ } else if ((calculated_ocv > (get_OCV_voltage(info, 9, USING)))
+ && (info->soca->Ibat_ave < 300))
+ {
+ info->soca->status = RICOH61x_SOCA_FULL;
+ g_full_flag = 0;
+ info->soca->last_soc_full = 0;
+ err = reset_FG_process(info);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto end_flow;
+ }
+ }
+
+ info->soca->soc = info->soca->init_pswr * 100;
+
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 0);
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ info->soca->cc_delta
+ = (is_charging == true) ? cc_cap : -cc_cap;
+
+ displayed_soc_temp
+ = info->soca->soc + info->soca->cc_delta;
+ if (displayed_soc_temp < 0)
+ displayed_soc_temp = 0;
+ displayed_soc_temp
+ = min(9850, displayed_soc_temp);
+ displayed_soc_temp = max(0, displayed_soc_temp);
+
+ info->soca->displayed_soc = displayed_soc_temp;
+
+ } else if (RICOH61x_SOCA_FG_RESET == info->soca->status) {
+ /* No update */
+ } else if (RICOH61x_SOCA_START == info->soca->status) {
+
+ err = measure_Ibatt_FG(info, &Ibat);
+ err = measure_vbatt_FG(info, &Vbat);
+ err = measure_vsys_ADC(info, &Vsys);
+
+ info->soca->Ibat_ave = Ibat;
+ info->soca->Vbat_ave = Vbat;
+ info->soca->Vsys_ave = Vsys;
+
+ err = check_jeita_status(info, &is_jeita_updated);
+ is_jeita_updated = false;
+ if (err < 0) {
+ dev_err(info->dev, "Error in updating JEITA %d\n", err);
+ }
+ err = ricoh61x_read(info->dev->parent, PSWR_REG, &val_pswr);
+ val_pswr &= 0x7f;
+
+ if (info->first_pwon) {
+ displayed_soc_temp = val_pswr * 100;
+
+ info->soca->soc = calc_capacity(info) * 100;
+
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 1);
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ info->soca->cc_delta
+ = (is_charging == true) ? cc_cap : -cc_cap;
+
+ //get DSOC temp value
+ if (displayed_soc_temp == 0) { //initial power on or some error case
+
+ displayed_soc_temp = info->soca->soc;
+ printk(KERN_INFO "PMU: %s : initial power on\n",__func__);
+
+ } else if ((Ibat > 0)
+ && (displayed_soc_temp < info->soca->soc)){ //charge and poff_DSOC < RSOC
+ displayed_soc_temp = info->soca->soc;
+ printk(KERN_INFO "PMU: %s : normal case Ibat is %dmA, poffDSOC is %d, RSOC is %dn\n"
+ ,__func__, Ibat, val_pswr*100, info->soca->soc);
+ } else if ((info->soca->cc_delta <= 0)
+ && (displayed_soc_temp > info->soca->soc)){ //discharge and poff_DSOC > RSOC
+
+ displayed_soc_temp = info->soca->soc;
+ printk(KERN_INFO "PMU: %s : normal case cc delta is %dmA, poffDSOC is %d, RSOC is %dn\n"
+ ,__func__, info->soca->cc_delta, val_pswr*100, info->soca->soc);
+
+ } else if ((info->soca->cc_delta > 0)
+ && (displayed_soc_temp < info->soca->soc)){ //charge and poff_DSOC < RSOC
+ displayed_soc_temp = info->soca->soc;
+ printk(KERN_INFO "PMU: %s : normal case cc delta is %dmA, poffDSOC is %d, RSOC is %dn\n"
+ ,__func__, info->soca->cc_delta, val_pswr*100, info->soca->soc);
+ } else {
+ //displayed_soc_temp = displayed_soc_temp;
+ printk(KERN_INFO "PMU: %s : error case cc delta is %dmA, poffDSOC is %d, RSOC is %dn\n"
+ ,__func__, info->soca->cc_delta, val_pswr*100, info->soca->soc);
+ }
+
+ //val = (info->soca->soc + 50)/100;
+ val = (displayed_soc_temp + 50)/100;
+ val &= 0x7f;
+ err = ricoh61x_write(info->dev->parent, PSWR_REG, val);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+ info->soca->init_pswr = val;
+ g_soc = val;
+ set_current_time2register(info);
+
+ if (0 == info->soca->jt_limit) {
+ check_charge_status_2(info, displayed_soc_temp);
+ } else {
+ info->soca->displayed_soc = displayed_soc_temp;
+ }
+ if (Ibat < 0) {
+ initSettingOfLowVoltage(info);
+ info->soca->status = RICOH61x_SOCA_LOW_VOL;
+ } else {
+ info->soca->status = RICOH61x_SOCA_DISP;
+ info->soca->soc_delta = 0;
+ info->soca->last_soc = displayed_soc_temp;
+ }
+
+ } else if (g_fg_on_mode && (val_pswr == 0x7f)) {
+ info->soca->soc = calc_capacity(info) * 100;
+ if (0 == info->soca->jt_limit) {
+ check_charge_status_2(info, info->soca->soc);
+ } else {
+ info->soca->displayed_soc = info->soca->soc;
+ }
+ info->soca->last_soc = info->soca->soc;
+ info->soca->status = RICOH61x_SOCA_STABLE;
+ } else {
+ info->soca->soc = val_pswr * 100;
+ if (err < 0) {
+ dev_err(info->dev,
+ "Error in reading PSWR_REG %d\n", err);
+ info->soca->soc
+ = calc_capacity(info) * 100;
+ }
+
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 2);
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ info->soca->cc_delta
+ = (is_charging == true) ? cc_cap : -cc_cap;
+ displayed_soc_temp
+ = info->soca->soc + (info->soca->cc_delta / 100) * 100;
+
+ displayed_soc_temp
+ = min(10000, displayed_soc_temp);
+ if (displayed_soc_temp <= 100) {
+ displayed_soc_temp = 100;
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 1);
+ }
+
+ printk(KERN_INFO "PMU: %s : dsoc_temp(%d), soc(%d), cc_delta(%d)\n",
+ __func__, displayed_soc_temp, info->soca->soc, info->soca->cc_delta);
+ printk(KERN_INFO "PMU: %s : status(%d), rsoc_ready_flag(%d)\n",
+ __func__, info->soca->status, info->soca->rsoc_ready_flag);
+
+ if (0 == info->soca->jt_limit) {
+ check_charge_status_2(info, displayed_soc_temp);
+ } else {
+ info->soca->displayed_soc = displayed_soc_temp;
+ }
+
+ val = (displayed_soc_temp + 50)/100;
+ val &= 0x7f;
+ err = ricoh61x_write(info->dev->parent, PSWR_REG, val);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+ info->soca->init_pswr = val;
+ g_soc = val;
+ set_current_time2register(info);
+
+ info->soca->last_soc = calc_capacity_2(info);
+
+ if(info->soca->rsoc_ready_flag == 0) {
+
+ info->soca->status = RICOH61x_SOCA_DISP;
+ info->soca->soc_delta = 0;
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU FG_RESET : %s : initial dsoc is %d\n",__func__,info->soca->displayed_soc);
+#endif
+ } else if (Ibat < 0) {
+ initSettingOfLowVoltage(info);
+ info->soca->status = RICOH61x_SOCA_LOW_VOL;
+ } else {
+ info->soca->status = RICOH61x_SOCA_DISP;
+ info->soca->soc_delta = 0;
+ }
+ }
+ }
+end_flow:
+ /* keep DSOC = 1 when Vbat is over 3.4V*/
+ if( info->fg_poff_vbat != 0) {
+ if (info->soca->zero_flg == 1) {
+ if ((info->soca->Ibat_ave >= 0)
+ || (info->soca->Vbat_ave >= (info->fg_poff_vbat+100)*1000)) {
+ info->soca->zero_flg = 0;
+ } else {
+ info->soca->displayed_soc = 0;
+ }
+ } else if (info->soca->displayed_soc < 50) {
+ if (info->soca->Vbat_ave < 2000*1000) { /* error value */
+ info->soca->displayed_soc = 100;
+ } else if (info->soca->Vbat_ave < info->fg_poff_vbat*1000) {
+ info->soca->displayed_soc = 0;
+ info->soca->zero_flg = 1;
+ } else {
+ info->soca->displayed_soc = 100;
+ }
+ }
+ }
+
+ if (g_fg_on_mode
+ && (info->soca->status == RICOH61x_SOCA_STABLE)) {
+ err = ricoh61x_write(info->dev->parent, PSWR_REG, 0x7f);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+ g_soc = 0x7F;
+ set_current_time2register(info);
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 1);
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ } else if (((RICOH61x_SOCA_UNSTABLE != info->soca->status)
+ && (info->soca->rsoc_ready_flag != 0))
+ || (RICOH61x_SOCA_LOW_VOL == info->soca->status)){
+ if ((info->soca->displayed_soc + 50)/100 <= 1) {
+ val = 1;
+ } else {
+ val = (info->soca->displayed_soc + 50)/100;
+ val &= 0x7f;
+ }
+ err = ricoh61x_write(info->dev->parent, PSWR_REG, val);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+
+ g_soc = val;
+ set_current_time2register(info);
+
+ info->soca->init_pswr = val;
+
+ if(RICOH61x_SOCA_LOW_VOL != info->soca->status)
+ {
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 1);
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+ printk(KERN_INFO "PMU: %s Full-Clear CC, PSWR(%d)\n",
+ __func__, val);
+ }
+ } else { /* Case of UNSTABLE STATE */
+ if ((info->soca->displayed_soc + 50)/100 <= 1) {
+ val = 1;
+ } else {
+ val = (info->soca->displayed_soc + 50)/100;
+ val &= 0x7f;
+ }
+ err = ricoh61x_write(info->dev->parent, PSWR_REG, val);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+
+ g_soc = val;
+ set_current_time2register(info);
+
+ err = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 2);
+ if (err < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+ info->soca->cc_delta
+ = (is_charging == true) ? cc_cap : -cc_cap;
+
+ val = info->soca->init_pswr + (info->soca->cc_delta/100);
+ val = min(100, val);
+ val = max(1, val);
+
+ info->soca->init_pswr = val;
+
+ info->soca->last_cc_rrf0 = info->soca->cc_delta%100;
+
+ printk(KERN_INFO "PMU: %s Half-Clear CC, init_pswr(%d), cc_delta(%d)\n",
+ __func__, info->soca->init_pswr, info->soca->cc_delta);
+
+ }
+
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU:STATUS= %d: IBAT= %d: VSYS= %d: VBAT= %d: DSOC= %d: RSOC= %d: cc_delta=%d: rrf= %d\n",
+ info->soca->status, info->soca->Ibat_ave, info->soca->Vsys_ave, info->soca->Vbat_ave,
+ info->soca->displayed_soc, info->soca->soc, info->soca->cc_delta, info->soca->rsoc_ready_flag);
+#endif
+
+// printk("PMU AGE*STATUS * %d*IBAT*%d*VSYS*%d*VBAT*%d*DSOC*%d*RSOC*%d*-------\n",
+// info->soca->status, info->soca->Ibat_ave, info->soca->Vsys_ave, info->soca->Vbat_ave,
+// info->soca->displayed_soc, info->soca->soc);
+
+#ifdef DISABLE_CHARGER_TIMER
+ /* clear charger timer */
+ if ( info->soca->chg_status == POWER_SUPPLY_STATUS_CHARGING ) {
+ err = ricoh61x_read(info->dev->parent, TIMSET_REG, &val);
+ if (err < 0)
+ dev_err(info->dev,
+ "Error in read TIMSET_REG%d\n", err);
+ /* to check bit 0-1 */
+ val2 = val & 0x03;
+
+ if (val2 == 0x02){
+ /* set rapid timer 240 -> 300 */
+ err = ricoh61x_set_bits(info->dev->parent, TIMSET_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ }
+ } else {
+ /* set rapid timer 300 -> 240 */
+ err = ricoh61x_clr_bits(info->dev->parent, TIMSET_REG, 0x01);
+ err = ricoh61x_set_bits(info->dev->parent, TIMSET_REG, 0x02);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ }
+ }
+ }
+#endif
+
+ if (0 == info->soca->ready_fg)
+ queue_delayed_work(info->monitor_wqueue, &info->displayed_work,
+ RICOH61x_FG_RESET_TIME * HZ);
+ else if (delay_flag == 1)
+ queue_delayed_work(info->monitor_wqueue, &info->displayed_work,
+ RICOH61x_DELAY_TIME * HZ);
+ else if ((RICOH61x_SOCA_DISP == info->soca->status)
+ && (info->soca->Ibat_ave > 0))
+ queue_delayed_work(info->monitor_wqueue, &info->displayed_work,
+ RICOH61x_DISP_CHG_UPDATE_TIME * HZ);
+ else if ((info->soca->hurry_up_flg == 1) && (RICOH61x_SOCA_LOW_VOL == info->soca->status))
+ queue_delayed_work(info->monitor_wqueue, &info->displayed_work,
+ RICOH61x_LOW_VOL_DOWN_TIME * HZ);
+ else
+ queue_delayed_work(info->monitor_wqueue, &info->displayed_work,
+ RICOH61x_DISPLAY_UPDATE_TIME * HZ);
+
+ mutex_unlock(&info->lock);
+
+ if((true == is_jeita_updated)
+ || (info->soca->last_displayed_soc/100 != (info->soca->displayed_soc+50)/100))
+ power_supply_changed(&info->battery);
+
+ info->soca->last_displayed_soc = info->soca->displayed_soc+50;
+
+ return;
+}
+
+static void ricoh61x_stable_charge_countdown_work(struct work_struct *work)
+{
+ int ret;
+ int max = 0;
+ int min = 100;
+ int i;
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, charge_stable_work.work);
+
+ if (info->entry_factory_mode)
+ return;
+
+ mutex_lock(&info->lock);
+ if (RICOH61x_SOCA_FG_RESET == info->soca->status)
+ info->soca->ready_fg = 1;
+
+ if (2 <= info->soca->stable_count) {
+ if (3 == info->soca->stable_count
+ && RICOH61x_SOCA_FG_RESET == info->soca->status) {
+ ret = reset_FG_process(info);
+ if (ret < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+ }
+ info->soca->stable_count = info->soca->stable_count - 1;
+ queue_delayed_work(info->monitor_wqueue,
+ &info->charge_stable_work,
+ RICOH61x_FG_STABLE_TIME * HZ / 10);
+ } else if (0 >= info->soca->stable_count) {
+ /* Finished queue, ignore */
+ } else if (1 == info->soca->stable_count) {
+ if (RICOH61x_SOCA_UNSTABLE == info->soca->status) {
+ /* Judge if FG need reset or Not */
+ info->soca->soc = calc_capacity(info) * 100;
+ if (info->chg_ctr != 0) {
+ queue_delayed_work(info->monitor_wqueue,
+ &info->charge_stable_work,
+ RICOH61x_FG_STABLE_TIME * HZ / 10);
+ mutex_unlock(&info->lock);
+ return;
+ }
+ /* Do reset setting */
+ ret = reset_FG_process(info);
+ if (ret < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+
+ info->soca->status = RICOH61x_SOCA_FG_RESET;
+
+ /* Delay for addition Reset Time (6s) */
+ queue_delayed_work(info->monitor_wqueue,
+ &info->charge_stable_work,
+ RICOH61x_FG_RESET_TIME*HZ);
+ } else if (RICOH61x_SOCA_FG_RESET == info->soca->status) {
+ info->soca->reset_soc[2] = info->soca->reset_soc[1];
+ info->soca->reset_soc[1] = info->soca->reset_soc[0];
+ info->soca->reset_soc[0] = calc_capacity(info) * 100;
+ info->soca->reset_count++;
+
+ if (info->soca->reset_count > 10) {
+ /* Reset finished; */
+ info->soca->soc = info->soca->reset_soc[0];
+ info->soca->stable_count = 0;
+ goto adjust;
+ }
+
+ for (i = 0; i < 3; i++) {
+ if (max < info->soca->reset_soc[i]/100)
+ max = info->soca->reset_soc[i]/100;
+ if (min > info->soca->reset_soc[i]/100)
+ min = info->soca->reset_soc[i]/100;
+ }
+
+ if ((info->soca->reset_count > 3) && ((max - min)
+ < RICOH61x_MAX_RESET_SOC_DIFF)) {
+ /* Reset finished; */
+ info->soca->soc = info->soca->reset_soc[0];
+ info->soca->stable_count = 0;
+ goto adjust;
+ } else {
+ /* Do reset setting */
+ ret = reset_FG_process(info);
+ if (ret < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+
+ /* Delay for addition Reset Time (6s) */
+ queue_delayed_work(info->monitor_wqueue,
+ &info->charge_stable_work,
+ RICOH61x_FG_RESET_TIME*HZ);
+ }
+ /* Finished queue From now, select FG as result; */
+ } else if (RICOH61x_SOCA_START == info->soca->status) {
+ /* Normal condition */
+ } else { /* other state ZERO/DISP/STABLE */
+ info->soca->stable_count = 0;
+ }
+
+ mutex_unlock(&info->lock);
+ return;
+
+adjust:
+ info->soca->last_soc = info->soca->soc;
+ info->soca->status = RICOH61x_SOCA_DISP;
+ info->soca->soc_delta = 0;
+
+ }
+ mutex_unlock(&info->lock);
+ return;
+}
+
+static void ricoh61x_charge_monitor_work(struct work_struct *work)
+{
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, charge_monitor_work.work);
+
+ get_power_supply_status(info);
+
+ if (POWER_SUPPLY_STATUS_DISCHARGING == info->soca->chg_status
+ || POWER_SUPPLY_STATUS_NOT_CHARGING == info->soca->chg_status) {
+ switch (info->soca->dischg_state) {
+ case 0:
+ info->soca->dischg_state = 1;
+ break;
+ case 1:
+ info->soca->dischg_state = 2;
+ break;
+
+ case 2:
+ default:
+ break;
+ }
+ } else {
+ info->soca->dischg_state = 0;
+ }
+
+ queue_delayed_work(info->monitor_wqueue, &info->charge_monitor_work,
+ RICOH61x_CHARGE_MONITOR_TIME * HZ);
+
+ return;
+}
+
+static void ricoh61x_get_charge_work(struct work_struct *work)
+{
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, get_charge_work.work);
+
+ int Vbat_temp, Vsys_temp, Ibat_temp;
+ int Vbat_sort[RICOH61x_GET_CHARGE_NUM];
+ int Vsys_sort[RICOH61x_GET_CHARGE_NUM];
+ int Ibat_sort[RICOH61x_GET_CHARGE_NUM];
+ int i, j;
+ int ret;
+
+ mutex_lock(&info->lock);
+
+ for (i = RICOH61x_GET_CHARGE_NUM-1; i > 0; i--) {
+ if (0 == info->soca->chg_count) {
+ info->soca->Vbat[i] = 0;
+ info->soca->Vsys[i] = 0;
+ info->soca->Ibat[i] = 0;
+ } else {
+ info->soca->Vbat[i] = info->soca->Vbat[i-1];
+ info->soca->Vsys[i] = info->soca->Vsys[i-1];
+ info->soca->Ibat[i] = info->soca->Ibat[i-1];
+ }
+ }
+
+ ret = measure_vbatt_FG(info, &info->soca->Vbat[0]);
+ ret = measure_vsys_ADC(info, &info->soca->Vsys[0]);
+ ret = measure_Ibatt_FG(info, &info->soca->Ibat[0]);
+
+ info->soca->chg_count++;
+
+ if (RICOH61x_GET_CHARGE_NUM != info->soca->chg_count) {
+ queue_delayed_work(info->monitor_wqueue, &info->get_charge_work,
+ RICOH61x_CHARGE_CALC_TIME * HZ);
+ mutex_unlock(&info->lock);
+ return ;
+ }
+
+ for (i = 0; i < RICOH61x_GET_CHARGE_NUM; i++) {
+ Vbat_sort[i] = info->soca->Vbat[i];
+ Vsys_sort[i] = info->soca->Vsys[i];
+ Ibat_sort[i] = info->soca->Ibat[i];
+ }
+
+ Vbat_temp = 0;
+ Vsys_temp = 0;
+ Ibat_temp = 0;
+ for (i = 0; i < RICOH61x_GET_CHARGE_NUM - 1; i++) {
+ for (j = RICOH61x_GET_CHARGE_NUM - 1; j > i; j--) {
+ if (Vbat_sort[j - 1] > Vbat_sort[j]) {
+ Vbat_temp = Vbat_sort[j];
+ Vbat_sort[j] = Vbat_sort[j - 1];
+ Vbat_sort[j - 1] = Vbat_temp;
+ }
+ if (Vsys_sort[j - 1] > Vsys_sort[j]) {
+ Vsys_temp = Vsys_sort[j];
+ Vsys_sort[j] = Vsys_sort[j - 1];
+ Vsys_sort[j - 1] = Vsys_temp;
+ }
+ if (Ibat_sort[j - 1] > Ibat_sort[j]) {
+ Ibat_temp = Ibat_sort[j];
+ Ibat_sort[j] = Ibat_sort[j - 1];
+ Ibat_sort[j - 1] = Ibat_temp;
+ }
+ }
+ }
+
+ Vbat_temp = 0;
+ Vsys_temp = 0;
+ Ibat_temp = 0;
+ for (i = 3; i < RICOH61x_GET_CHARGE_NUM-3; i++) {
+ Vbat_temp = Vbat_temp + Vbat_sort[i];
+ Vsys_temp = Vsys_temp + Vsys_sort[i];
+ Ibat_temp = Ibat_temp + Ibat_sort[i];
+ }
+ Vbat_temp = Vbat_temp / (RICOH61x_GET_CHARGE_NUM - 6);
+ Vsys_temp = Vsys_temp / (RICOH61x_GET_CHARGE_NUM - 6);
+ Ibat_temp = Ibat_temp / (RICOH61x_GET_CHARGE_NUM - 6);
+
+ if (0 == info->soca->chg_count) {
+ queue_delayed_work(info->monitor_wqueue, &info->get_charge_work,
+ RICOH61x_CHARGE_UPDATE_TIME * HZ);
+ mutex_unlock(&info->lock);
+ return;
+ } else {
+ info->soca->Vbat_ave = Vbat_temp;
+ info->soca->Vsys_ave = Vsys_temp;
+ info->soca->Ibat_ave = Ibat_temp;
+ }
+
+ info->soca->chg_count = 0;
+ queue_delayed_work(info->monitor_wqueue, &info->get_charge_work,
+ RICOH61x_CHARGE_UPDATE_TIME * HZ);
+ mutex_unlock(&info->lock);
+ return;
+}
+
+/* Initial setting of FuelGauge SOCA function */
+static int ricoh61x_init_fgsoca(struct ricoh61x_battery_info *info)
+{
+ int i;
+ int err;
+ uint8_t val;
+
+ for (i = 0; i <= 10; i = i+1) {
+ info->soca->ocv_table[i] = get_OCV_voltage(info, i, USING);
+ printk(KERN_INFO "PMU: %s : * %d0%% voltage = %d uV\n",
+ __func__, i, info->soca->ocv_table[i]);
+ }
+
+ for (i = 0; i < 3; i = i+1)
+ info->soca->reset_soc[i] = 0;
+ info->soca->reset_count = 0;
+
+ if (info->first_pwon) {
+
+ err = ricoh61x_read(info->dev->parent, CHGISET_REG, &val);
+ if (err < 0)
+ dev_err(info->dev,
+ "Error in read CHGISET_REG%d\n", err);
+
+ err = ricoh61x_write(info->dev->parent, CHGISET_REG, 0);
+ if (err < 0)
+ dev_err(info->dev,
+ "Error in writing CHGISET_REG%d\n", err);
+ /* msleep(1000); */
+
+ if (!info->entry_factory_mode) {
+ err = ricoh61x_write(info->dev->parent,
+ FG_CTRL_REG, 0x51);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+ }
+
+ info->soca->rsoc_ready_flag = 1;
+
+ /* msleep(6000); */
+
+ err = ricoh61x_write(info->dev->parent, CHGISET_REG, val);
+ if (err < 0)
+ dev_err(info->dev,
+ "Error in writing CHGISET_REG%d\n", err);
+ }
+
+ /* Rbat : Transfer */
+ info->soca->Rbat = get_OCV_init_Data(info, 12, USING) * 1000 / 512
+ * 5000 / 4095;
+ info->soca->n_cap = get_OCV_init_Data(info, 11, USING);
+
+
+ info->soca->displayed_soc = 0;
+ info->soca->last_displayed_soc = 0;
+ info->soca->suspend_soc = 0;
+ info->soca->suspend_full_flg = false;
+ info->soca->ready_fg = 0;
+ info->soca->soc_delta = 0;
+ info->soca->full_reset_count = 0;
+ info->soca->soc_full = 0;
+ info->soca->fc_cap = 0;
+ info->soca->status = RICOH61x_SOCA_START;
+ /* stable count down 11->2, 1: reset; 0: Finished; */
+ info->soca->stable_count = 11;
+ info->soca->dischg_state = 0;
+ info->soca->Vbat_ave = 0;
+ info->soca->Vbat_old = 0;
+ info->soca->Vsys_ave = 0;
+ info->soca->Ibat_ave = 0;
+ info->soca->chg_count = 0;
+ info->soca->hurry_up_flg = 0;
+ info->soca->re_cap_old = 0;
+ info->soca->jt_limit = 0;
+ info->soca->zero_flg = 0;
+ info->soca->cc_cap_offset = 0;
+ info->soca->sus_cc_cap_offset = 0;
+ info->soca->last_soc_full = 0;
+ info->soca->rsoc_limit = 0;
+ info->soca->last_cc_rrf0 = 0;
+ info->soca->last_cc_delta_cap = 0;
+ info->soca->last_cc_delta_cap_mas = 0;
+ info->soca->temp_cc_delta_cap_mas = 0;
+ info->soca->temp_cc_delta_cap = 0;
+
+ info->soca->store_fl_current = RICOH61x_FL_CURRENT_DEF;
+ info->soca->store_slp_state = 0;
+ info->soca->store_sus_current = RICOH61x_SUS_CURRENT_DEF;
+ info->soca->store_hiber_current = RICOH61x_HIBER_CURRENT_DEF;
+
+ for (i = 0; i < 11; i++) {
+ info->soca->ocv_table_low[i] = 0;
+ }
+
+ for (i = 0; i < RICOH61x_GET_CHARGE_NUM; i++) {
+ info->soca->Vbat[i] = 0;
+ info->soca->Vsys[i] = 0;
+ info->soca->Ibat[i] = 0;
+ }
+
+ /*********************************/
+ //fl_level = RICOH61x_FL_LEVEL_DEF;
+ //fl_current = RICOH61x_FL_CURRENT_DEF;
+ //slp_state = 0;
+ //idle_current = RICOH61x_IDLE_CURRENT_DEF;
+ //sus_current = RICOH61x_SUS_CURRENT_DEF;
+ //hiber_current = RICOH61x_HIBER_CURRENT_DEF;
+ //bat_alert_req_flg = 0;
+#ifdef STANDBY_MODE_DEBUG
+ multiple_sleep_mode = 0;
+#endif
+ /*********************************/
+
+#ifdef ENABLE_FG_KEEP_ON_MODE
+ g_fg_on_mode = 1;
+ info->soca->rsoc_ready_flag = 1;
+#else
+ g_fg_on_mode = 0;
+#endif
+
+
+ /* Start first Display job */
+ if(info->first_pwon) {
+ queue_delayed_work(info->monitor_wqueue, &info->displayed_work,
+ RICOH61x_FG_RESET_TIME*HZ);
+ }else {
+ queue_delayed_work(info->monitor_wqueue, &info->displayed_work,
+ RICOH61x_MAIN_START_TIME*HZ);
+ }
+
+ /* Start first Waiting stable job */
+ queue_delayed_work(info->monitor_wqueue, &info->charge_stable_work,
+ RICOH61x_FG_STABLE_TIME*HZ/10);
+
+ queue_delayed_work(info->monitor_wqueue, &info->charge_monitor_work,
+ RICOH61x_CHARGE_MONITOR_TIME * HZ);
+
+ queue_delayed_work(info->monitor_wqueue, &info->get_charge_work,
+ RICOH61x_CHARGE_MONITOR_TIME * HZ);
+ if (info->jt_en) {
+ if (info->jt_hw_sw) {
+ /* Enable JEITA function supported by H/W */
+ err = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x04);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+ } else {
+ /* Disable JEITA function supported by H/W */
+ err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x04);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+ queue_delayed_work(info->monitor_wqueue, &info->jeita_work,
+ RICOH61x_FG_RESET_TIME * HZ);
+ }
+ } else {
+ /* Disable JEITA function supported by H/W */
+ err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x04);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing the control register\n");
+ if (0xff != info->ch_ilim_adp && (info->ch_ilim_adp <= 0x1D)) {
+ /* REGISET1:(0xB6) setting */
+ err = ricoh61x_write(info->dev->parent, REGISET1_REG, info->ch_ilim_adp);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing REGISET1_REG %d\n",err);
+ if (0xff != info->jt_ichg_h && (info->jt_ichg_h <= 0x1D)) {
+ /* CHGISET:(0xB8) setting */
+ err = ricoh61x_write(info->dev->parent, CHGISET_REG, info->jt_ichg_h);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing CHGISET_REG %d\n",err);
+ }
+ }
+ }
+
+ printk(KERN_INFO "PMU: %s : * Rbat = %d mOhm n_cap = %d mAH\n",
+ __func__, info->soca->Rbat, info->soca->n_cap);
+ return 1;
+}
+#endif
+
+static void ricoh61x_changed_work(struct work_struct *work)
+{
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, changed_work.work);
+
+ printk(KERN_INFO "PMU: %s\n", __func__);
+ power_supply_changed(&info->battery);
+
+ return;
+}
+
+static int check_jeita_status(struct ricoh61x_battery_info *info, bool *is_jeita_updated)
+/* JEITA Parameter settings
+*
+* VCHG
+* |
+* jt_vfchg_h~+~~~~~~~~~~~~~~~~~~~+
+* | |
+* jt_vfchg_l-| - - - - - - - - - +~~~~~~~~~~+
+* | Charge area + |
+* -------0--+-------------------+----------+--- Temp
+* ! +
+* ICHG
+* | +
+* jt_ichg_h-+ - -+~~~~~~~~~~~~~~+~~~~~~~~~~+
+* + | + |
+* jt_ichg_l-+~~~~+ Charge area |
+* | + + |
+* 0--+----+--------------+----------+--- Temp
+* 0 jt_temp_l jt_temp_h 55
+*/
+{
+ int temp;
+ int err = 0;
+ int vfchg;
+ uint8_t chgiset_org;
+ uint8_t batset2_org;
+ uint8_t set_vchg_h, set_vchg_l;
+ uint8_t set_ichg_h, set_ichg_l;
+
+ *is_jeita_updated = false;
+ /* No execute if JEITA disabled */
+ if (!info->jt_en || info->jt_hw_sw)
+ return 0;
+
+ /* Check FG Reset */
+ if (info->soca->ready_fg) {
+ temp = get_battery_temp_2(info) / 10;
+ } else {
+ printk(KERN_INFO "JEITA: %s *** cannot update by resetting FG ******\n", __func__);
+ goto out;
+ }
+
+ /* Read BATSET2 */
+ err = ricoh61x_read(info->dev->parent, BATSET2_REG, &batset2_org);
+ if (err < 0) {
+ dev_err(info->dev, "Error in readng the battery setting register\n");
+ goto out;
+ }
+ vfchg = (batset2_org & 0x70) >> 4;
+ batset2_org &= 0x8F;
+
+ /* Read CHGISET */
+ err = ricoh61x_read(info->dev->parent, CHGISET_REG, &chgiset_org);
+ if (err < 0) {
+ dev_err(info->dev, "Error in readng the chrage setting register\n");
+ goto out;
+ }
+ chgiset_org &= 0xC0;
+
+ set_ichg_h = (uint8_t)(chgiset_org | info->jt_ichg_h);
+ set_ichg_l = (uint8_t)(chgiset_org | info->jt_ichg_l);
+
+ set_vchg_h = (uint8_t)((info->jt_vfchg_h << 4) | batset2_org);
+ set_vchg_l = (uint8_t)((info->jt_vfchg_l << 4) | batset2_org);
+
+ printk(KERN_INFO "PMU: %s *** Temperature: %d, vfchg: %d, SW status: %d, chg_status: %d ******\n",
+ __func__, temp, vfchg, info->soca->status, info->soca->chg_status);
+
+ if (temp <= 0 || 55 <= temp) {
+ /* 1st and 5th temperature ranges (~0, 55~) */
+ printk(KERN_INFO "PMU: %s *** Temp(%d) is out of 0-55 ******\n", __func__, temp);
+ err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto out;
+ }
+ info->soca->jt_limit = 0;
+ *is_jeita_updated = true;
+ } else if (temp < info->jt_temp_l) {
+ /* 2nd temperature range (0~12) */
+ if (vfchg != info->jt_vfchg_h) {
+ printk(KERN_INFO "PMU: %s *** 0<Temp<12, update to vfchg=%d ******\n",
+ __func__, info->jt_vfchg_h);
+ err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto out;
+ }
+
+ /* set VFCHG/VRCHG */
+ err = ricoh61x_write(info->dev->parent,
+ BATSET2_REG, set_vchg_h);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the battery setting register\n");
+ goto out;
+ }
+ info->soca->jt_limit = 0;
+ *is_jeita_updated = true;
+ } else
+ printk(KERN_INFO "PMU: %s *** 0<Temp<50, already set vfchg=%d, so no need to update ******\n",
+ __func__, info->jt_vfchg_h);
+
+ /* set ICHG */
+ err = ricoh61x_write(info->dev->parent, CHGISET_REG, set_ichg_l);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the battery setting register\n");
+ goto out;
+ }
+ err = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto out;
+ }
+ } else if (temp < info->jt_temp_h) {
+ /* 3rd temperature range (12~50) */
+ if (vfchg != info->jt_vfchg_h) {
+ printk(KERN_INFO "PMU: %s *** 12<Temp<50, update to vfchg==%d ******\n", __func__, info->jt_vfchg_h);
+
+ err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto out;
+ }
+ /* set VFCHG/VRCHG */
+ err = ricoh61x_write(info->dev->parent,
+ BATSET2_REG, set_vchg_h);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the battery setting register\n");
+ goto out;
+ }
+ info->soca->jt_limit = 0;
+ *is_jeita_updated = true;
+ } else
+ printk(KERN_INFO "PMU: %s *** 12<Temp<50, already set vfchg==%d, so no need to update ******\n",
+ __func__, info->jt_vfchg_h);
+
+ /* set ICHG */
+ err = ricoh61x_write(info->dev->parent, CHGISET_REG, set_ichg_h);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the battery setting register\n");
+ goto out;
+ }
+ err = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto out;
+ }
+ } else if (temp < 55) {
+ /* 4th temperature range (50~55) */
+ if (vfchg != info->jt_vfchg_l) {
+ printk(KERN_INFO "PMU: %s *** 50<Temp<55, update to vfchg==%d ******\n", __func__, info->jt_vfchg_l);
+
+ err = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto out;
+ }
+ /* set VFCHG/VRCHG */
+ err = ricoh61x_write(info->dev->parent,
+ BATSET2_REG, set_vchg_l);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the battery setting register\n");
+ goto out;
+ }
+ info->soca->jt_limit = 1;
+ *is_jeita_updated = true;
+ } else
+ printk(KERN_INFO "JEITA: %s *** 50<Temp<55, already set vfchg==%d, so no need to update ******\n",
+ __func__, info->jt_vfchg_l);
+
+ /* set ICHG */
+ err = ricoh61x_write(info->dev->parent, CHGISET_REG, set_ichg_h);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the battery setting register\n");
+ goto out;
+ }
+ err = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto out;
+ }
+ }
+
+ get_power_supply_status(info);
+ printk(KERN_INFO "PMU: %s *** Hope updating value in this timing after checking jeita, chg_status: %d, is_jeita_updated: %d ******\n",
+ __func__, info->soca->chg_status, *is_jeita_updated);
+
+ return 0;
+
+out:
+ printk(KERN_INFO "PMU: %s ERROR ******\n", __func__);
+ return err;
+}
+
+static void ricoh61x_jeita_work(struct work_struct *work)
+{
+ int ret;
+ bool is_jeita_updated = false;
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, jeita_work.work);
+
+ mutex_lock(&info->lock);
+
+ ret = check_jeita_status(info, &is_jeita_updated);
+ if (0 == ret) {
+ queue_delayed_work(info->monitor_wqueue, &info->jeita_work,
+ RICOH61x_JEITA_UPDATE_TIME * HZ);
+ } else {
+ printk(KERN_INFO "PMU: %s *** Call check_jeita_status() in jeita_work, err:%d ******\n",
+ __func__, ret);
+ queue_delayed_work(info->monitor_wqueue, &info->jeita_work,
+ RICOH61x_FG_RESET_TIME * HZ);
+ }
+
+ mutex_unlock(&info->lock);
+
+ if(true == is_jeita_updated)
+ power_supply_changed(&info->battery);
+
+ return;
+}
+
+#ifdef ENABLE_FACTORY_MODE
+/*------------------------------------------------------*/
+/* Factory Mode */
+/* Check Battery exist or not */
+/* If not, disabled Rapid to Complete State change */
+/*------------------------------------------------------*/
+static int ricoh61x_factory_mode(struct ricoh61x_battery_info *info)
+{
+ int ret = 0;
+ uint8_t val = 0;
+
+ ret = ricoh61x_read(info->dev->parent, RICOH61x_INT_MON_CHGCTR, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ return ret;
+ }
+ if (!(val & 0x01)) /* No Adapter connected */
+ return ret;
+
+ /* Rapid to Complete State change disable */
+ ret = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, 0x40);
+
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ return ret;
+ }
+
+ /* Wait 1s for checking Charging State */
+ queue_delayed_work(info->factory_mode_wqueue, &info->factory_mode_work,
+ 1*HZ);
+
+ return ret;
+}
+
+static void check_charging_state_work(struct work_struct *work)
+{
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, factory_mode_work.work);
+
+ int ret = 0;
+ uint8_t val = 0;
+ int chargeCurrent = 0;
+
+ ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ return;
+ }
+
+
+ chargeCurrent = get_check_fuel_gauge_reg(info, CC_AVERAGE1_REG,
+ CC_AVERAGE0_REG, 0x3fff);
+ if (chargeCurrent < 0) {
+ dev_err(info->dev, "Error in reading the FG register\n");
+ return;
+ }
+
+ /* Repid State && Charge Current about 0mA */
+ if (((chargeCurrent >= 0x3ffc && chargeCurrent <= 0x3fff)
+ || chargeCurrent < 0x05) && val == 0x43) {
+ printk(KERN_INFO "PMU:%s --- No battery !! Enter Factory mode ---\n"
+ , __func__);
+ info->entry_factory_mode = true;
+ /* clear FG_ACC bit */
+ ret = ricoh61x_clr_bits(info->dev->parent, RICOH61x_FG_CTRL, 0x10);
+ if (ret < 0)
+ dev_err(info->dev, "Error in writing FG_CTRL\n");
+
+ return; /* Factory Mode */
+ }
+
+ /* Return Normal Mode --> Rapid to Complete State change enable */
+ /* disable the status change from Rapid Charge to Charge Complete */
+
+ ret = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, 0x40);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ return;
+ }
+ printk(KERN_INFO "PMU:%s --- Battery exist !! Return Normal mode ---0x%2x\n"
+ , __func__, val);
+
+ return;
+}
+#endif /* ENABLE_FACTORY_MODE */
+
+static int Calc_Linear_Interpolation(int x0, int y0, int x1, int y1, int y)
+{
+ int alpha;
+ int x;
+
+ alpha = (y - y0)*100 / (y1 - y0);
+
+ x = ((100 - alpha) * x0 + alpha * x1) / 100;
+
+ return x;
+}
+
+static void ricoh61x_scaling_OCV_table(struct ricoh61x_battery_info *info, int cutoff_vol, int full_vol, int *start_per, int *end_per)
+{
+ int i, j;
+ int temp;
+ int percent_step;
+ int OCV_percent_new[11];
+
+ /* get ocv table. this table is calculated by Apprication */
+ //printk("PMU : %s : original table\n",__func__);
+ //for (i = 0; i <= 10; i = i+1) {
+ // printk(KERN_INFO "PMU: %s : %d0%% voltage = %d uV\n",
+ // __func__, i, info->soca->ocv_table_def[i]);
+ //}
+ //printk("PMU: %s : cutoff_vol %d full_vol %d\n",
+ // __func__, cutoff_vol,full_vol);
+
+ /* Check Start % */
+ if (info->soca->ocv_table_def[0] > cutoff_vol * 1000) {
+ *start_per = 0;
+ printk("PMU : %s : setting value of cuttoff_vol(%d) is out of range(%d) \n",__func__, cutoff_vol, info->soca->ocv_table_def[0]);
+ } else {
+ for (i = 1; i < 11; i++) {
+ if (info->soca->ocv_table_def[i] >= cutoff_vol * 1000) {
+ /* unit is 0.001% */
+ *start_per = Calc_Linear_Interpolation(
+ (i-1)*1000, info->soca->ocv_table_def[i-1], i*1000,
+ info->soca->ocv_table_def[i], (cutoff_vol * 1000));
+ break;
+ }
+ }
+ }
+
+ /* Check End % */
+ for (i = 1; i < 11; i++) {
+ if (info->soca->ocv_table_def[i] >= full_vol * 1000) {
+ /* unit is 0.001% */
+ *end_per = Calc_Linear_Interpolation(
+ (i-1)*1000, info->soca->ocv_table_def[i-1], i*1000,
+ info->soca->ocv_table_def[i], (full_vol * 1000));
+ break;
+ }
+ }
+
+ /* calc new ocv percent */
+ percent_step = ( *end_per - *start_per) / 10;
+ //printk("PMU : %s : percent_step is %d end per is %d start per is %d\n",__func__, percent_step, *end_per, *start_per);
+
+ for (i = 0; i < 11; i++) {
+ OCV_percent_new[i]
+ = *start_per + percent_step*(i - 0);
+ }
+
+ /* calc new ocv voltage */
+ for (i = 0; i < 11; i++) {
+ for (j = 1; j < 11; j++) {
+ if (1000*j >= OCV_percent_new[i]) {
+ temp = Calc_Linear_Interpolation(
+ info->soca->ocv_table_def[j-1], (j-1)*1000,
+ info->soca->ocv_table_def[j] , j*1000,
+ OCV_percent_new[i]);
+
+ temp = ( (temp/1000) * 4095 ) / 5000;
+
+ battery_init_para[info->num][i*2 + 1] = temp;
+ battery_init_para[info->num][i*2] = temp >> 8;
+
+ break;
+ }
+ }
+ }
+ printk("PMU : %s : new table\n",__func__);
+ for (i = 0; i <= 10; i = i+1) {
+ temp = (battery_init_para[info->num][i*2]<<8)
+ | (battery_init_para[info->num][i*2+1]);
+ /* conversion unit 1 Unit is 1.22mv (5000/4095 mv) */
+ temp = ((temp * 50000 * 10 / 4095) + 5) / 10;
+ printk("PMU : %s : ocv_table %d is %d v\n",__func__, i, temp);
+ }
+
+}
+
+static int ricoh61x_set_OCV_table(struct ricoh61x_battery_info *info)
+{
+ int ret = 0;
+ int i;
+ int full_ocv;
+ int available_cap;
+ int available_cap_ori;
+ int temp;
+ int temp1;
+ int start_per = 0;
+ int end_per = 0;
+ int Rbat;
+ int Ibat_min;
+ uint8_t val;
+ uint8_t val2;
+ uint8_t val_temp;
+
+
+ //get ocv table
+ for (i = 0; i <= 10; i = i+1) {
+ info->soca->ocv_table_def[i] = get_OCV_voltage(info, i, USING);
+ printk(KERN_INFO "PMU: %s : %d0%% voltage = %d uV\n",
+ __func__, i, info->soca->ocv_table_def[i]);
+ }
+
+ //save original header file data
+ for (i = 0; i < 32; i++){
+ info->soca->battery_init_para_original[i] = battery_init_para[info->num][i];
+ }
+
+ temp = (battery_init_para[info->num][24]<<8) | (battery_init_para[info->num][25]);
+ Rbat = temp * 1000 / 512 * 5000 / 4095;
+ info->soca->Rsys = Rbat + 55;
+
+ if ((info->fg_target_ibat == 0) || (info->fg_target_vsys == 0)) { /* normal version */
+
+ temp = (battery_init_para[info->num][22]<<8) | (battery_init_para[info->num][23]);
+ //fa_cap = get_check_fuel_gauge_reg(info, FA_CAP_H_REG, FA_CAP_L_REG,
+ // 0x7fff);
+
+ info->soca->target_ibat = temp*2/10; /* calc 0.2C*/
+ temp1 = (battery_init_para[info->num][0]<<8) | (battery_init_para[info->num][1]);
+// temp = get_OCV_voltage(info, 0) / 1000; /* unit is 1mv*/
+// info->soca->cutoff_ocv = info->soca->target_vsys - Ibat_min * info->soca->Rsys / 1000;
+
+ info->soca->target_vsys = temp1 + ( info->soca->target_ibat * info->soca->Rsys ) / 1000;
+
+
+ } else {
+ info->soca->target_ibat = info->fg_target_ibat;
+ /* calc min vsys value */
+ temp1 = (battery_init_para[info->num][0]<<8) | (battery_init_para[info->num][1]);
+ temp = temp1 + ( info->soca->target_ibat * info->soca->Rsys ) / 1000;
+ if( temp < info->fg_target_vsys) {
+ info->soca->target_vsys = info->fg_target_vsys;
+ } else {
+ info->soca->target_vsys = temp;
+ printk("PMU : %s : setting value of target vsys(%d) is out of range(%d)\n",__func__, info->fg_target_vsys, temp);
+ }
+ }
+
+ //for debug
+ printk("PMU : %s : target_vsys is %d target_ibat is %d\n",__func__,info->soca->target_vsys,info->soca->target_ibat);
+
+ if ((info->soca->target_ibat == 0) || (info->soca->target_vsys == 0)) { /* normal version */
+ } else { /*Slice cutoff voltage version. */
+
+ Ibat_min = -1 * info->soca->target_ibat;
+ info->soca->cutoff_ocv = info->soca->target_vsys - Ibat_min * info->soca->Rsys / 1000;
+
+ full_ocv = (battery_init_para[info->num][20]<<8) | (battery_init_para[info->num][21]);
+ full_ocv = full_ocv * 5000 / 4095;
+
+ ricoh61x_scaling_OCV_table(info, info->soca->cutoff_ocv, full_ocv, &start_per, &end_per);
+
+ /* calc available capacity */
+ /* get avilable capacity */
+ /* battery_init_para23-24 is designe capacity */
+ available_cap = (battery_init_para[info->num][22]<<8)
+ | (battery_init_para[info->num][23]);
+
+ available_cap = available_cap
+ * ((10000 - start_per) / 100) / 100 ;
+
+
+ battery_init_para[info->num][23] = available_cap;
+ battery_init_para[info->num][22] = available_cap >> 8;
+
+ }
+ ret = ricoh61x_clr_bits(info->dev->parent, FG_CTRL_REG, 0x01);
+ if (ret < 0) {
+ dev_err(info->dev, "error in FG_En off\n");
+ goto err;
+ }
+ /////////////////////////////////
+ ret = ricoh61x_read_bank1(info->dev->parent, 0xDC, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ goto err;
+ }
+
+ val_temp = val;
+ val &= 0x0F; //clear bit 4-7
+ val |= 0x10; //set bit 4
+
+ ret = ricoh61x_write_bank1(info->dev->parent, 0xDC, val);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ goto err;
+ }
+
+ ret = ricoh61x_read_bank1(info->dev->parent, 0xDC, &val2);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ goto err;
+ }
+
+ ret = ricoh61x_write_bank1(info->dev->parent, 0xDC, val_temp);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ goto err;
+ }
+
+ //printk("PMU : %s : original 0x%x, before 0x%x, after 0x%x\n",__func__, val_temp, val, val2);
+
+ if (val != val2) {
+ ret = ricoh61x_bulk_writes_bank1(info->dev->parent,
+ BAT_INIT_TOP_REG, 30, battery_init_para[info->num]);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ goto err;
+ }
+ } else {
+ ret = ricoh61x_read_bank1(info->dev->parent, 0xD2, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ goto err;
+ }
+
+ ret = ricoh61x_read_bank1(info->dev->parent, 0xD3, &val2);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ goto err;
+ }
+
+ available_cap_ori = val2 + (val << 8);
+ available_cap = battery_init_para[info->num][23]
+ + (battery_init_para[info->num][22] << 8);
+
+ if (available_cap_ori == available_cap) {
+ ret = ricoh61x_bulk_writes_bank1(info->dev->parent,
+ BAT_INIT_TOP_REG, 22, battery_init_para[info->num]);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ return ret;
+ }
+
+ for (i = 0; i < 6; i++) {
+ ret = ricoh61x_write_bank1(info->dev->parent, 0xD4+i, battery_init_para[info->num][24+i]);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ return ret;
+ }
+ }
+ } else {
+ ret = ricoh61x_bulk_writes_bank1(info->dev->parent,
+ BAT_INIT_TOP_REG, 30, battery_init_para[info->num]);
+ if (ret < 0) {
+ dev_err(info->dev, "batterry initialize error\n");
+ goto err;
+ }
+ }
+ }
+
+ ////////////////////////////////
+
+ return 0;
+err:
+ return ret;
+}
+
+/* Initial setting of battery */
+static int ricoh61x_init_battery(struct ricoh61x_battery_info *info)
+{
+ int ret = 0;
+ uint8_t val;
+ uint8_t val2;
+ unsigned long hour_power_off;
+ unsigned long hour_power_on;
+ long power_off_period;
+ unsigned long seconds;
+ int cc_cap = 0;
+ long cc_cap_mas = 0;
+ bool is_charging = true;
+
+ /* Need to implement initial setting of batery and error */
+ /* -------------------------- */
+#ifdef ENABLE_FUEL_GAUGE_FUNCTION
+
+ /* set relaxation state */
+ if (RICOH61x_REL1_SEL_VALUE > 240)
+ val = 0x0F;
+ else
+ val = RICOH61x_REL1_SEL_VALUE / 16 ;
+
+ /* set relaxation state */
+ if (RICOH61x_REL2_SEL_VALUE > 120)
+ val2 = 0x0F;
+ else
+ val2 = RICOH61x_REL2_SEL_VALUE / 8 ;
+
+ val = val + (val2 << 4);
+
+ //ret = ricoh61x_write_bank1(info->dev->parent, BAT_REL_SEL_REG, val);
+ ret = ricoh61x_write_bank1(info->dev->parent, BAT_REL_SEL_REG, 0);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing BAT_REL_SEL_REG\n");
+ return ret;
+ }
+
+ ret = ricoh61x_read_bank1(info->dev->parent, BAT_REL_SEL_REG, &val);
+ //printk("PMU: ------- BAT_REL_SEL= %xh: =======\n",
+ // val);
+
+ ret = ricoh61x_write_bank1(info->dev->parent, BAT_TA_SEL_REG, 0x00);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing BAT_TA_SEL_REG\n");
+ return ret;
+ }
+
+ //check first power on condition
+ //initial value
+ info->first_pwon = 0;
+ ret = ricoh61x_read(info->dev->parent, PSWR_REG, &val);
+ if (ret < 0) {
+ dev_err(info->dev,"Error in reading PSWR_REG %d\n", ret);
+ return ret;
+ }
+
+ g_soc = val & 0x7f;
+ info->soca->init_pswr = val & 0x7f;
+ printk("PMU FG_RESET : %s : initial pswr = %d\n",__func__,info->soca->init_pswr);
+
+ if(val == 0){
+ printk("PMU : %s : first attached battery\n", __func__);
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 1);
+ info->first_pwon = 1;
+ }
+
+ //info->first_pwon = (val == 0) ? 1 : 0;
+
+ ret = ricoh61x_read(info->dev->parent, RICOH61x_PWR_OFF_HIS, &val);
+ if (ret < 0) {
+ dev_err(info->dev,"Error in reading PWER OFF HIS %d\n", ret);
+ return ret;
+ }
+ printk("PMU : %s : POWER off history 0x%02x is 0x%02x \n", __func__,RICOH61x_PWR_OFF_HIS ,val);
+ //check bit 0 status
+ if(val & 0x01){
+ printk("PMU : %s : Long power on key press\n", __func__);
+ info->first_pwon = 1;
+ }
+
+ ret = ricoh61x_read(info->dev->parent, RICOH61x_PWR_FUNC, &val);
+ if (ret < 0) {
+ dev_err(info->dev,"Error in reading PWER FUNC %d\n", ret);
+ return ret;
+ }
+ printk("PMU : %s : POWER control function 0x%02x is 0x%02x \n", __func__,RICOH61x_PWR_FUNC ,val);
+#if 0
+ //check all bit is clear or not
+ if((val & 0xFF) == 0){
+ printk("PMU : %s : cold boot\n", __func__);
+ info->first_pwon = 1;
+ }
+#endif
+ //end first power on condition
+
+ if(info->first_pwon == 0){
+ //check Power off period
+ //if upper 1day, this power on sequence become first power on
+ hour_power_off = get_storedTime_from_register(info);
+ get_current_time(info, &seconds);
+ hour_power_on = seconds / 3600;
+
+ hour_power_on &= 0xFFFFFF;
+
+ power_off_period = hour_power_on - hour_power_off;
+ if(power_off_period >= 24) {
+ bat_alert_req_flg = 1;
+ } else if(power_off_period < 0){
+ //error case
+ bat_alert_req_flg = 1;
+ } else {
+ bat_alert_req_flg = 0;
+ }
+ printk("PMU : %s : off is %lu, on is %lu, period is %lu, fpon_flag is %d\n", __func__, hour_power_off, hour_power_on, power_off_period, info->first_pwon);
+ }
+
+ if(info->first_pwon) {
+ info->soca->rsoc_ready_flag = 1;
+ }else {
+ info->soca->rsoc_ready_flag = 0;
+ }
+
+ ret = ricoh61x_set_OCV_table(info);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing the OCV Tabler\n");
+ return ret;
+ }
+
+ ret = ricoh61x_write(info->dev->parent, FG_CTRL_REG, 0x11);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ return ret;
+ }
+
+
+ Enable_Test_Register(info);
+
+#endif
+
+#if 0
+ ret = ricoh61x_write(info->dev->parent, VINDAC_REG, 0x01);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ return ret;
+ }
+#endif
+
+#if 0
+ if (info->alarm_vol_mv < 2700 || info->alarm_vol_mv > 3400) {
+ dev_err(info->dev, "alarm_vol_mv is out of range!\n");
+ return -1;
+ }
+#endif
+
+ return ret;
+}
+
+/* Initial setting of charger */
+static int ricoh61x_init_charger(struct ricoh61x_battery_info *info)
+{
+ int err;
+ uint8_t val;
+ uint8_t val2;
+ uint8_t val3;
+ int charge_status;
+ int vfchg_val;
+ int icchg_val;
+ int rbat;
+ int temp;
+
+ info->chg_ctr = 0;
+ info->chg_stat1 = 0;
+
+ err = ricoh61x_set_bits(info->dev->parent, RICOH61x_PWR_FUNC, 0x20);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the PWR FUNC register\n");
+ goto free_device;
+ }
+
+ charge_status = get_power_supply_status(info);
+
+ if (charge_status != POWER_SUPPLY_STATUS_FULL)
+ {
+ /* Disable charging */
+ err = ricoh61x_clr_bits(info->dev->parent,CHGCTL1_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto free_device;
+ }
+ }
+
+
+ err = ricoh61x_read(info->dev->parent, 0xDA, &val);
+ printk("PMU : %s : GCHGDET (0xDA) is 0x%x\n",__func__,val);
+ if (val & 0x30) {
+ /* REGISET1:(0xB6) setting */
+ if ((info->ch_ilim_adp != 0xFF) || (info->ch_ilim_adp <= 0x1D)) {
+ val = info->ch_ilim_adp;
+
+ }
+ else
+ val = 0x0D;
+ err = ricoh61x_write(info->dev->parent, REGISET1_REG,val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing REGISET1_REG %d\n",
+ err);
+ goto free_device;
+ }
+ }
+
+ /* REGISET2:(0xB7) setting */
+ err = ricoh61x_read(info->dev->parent, REGISET2_REG, &val);
+ if (err < 0) {
+ dev_err(info->dev,
+ "Error in read REGISET2_REG %d\n", err);
+ goto free_device;
+ }
+
+ if ((info->ch_ilim_usb != 0xFF) || (info->ch_ilim_usb <= 0x1D)) {
+ val2 = info->ch_ilim_usb;
+ } else {/* Keep OTP value */
+ val2 = (val & 0x1F);
+ }
+
+ /* keep bit 5-7 */
+ val &= 0xE0;
+
+ val = val + val2;
+
+ val |= 0xA0; // Set SDPOVRLIM to allow charge limit 500mA
+
+ err = ricoh61x_write(info->dev->parent, REGISET2_REG,val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing REGISET2_REG %d\n",
+ err);
+ goto free_device;
+ }
+
+ err = ricoh61x_read(info->dev->parent, CHGISET_REG, &val);
+ if (err < 0) {
+ dev_err(info->dev,
+ "Error in read CHGISET_REG %d\n", err);
+ goto free_device;
+ }
+
+ /* Define Current settings value for charging (bit 4~0)*/
+ if ((info->ch_ichg != 0xFF) || (info->ch_ichg <= 0x1D)) {
+ val2 = info->ch_ichg;
+ } else { /* Keep OTP value */
+ val2 = (val & 0x1F);
+ }
+
+ /* Define Current settings at the charge completion (bit 7~6)*/
+ if ((info->ch_icchg != 0xFF) || (info->ch_icchg <= 0x03)) {
+ val3 = info->ch_icchg << 6;
+ } else { /* Keep OTP value */
+ val3 = (val & 0xC0);
+ }
+
+ val = val2 + val3;
+
+ err = ricoh61x_write(info->dev->parent, CHGISET_REG, val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing CHGISET_REG %d\n",
+ err);
+ goto free_device;
+ }
+
+ //debug messeage
+ err = ricoh61x_read(info->dev->parent, CHGISET_REG,&val);
+ printk("PMU : %s : after CHGISET_REG (0x%x) is 0x%x info->ch_ichg is 0x%x info->ch_icchg is 0x%x\n",__func__,CHGISET_REG,val,info->ch_ichg,info->ch_icchg);
+
+ //debug messeage
+ err = ricoh61x_read(info->dev->parent, BATSET1_REG,&val);
+ printk("PMU : %s : before BATSET1_REG (0x%x) is 0x%x info->ch_vbatovset is 0x%x\n",__func__,BATSET1_REG,val,info->ch_vbatovset);
+
+ /* BATSET1_REG(0xBA) setting */
+ err = ricoh61x_read(info->dev->parent, BATSET1_REG, &val);
+ if (err < 0) {
+ dev_err(info->dev,
+ "Error in read BATSET1 register %d\n", err);
+ goto free_device;
+ }
+
+ /* Define Battery overvoltage (bit 4)*/
+ if ((info->ch_vbatovset != 0xFF) || (info->ch_vbatovset <= 0x1)) {
+ val2 = info->ch_vbatovset;
+ val2 = val2 << 4;
+ } else { /* Keep OTP value */
+ val2 = (val & 0x10);
+ }
+
+ /* keep bit 0-3 and bit 5-7 */
+ val = (val & 0xEF);
+
+ val = val + val2;
+
+ val |= 0x08; // set vweak to 3.3
+
+ err = ricoh61x_write(info->dev->parent, BATSET1_REG, val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing BAT1_REG %d\n",
+ err);
+ goto free_device;
+ }
+ //debug messeage
+ err = ricoh61x_read(info->dev->parent, BATSET1_REG,&val);
+ printk("PMU : %s : after BATSET1_REG (0x%x) is 0x%x info->ch_vbatovset is 0x%x\n",__func__,BATSET1_REG,val,info->ch_vbatovset);
+
+ //debug messeage
+ err = ricoh61x_read(info->dev->parent, BATSET2_REG,&val);
+ printk("PMU : %s : before BATSET2_REG (0x%x) is 0x%x info->ch_vrchg is 0x%x info->ch_vfchg is 0x%x \n",__func__,BATSET2_REG,val,info->ch_vrchg,info->ch_vfchg);
+
+
+ /* BATSET2_REG(0xBB) setting */
+ err = ricoh61x_read(info->dev->parent, BATSET2_REG, &val);
+ if (err < 0) {
+ dev_err(info->dev,
+ "Error in read BATSET2 register %d\n", err);
+ goto free_device;
+ }
+
+ /* Define Re-charging voltage (bit 2~0)*/
+ if ((info->ch_vrchg != 0xFF) || (info->ch_vrchg <= 0x04)) {
+ val2 = info->ch_vrchg;
+ } else { /* Keep OTP value */
+ val2 = (val & 0x07);
+ }
+
+ /* Define FULL charging voltage (bit 6~4)*/
+ if ((info->ch_vfchg != 0xFF) || (info->ch_vfchg <= 0x04)) {
+ val3 = info->ch_vfchg;
+ val3 = val3 << 4;
+ } else { /* Keep OTP value */
+ val3 = (val & 0x70);
+ }
+
+ /* keep bit 3 and bit 7 */
+ val = (val & 0x88);
+
+ val = val + val2 + val3;
+
+ err = ricoh61x_write(info->dev->parent, BATSET2_REG, val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing RICOH61x_RE_CHARGE_VOLTAGE %d\n",
+ err);
+ goto free_device;
+ }
+
+ /* Set rising edge setting ([1:0]=01b)for INT in charging */
+ /* and rising edge setting ([3:2]=01b)for charge completion */
+ err = ricoh61x_read(info->dev->parent, RICOH61x_CHG_STAT_DETMOD1, &val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in reading CHG_STAT_DETMOD1 %d\n",
+ err);
+ goto free_device;
+ }
+ val &= 0xf0;
+ val |= 0x05;
+ err = ricoh61x_write(info->dev->parent, RICOH61x_CHG_STAT_DETMOD1, val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing CHG_STAT_DETMOD1 %d\n",
+ err);
+ goto free_device;
+ }
+
+ /* Unmask In charging/charge completion */
+ err = ricoh61x_write(info->dev->parent, RICOH61x_INT_MSK_CHGSTS1, 0xfc);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing INT_MSK_CHGSTS1 %d\n",
+ err);
+ goto free_device;
+ }
+
+ /* Set both edge for VUSB([3:2]=11b)/VADP([1:0]=11b) detect */
+ err = ricoh61x_read(info->dev->parent, RICOH61x_CHG_CTRL_DETMOD1, &val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in reading CHG_CTRL_DETMOD1 %d\n",
+ err);
+ goto free_device;
+ }
+ val &= 0xf0;
+ val |= 0x0f;
+ err = ricoh61x_write(info->dev->parent, RICOH61x_CHG_CTRL_DETMOD1, val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing CHG_CTRL_DETMOD1 %d\n",
+ err);
+ goto free_device;
+ }
+
+ /* Unmask In VUSB/VADP completion */
+ err = ricoh61x_write(info->dev->parent, RICOH61x_INT_MSK_CHGCTR, 0xfc);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing INT_MSK_CHGSTS1 %d\n",
+ err);
+ goto free_device;
+ }
+
+ if (charge_status != POWER_SUPPLY_STATUS_FULL)
+ {
+ /* Enable charging */
+ err = ricoh61x_set_bits(info->dev->parent,CHGCTL1_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto free_device;
+ }
+ }
+ /* get OCV100_min, OCV100_min*/
+ temp = (battery_init_para[info->num][24]<<8) | (battery_init_para[info->num][25]);
+ rbat = temp * 1000 / 512 * 5000 / 4095;
+
+ /* get vfchg value */
+ err = ricoh61x_read(info->dev->parent, BATSET2_REG, &val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in reading the batset2reg\n");
+ goto free_device;
+ }
+ val &= 0x70;
+ val2 = val >> 4;
+ if (val2 <= 3) {
+ vfchg_val = 4050 + val2 * 50;
+ } else {
+ vfchg_val = 4350;
+ }
+ printk("PMU : %s : test test val %d, val2 %d vfchg %d\n", __func__, val, val2, vfchg_val);
+
+ /* get value */
+ err = ricoh61x_read(info->dev->parent, CHGISET_REG, &val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in reading the chgisetreg\n");
+ goto free_device;
+ }
+ val &= 0xC0;
+ val2 = val >> 6;
+ icchg_val = 50 + val2 * 50;
+ printk("PMU : %s : test test val %d, val2 %d icchg %d\n", __func__, val, val2, icchg_val);
+
+ info->soca->OCV100_min = ( vfchg_val * 99 / 100 - (icchg_val * (rbat +20))/1000 - 20 ) * 1000;
+ info->soca->OCV100_max = ( vfchg_val * 101 / 100 - (icchg_val * (rbat +20))/1000 + 20 ) * 1000;
+
+ printk("PMU : %s : 100 min %d, 100 max %d vfchg %d icchg %d rbat %d\n",__func__,
+ info->soca->OCV100_min,info->soca->OCV100_max,vfchg_val,icchg_val,rbat);
+
+#ifdef ENABLE_LOW_BATTERY_DETECTION
+ /* Set ADRQ=00 to stop ADC */
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT3, 0x0);
+#if 0
+ /* Enable VSYS threshold Low interrupt */
+ ricoh61x_write(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x10);
+ /* Set ADC auto conversion interval 250ms */
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT2, 0x0);
+ /* Enable VSYS pin conversion in auto-ADC */
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT1, 0x10);
+ /* Set VSYS threshold low voltage value = (voltage(V)*255)/(3*2.5) */
+ val = info->alarm_vol_mv * 255 / 7500;
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_VSYS_THL, val);
+#else
+ /* Enable VBAT threshold Low interrupt */
+ ricoh61x_write(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x02);
+ /* Set ADC auto conversion interval 250ms */
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT2, 0x0);
+ /* Enable VBAT pin conversion in auto-ADC */
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT1, 0x12);
+ /* Set VBAT threshold low voltage value = (voltage(V)*255)/(2*2.5) */
+ val = (info->alarm_vol_mv - 20) * 255 / 5000;
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_VBAT_THL, val);
+#endif
+#ifdef ENABLE_BATTERY_TEMP_DETECTION
+ /* Enable VTHM threshold Low interrupt */
+ ricoh61x_set_bits(info->dev->parent, RICOH61x_ADC_CNT1, 0x20);
+ /* Enable VBAT threshold Low interrupt */
+ ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x20);
+ /* Set VTHM threshold low voltage value = (voltage(V)*255)/(2.5) */
+ val = HIGH_BATTERY_TEMP_VOL * 255 / 2500;
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THL, val);
+ /* Enable VTHM threshold high interrupt */
+ ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC2, 0x20);
+ /* Set VTHM threshold high voltage value = (voltage(V)*255)/(2.5) */
+ val = LOW_BATTERY_TEMP_VOL * 255 / 2500;
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THH, val);
+#endif
+
+ /* Start auto-mode & average 4-time conversion mode for ADC */
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_CNT3, 0x28);
+
+#endif
+
+free_device:
+ return err;
+}
+
+
+static int get_power_supply_status(struct ricoh61x_battery_info *info)
+{
+ uint8_t status;
+ uint8_t supply_state;
+ uint8_t charge_state;
+ int ret = 0;
+
+ /* get power supply status */
+ ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &status);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ return ret;
+ }
+
+ charge_state = (status & 0x1F);
+ supply_state = ((status & 0xC0) >> 6);
+
+ if (info->entry_factory_mode)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ if (supply_state == SUPPLY_STATE_BAT) {
+ info->soca->chg_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else {
+ switch (charge_state) {
+ case CHG_STATE_CHG_OFF:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case CHG_STATE_CHG_READY_VADP:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHG_STATE_CHG_TRICKLE:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case CHG_STATE_CHG_RAPID:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case CHG_STATE_CHG_COMPLETE:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case CHG_STATE_SUSPEND:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case CHG_STATE_VCHG_OVER_VOL:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case CHG_STATE_BAT_ERROR:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHG_STATE_NO_BAT:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHG_STATE_BAT_OVER_VOL:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHG_STATE_BAT_TEMP_ERR:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHG_STATE_DIE_ERR:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHG_STATE_DIE_SHUTDOWN:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case CHG_STATE_NO_BAT2:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHG_STATE_CHG_READY_VUSB:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ default:
+ info->soca->chg_status
+ = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+ }
+
+ return info->soca->chg_status;
+}
+
+static int get_power_supply_Android_status(struct ricoh61x_battery_info *info)
+{
+
+ get_power_supply_status(info);
+
+ /* get power supply status */
+ if (info->entry_factory_mode)
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ switch (info->soca->chg_status) {
+ case POWER_SUPPLY_STATUS_UNKNOWN:
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+
+ case POWER_SUPPLY_STATUS_CHARGING:
+ return POWER_SUPPLY_STATUS_CHARGING;
+ break;
+
+ case POWER_SUPPLY_STATUS_FULL:
+ if(info->soca->displayed_soc == 100 * 100) {
+ return POWER_SUPPLY_STATUS_FULL;
+ } else {
+ return POWER_SUPPLY_STATUS_CHARGING;
+ }
+ break;
+ default:
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+}
+
+extern int ricoh619_charger_detect(void);
+typedef void (*usb_insert_handler) (char inserted);
+extern usb_insert_handler mxc_misc_report_usb;
+
+static int giRICOH619_DCIN;
+int ricoh619_dcin_status(void)
+{
+ return giRICOH619_DCIN;
+}
+
+static void charger_irq_work(struct work_struct *work)
+{
+ struct ricoh61x_battery_info *info
+ = container_of(work, struct ricoh61x_battery_info, irq_work);
+ //uint8_t status;
+ int ret = 0;
+ uint8_t val = 0, ilim_adp = 0, ichg = 0;
+ //uint8_t adp_current_val = 0x0E;
+ //uint8_t usb_current_val = 0x04;
+ extern void led_red(int isOn);
+
+ printk(KERN_INFO "PMU:%s In\n", __func__);
+
+ power_supply_changed(&info->battery);
+
+#if defined (STANDBY_MODE_DEBUG)
+ ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val);
+ if (val & 0xc0) {
+ if(multiple_sleep_mode == 0) {
+ multiple_sleep_mode = 1;
+ printk("PMU: %s sleep time ratio = x60 *****************\n", __func__);
+ } else if(multiple_sleep_mode == 1) {
+ multiple_sleep_mode = 2;
+ printk("PMU: %s sleep time ratio = x3600 *****************\n", __func__);
+ } else if(multiple_sleep_mode == 2) {
+ multiple_sleep_mode = 0;
+ printk("PMU: %s sleep time ratio = x1 *****************\n", __func__);
+ }
+ }
+#elif defined(CHANGE_FL_MODE_DEBUG)
+ ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val);
+ if (val & 0xc0) {
+ if (fl_current < 10000) {
+ fl_current = 10000; // If FL<10mA, Set FL=10mA
+ } else if (fl_current < 20000) {
+ fl_current = 20000; // If FL<20mA, Set FL=20mA
+ } else if (fl_current < 30000) {
+ fl_current = 30000; // If FL<30mA, Set FL=30mA
+ } else if (fl_current < 40000) {
+ fl_current = 40000; // If FL<40mA, Set FL=40mA
+ } else {
+ fl_current = 5000; // If FL>40mA, Set FL=5mA
+ }
+ printk("PMU: %s FL(%d) mA *****************\n", __func__, fl_current/1000);
+ }
+#endif
+// mutex_lock(&info->lock);
+
+#if 0
+ if(info->chg_ctr & 0x02) {
+ uint8_t sts;
+ ret = ricoh61x_read(info->dev->parent, RICOH61x_INT_MON_CHGCTR, &sts);
+ if (ret < 0)
+ dev_err(info->dev, "Error in reading the control register\n");
+
+ sts &= 0x02;
+
+ /* If "sts" is true, USB is plugged. If not, unplugged. */
+ }
+#endif
+ info->chg_ctr = 0;
+ info->chg_stat1 = 0;
+
+ /* Enable Interrupt for VADP/VUSB */
+ ret = ricoh61x_write(info->dev->parent, RICOH61x_INT_MSK_CHGCTR, 0xfc);
+ if (ret < 0)
+ dev_err(info->dev,
+ "%s(): Error in enable charger mask INT %d\n",
+ __func__, ret);
+
+ /* Enable Interrupt for Charging & complete */
+ ret = ricoh61x_write(info->dev->parent, RICOH61x_INT_MSK_CHGSTS1, 0xfc);
+ if (ret < 0)
+ dev_err(info->dev,
+ "%s(): Error in enable charger mask INT %d\n",
+ __func__, ret);
+
+ /* set USB/ADP ILIM */
+ ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ return;
+ }
+
+ val = (val & 0xC0) >> 6;
+ switch (val) {
+ case 0: // plug out USB/ADP
+ printk("%s : val = %d plug out\n",__func__, val);
+ break;
+ case 1: // plug in ADP
+ printk("%s : val = %d plug in ADPt\n",__func__, val);
+ //Add the code of AC adapter Charge and Limit current settings
+ //ret = ricoh61x_write(info->dev->parent, REGISET1_REG, adp_current_val);
+ break;
+ case 2:// plug in USB
+ printk("%s : val = %d plug in USB\n",__func__, val);
+ //Add the code of USB Charge and Limit current settings
+ //ret = ricoh61x_write(info->dev->parent, REGISET2_REG, usb_current_val)
+ break;
+ case 3:// plug in USB/ADP
+ printk("%s : val = %d plug in ADP USB\n",__func__, val);
+ break;
+ default:
+ printk("%s : val = %d unknown\n",__func__, val);
+ break;
+ }
+
+
+ giRICOH619_DCIN = ricoh619_charger_detect();
+ if(giRICOH619_DCIN) {
+ led_red(1);
+ }
+ else {
+ led_red(0);
+ }
+
+ //ricoh61x_read(info->dev->parent, 0xDA, &status);
+ ricoh61x_read(info->dev->parent, CHGISET_REG, &val);
+ val &= 0xe0;
+ //if (status&0x30)
+ if(giRICOH619_DCIN==CDP_CHARGER||giRICOH619_DCIN==DCP_CHARGER)
+ { // set 1000mA if DCP(10) or CDP(01) .
+ switch (gptHWCFG->m_val.bPCB) {
+ case 49: //E60QDX
+ ilim_adp = 0x09; //1000mA
+ ichg = 0x07; //800mA
+ break;
+ default:
+ ilim_adp = 0x0D; //1400mA
+ ichg = 0x09; //1000mA
+ break;
+ }
+ ricoh61x_write(info->dev->parent, REGISET1_REG, ilim_adp);
+ ricoh61x_write(info->dev->parent, CHGISET_REG, val|ichg);
+ }
+ else
+ {
+ ricoh61x_write(info->dev->parent, REGISET1_REG, 0x04);
+ ricoh61x_write(info->dev->parent, CHGISET_REG, val|0x04);
+ }
+ if(mxc_misc_report_usb) {
+ mxc_misc_report_usb(giRICOH619_DCIN?1:0);
+ }
+
+// mutex_unlock(&info->lock);
+ printk(KERN_INFO "PMU:%s Out\n", __func__);
+}
+
+#ifdef ENABLE_LOW_BATTERY_DETECTION
+static void low_battery_irq_work(struct work_struct *work)
+{
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, low_battery_work.work);
+
+ int ret = 0;
+
+ printk(KERN_INFO "PMU:%s In\n", __func__);
+
+ critical_low_flag = 1;
+ power_supply_changed(&info->battery);
+ info->suspend_state = false;
+ printk(KERN_INFO "PMU:%s Set ciritical_low_flag = 1 **********\n", __func__);
+
+#if 0
+ /* Enable VSYS threshold Low interrupt */
+ ricoh61x_write(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x10);
+ if (ret < 0)
+ dev_err(info->dev,
+ "%s(): Error in enable adc mask INT %d\n",
+ __func__, ret);
+#endif
+}
+#endif
+
+#ifdef ENABLE_BATTERY_TEMP_DETECTION
+static void battery_temp_irq_work(struct work_struct *work)
+{
+ struct ricoh61x_battery_info *info = container_of(work,
+ struct ricoh61x_battery_info, battery_temp_work.work);
+
+ int ret = 0;
+ uint8_t val;
+ uint8_t high_temp_vol = HIGH_BATTERY_TEMP_VOL*255/2500;
+ uint8_t low_temp_vol = LOW_BATTERY_TEMP_VOL*255/2500;
+
+ printk(KERN_INFO "PMU:%s In\n", __func__);
+
+ power_supply_changed(&info->battery);
+
+ ricoh61x_read(info->dev->parent, RICOH61x_ADC_VTHMDATAH, &val);
+ printk(KERN_INFO "PMU:%s Battery temperature triggered (VTHMDATA 0x%02X)**********\n", __func__, val);
+
+ if (val < high_temp_vol) {
+ /* Set VTHM threshold high voltage value = (voltage(V)*255)/(2.5) */
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THH, high_temp_vol);
+ printk(KERN_INFO "PMU:%s set VTHM_THH to %02X\n", __func__, high_temp_vol);
+ /* Enable VTHM threshold high interrupt */
+ ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC2, 0x20);
+ }
+ else if (val > low_temp_vol) {
+ /* Set VTHM threshold low voltage value = (voltage(V)*255)/(2.5) */
+ printk(KERN_INFO "PMU:%s set VTHM_THL to %02X\n", __func__, low_temp_vol);
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THL, low_temp_vol);
+ /* Enable VBAT threshold Low interrupt */
+ ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x20);
+ }
+ else {
+ /* Set VTHM threshold low voltage value = (voltage(V)*255)/(2.5) */
+ val = HIGH_BATTERY_TEMP_VOL * 255 / 2500;
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THL, val);
+ printk(KERN_INFO "PMU:%s set VTHM_THL to %02X\n", __func__, val);
+ /* Enable VBAT threshold Low interrupt */
+ ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x20);
+ /* Set VTHM threshold high voltage value = (voltage(V)*255)/(2.5) */
+ val = LOW_BATTERY_TEMP_VOL * 255 / 2500;
+ ricoh61x_write(info->dev->parent, RICOH61x_ADC_VTHM_THH, val);
+ printk(KERN_INFO "PMU:%s set VTHM_THH to %02X\n", __func__, val);
+ /* Enable VTHM threshold high interrupt */
+ ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC2, 0x20);
+ }
+}
+#endif
+
+static irqreturn_t charger_in_isr(int irq, void *battery_info)
+{
+ struct ricoh61x_battery_info *info = battery_info;
+ printk(KERN_INFO "PMU:%s\n", __func__);
+
+ info->chg_stat1 |= 0x01;
+ queue_work(info->workqueue, &info->irq_work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t charger_complete_isr(int irq, void *battery_info)
+{
+ struct ricoh61x_battery_info *info = battery_info;
+ printk(KERN_INFO "PMU:%s\n", __func__);
+
+ info->chg_stat1 |= 0x02;
+ queue_work(info->workqueue, &info->irq_work);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t charger_usb_isr(int irq, void *battery_info)
+{
+ struct ricoh61x_battery_info *info = battery_info;
+ printk(KERN_INFO "PMU:%s\n", __func__);
+
+ info->chg_ctr |= 0x02;
+
+ queue_work(info->workqueue, &info->irq_work);
+
+ info->soca->dischg_state = 0;
+ info->soca->chg_count = 0;
+ if (RICOH61x_SOCA_UNSTABLE == info->soca->status
+ || RICOH61x_SOCA_FG_RESET == info->soca->status)
+ info->soca->stable_count = 11;
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t charger_adp_isr(int irq, void *battery_info)
+{
+ struct ricoh61x_battery_info *info = battery_info;
+ printk(KERN_INFO "PMU:%s\n", __func__);
+
+ info->chg_ctr |= 0x01;
+ queue_work(info->workqueue, &info->irq_work);
+
+ info->soca->dischg_state = 0;
+ info->soca->chg_count = 0;
+ if (RICOH61x_SOCA_UNSTABLE == info->soca->status
+ || RICOH61x_SOCA_FG_RESET == info->soca->status)
+ info->soca->stable_count = 11;
+
+ return IRQ_HANDLED;
+}
+
+
+#ifdef ENABLE_LOW_BATTERY_DETECTION
+/*************************************************************/
+/* for Detecting Low Battery */
+/*************************************************************/
+
+static irqreturn_t adc_vsysl_isr(int irq, void *battery_info)
+{
+
+ struct ricoh61x_battery_info *info = battery_info;
+
+ printk(KERN_INFO "PMU:%s\n", __func__);
+ printk(KERN_INFO "PMU:%s Detect Low Battery Interrupt **********\n", __func__);
+ // critical_low_flag = 1;
+ queue_delayed_work(info->monitor_wqueue, &info->low_battery_work,
+ LOW_BATTERY_DETECTION_TIME*HZ);
+
+ return IRQ_HANDLED;
+}
+#endif
+
+#ifdef ENABLE_BATTERY_TEMP_DETECTION
+/*************************************************************/
+/* for Detecting Battery Temperature */
+/*************************************************************/
+
+static irqreturn_t adc_vtherm_isr(int irq, void *battery_info)
+{
+
+ struct ricoh61x_battery_info *info = battery_info;
+
+ printk(KERN_INFO "PMU:%s\n", __func__);
+ printk(KERN_INFO "PMU:%s Detect Battery Temperature Interrupt **********\n", __func__);
+ queue_delayed_work(info->monitor_wqueue, &info->battery_temp_work, 0);
+
+ return IRQ_HANDLED;
+}
+#endif
+
+/*
+ * Get Charger Priority
+ * - get higher-priority between VADP and VUSB
+ * @ data: higher-priority is stored
+ * true : VUSB
+ * false: VADP
+ */
+static int get_charge_priority(struct ricoh61x_battery_info *info, bool *data)
+{
+ int ret = 0;
+ uint8_t val = 0;
+
+ ret = ricoh61x_read(info->dev->parent, CHGCTL1_REG, &val);
+ val = val >> 7;
+ *data = (bool)val;
+
+ return ret;
+}
+
+/*
+ * Set Charger Priority
+ * - set higher-priority between VADP and VUSB
+ * - data: higher-priority is stored
+ * true : VUSB
+ * false: VADP
+ */
+static int set_charge_priority(struct ricoh61x_battery_info *info, bool *data)
+{
+ int ret = 0;
+ uint8_t val = 0x80;
+
+ if (*data == 1)
+ ret = ricoh61x_set_bits(info->dev->parent, CHGCTL1_REG, val);
+ else
+ ret = ricoh61x_clr_bits(info->dev->parent, CHGCTL1_REG, val);
+
+ return ret;
+}
+
+#ifdef ENABLE_FUEL_GAUGE_FUNCTION
+static int get_check_fuel_gauge_reg(struct ricoh61x_battery_info *info,
+ int Reg_h, int Reg_l, int enable_bit)
+{
+ uint8_t get_data_h, get_data_l;
+ int old_data, current_data;
+ int i;
+ int ret = 0;
+
+ old_data = 0;
+
+ for (i = 0; i < 5 ; i++) {
+ ret = ricoh61x_read(info->dev->parent, Reg_h, &get_data_h);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ return ret;
+ }
+
+ ret = ricoh61x_read(info->dev->parent, Reg_l, &get_data_l);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ return ret;
+ }
+
+ current_data = ((get_data_h & 0xff) << 8) | (get_data_l & 0xff);
+ current_data = (current_data & enable_bit);
+
+ if (current_data == old_data)
+ return current_data;
+ else
+ old_data = current_data;
+ }
+
+ return current_data;
+}
+
+static int calc_capacity(struct ricoh61x_battery_info *info)
+{
+ uint8_t capacity;
+ long capacity_l;
+ int temp;
+ int ret = 0;
+ int nt;
+ int temperature;
+ int cc_cap = 0;
+ long cc_cap_mas =0;
+ int cc_delta;
+ bool is_charging = true;
+
+ if (info->soca->rsoc_ready_flag != 0) {
+ /* get remaining battery capacity from fuel gauge */
+ ret = ricoh61x_read(info->dev->parent, SOC_REG, &capacity);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ return ret;
+ }
+ capacity_l = (long)capacity;
+ } else {
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0);
+ cc_delta = (is_charging == true) ? cc_cap : -cc_cap;
+ capacity_l = (info->soca->init_pswr * 100 + cc_delta) / 100;
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU FG_RESET : %s : capacity %d init_pswr %d cc_delta %d\n",__func__, capacity_l, info->soca->init_pswr, cc_delta);
+#endif
+ }
+
+ temperature = get_battery_temp_2(info) / 10; /* unit 0.1 degree -> 1 degree */
+
+ if (temperature >= 25) {
+ nt = 0;
+ } else if (temperature >= 5) {
+ nt = (25 - temperature) * RICOH61x_TAH_SEL2 * 625 / 100;
+ } else {
+ nt = (625 + (5 - temperature) * RICOH61x_TAL_SEL2 * 625 / 100);
+ }
+
+ temp = capacity_l * 100 * 100 / (10000 - nt);
+
+ temp = min(100, temp);
+ temp = max(0, temp);
+
+ return temp; /* Unit is 1% */
+}
+
+static int calc_capacity_2(struct ricoh61x_battery_info *info)
+{
+ uint8_t val;
+ long capacity;
+ int re_cap, fa_cap;
+ int temp;
+ int ret = 0;
+ int nt;
+ int temperature;
+ int cc_cap = 0;
+ long cc_cap_mas =0;
+ int cc_delta;
+ bool is_charging = true;
+
+
+ if (info->soca->rsoc_ready_flag != 0) {
+ re_cap = get_check_fuel_gauge_reg(info, RE_CAP_H_REG, RE_CAP_L_REG,
+ 0x7fff);
+ fa_cap = get_check_fuel_gauge_reg(info, FA_CAP_H_REG, FA_CAP_L_REG,
+ 0x7fff);
+
+ if (fa_cap != 0) {
+ capacity = ((long)re_cap * 100 * 100 / fa_cap);
+ capacity = (long)(min(10000, (int)capacity));
+ capacity = (long)(max(0, (int)capacity));
+ } else {
+ ret = ricoh61x_read(info->dev->parent, SOC_REG, &val);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ return ret;
+ }
+ capacity = (long)val * 100;
+ }
+ } else {
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas, &is_charging, 0);
+ cc_delta = (is_charging == true) ? cc_cap : -cc_cap;
+ capacity = info->soca->init_pswr * 100 + cc_delta;
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU FG_RESET : %s : capacity %d init_pswr %d cc_delta %d\n",__func__, (int)capacity, info->soca->init_pswr, cc_delta);
+#endif
+ }
+
+ temperature = get_battery_temp_2(info) / 10; /* unit 0.1 degree -> 1 degree */
+
+ if (temperature >= 25) {
+ nt = 0;
+ } else if (temperature >= 5) {
+ nt = (25 - temperature) * RICOH61x_TAH_SEL2 * 625 / 100;
+ } else {
+ nt = (625 + (5 - temperature) * RICOH61x_TAL_SEL2 * 625 / 100);
+ }
+
+ temp = (int)(capacity * 100 * 100 / (10000 - nt));
+
+ temp = min(10000, temp);
+ temp = max(0, temp);
+
+ return temp; /* Unit is 0.01% */
+}
+
+static int get_battery_temp(struct ricoh61x_battery_info *info)
+{
+ int ret = 0;
+ int sign_bit;
+
+ ret = get_check_fuel_gauge_reg(info, TEMP_1_REG, TEMP_2_REG, 0x0fff);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the fuel gauge control register\n");
+ return ret;
+ }
+
+ /* bit3 of 0xED(TEMP_1) is sign_bit */
+ sign_bit = ((ret & 0x0800) >> 11);
+
+ ret = (ret & 0x07ff);
+
+ if (sign_bit == 0) /* positive value part */
+ /* conversion unit */
+ /* 1 unit is 0.0625 degree and retun unit
+ * should be 0.1 degree,
+ */
+ ret = ret * 625 / 1000;
+ else { /*negative value part */
+ ret = (~ret + 1) & 0x7ff;
+ ret = -1 * ret * 625 / 1000;
+ }
+
+ return ret;
+}
+
+static int get_battery_temp_2(struct ricoh61x_battery_info *info)
+{
+ uint8_t reg_buff[2];
+ long temp, temp_off, temp_gain;
+ bool temp_sign, temp_off_sign, temp_gain_sign;
+ int Vsns = 0;
+ int Iout = 0;
+ int Vthm, Rthm;
+ int reg_val = 0;
+ int new_temp;
+ long R_ln1, R_ln2;
+ int ret = 0;
+
+ /* Calculate TEMP */
+ ret = get_check_fuel_gauge_reg(info, TEMP_1_REG, TEMP_2_REG, 0x0fff);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the fuel gauge register\n");
+ goto out;
+ }
+
+ reg_val = ret;
+ temp_sign = (reg_val & 0x0800) >> 11;
+ reg_val = (reg_val & 0x07ff);
+
+ if (temp_sign == 0) /* positive value part */
+ /* the unit is 0.0001 degree */
+ temp = (long)reg_val * 625;
+ else { /*negative value part */
+ reg_val = (~reg_val + 1) & 0x7ff;
+ temp = -1 * (long)reg_val * 625;
+ }
+
+ /* Calculate TEMP_OFF */
+ ret = ricoh61x_bulk_reads_bank1(info->dev->parent,
+ TEMP_OFF_H_REG, 2, reg_buff);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the fuel gauge register\n");
+ goto out;
+ }
+
+ reg_val = reg_buff[0] << 8 | reg_buff[1];
+ temp_off_sign = (reg_val & 0x0800) >> 11;
+ reg_val = (reg_val & 0x07ff);
+
+ if (temp_off_sign == 0) /* positive value part */
+ /* the unit is 0.0001 degree */
+ temp_off = (long)reg_val * 625;
+ else { /*negative value part */
+ reg_val = (~reg_val + 1) & 0x7ff;
+ temp_off = -1 * (long)reg_val * 625;
+ }
+
+ /* Calculate TEMP_GAIN */
+ ret = ricoh61x_bulk_reads_bank1(info->dev->parent,
+ TEMP_GAIN_H_REG, 2, reg_buff);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the fuel gauge register\n");
+ goto out;
+ }
+
+ reg_val = reg_buff[0] << 8 | reg_buff[1];
+ temp_gain_sign = (reg_val & 0x0800) >> 11;
+ reg_val = (reg_val & 0x07ff);
+
+ if (temp_gain_sign == 0) /* positive value part */
+ /* 1 unit is 0.000488281. the result is 0.01 */
+ temp_gain = (long)reg_val * 488281 / 100000;
+ else { /*negative value part */
+ reg_val = (~reg_val + 1) & 0x7ff;
+ temp_gain = -1 * (long)reg_val * 488281 / 100000;
+ }
+
+ /* Calculate VTHM */
+ if (0 != temp_gain)
+ Vthm = (int)((temp - temp_off) / 4095 * 2500 / temp_gain);
+ else {
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU %s Skip to compensate temperature\n", __func__);
+#endif
+ goto out;
+ }
+
+ ret = measure_Ibatt_FG(info, &Iout);
+ Vsns = Iout * 2 / 100;
+
+ if (temp < -120000) {
+ /* Low Temperature */
+ if (0 != (2500 - Vthm)) {
+ Rthm = 10 * 10 * (Vthm - Vsns) / (2500 - Vthm);
+ } else {
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU %s Skip to compensate temperature\n", __func__);
+#endif
+ goto out;
+ }
+
+ R_ln1 = Rthm / 10;
+ R_ln2 = (R_ln1 * R_ln1 * R_ln1 * R_ln1 * R_ln1 / 100000
+ - R_ln1 * R_ln1 * R_ln1 * R_ln1 * 2 / 100
+ + R_ln1 * R_ln1 * R_ln1 * 11
+ - R_ln1 * R_ln1 * 2980
+ + R_ln1 * 449800
+ - 784000) / 10000;
+
+ /* the unit of new_temp is 0.1 degree */
+ new_temp = (int)((100 * 1000 * B_VALUE / (R_ln2 + B_VALUE * 100 * 1000 / 29815) - 27315) / 10);
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU %s low temperature %d\n", __func__, new_temp/10);
+#endif
+ } else if (temp > 520000) {
+ /* High Temperature */
+ if (0 != (2500 - Vthm)) {
+ Rthm = 100 * 10 * (Vthm - Vsns) / (2500 - Vthm);
+ } else {
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU %s Skip to compensate temperature\n", __func__);
+#endif
+ goto out;
+ }
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU %s [Rthm] Rthm %d[ohm]\n", __func__, Rthm);
+#endif
+
+ R_ln1 = Rthm / 10;
+ R_ln2 = (R_ln1 * R_ln1 * R_ln1 * R_ln1 * R_ln1 / 100000 * 15652 / 100
+ - R_ln1 * R_ln1 * R_ln1 * R_ln1 / 1000 * 23103 / 100
+ + R_ln1 * R_ln1 * R_ln1 * 1298 / 100
+ - R_ln1 * R_ln1 * 35089 / 100
+ + R_ln1 * 50334 / 10
+ - 48569) / 100;
+ /* the unit of new_temp is 0.1 degree */
+ new_temp = (int)((100 * 100 * B_VALUE / (R_ln2 + B_VALUE * 100 * 100 / 29815) - 27315) / 10);
+#ifdef _RICOH619_DEBUG_
+ printk(KERN_DEBUG"PMU %s high temperature %d\n", __func__, new_temp/10);
+#endif
+ } else {
+ /* the unit of new_temp is 0.1 degree */
+ new_temp = temp / 1000;
+ }
+
+ return new_temp;
+
+out:
+ new_temp = get_battery_temp(info);
+ return new_temp;
+}
+
+static int get_time_to_empty(struct ricoh61x_battery_info *info)
+{
+ int ret = 0;
+
+ ret = get_check_fuel_gauge_reg(info, TT_EMPTY_H_REG, TT_EMPTY_L_REG,
+ 0xffff);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the fuel gauge control register\n");
+ return ret;
+ }
+
+ /* conversion unit */
+ /* 1unit is 1miniute and return nnit should be 1 second */
+ ret = ret * 60;
+
+ return ret;
+}
+
+static int get_time_to_full(struct ricoh61x_battery_info *info)
+{
+ int ret = 0;
+
+ ret = get_check_fuel_gauge_reg(info, TT_FULL_H_REG, TT_FULL_L_REG,
+ 0xffff);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the fuel gauge control register\n");
+ return ret;
+ }
+
+ ret = ret * 60;
+
+ return ret;
+}
+
+/* battery voltage is get from Fuel gauge */
+static int measure_vbatt_FG(struct ricoh61x_battery_info *info, int *data)
+{
+ int ret = 0;
+
+ if(info->soca->ready_fg == 1) {
+ ret = get_check_fuel_gauge_reg(info, VOLTAGE_1_REG, VOLTAGE_2_REG,
+ 0x0fff);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the fuel gauge control register\n");
+ return ret;
+ }
+
+ *data = ret;
+ /* conversion unit 1 Unit is 1.22mv (5000/4095 mv) */
+ *data = *data * 50000 / 4095;
+ /* return unit should be 1uV */
+ *data = *data * 100;
+ info->soca->Vbat_old = *data;
+ } else {
+ *data = info->soca->Vbat_old;
+ }
+
+ return ret;
+}
+
+static int measure_Ibatt_FG(struct ricoh61x_battery_info *info, int *data)
+{
+ int ret = 0;
+
+ ret = get_check_fuel_gauge_reg(info, CC_AVERAGE1_REG,
+ CC_AVERAGE0_REG, 0x3fff);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the fuel gauge control register\n");
+ return ret;
+ }
+
+ *data = (ret > 0x1fff) ? (ret - 0x4000) : ret;
+ return ret;
+}
+
+/**
+* index : index No.(2 -> 20%)
+* table_num : ocv table selection
+* 0 : Original Table(defined by header file)
+* 1 : Using Table
+*/
+static int get_OCV_init_Data(struct ricoh61x_battery_info *info, int index, int table_num)
+{
+ int ret = 0;
+ if (table_num == USING) {
+ ret = (battery_init_para[info->num][index*2]<<8) | (battery_init_para[info->num][index*2+1]);
+ } else if (table_num == ORIGINAL) {
+ ret = (info->soca->battery_init_para_original[index*2]<<8)
+ | (info->soca->battery_init_para_original[index*2+1]);
+ }
+ return ret;
+}
+
+/**
+* index : index No.(2 -> 20%)
+* table_num : ocv table selection
+* 0 : Original Table
+* 1 : Using Table
+*/
+static int get_OCV_voltage(struct ricoh61x_battery_info *info, int index, int table_num)
+{
+ int ret = 0;
+ ret = get_OCV_init_Data(info, index, table_num);
+ /* conversion unit 1 Unit is 1.22mv (5000/4095 mv) */
+ ret = ret * 50000 / 4095;
+ /* return unit should be 1uV */
+ ret = ret * 100;
+ return ret;
+}
+
+#else
+/* battery voltage is get from ADC */
+static int measure_vbatt_ADC(struct ricoh61x_battery_info *info, int *data)
+{
+ int i;
+ uint8_t data_l = 0, data_h = 0;
+ int ret;
+
+ /* ADC interrupt enable */
+ ret = ricoh61x_set_bits(info->dev->parent, INTEN_REG, 0x08);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in setting the control register bit\n");
+ goto err;
+ }
+
+ /* enable interrupt request of single mode */
+ ret = ricoh61x_set_bits(info->dev->parent, EN_ADCIR3_REG, 0x01);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in setting the control register bit\n");
+ goto err;
+ }
+
+ /* single request */
+ ret = ricoh61x_write(info->dev->parent, ADCCNT3_REG, 0x10);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ goto err;
+ }
+
+ for (i = 0; i < 5; i++) {
+ usleep(1000);
+ dev_info(info->dev, "ADC conversion times: %d\n", i);
+ /* read completed flag of ADC */
+ ret = ricoh61x_read(info->dev->parent, EN_ADCIR3_REG, &data_h);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ goto err;
+ }
+
+ if (data_h & 0x01)
+ goto done;
+ }
+
+ dev_err(info->dev, "ADC conversion too long!\n");
+ goto err;
+
+done:
+ ret = ricoh61x_read(info->dev->parent, VBATDATAH_REG, &data_h);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ goto err;
+ }
+
+ ret = ricoh61x_read(info->dev->parent, VBATDATAL_REG, &data_l);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ goto err;
+ }
+
+ *data = ((data_h & 0xff) << 4) | (data_l & 0x0f);
+ /* conversion unit 1 Unit is 1.22mv (5000/4095 mv) */
+ *data = *data * 5000 / 4095;
+ /* return unit should be 1uV */
+ *data = *data * 1000;
+
+ return 0;
+
+err:
+ return -1;
+}
+#endif
+
+static int measure_vsys_ADC(struct ricoh61x_battery_info *info, int *data)
+{
+ uint8_t data_l = 0, data_h = 0;
+ int ret;
+
+ ret = ricoh61x_read(info->dev->parent, VSYSDATAH_REG, &data_h);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ }
+
+ ret = ricoh61x_read(info->dev->parent, VSYSDATAL_REG, &data_l);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ }
+
+ *data = ((data_h & 0xff) << 4) | (data_l & 0x0f);
+ *data = *data * 1000 * 3 * 5 / 2 / 4095;
+ /* return unit should be 1uV */
+ *data = *data * 1000;
+
+ return 0;
+}
+
+static void ricoh61x_external_power_changed(struct power_supply *psy)
+{
+ struct ricoh61x_battery_info *info;
+
+ info = container_of(psy, struct ricoh61x_battery_info, battery);
+ queue_delayed_work(info->monitor_wqueue,
+ &info->changed_work, HZ / 2);
+ return;
+}
+
+static int gRicoh619_cur_voltage;
+
+int ricoh619_battery_2_msp430_adc(void)
+{
+ int i, battValue, result;
+ const unsigned short battGasgauge[] = {
+ // 3.0V, 3.1V, 3.2V, 3.3V, 3.4V, 3.5V, 3.6V, 3.7V, 3.8V, 3.9V, 4.0V, 4.1V, 4.2V,
+ 767, 791, 812, 833, 852, 877, 903, 928, 950, 979, 993, 1019, 1023,
+ };
+ if (critical_low_flag) return 0;
+
+ if ((!gRicoh619_cur_voltage) || (3000 > gRicoh619_cur_voltage) || (4200 < gRicoh619_cur_voltage))
+ return 1023;
+
+ i = (gRicoh619_cur_voltage - 3000)/100;
+ if (gRicoh619_cur_voltage % 100) {
+ result = (gRicoh619_cur_voltage % 100)/ (100 / (battGasgauge[i+1]-battGasgauge[i]));
+ result += battGasgauge[i];
+ }
+ else
+ result = battGasgauge[i];
+ return result;
+}
+
+static int ricoh61x_batt_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ricoh61x_battery_info *info = dev_get_drvdata(psy->dev->parent);
+ int data = 0;
+ int ret = 0;
+ uint8_t status;
+
+ mutex_lock(&info->lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &status);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the control register\n");
+ mutex_unlock(&info->lock);
+ return ret;
+ }
+ //printk("status : 0x%02x \n", status)
+ if (psy->type == POWER_SUPPLY_TYPE_MAINS)
+ val->intval = (status & 0x80 ? 3 : 0);
+ else if (psy->type == POWER_SUPPLY_TYPE_USB)
+ val->intval = (status & 0x40 ? 3 : 0);
+ else
+ val->intval = (status & 0xC0 ? 3 : 0);
+ //yian, check charge full
+ if (val->intval == 3) {
+ uint8_t rd_status = (status & 0x1F);
+ //printk("rd_status : 0x%02x \n", rd_status);
+ if (rd_status == 0x04) //00100 Charge Complete
+ val->intval = 1;
+ }
+ break;
+ /* this setting is same as battery driver of 584 */
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = get_power_supply_Android_status(info);
+ if (POWER_SUPPLY_STATUS_FULL == ret)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ val->intval = ret;
+ info->status = ret;
+ dev_dbg(info->dev, "Power Supply Status is %d\n",
+ info->status);
+ break;
+
+ /* this setting is same as battery driver of 584 */
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = info->present;
+ break;
+
+ /* current voltage is got from fuel gauge */
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ /* return real vbatt Voltage */
+#ifdef ENABLE_FUEL_GAUGE_FUNCTION
+ if (info->soca->ready_fg)
+ ret = measure_vbatt_FG(info, &data);
+ else {
+ //val->intval = -EINVAL;
+ data = info->cur_voltage * 1000;
+ }
+#else
+ ret = measure_vbatt_ADC(info, &data);
+#endif
+ val->intval = data;
+ /* convert unit uV -> mV */
+ info->cur_voltage = data / 1000;
+
+ gRicoh619_cur_voltage = info->cur_voltage;
+#ifdef _RICOH619_DEBUG_
+ dev_dbg(info->dev, "battery voltage is %d mV\n",
+ info->cur_voltage);
+#endif
+ break;
+
+#ifdef ENABLE_FUEL_GAUGE_FUNCTION
+ /* current battery capacity is get from fuel gauge */
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (info->entry_factory_mode){
+ val->intval = 100;
+ info->capacity = 100;
+ } else if (info->soca->displayed_soc <= 0) {
+ val->intval = 0;
+ info->capacity = 0;
+ } else {
+ val->intval = (info->soca->displayed_soc + 50)/100;
+ info->capacity = (info->soca->displayed_soc + 50)/100;
+ }
+
+ if (critical_low_flag) {
+ uint8_t chg_sts = 0;
+ ret = ricoh61x_read(info->dev->parent, CHGSTATE_REG, &chg_sts);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in reading the status register\n");
+ chg_sts = 0xC0;
+ }
+ if (chg_sts & 0xC0) {
+ critical_low_flag = 0;
+ } else {
+ val->intval = 0;
+ info->capacity = 0;
+ }
+ }
+
+#ifdef _RICOH619_DEBUG_
+ dev_dbg(info->dev, "battery capacity is %d%%\n",
+ info->capacity);
+#endif
+ break;
+
+ /* current temperature of battery */
+ case POWER_SUPPLY_PROP_TEMP:
+ if (info->soca->ready_fg) {
+ ret = 0;
+ val->intval = get_battery_temp_2(info);
+ info->battery_temp = val->intval/10;
+#ifdef _RICOH619_DEBUG_
+ dev_dbg(info->dev,
+ "battery temperature is %d degree\n",
+ info->battery_temp);
+#endif
+ } else {
+ val->intval = info->battery_temp * 10;
+#ifdef _RICOH619_DEBUG_
+ dev_dbg(info->dev, "battery temperature is %d degree\n", info->battery_temp);
+#endif
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ if (info->soca->ready_fg) {
+ ret = get_time_to_empty(info);
+ val->intval = ret;
+ info->time_to_empty = ret/60;
+#ifdef _RICOH619_DEBUG_
+ dev_dbg(info->dev,
+ "time of empty battery is %d minutes\n",
+ info->time_to_empty);
+#endif
+ } else {
+ //val->intval = -EINVAL;
+ val->intval = info->time_to_empty * 60;
+#ifdef _RICOH619_DEBUG_
+ dev_dbg(info->dev, "time of empty battery is %d minutes\n", info->time_to_empty);
+#endif
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ if (info->soca->ready_fg) {
+ ret = get_time_to_full(info);
+ val->intval = ret;
+ info->time_to_full = ret/60;
+#ifdef _RICOH619_DEBUG_
+ dev_dbg(info->dev,
+ "time of full battery is %d minutes\n",
+ info->time_to_full);
+#endif
+ } else {
+ //val->intval = -EINVAL;
+ val->intval = info->time_to_full * 60;
+#ifdef _RICOH619_DEBUG_
+ dev_dbg(info->dev, "time of full battery is %d minutes\n", info->time_to_full);
+#endif
+ }
+ break;
+#endif
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ ret = 0;
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ ret = 0;
+ break;
+
+ default:
+ mutex_unlock(&info->lock);
+ return -ENODEV;
+ }
+
+ mutex_unlock(&info->lock);
+
+ return ret;
+}
+
+static enum power_supply_property ricoh61x_batt_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+
+#ifdef ENABLE_FUEL_GAUGE_FUNCTION
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+#endif
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+static enum power_supply_property ricoh61x_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+struct power_supply power_ntx = {
+ .name = "mc13892_charger",
+ .type = POWER_SUPPLY_TYPE_MAINS|POWER_SUPPLY_TYPE_USB,
+ .properties = ricoh61x_power_props,
+ .num_properties = ARRAY_SIZE(ricoh61x_power_props),
+ .get_property = ricoh61x_batt_get_prop,
+};
+
+struct power_supply powerac = {
+ .name = "acpwr",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = ricoh61x_power_props,
+ .num_properties = ARRAY_SIZE(ricoh61x_power_props),
+ .get_property = ricoh61x_batt_get_prop,
+};
+
+struct power_supply powerusb = {
+ .name = "usbpwr",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = ricoh61x_power_props,
+ .num_properties = ARRAY_SIZE(ricoh61x_power_props),
+ .get_property = ricoh61x_batt_get_prop,
+};
+
+static __devinit int ricoh61x_battery_probe(struct platform_device *pdev)
+{
+ struct ricoh61x_battery_info *info;
+ struct ricoh619_battery_platform_data *pdata;
+ int type_n;
+ int ret, temp;
+
+ printk(KERN_EMERG "PMU: %s : version is %s\n", __func__,RICOH61x_BATTERY_VERSION);
+
+ info = kzalloc(sizeof(struct ricoh61x_battery_info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ info->soca = kzalloc(sizeof(struct ricoh61x_soca_info), GFP_KERNEL);
+ if (!info->soca)
+ return -ENOMEM;
+
+ info->dev = &pdev->dev;
+ info->status = POWER_SUPPLY_STATUS_CHARGING;
+ pdata = pdev->dev.platform_data;
+ info->monitor_time = pdata->monitor_time * HZ;
+ info->alarm_vol_mv = pdata->alarm_vol_mv;
+
+ /* check rage of b,.attery type */
+ type_n = Battery_Type();
+ temp = sizeof(pdata->type)/(sizeof(struct ricoh619_battery_type_data));
+ if(type_n >= temp)
+ {
+ printk("%s : Battery type num is out of range\n", __func__);
+ type_n = 0;
+ }
+ printk("%s type_n=%d,temp is %d\n", __func__, type_n,temp);
+
+ /* check rage of battery num */
+ info->num = Battery_Table();
+ temp = sizeof(battery_init_para)/(sizeof(uint8_t)*32);
+ if(info->num >= (sizeof(battery_init_para)/(sizeof(uint8_t)*32)))
+ {
+ printk("%s : Battery num is out of range\n", __func__);
+ info->num = 0;
+ }
+ printk("%s info->num=%d,temp is %d\n", __func__, info->num,temp);
+
+ /* these valuse are set in platform */
+ info->ch_vfchg = pdata->type[type_n].ch_vfchg;
+ info->ch_vrchg = pdata->type[type_n].ch_vrchg;
+ info->ch_vbatovset = pdata->type[type_n].ch_vbatovset;
+ info->ch_ichg = pdata->type[type_n].ch_ichg;
+ info->ch_ilim_adp = pdata->type[type_n].ch_ilim_adp;
+ info->ch_ilim_usb = pdata->type[type_n].ch_ilim_usb;
+ info->ch_icchg = pdata->type[type_n].ch_icchg;
+ info->fg_target_vsys = pdata->type[type_n].fg_target_vsys;
+ info->fg_target_ibat = pdata->type[type_n].fg_target_ibat;
+ info->fg_poff_vbat = pdata->type[type_n].fg_poff_vbat;
+ info->jt_en = pdata->type[type_n].jt_en;
+ info->jt_hw_sw = pdata->type[type_n].jt_hw_sw;
+ info->jt_temp_h = pdata->type[type_n].jt_temp_h;
+ info->jt_temp_l = pdata->type[type_n].jt_temp_l;
+ info->jt_vfchg_h = pdata->type[type_n].jt_vfchg_h;
+ info->jt_vfchg_l = pdata->type[type_n].jt_vfchg_l;
+ info->jt_ichg_h = pdata->type[type_n].jt_ichg_h;
+ info->jt_ichg_l = pdata->type[type_n].jt_ichg_l;
+
+ /*
+ printk("%s setting value\n", __func__);
+ printk("%s info->ch_vfchg = 0x%x\n", __func__, info->ch_vfchg);
+ printk("%s info->ch_vrchg = 0x%x\n", __func__, info->ch_vrchg);
+ printk("%s info->ch_vbatovset =0x%x\n", __func__, info->ch_vbatovset);
+ printk("%s info->ch_ichg = 0x%x\n", __func__, info->ch_ichg);
+ printk("%s info->ch_ilim_adp =0x%x \n", __func__, info->ch_ilim_adp);
+ printk("%s info->ch_ilim_usb = 0x%x\n", __func__, info->ch_ilim_usb);
+ printk("%s info->ch_icchg = 0x%x\n", __func__, info->ch_icchg);
+ printk("%s info->fg_target_vsys = 0x%x\n", __func__, info->fg_target_vsys);
+ printk("%s info->fg_target_ibat = 0x%x\n", __func__, info->fg_target_ibat);
+ printk("%s info->jt_en = 0x%x\n", __func__, info->jt_en);
+ printk("%s info->jt_hw_sw = 0x%x\n", __func__, info->jt_hw_sw);
+ printk("%s info->jt_temp_h = 0x%x\n", __func__, info->jt_temp_h);
+ printk("%s info->jt_temp_l = 0x%x\n", __func__, info->jt_temp_l);
+ printk("%s info->jt_vfchg_h = 0x%x\n", __func__, info->jt_vfchg_h);
+ printk("%s info->jt_vfchg_l = 0x%x\n", __func__, info->jt_vfchg_l);
+ printk("%s info->jt_ichg_h = 0x%x\n", __func__, info->jt_ichg_h);
+ printk("%s info->jt_ichg_l = 0x%x\n", __func__, info->jt_ichg_l);
+ */
+
+ info->adc_vdd_mv = ADC_VDD_MV; /* 2800; */
+ info->min_voltage = MIN_VOLTAGE; /* 3100; */
+ info->max_voltage = MAX_VOLTAGE; /* 4200; */
+ info->delay = 500;
+ info->entry_factory_mode = false;
+ info->suspend_state = false;
+ info->stop_disp = false;
+
+ mutex_init(&info->lock);
+ platform_set_drvdata(pdev, info);
+
+// info->battery.name = "battery";
+ info->battery.name = "mc13892_bat";
+ info->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ info->battery.properties = ricoh61x_batt_props;
+ info->battery.num_properties = ARRAY_SIZE(ricoh61x_batt_props);
+ info->battery.get_property = ricoh61x_batt_get_prop;
+ info->battery.set_property = NULL;
+ info->battery.external_power_changed
+ = ricoh61x_external_power_changed;
+
+ /* Disable Charger/ADC interrupt */
+ ret = ricoh61x_clr_bits(info->dev->parent, RICOH61x_INTC_INTEN,
+ CHG_INT | ADC_INT);
+ if (ret)
+ goto out;
+
+ ret = ricoh61x_init_battery(info);
+ if (ret)
+ goto out;
+
+#ifdef ENABLE_FACTORY_MODE
+ info->factory_mode_wqueue
+ = create_singlethread_workqueue("ricoh61x_factory_mode");
+ INIT_DELAYED_WORK_DEFERRABLE(&info->factory_mode_work,
+ check_charging_state_work);
+
+ ret = ricoh61x_factory_mode(info);
+ if (ret)
+ goto out;
+
+#endif
+
+ ret = power_supply_register(&pdev->dev, &info->battery);
+
+ if (ret)
+ info->battery.dev->parent = &pdev->dev;
+
+ ret = power_supply_register(&pdev->dev, &powerac);
+ ret = power_supply_register(&pdev->dev, &powerusb);
+ ret = power_supply_register(&pdev->dev, &power_ntx);
+
+ info->monitor_wqueue
+ = create_singlethread_workqueue("ricoh61x_battery_monitor");
+ info->workqueue = create_singlethread_workqueue("r5t61x_charger_in");
+ INIT_WORK(&info->irq_work, charger_irq_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&info->monitor_work,
+ ricoh61x_battery_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&info->displayed_work,
+ ricoh61x_displayed_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&info->charge_stable_work,
+ ricoh61x_stable_charge_countdown_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&info->charge_monitor_work,
+ ricoh61x_charge_monitor_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&info->get_charge_work,
+ ricoh61x_get_charge_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&info->jeita_work, ricoh61x_jeita_work);
+ INIT_DELAYED_WORK(&info->changed_work, ricoh61x_changed_work);
+
+ /* Charger IRQ workqueue settings */
+ charger_irq = pdata->irq;
+
+
+ ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_FONCHGINT,
+ NULL, charger_in_isr, IRQF_ONESHOT,
+ "r5t61x_charger_in", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Can't get CHG_INT IRQ for chrager: %d\n",
+ ret);
+ goto out;
+ }
+
+ ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_FCHGCMPINT,
+ NULL, charger_complete_isr,
+ IRQF_ONESHOT, "r5t61x_charger_comp",
+ info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Can't get CHG_COMP IRQ for chrager: %d\n",
+ ret);
+ goto out;
+ }
+
+ ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_FVUSBDETSINT,
+ NULL, charger_usb_isr, IRQF_ONESHOT,
+ "r5t61x_usb_det", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Can't get USB_DET IRQ for chrager: %d\n",
+ ret);
+ goto out;
+ }
+
+ ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_FVADPDETSINT,
+ NULL, charger_adp_isr, IRQF_ONESHOT,
+ "r5t61x_adp_det", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Can't get ADP_DET IRQ for chrager: %d\n", ret);
+ goto out;
+ }
+
+
+#ifdef ENABLE_LOW_BATTERY_DETECTION
+// ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_VSYSLIR,
+// NULL, adc_vsysl_isr, IRQF_ONESHOT,
+// "r5t61x_adc_vsysl", info);
+ ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_VBATLIR,
+ NULL, adc_vsysl_isr, IRQF_ONESHOT,
+ "r5t61x_adc_vsysl", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Can't get ADC_VSYSL IRQ for chrager: %d\n", ret);
+ goto out;
+ }
+ INIT_DELAYED_WORK_DEFERRABLE(&info->low_battery_work,
+ low_battery_irq_work);
+#endif
+
+#ifdef ENABLE_BATTERY_TEMP_DETECTION
+ ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_VTHMLIR,
+ NULL, adc_vtherm_isr, IRQF_ONESHOT,
+ "r5t61x_adc_vtherm", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Can't get ADC_VTHML IRQ for chrager: %d\n", ret);
+ goto out;
+ }
+
+ ret = request_threaded_irq(charger_irq + RICOH61x_IRQ_VTHMHIR,
+ NULL, adc_vtherm_isr, IRQF_ONESHOT,
+ "r5t61x_adc_vtherm", info);
+ if (ret < 0) {
+ dev_err(&pdev->dev,
+ "Can't get ADC_VTHMH IRQ for chrager: %d\n", ret);
+ goto out;
+ }
+
+ INIT_DELAYED_WORK_DEFERRABLE(&info->battery_temp_work,
+ battery_temp_irq_work);
+#endif
+
+ /* Charger init and IRQ setting */
+ ret = ricoh61x_init_charger(info);
+ if (ret)
+ goto out;
+
+#ifdef ENABLE_FUEL_GAUGE_FUNCTION
+ ret = ricoh61x_init_fgsoca(info);
+#endif
+ queue_delayed_work(info->monitor_wqueue, &info->monitor_work,
+ RICOH61x_MONITOR_START_TIME*HZ);
+
+
+ /* Enable Charger/ADC interrupt */
+ ricoh61x_set_bits(info->dev->parent, RICOH61x_INTC_INTEN, CHG_INT | ADC_INT);
+
+// if(sysfs_create_link(&info->battery.dev->kobj, &info->battery.dev->kobj, "mc13892_bat")) {
+// printk("[%s-%d] create mc13892_bat link fail !\n", __func__, __LINE__);
+// }
+ if(sysfs_create_link(&info->dev->parent->parent->parent->parent->kobj, &info->dev->kobj, "pmic_battery.1")) {
+ printk("[%s-%d] create pmic_battery.1 link fail !\n", __func__, __LINE__);
+ }
+
+ return 0;
+
+out:
+ kfree(info);
+ return ret;
+}
+
+static int __devexit ricoh61x_battery_remove(struct platform_device *pdev)
+{
+ struct ricoh61x_battery_info *info = platform_get_drvdata(pdev);
+ uint8_t val;
+ int ret;
+ int err;
+ int cc_cap = 0;
+ long cc_cap_mas = 0;
+ bool is_charging = true;
+
+ if (g_fg_on_mode
+ && (info->soca->status == RICOH61x_SOCA_STABLE)) {
+ err = ricoh61x_write(info->dev->parent, PSWR_REG, 0x7f);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+ g_soc = 0x7f;
+ set_current_time2register(info);
+
+ } else if (info->soca->status != RICOH61x_SOCA_START
+ && info->soca->status != RICOH61x_SOCA_UNSTABLE
+ && info->soca->rsoc_ready_flag != 0) {
+ if (info->soca->displayed_soc < 50) {
+ val = 1;
+ } else {
+ val = (info->soca->displayed_soc + 50)/100;
+ val &= 0x7f;
+ }
+ ret = ricoh61x_write(info->dev->parent, PSWR_REG, val);
+ if (ret < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+
+ g_soc = val;
+ set_current_time2register(info);
+
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 1);
+ if (ret < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+ }
+
+ if (g_fg_on_mode == 0) {
+ ret = ricoh61x_clr_bits(info->dev->parent,
+ FG_CTRL_REG, 0x01);
+ if (ret < 0)
+ dev_err(info->dev, "Error in clr FG EN\n");
+ }
+
+ /* set rapid timer 300 min */
+ err = ricoh61x_set_bits(info->dev->parent, TIMSET_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ }
+
+ free_irq(charger_irq + RICOH61x_IRQ_FONCHGINT, &info);
+ free_irq(charger_irq + RICOH61x_IRQ_FCHGCMPINT, &info);
+ free_irq(charger_irq + RICOH61x_IRQ_FVUSBDETSINT, &info);
+ free_irq(charger_irq + RICOH61x_IRQ_FVADPDETSINT, &info);
+#ifdef ENABLE_LOW_BATTERY_DETECTION
+// free_irq(charger_irq + RICOH61x_IRQ_VSYSLIR, &info);
+ free_irq(charger_irq + RICOH61x_IRQ_VADPLIR, &info);
+#endif
+#ifdef ENABLE_BATTERY_TEMP_DETECTION
+ free_irq(charger_irq + RICOH61x_IRQ_VTHMLIR, &info);
+ free_irq(charger_irq + RICOH61x_IRQ_VTHMHIR, &info);
+#endif
+
+
+ cancel_delayed_work(&info->monitor_work);
+ cancel_delayed_work(&info->charge_stable_work);
+ cancel_delayed_work(&info->charge_monitor_work);
+ cancel_delayed_work(&info->get_charge_work);
+ cancel_delayed_work(&info->displayed_work);
+ cancel_delayed_work(&info->changed_work);
+#ifdef ENABLE_LOW_BATTERY_DETECTION
+ cancel_delayed_work(&info->low_battery_work);
+#endif
+#ifdef ENABLE_BATTERY_TEMP_DETECTION
+ cancel_delayed_work(&info->battery_temp_work);
+#endif
+#ifdef ENABLE_FACTORY_MODE
+ cancel_delayed_work(&info->factory_mode_work);
+#endif
+ cancel_delayed_work(&info->jeita_work);
+ cancel_work_sync(&info->irq_work);
+
+ flush_workqueue(info->monitor_wqueue);
+ flush_workqueue(info->workqueue);
+#ifdef ENABLE_FACTORY_MODE
+ flush_workqueue(info->factory_mode_wqueue);
+#endif
+
+ destroy_workqueue(info->monitor_wqueue);
+ destroy_workqueue(info->workqueue);
+#ifdef ENABLE_FACTORY_MODE
+ destroy_workqueue(info->factory_mode_wqueue);
+#endif
+
+ power_supply_unregister(&info->battery);
+ kfree(info);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+extern void ricoh_suspend_state_sync(void);
+static int ricoh61x_battery_suspend(struct device *dev)
+{
+ struct ricoh61x_battery_info *info = dev_get_drvdata(dev);
+ uint8_t val;
+ uint8_t val2;
+ int ret;
+ int err;
+ int cc_display2suspend = 0;
+ int cc_cap = 0;
+ long cc_cap_mas =0;
+ bool is_charging = true;
+ int displayed_soc_temp;
+
+ printk("PMU: %s START ================================================================================\n", __func__);
+ ricoh_suspend_state_sync();
+
+ get_current_time(info, &info->sleepEntryTime);
+ dev_info(info->dev, "sleep entry time : %lu secs\n",
+ info->sleepEntryTime);
+
+#ifdef ENABLE_MASKING_INTERRUPT_IN_SLEEP
+ ricoh61x_clr_bits(dev->parent, RICOH61x_INTC_INTEN, CHG_INT);
+#endif
+ info->stop_disp = true;
+ info->soca->suspend_full_flg = false;
+
+ if (g_fg_on_mode
+ && (info->soca->status == RICOH61x_SOCA_STABLE)) {
+ err = ricoh61x_write(info->dev->parent, PSWR_REG, 0x7f);
+ if (err < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+ g_soc = 0x7F;
+ set_current_time2register(info);
+
+ info->soca->suspend_soc = info->soca->displayed_soc;
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 1);
+ if (ret < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ info->soca->suspend_cc = 0;
+
+ } else {
+ if(info->soca->status == RICOH61x_SOCA_LOW_VOL){
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 1);
+ if (ret < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ if(is_charging == true){
+ info->soca->cc_delta = cc_cap;
+ //cc_cap_mas;
+ }else {
+ info->soca->cc_delta = -cc_cap;
+ cc_cap_mas = -cc_cap_mas;
+ }
+
+ info->soca->temp_cc_delta_cap_mas += cc_cap_mas - info->soca->last_cc_delta_cap_mas;
+
+ printk("PMU : %s : Suspend : temp_cc_delta_cap_mas is %ld, cc_delta is %ld, last_cc_delta_cap_mas is %ld\n"
+ ,__func__, info->soca->temp_cc_delta_cap_mas, cc_cap_mas, info->soca->last_cc_delta_cap_mas);
+ displayed_soc_temp = info->soca->displayed_soc;
+
+ if ((info->soca->displayed_soc + 50)/100 >= 100) {
+ displayed_soc_temp = min(10000, displayed_soc_temp);
+ } else {
+ displayed_soc_temp = min(9949, displayed_soc_temp);
+ }
+ displayed_soc_temp = max(0, displayed_soc_temp);
+ info->soca->displayed_soc = displayed_soc_temp;
+
+ info->soca->suspend_soc = info->soca->displayed_soc;
+ info->soca->suspend_cc = 0;
+ info->soca->suspend_rsoc = calc_capacity_2(info);
+
+ }else if (info->soca->rsoc_ready_flag == 0
+ || info->soca->status == RICOH61x_SOCA_START
+ || info->soca->status == RICOH61x_SOCA_UNSTABLE) {
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 2);
+ if (ret < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ if(is_charging == true){
+ info->soca->cc_delta = cc_cap;
+ //cc_cap_mas;
+ }else {
+ info->soca->cc_delta = -cc_cap;
+ cc_cap_mas = -cc_cap_mas;
+ }
+
+ //info->soca->temp_cc_delta_cap_mas += cc_cap_mas - info->soca->last_cc_delta_cap_mas;
+
+ //get charge/discharge value from displayed work to suspend start
+ cc_display2suspend = info->soca->cc_delta - info->soca->last_cc_rrf0;
+ cc_display2suspend = min(400, cc_display2suspend); //fail-safe
+ cc_display2suspend = max(-400, cc_display2suspend);
+ info->soca->last_cc_rrf0 = 0;
+
+ printk("PMU : %s : Suspend : temp_cc_delta_cap_mas is %ld, cc_delta is %ld, last_cc_delta_cap_mas is %ld, cc_display2suspend is %d\n"
+ ,__func__, info->soca->temp_cc_delta_cap_mas, cc_cap_mas, info->soca->last_cc_delta_cap_mas, cc_display2suspend);
+
+ if (info->soca->status == RICOH61x_SOCA_START
+ || info->soca->status == RICOH61x_SOCA_UNSTABLE
+ || info->soca->status == RICOH61x_SOCA_STABLE) {
+ displayed_soc_temp
+ = info->soca->init_pswr * 100 + info->soca->cc_delta;
+ } else {
+ if(info->soca->status == RICOH61x_SOCA_FULL){
+ info->soca->temp_cc_delta_cap += cc_display2suspend;
+ displayed_soc_temp
+ = info->soca->displayed_soc;// + (info->soca->cc_delta/100) *100;
+ } else {
+ displayed_soc_temp
+ = info->soca->displayed_soc + cc_display2suspend;
+ }
+ }
+
+ if ((info->soca->displayed_soc + 50)/100 >= 100) {
+ displayed_soc_temp = min(10000, displayed_soc_temp);
+ } else {
+ displayed_soc_temp = min(9949, displayed_soc_temp);
+ }
+ displayed_soc_temp = max(0, displayed_soc_temp);
+ info->soca->displayed_soc = displayed_soc_temp;
+
+ info->soca->suspend_soc = info->soca->displayed_soc;
+ info->soca->suspend_cc = info->soca->cc_delta % 100;
+
+ val = info->soca->init_pswr + (info->soca->cc_delta/100);
+ val = min(100, val);
+ val = max(1, val);
+
+ info->soca->init_pswr = val;
+ info->soca->suspend_rsoc = (info->soca->init_pswr * 100) + (info->soca->cc_delta % 100);
+
+ } else {
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 1);
+ if (ret < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+
+ if(is_charging == true){
+ info->soca->cc_delta = cc_cap;
+ //cc_cap_mas;
+ }else {
+ info->soca->cc_delta = -cc_cap;
+ cc_cap_mas = -cc_cap_mas;
+ }
+
+ //info->soca->temp_cc_delta_cap_mas += cc_cap_mas - info->soca->last_cc_delta_cap_mas;
+ printk("PMU : %s : Suspend : temp_cc_delta_cap_mas is %ld, cc_delta is %ld, last_cc_delta_cap_mas is %ld\n"
+ ,__func__, info->soca->temp_cc_delta_cap_mas, cc_cap_mas, info->soca->last_cc_delta_cap_mas);
+
+ if (info->soca->status == RICOH61x_SOCA_FULL){
+ info->soca->temp_cc_delta_cap += info->soca->cc_delta;
+ displayed_soc_temp = info->soca->displayed_soc;
+ } else {
+ displayed_soc_temp
+ = info->soca->displayed_soc + info->soca->cc_delta;
+ }
+
+ if ((info->soca->displayed_soc + 50)/100 >= 100) {
+ displayed_soc_temp = min(10000, displayed_soc_temp);
+ } else {
+ displayed_soc_temp = min(9949, displayed_soc_temp);
+ }
+ displayed_soc_temp = max(0, displayed_soc_temp);
+ info->soca->displayed_soc = displayed_soc_temp;
+
+ info->soca->suspend_soc = info->soca->displayed_soc;
+ info->soca->suspend_cc = 0;
+ info->soca->suspend_rsoc = calc_capacity_2(info);
+
+ }
+
+ printk(KERN_INFO "PMU: %s status(%d), rrf(%d), suspend_soc(%d), suspend_cc(%d)\n",
+ __func__, info->soca->status, info->soca->rsoc_ready_flag, info->soca->suspend_soc, info->soca->suspend_cc);
+ printk(KERN_INFO "PMU: %s DSOC(%d), init_pswr(%d), cc_delta(%d)\n",
+ __func__, info->soca->displayed_soc, info->soca->init_pswr, info->soca->cc_delta);
+
+ if (info->soca->displayed_soc < 50) {
+ val = 1;
+ } else {
+ val = (info->soca->displayed_soc + 50)/100;
+ val &= 0x7f;
+ }
+ ret = ricoh61x_write(info->dev->parent, PSWR_REG, val);
+ if (ret < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+
+ g_soc = val;
+ set_current_time2register(info);
+
+ }
+
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 0);
+ info->soca->cc_delta
+ = (is_charging == true) ? cc_cap : -cc_cap;
+
+ printk(KERN_INFO "PMU: %s : STATUS(%d), DSOC(%d), RSOC(%d), init_pswr*100(%d), cc_delta(%d) ====================\n",
+ __func__, info->soca->status, displayed_soc_temp,
+ calc_capacity_2(info), info->soca->init_pswr*100, info->soca->cc_delta);
+
+ if (info->soca->status == RICOH61x_SOCA_DISP
+ || info->soca->status == RICOH61x_SOCA_STABLE
+ || info->soca->status == RICOH61x_SOCA_FULL) {
+ info->soca->soc = calc_capacity_2(info);
+ info->soca->soc_delta =
+ info->soca->soc_delta + (info->soca->soc - info->soca->last_soc);
+
+ } else {
+ info->soca->soc_delta = 0;
+ }
+
+ if (info->soca->status == RICOH61x_SOCA_FULL)
+ {
+ info->soca->suspend_full_flg = true;
+ info->soca->status = RICOH61x_SOCA_DISP;
+ }
+
+ if (info->soca->status == RICOH61x_SOCA_LOW_VOL)
+ {
+ //reset current information
+ info->soca->hurry_up_flg = 0;
+ }
+
+ info->soca->store_fl_current = fl_current;
+ info->soca->store_slp_state = slp_state;
+ info->soca->store_sus_current = sus_current;
+ info->soca->store_hiber_current = hiber_current;
+
+ printk(KERN_INFO "PMU: %s : fl_current(%d), slp_state(%d), sus_current(%d), hiber_current(%d)\n",
+ __func__, info->soca->store_fl_current, info->soca->store_slp_state,
+ info->soca->store_sus_current, info->soca->store_hiber_current);
+
+ /* set rapid timer 300 min */
+ err = ricoh61x_set_bits(info->dev->parent, TIMSET_REG, 0x03);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing the control register\n");
+ }
+
+ /* Enable VBAT threshold Low interrupt */
+ err = ricoh61x_set_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x02);
+ if (err < 0) {
+ dev_err(info->dev, "Error in settind VBAT ThrLow\n");
+ }
+ info->suspend_state = true;
+
+ //Enable relaxtion state
+ /* set relaxation state */
+ if (RICOH61x_REL1_SEL_VALUE > 240)
+ val = 0x0F;
+ else
+ val = RICOH61x_REL1_SEL_VALUE / 16 ;
+
+ /* set relaxation state */
+ if (RICOH61x_REL2_SEL_VALUE > 120)
+ val2 = 0x0F;
+ else
+ val2 = RICOH61x_REL2_SEL_VALUE / 8 ;
+
+ val = val + (val2 << 4);
+
+ err = ricoh61x_write_bank1(info->dev->parent, BAT_REL_SEL_REG, val);
+ if (err < 0) {
+ dev_err(info->dev, "Error in writing BAT_REL_SEL_REG\n");
+ }
+
+
+ cancel_delayed_work(&info->monitor_work);
+ cancel_delayed_work(&info->displayed_work);
+ cancel_delayed_work(&info->charge_stable_work);
+ cancel_delayed_work(&info->charge_monitor_work);
+ cancel_delayed_work(&info->get_charge_work);
+ cancel_delayed_work(&info->changed_work);
+#ifdef ENABLE_LOW_BATTERY_DETECTION
+ cancel_delayed_work(&info->low_battery_work);
+#endif
+#ifdef ENABLE_BATTERY_TEMP_DETECTION
+ cancel_delayed_work(&info->battery_temp_work);
+#endif
+#ifdef ENABLE_FACTORY_MODE
+ cancel_delayed_work(&info->factory_mode_work);
+#endif
+ cancel_delayed_work(&info->jeita_work);
+/* flush_work(&info->irq_work); */
+
+ return 0;
+}
+
+/**
+* get SOC value during period of Suspend/Hibernation
+* this function is only run discharge state
+* info : battery info
+* Period : sleep period
+* sleepCurrent : sleep current
+*
+* return value : delta soc, unit is "minus" 0.01%
+*/
+
+static int calc_cc_value_by_sleepPeriod(struct ricoh61x_battery_info *info, unsigned long Period, int sleepCurrent)
+{
+ int fa_cap; //unit is mAh
+ unsigned long delta_cap; //unit is uAs
+ unsigned long delta_soc; //unit is 0.01%
+
+ fa_cap = (battery_init_para[info->num][22]<<8)
+ | (battery_init_para[info->num][23]);
+
+ if(fa_cap == 0){
+ // avoiding 0 divied
+ return 0;
+ } else {
+ // Check Suspend current is normal
+
+ // delta_cap[uAs] = Period[s] * (Suspend current + FL current)[uA]
+ delta_cap = (Period * sleepCurrent) + info->soca->sus_cc_cap_offset;
+ //delta_soc[0.01%] = (delta_cap/1000)[mAs] * 10000/ (fa_cap * 60 * 60)[mAs];
+ delta_soc = (delta_cap / (fa_cap * 36)) / 10;
+
+ //info->soca->sus_cc_cap_offset[uAs] = delta_cap[uAs] - (delta_soc[0.01%] * (fa_cap[mAs] * 60 * 60/ (100 * 100)))*1000;
+ info->soca->sus_cc_cap_offset = delta_cap - (delta_soc * (fa_cap * 360));
+ //0.01% uAs => fa_cap*360
+ info->soca->sus_cc_cap_offset = min((360*fa_cap), info->soca->sus_cc_cap_offset);
+ info->soca->sus_cc_cap_offset = max(0, info->soca->sus_cc_cap_offset);
+
+ delta_soc = min(10000, delta_soc);
+ delta_soc = max(0, delta_soc);
+
+ printk("PMU : %s : delta_cap is %ld [uAs], Period is %ld [s], fa_cap is %d [mAh], delta_soc is %ld [0.01%%], offset is %d [uAs]\n"
+ ,__func__, delta_cap, Period, fa_cap, delta_soc, info->soca->sus_cc_cap_offset);
+ }
+
+ return (-1 * delta_soc);
+}
+
+/**
+* get SOC value during period of Suspend/Hibernate with current method
+* info : battery info
+* Period : sleep period
+*
+* return value : soc, unit is 0.01%
+*/
+
+static int calc_soc_by_currentMethod(struct ricoh61x_battery_info *info, unsigned long Period)
+{
+ int soc;
+ int sleepCurrent; // unit is uA
+
+ if(info->soca->store_slp_state == 0) {
+ // Check Suspend current is normal
+ if ((info->soca->store_sus_current <= 0)
+ || (info->soca->store_sus_current > RICOH61x_SLEEP_CURRENT_LIMIT)) {
+ if ((sus_current <= 0) || (sus_current > RICOH61x_SLEEP_CURRENT_LIMIT)) {
+ info->soca->store_sus_current = RICOH61x_SUS_CURRENT_DEF;
+ } else {
+ info->soca->store_sus_current = sus_current;
+ }
+ }
+
+ if ((info->soca->store_fl_current < 0)
+ || (info->soca->store_fl_current > RICOH61x_FL_CURRENT_LIMIT)) {
+ if ((fl_current < 0) || (fl_current > RICOH61x_FL_CURRENT_LIMIT)) {
+ info->soca->store_fl_current = RICOH61x_FL_CURRENT_DEF;
+ } else {
+ info->soca->store_fl_current = fl_current;
+ }
+ }
+
+ sleepCurrent = info->soca->store_sus_current + info->soca->store_fl_current;
+
+ if (sleepCurrent < RICOH61x_SUS_CURRENT_THRESH) {
+ // Calculate cc_delta from [Suspend current * Sleep period]
+ info->soca->cc_delta = calc_cc_value_by_sleepPeriod(info, Period, sleepCurrent);
+ printk(KERN_INFO "PMU: %s Suspend(S/W) slp_current(%d), sus_current(%d), fl_current(%d), cc_delta(%d) ----------\n",
+ __func__, sleepCurrent, info->soca->store_sus_current, info->soca->store_fl_current, info->soca->cc_delta);
+ } else {
+ // Calculate cc_delta between Sleep-In and Sleep-Out
+ info->soca->cc_delta -= info->soca->suspend_cc;
+ printk(KERN_INFO "PMU: %s Suspend(H/W) slp_current(%d), sus_current(%d), fl_current(%d), cc_delta(%d) ----------\n",
+ __func__, sleepCurrent, info->soca->store_sus_current, info->soca->store_fl_current, info->soca->cc_delta);
+ }
+ } else {
+ // Check Hibernate current is normal
+ if ((info->soca->store_hiber_current <= 0)
+ || (info->soca->store_hiber_current > RICOH61x_SLEEP_CURRENT_LIMIT)) {
+ if ((hiber_current <= 0) || (hiber_current > RICOH61x_SLEEP_CURRENT_LIMIT)) {
+ sleepCurrent = RICOH61x_HIBER_CURRENT_DEF;
+ } else {
+ sleepCurrent = hiber_current;
+ }
+ } else {
+ sleepCurrent = info->soca->store_hiber_current;
+ }
+ // Calculate cc_delta from [Hibernate current * Sleep period]
+ info->soca->cc_delta = calc_cc_value_by_sleepPeriod(info, Period, sleepCurrent);
+ printk(KERN_INFO "PMU: %s Hibernate(S/W) hiber_current(%d), cc_delta(%d) ----------\n",
+ __func__, sleepCurrent, info->soca->cc_delta);
+ }
+
+ soc = info->soca->suspend_soc + info->soca->cc_delta;
+
+ printk("PMU : %s : slp_state is %d, soc is %d [0.01%%] ----------\n"
+ , __func__, info->soca->store_slp_state, soc);
+
+ // soc range is 0~10000
+ return soc;
+}
+
+
+static int ricoh61x_battery_resume(struct device *dev)
+{
+ struct ricoh61x_battery_info *info = dev_get_drvdata(dev);
+ uint8_t val;
+ int ret;
+ int displayed_soc_temp;
+ int cc_cap = 0;
+ long cc_cap_mas = 0;
+ bool is_charging = true;
+ bool is_jeita_updated;
+ int i;
+ unsigned long suspend_period_time; //unit is sec
+ int soc_voltage, soc_current;
+ int resume_rsoc;
+
+ printk("PMU: %s START ================================================================================\n", __func__);
+
+ get_current_time(info, &info->sleepExitTime);
+ dev_info(info->dev, "sleep exit time : %lu secs\n",
+ info->sleepExitTime);
+
+ suspend_period_time = info->sleepExitTime - info->sleepEntryTime;
+
+#ifdef STANDBY_MODE_DEBUG
+ if(multiple_sleep_mode == 0) {
+// suspend_period_time *= 1;
+ } else if(multiple_sleep_mode == 1) {
+ suspend_period_time *= 60;
+ } else if(multiple_sleep_mode == 2) {
+ suspend_period_time *= 3600;
+ }
+#endif
+
+ printk("PMU : %s : suspend_period_time is %lu, sleepExitTime is %lu sleepEntryTime is %lu ==========\n",
+ __func__, suspend_period_time,info->sleepExitTime,info->sleepEntryTime);
+
+ /* Clear VBAT threshold Low interrupt */
+ ret = ricoh61x_clr_bits(info->dev->parent, RICOH61x_INT_EN_ADC1, 0x02);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in clearing VBAT ThrLow\n");
+ }
+
+
+#ifdef ENABLE_MASKING_INTERRUPT_IN_SLEEP
+ ricoh61x_set_bits(dev->parent, RICOH61x_INTC_INTEN, CHG_INT);
+#endif
+ ret = check_jeita_status(info, &is_jeita_updated);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in updating JEITA %d\n", ret);
+ }
+
+ if (info->entry_factory_mode) {
+ info->soca->displayed_soc = -EINVAL;
+ } else {
+ info->soca->soc = info->soca->suspend_soc + info->soca->suspend_cc;
+
+ if (RICOH61x_SOCA_START == info->soca->status
+ || RICOH61x_SOCA_UNSTABLE == info->soca->status
+ || info->soca->rsoc_ready_flag == 0) {
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 2);
+ if (ret < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+ info->soca->cc_delta
+ = (is_charging == true) ? cc_cap : -cc_cap;
+
+ //this is for CC delta issue for Hibernate
+ if((info->soca->cc_delta - info->soca->suspend_cc) <= 0){
+ // Discharge Processing
+ printk(KERN_INFO "PMU: %s : Discharge Processing (rrf=0)\n", __func__);
+
+ // Calculate SOC by Current Method
+ soc_current = calc_soc_by_currentMethod(info, suspend_period_time);
+
+ // Calculate SOC by Voltage Method
+ soc_voltage = calc_soc_by_voltageMethod(info);
+
+ printk(KERN_INFO "PMU: %s : soc_current(%d), soc_voltage(%d), Diff(%d) ==========\n",
+ __func__, soc_current, soc_voltage, (soc_current - soc_voltage));
+
+ // If difference is small, use current method. If not, use voltage method.
+ if ((soc_current - soc_voltage) < 1000) {
+ // Use Current method if difference is small
+ displayed_soc_temp = soc_current;
+ update_rsoc_on_currentMethod(info, soc_current);
+ } else {
+ // Use Voltage method if difference is large
+ displayed_soc_temp = soc_voltage;
+ update_rsoc_on_voltageMethod(info, soc_voltage);
+ }
+ } else {
+ // Charge Processing
+ val = info->soca->init_pswr + (info->soca->cc_delta/100);
+ val = min(100, val);
+ val = max(1, val);
+
+ info->soca->init_pswr = val;
+
+ if (RICOH61x_SOCA_START == info->soca->status
+ || RICOH61x_SOCA_UNSTABLE == info->soca->status
+ || RICOH61x_SOCA_STABLE == info->soca->status) {
+// displayed_soc_temp = info->soca->suspend_soc + info->soca->cc_delta;
+ displayed_soc_temp = (info->soca->init_pswr*100) + info->soca->cc_delta%100;
+ } else {
+ info->soca->cc_delta = info->soca->cc_delta - info->soca->suspend_cc;
+ if ((info->soca->cc_delta < 400) && (info->soca->suspend_full_flg == true)){
+ displayed_soc_temp = info->soca->suspend_soc;
+ info->soca->temp_cc_delta_cap += info->soca->cc_delta;
+ info->soca->cc_delta = info->soca->suspend_cc;
+ printk("PMU: %s : under 400 cc_delta is %d, temp_cc_delta is %d\n",
+ __func__, info->soca->cc_delta, info->soca->temp_cc_delta_cap);
+ }else {
+ displayed_soc_temp = info->soca->suspend_soc + info->soca->cc_delta;
+ info->soca->temp_cc_delta_cap = 0;
+ }
+ }
+ }
+
+ } else {
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 1);
+ if (ret < 0)
+ dev_err(info->dev, "Read cc_sum Error !!-----\n");
+ info->soca->cc_delta
+ = (is_charging == true) ? cc_cap : -cc_cap;
+
+ printk("PMU: %s cc_delta(%d), suspend_cc(%d), Diff(%d)\n",
+ __func__, info->soca->cc_delta, info->soca->suspend_cc, (info->soca->cc_delta - info->soca->suspend_cc));
+ //this is for CC delta issue for Hibernate
+ if((info->soca->cc_delta - info->soca->suspend_cc) <= 0){
+ // Discharge Processing
+ printk(KERN_INFO "PMU: %s : Discharge Processing (rrf=1)\n", __func__);
+
+ // Calculate SOC by Current Method
+ soc_current = calc_soc_by_currentMethod(info, suspend_period_time);
+
+ // Calculate SOC by Voltage Method
+ soc_voltage = calc_soc_by_voltageMethod(info);
+
+ printk(KERN_INFO "PMU: %s : soc_current(%d), soc_voltage(%d), Diff(%d)\n",
+ __func__, soc_current, soc_voltage, (soc_current - soc_voltage));
+
+ // If difference is small, use current method. If not, use voltage method.
+ if ((soc_current - soc_voltage) < 1000) {
+ // Use Current method if difference is small
+ displayed_soc_temp = soc_current;
+ update_rsoc_on_currentMethod(info, soc_current);
+ } else {
+ // Use Voltage method if difference is large
+ displayed_soc_temp = soc_voltage;
+ update_rsoc_on_voltageMethod(info, soc_voltage);
+ }
+ } else {
+ // Charge Processing
+ info->soca->cc_delta = info->soca->cc_delta - info->soca->suspend_cc;
+ if ((info->soca->cc_delta < 400) && (info->soca->suspend_full_flg == true)){
+ displayed_soc_temp = info->soca->suspend_soc;
+ info->soca->temp_cc_delta_cap += info->soca->cc_delta;
+ info->soca->cc_delta = info->soca->suspend_cc;
+ printk("PMU: %s : under 400 cc_delta is %d, temp_cc_delta is %d\n",
+ __func__, info->soca->cc_delta, info->soca->temp_cc_delta_cap);
+ }else {
+ displayed_soc_temp = info->soca->suspend_soc + info->soca->cc_delta;
+ info->soca->temp_cc_delta_cap = 0;
+ }
+ }
+ }
+
+ /* Check "zero_flg" in all states */
+ if (info->soca->zero_flg == 1) {
+ if((info->soca->Ibat_ave >= 0)
+ || (displayed_soc_temp >= 100)){
+ info->soca->zero_flg = 0;
+ } else {
+ displayed_soc_temp = 0;
+ }
+ } else if (displayed_soc_temp < 100) {
+ /* keep DSOC = 1 when Vbat is over 3.4V*/
+ if( info->fg_poff_vbat != 0) {
+ if (info->soca->Vbat_ave < 2000*1000) { /* error value */
+ displayed_soc_temp = 100;
+ } else if (info->soca->Vbat_ave < info->fg_poff_vbat*1000) {
+ displayed_soc_temp = 0;
+ info->soca->zero_flg = 1;
+ } else {
+ displayed_soc_temp = 100;
+ }
+ }
+ }
+ displayed_soc_temp = min(10000, displayed_soc_temp);
+ displayed_soc_temp = max(0, displayed_soc_temp);
+
+ if (0 == info->soca->jt_limit) {
+ check_charge_status_2(info, displayed_soc_temp);
+ } else {
+ info->soca->displayed_soc = displayed_soc_temp;
+ }
+
+ val = (info->soca->displayed_soc + 50)/100;
+ val &= 0x7f;
+ ret = ricoh61x_write(info->dev->parent, PSWR_REG, val);
+ if (ret < 0)
+ dev_err(info->dev, "Error in writing PSWR_REG\n");
+
+ g_soc = val;
+ set_current_time2register(info);
+
+
+ if ((RICOH61x_SOCA_DISP == info->soca->status)
+ || (RICOH61x_SOCA_STABLE == info->soca->status)){
+ info->soca->last_soc = calc_capacity_2(info);
+ }
+ }
+
+ ret = calc_capacity_in_period(info, &cc_cap, &cc_cap_mas,
+ &is_charging, 0);
+ if(is_charging == true) {
+ info->soca->cc_delta = cc_cap;
+ //cc_cap_mas;
+ } else {
+ info->soca->cc_delta = -cc_cap;
+ cc_cap_mas = -cc_cap_mas;
+ }
+
+ //if (info->soca->status == RICOH61x_SOCA_LOW_VOL)
+ //{
+ info->soca->last_cc_delta_cap = info->soca->cc_delta;
+ info->soca->last_cc_delta_cap_mas = cc_cap_mas;
+ //}
+
+
+
+ printk(KERN_INFO "PMU: %s : STATUS(%d), DSOC(%d), RSOC(%d), init_pswr*100(%d), cc_delta(%d) ====================\n",
+ __func__, info->soca->status, displayed_soc_temp, calc_capacity_2(info), info->soca->init_pswr*100, info->soca->cc_delta);
+
+ ret = measure_vbatt_FG(info, &info->soca->Vbat_ave);
+ ret = measure_vsys_ADC(info, &info->soca->Vsys_ave);
+ ret = measure_Ibatt_FG(info, &info->soca->Ibat_ave);
+
+ //Disable relaxtion state
+ ret = ricoh61x_write_bank1(info->dev->parent, BAT_REL_SEL_REG, 0);
+ if (ret < 0) {
+ dev_err(info->dev, "Error in writing BAT_REL_SEL_REG\n");
+ }
+
+ info->stop_disp = false;
+
+ power_supply_changed(&info->battery);
+ queue_delayed_work(info->monitor_wqueue, &info->displayed_work, HZ);
+
+ if (RICOH61x_SOCA_UNSTABLE == info->soca->status) {
+ info->soca->stable_count = 10;
+ queue_delayed_work(info->monitor_wqueue,
+ &info->charge_stable_work,
+ RICOH61x_FG_STABLE_TIME*HZ/10);
+ } else if (RICOH61x_SOCA_FG_RESET == info->soca->status) {
+ info->soca->stable_count = 1;
+
+ for (i = 0; i < 3; i = i+1)
+ info->soca->reset_soc[i] = 0;
+ info->soca->reset_count = 0;
+
+ queue_delayed_work(info->monitor_wqueue,
+ &info->charge_stable_work,
+ RICOH61x_FG_RESET_TIME*HZ);
+ }
+
+ queue_delayed_work(info->monitor_wqueue, &info->monitor_work,
+ info->monitor_time);
+
+ queue_delayed_work(info->monitor_wqueue, &info->charge_monitor_work,
+ RICOH61x_CHARGE_RESUME_TIME * HZ);
+
+ info->soca->chg_count = 0;
+ queue_delayed_work(info->monitor_wqueue, &info->get_charge_work,
+ RICOH61x_CHARGE_RESUME_TIME * HZ);
+ if (info->jt_en) {
+ if (!info->jt_hw_sw) {
+ queue_delayed_work(info->monitor_wqueue, &info->jeita_work,
+ RICOH61x_JEITA_UPDATE_TIME * HZ);
+ }
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops ricoh61x_battery_pm_ops = {
+ .suspend = ricoh61x_battery_suspend,
+ .resume = ricoh61x_battery_resume,
+};
+#endif
+
+static struct platform_driver ricoh61x_battery_driver = {
+ .driver = {
+ .name = "ricoh619-battery",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &ricoh61x_battery_pm_ops,
+#endif
+ },
+ .probe = ricoh61x_battery_probe,
+ .remove = __devexit_p(ricoh61x_battery_remove),
+};
+
+static int __init ricoh61x_battery_init(void)
+{
+ printk(KERN_INFO "PMU: %s\n", __func__);
+ return platform_driver_register(&ricoh61x_battery_driver);
+}
+module_init(ricoh61x_battery_init);
+
+static void __exit ricoh61x_battery_exit(void)
+{
+ platform_driver_unregister(&ricoh61x_battery_driver);
+}
+module_exit(ricoh61x_battery_exit);
+
+MODULE_DESCRIPTION("RICOH R5T619 Battery driver");
+MODULE_ALIAS("platform:ricoh619-battery");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c
new file mode 100644
index 00000000..d36c289a
--- /dev/null
+++ b/drivers/power/s3c_adc_battery.c
@@ -0,0 +1,437 @@
+/*
+ * iPAQ h1930/h1940/rx1950 battery controller driver
+ * Copyright (c) Vasily Khoruzhick
+ * Based on h1940_battery.c by Arnaud Patard
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/leds.h>
+#include <linux/gpio.h>
+#include <linux/err.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/s3c_adc_battery.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+
+#include <plat/adc.h>
+
+#define BAT_POLL_INTERVAL 10000 /* ms */
+#define JITTER_DELAY 500 /* ms */
+
+struct s3c_adc_bat {
+ struct power_supply psy;
+ struct s3c_adc_client *client;
+ struct s3c_adc_bat_pdata *pdata;
+ int volt_value;
+ int cur_value;
+ unsigned int timestamp;
+ int level;
+ int status;
+ int cable_plugged:1;
+};
+
+static struct delayed_work bat_work;
+
+static void s3c_adc_bat_ext_power_changed(struct power_supply *psy)
+{
+ schedule_delayed_work(&bat_work,
+ msecs_to_jiffies(JITTER_DELAY));
+}
+
+static enum power_supply_property s3c_adc_backup_bat_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+};
+
+static int s3c_adc_backup_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct s3c_adc_bat *bat = container_of(psy, struct s3c_adc_bat, psy);
+
+ if (!bat) {
+ dev_err(psy->dev, "%s: no battery infos ?!\n", __func__);
+ return -EINVAL;
+ }
+
+ if (bat->volt_value < 0 ||
+ jiffies_to_msecs(jiffies - bat->timestamp) >
+ BAT_POLL_INTERVAL) {
+ bat->volt_value = s3c_adc_read(bat->client,
+ bat->pdata->backup_volt_channel);
+ bat->volt_value *= bat->pdata->backup_volt_mult;
+ bat->timestamp = jiffies;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = bat->volt_value;
+ return 0;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = bat->pdata->backup_volt_min;
+ return 0;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = bat->pdata->backup_volt_max;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static struct s3c_adc_bat backup_bat = {
+ .psy = {
+ .name = "backup-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = s3c_adc_backup_bat_props,
+ .num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props),
+ .get_property = s3c_adc_backup_bat_get_property,
+ .use_for_apm = 1,
+ },
+};
+
+static enum power_supply_property s3c_adc_main_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static int calc_full_volt(int volt_val, int cur_val, int impedance)
+{
+ return volt_val + cur_val * impedance / 1000;
+}
+
+static int charge_finished(struct s3c_adc_bat *bat)
+{
+ return bat->pdata->gpio_inverted ?
+ !gpio_get_value(bat->pdata->gpio_charge_finished) :
+ gpio_get_value(bat->pdata->gpio_charge_finished);
+}
+
+static int s3c_adc_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct s3c_adc_bat *bat = container_of(psy, struct s3c_adc_bat, psy);
+
+ int new_level;
+ int full_volt;
+ const struct s3c_adc_bat_thresh *lut = bat->pdata->lut_noac;
+ unsigned int lut_size = bat->pdata->lut_noac_cnt;
+
+ if (!bat) {
+ dev_err(psy->dev, "no battery infos ?!\n");
+ return -EINVAL;
+ }
+
+ if (bat->volt_value < 0 || bat->cur_value < 0 ||
+ jiffies_to_msecs(jiffies - bat->timestamp) >
+ BAT_POLL_INTERVAL) {
+ bat->volt_value = s3c_adc_read(bat->client,
+ bat->pdata->volt_channel) * bat->pdata->volt_mult;
+ bat->cur_value = s3c_adc_read(bat->client,
+ bat->pdata->current_channel) * bat->pdata->current_mult;
+ bat->timestamp = jiffies;
+ }
+
+ if (bat->cable_plugged &&
+ ((bat->pdata->gpio_charge_finished < 0) ||
+ !charge_finished(bat))) {
+ lut = bat->pdata->lut_acin;
+ lut_size = bat->pdata->lut_acin_cnt;
+ }
+
+ new_level = 100000;
+ full_volt = calc_full_volt((bat->volt_value / 1000),
+ (bat->cur_value / 1000), bat->pdata->internal_impedance);
+
+ if (full_volt < calc_full_volt(lut->volt, lut->cur,
+ bat->pdata->internal_impedance)) {
+ lut_size--;
+ while (lut_size--) {
+ int lut_volt1;
+ int lut_volt2;
+
+ lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur,
+ bat->pdata->internal_impedance);
+ lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur,
+ bat->pdata->internal_impedance);
+ if (full_volt < lut_volt1 && full_volt >= lut_volt2) {
+ new_level = (lut[1].level +
+ (lut[0].level - lut[1].level) *
+ (full_volt - lut_volt2) /
+ (lut_volt1 - lut_volt2)) * 1000;
+ break;
+ }
+ new_level = lut[1].level * 1000;
+ lut++;
+ }
+ }
+
+ bat->level = new_level;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (bat->pdata->gpio_charge_finished < 0)
+ val->intval = bat->level == 100000 ?
+ POWER_SUPPLY_STATUS_FULL : bat->status;
+ else
+ val->intval = bat->status;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = 100000;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN:
+ val->intval = 0;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = bat->level;
+ return 0;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = bat->volt_value;
+ return 0;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = bat->cur_value;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static struct s3c_adc_bat main_bat = {
+ .psy = {
+ .name = "main-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = s3c_adc_main_bat_props,
+ .num_properties = ARRAY_SIZE(s3c_adc_main_bat_props),
+ .get_property = s3c_adc_bat_get_property,
+ .external_power_changed = s3c_adc_bat_ext_power_changed,
+ .use_for_apm = 1,
+ },
+};
+
+static void s3c_adc_bat_work(struct work_struct *work)
+{
+ struct s3c_adc_bat *bat = &main_bat;
+ int is_charged;
+ int is_plugged;
+ static int was_plugged;
+
+ is_plugged = power_supply_am_i_supplied(&bat->psy);
+ bat->cable_plugged = is_plugged;
+ if (is_plugged != was_plugged) {
+ was_plugged = is_plugged;
+ if (is_plugged) {
+ if (bat->pdata->enable_charger)
+ bat->pdata->enable_charger();
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ if (bat->pdata->disable_charger)
+ bat->pdata->disable_charger();
+ bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ } else {
+ if ((bat->pdata->gpio_charge_finished >= 0) && is_plugged) {
+ is_charged = charge_finished(&main_bat);
+ if (is_charged) {
+ if (bat->pdata->disable_charger)
+ bat->pdata->disable_charger();
+ bat->status = POWER_SUPPLY_STATUS_FULL;
+ } else {
+ if (bat->pdata->enable_charger)
+ bat->pdata->enable_charger();
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ }
+ }
+
+ power_supply_changed(&bat->psy);
+}
+
+static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id)
+{
+ schedule_delayed_work(&bat_work,
+ msecs_to_jiffies(JITTER_DELAY));
+ return IRQ_HANDLED;
+}
+
+static int __init s3c_adc_bat_probe(struct platform_device *pdev)
+{
+ struct s3c_adc_client *client;
+ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
+ int ret;
+
+ client = s3c_adc_register(pdev, NULL, NULL, 0);
+ if (IS_ERR(client)) {
+ dev_err(&pdev->dev, "cannot register adc\n");
+ return PTR_ERR(client);
+ }
+
+ platform_set_drvdata(pdev, client);
+
+ main_bat.client = client;
+ main_bat.pdata = pdata;
+ main_bat.volt_value = -1;
+ main_bat.cur_value = -1;
+ main_bat.cable_plugged = 0;
+ main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ ret = power_supply_register(&pdev->dev, &main_bat.psy);
+ if (ret)
+ goto err_reg_main;
+ if (pdata->backup_volt_mult) {
+ backup_bat.client = client;
+ backup_bat.pdata = pdev->dev.platform_data;
+ backup_bat.volt_value = -1;
+ ret = power_supply_register(&pdev->dev, &backup_bat.psy);
+ if (ret)
+ goto err_reg_backup;
+ }
+
+ INIT_DELAYED_WORK(&bat_work, s3c_adc_bat_work);
+
+ if (pdata->gpio_charge_finished >= 0) {
+ ret = gpio_request(pdata->gpio_charge_finished, "charged");
+ if (ret)
+ goto err_gpio;
+
+ ret = request_irq(gpio_to_irq(pdata->gpio_charge_finished),
+ s3c_adc_bat_charged,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "battery charged", NULL);
+ if (ret)
+ goto err_irq;
+ }
+
+ if (pdata->init) {
+ ret = pdata->init();
+ if (ret)
+ goto err_platform;
+ }
+
+ dev_info(&pdev->dev, "successfully loaded\n");
+ device_init_wakeup(&pdev->dev, 1);
+
+ /* Schedule timer to check current status */
+ schedule_delayed_work(&bat_work,
+ msecs_to_jiffies(JITTER_DELAY));
+
+ return 0;
+
+err_platform:
+ if (pdata->gpio_charge_finished >= 0)
+ free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL);
+err_irq:
+ if (pdata->gpio_charge_finished >= 0)
+ gpio_free(pdata->gpio_charge_finished);
+err_gpio:
+ if (pdata->backup_volt_mult)
+ power_supply_unregister(&backup_bat.psy);
+err_reg_backup:
+ power_supply_unregister(&main_bat.psy);
+err_reg_main:
+ return ret;
+}
+
+static int s3c_adc_bat_remove(struct platform_device *pdev)
+{
+ struct s3c_adc_client *client = platform_get_drvdata(pdev);
+ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
+
+ power_supply_unregister(&main_bat.psy);
+ if (pdata->backup_volt_mult)
+ power_supply_unregister(&backup_bat.psy);
+
+ s3c_adc_release(client);
+
+ if (pdata->gpio_charge_finished >= 0) {
+ free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL);
+ gpio_free(pdata->gpio_charge_finished);
+ }
+
+ cancel_delayed_work(&bat_work);
+
+ if (pdata->exit)
+ pdata->exit();
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c_adc_bat_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
+
+ if (pdata->gpio_charge_finished >= 0) {
+ if (device_may_wakeup(&pdev->dev))
+ enable_irq_wake(
+ gpio_to_irq(pdata->gpio_charge_finished));
+ else {
+ disable_irq(gpio_to_irq(pdata->gpio_charge_finished));
+ main_bat.pdata->disable_charger();
+ }
+ }
+
+ return 0;
+}
+
+static int s3c_adc_bat_resume(struct platform_device *pdev)
+{
+ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
+
+ if (pdata->gpio_charge_finished >= 0) {
+ if (device_may_wakeup(&pdev->dev))
+ disable_irq_wake(
+ gpio_to_irq(pdata->gpio_charge_finished));
+ else
+ enable_irq(gpio_to_irq(pdata->gpio_charge_finished));
+ }
+
+ /* Schedule timer to check current status */
+ schedule_delayed_work(&bat_work,
+ msecs_to_jiffies(JITTER_DELAY));
+
+ return 0;
+}
+#else
+#define s3c_adc_bat_suspend NULL
+#define s3c_adc_bat_resume NULL
+#endif
+
+static struct platform_driver s3c_adc_bat_driver = {
+ .driver = {
+ .name = "s3c-adc-battery",
+ },
+ .probe = s3c_adc_bat_probe,
+ .remove = s3c_adc_bat_remove,
+ .suspend = s3c_adc_bat_suspend,
+ .resume = s3c_adc_bat_resume,
+};
+
+static int __init s3c_adc_bat_init(void)
+{
+ return platform_driver_register(&s3c_adc_bat_driver);
+}
+module_init(s3c_adc_bat_init);
+
+static void __exit s3c_adc_bat_exit(void)
+{
+ platform_driver_unregister(&s3c_adc_bat_driver);
+}
+module_exit(s3c_adc_bat_exit);
+
+MODULE_AUTHOR("Vasily Khoruzhick <anarsoul@gmail.com>");
+MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/sabresd_battery.c b/drivers/power/sabresd_battery.c
new file mode 100755
index 00000000..64892213
--- /dev/null
+++ b/drivers/power/sabresd_battery.c
@@ -0,0 +1,985 @@
+/*
+ * sabresd_battery.c - Maxim 8903 USB/Adapter Charger Driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * Copyright (C) 2011-2012 Freescale Semiconductor, Inc.
+ * Based on max8903_charger.c
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/power/sabresd_battery.h>
+#include <linux/sort.h>
+
+
+#define BATTERY_UPDATE_INTERVAL 5 /*seconds*/
+#define LOW_VOLT_THRESHOLD 2800000
+#define HIGH_VOLT_THRESHOLD 4200000
+#define ADC_SAMPLE_COUNT 6
+
+struct max8903_data {
+ struct max8903_pdata *pdata;
+ struct device *dev;
+ struct power_supply psy;
+ struct power_supply usb;
+ bool fault;
+ bool usb_in;
+ bool ta_in;
+ bool chg_state;
+ struct delayed_work work;
+ unsigned int interval;
+ unsigned short thermal_raw;
+ int voltage_uV;
+ int current_uA;
+ int battery_status;
+ int charger_online;
+ int charger_voltage_uV;
+ int real_capacity;
+ int percent;
+ int old_percent;
+ int usb_charger_online;
+ int first_delay_count;
+ struct power_supply bat;
+ struct power_supply detect_usb;
+ struct mutex work_lock;
+};
+
+typedef struct {
+ u32 voltage;
+ u32 percent;
+} battery_capacity , *pbattery_capacity;
+static int cpu_type_flag;
+static int offset_discharger;
+static int offset_charger;
+static int offset_usb_charger;
+
+static battery_capacity chargingTable[] = {
+ {4050, 99},
+ {4040, 98},
+ {4020, 97},
+ {4010, 96},
+ {3990, 95},
+ {3980, 94},
+ {3970, 93},
+ {3960, 92},
+ {3950, 91},
+ {3940, 90},
+ {3930, 85},
+ {3920, 81},
+ {3910, 77},
+ {3900, 73},
+ {3890, 70},
+ {3860, 65},
+ {3830, 60},
+ {3780, 55},
+ {3760, 50},
+ {3740, 45},
+ {3720, 40},
+ {3700, 35},
+ {3680, 30},
+ {3660, 25},
+ {3640, 20},
+ {3620, 17},
+ {3600, 14},
+ {3580, 13},
+ {3560, 12},
+ {3540, 11},
+ {3520, 10},
+ {3500, 9},
+ {3480, 8},
+ {3460, 7},
+ {3440, 6},
+ {3430, 5},
+ {3420, 4},
+ {3020, 0},
+};
+static battery_capacity dischargingTable[] = {
+ {4050, 100},
+ {4035, 99},
+ {4020, 98},
+ {4010, 97},
+ {4000, 96},
+ {3990, 96},
+ {3980, 95},
+ {3970, 92},
+ {3960, 91},
+ {3950, 90},
+ {3940, 88},
+ {3930, 86},
+ {3920, 84},
+ {3910, 82},
+ {3900, 80},
+ {3890, 74},
+ {3860, 69},
+ {3830, 64},
+ {3780, 59},
+ {3760, 54},
+ {3740, 49},
+ {3720, 44},
+ {3700, 39},
+ {3680, 34},
+ {3660, 29},
+ {3640, 24},
+ {3620, 19},
+ {3600, 14},
+ {3580, 13},
+ {3560, 12},
+ {3540, 11},
+ {3520, 10},
+ {3500, 9},
+ {3480, 8},
+ {3460, 7},
+ {3440, 6},
+ {3430, 5},
+ {3420, 4},
+ {3020, 0},
+};
+
+u32 calibrate_battery_capability_percent(struct max8903_data *data)
+{
+ u8 i;
+ pbattery_capacity pTable;
+ u32 tableSize;
+ if (data->battery_status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ pTable = dischargingTable;
+ tableSize = sizeof(dischargingTable)/sizeof(dischargingTable[0]);
+ } else {
+ pTable = chargingTable;
+ tableSize = sizeof(chargingTable)/sizeof(chargingTable[0]);
+ }
+ for (i = 0; i < tableSize; i++) {
+ if (data->voltage_uV >= pTable[i].voltage)
+ return pTable[i].percent;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property max8903_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property max8903_battery_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+};
+#ifdef CONFIG_TOUCHSCREEN_MAX11801
+extern u32 max11801_read_adc(void);
+#endif
+static void max8903_charger_update_status(struct max8903_data *data)
+{
+ if (data->usb_in || data->ta_in) {
+ if (data->ta_in)
+ data->charger_online = 1;
+
+ if (data->usb_in)
+ data->usb_charger_online = 1;
+ } else {
+ data->charger_online = 0;
+ data->usb_charger_online = 0;
+ }
+ if (data->charger_online == 0 && data->usb_charger_online == 0) {
+ data->battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else {
+ if (gpio_get_value(data->pdata->chg) == 0) {
+ data->battery_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else if (data->ta_in && gpio_get_value(data->pdata->chg) == 1) {
+ if (!data->pdata->feature_flag) {
+ if (data->percent >= 99)
+ data->battery_status = POWER_SUPPLY_STATUS_FULL;
+ else
+ data->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ data->battery_status = POWER_SUPPLY_STATUS_FULL;
+ }
+ } else if (data->usb_in && gpio_get_value(data->pdata->chg) == 1) {
+ if (!data->pdata->feature_flag) {
+ if (data->percent >= 99)
+ data->battery_status = POWER_SUPPLY_STATUS_FULL;
+ else
+ data->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ data->battery_status = POWER_SUPPLY_STATUS_FULL;
+ }
+ }
+ }
+ pr_debug("chg: %d\n", gpio_get_value(data->pdata->chg));
+ pr_debug("dc: %d\n", gpio_get_value(data->pdata->dok));
+ pr_debug("flt: %d\n", gpio_get_value(data->pdata->flt));
+}
+
+#ifdef CONFIG_TOUCHSCREEN_MAX11801
+static int cmp_func(const void *_a, const void *_b)
+{
+ const int *a = _a, *b = _b;
+
+ if (*a > *b)
+ return 1;
+ if (*a < *b)
+ return -1;
+ return 0;
+}
+
+u32 calibration_voltage(struct max8903_data *data)
+{
+ int volt[ADC_SAMPLE_COUNT];
+ u32 voltage_data;
+ int i;
+ for (i = 0; i < ADC_SAMPLE_COUNT; i++) {
+ if (cpu_type_flag == 1) {
+ if (data->charger_online == 0 && data->usb_charger_online == 0) {
+ /* ADC offset when battery is discharger*/
+ volt[i] = max11801_read_adc()-offset_discharger;
+ } else {
+ if (data->charger_online == 1)
+ volt[i] = max11801_read_adc()-offset_charger;
+ else if (data->usb_charger_online == 1)
+ volt[i] = max11801_read_adc()-offset_usb_charger;
+ else if (data->charger_online == 1 && data->usb_charger_online == 1)
+ volt[i] = max11801_read_adc()-offset_charger;
+ }
+ }
+ if (cpu_type_flag == 0) {
+ if (data->charger_online == 0 && data->usb_charger_online == 0) {
+ /* ADC offset when battery is discharger*/
+ volt[i] = max11801_read_adc()-offset_discharger;
+ } else {
+ if (data->charger_online == 1)
+ volt[i] = max11801_read_adc()-offset_charger;
+ else if (data->usb_charger_online == 1)
+ volt[i] = max11801_read_adc()-offset_usb_charger;
+ else if (data->charger_online == 1 && data->usb_charger_online == 1)
+ volt[i] = max11801_read_adc()-offset_charger;
+ }
+ }
+ }
+ sort(volt, i, 4, cmp_func, NULL);
+ for (i = 0; i < ADC_SAMPLE_COUNT; i++)
+ pr_debug("volt_sorted[%2d]: %d\n", i, volt[i]);
+ /* get the average of second max/min of remained. */
+ voltage_data = (volt[2] + volt[ADC_SAMPLE_COUNT - 3]) / 2;
+ return voltage_data;
+}
+#endif
+static void max8903_battery_update_status(struct max8903_data *data)
+{
+ int temp = 0;
+ static int temp_last;
+ bool changed_flag;
+ changed_flag = false;
+ mutex_lock(&data->work_lock);
+ if (!data->pdata->feature_flag) {
+#ifdef CONFIG_TOUCHSCREEN_MAX11801
+ temp = calibration_voltage(data);
+#endif
+ if (temp_last == 0) {
+ data->voltage_uV = temp;
+ temp_last = temp;
+ }
+ if (data->charger_online == 0 && temp_last != 0) {
+ if (temp < temp_last) {
+ temp_last = temp;
+ data->voltage_uV = temp;
+ } else {
+ data->voltage_uV = temp_last;
+ }
+ }
+ if (data->charger_online == 1 || data->usb_charger_online == 1) {
+ data->voltage_uV = temp;
+ temp_last = temp;
+ }
+ data->percent = calibrate_battery_capability_percent(data);
+ if (data->percent != data->old_percent) {
+ data->old_percent = data->percent;
+ changed_flag = true;
+ }
+ if (changed_flag) {
+ changed_flag = false;
+ power_supply_changed(&data->bat);
+ }
+ /*
+ *because boot time gap between led framwork and charger
+ *framwork,when system boots with charger attatched,
+ *charger led framwork loses the first charger online event,
+ *add once extra power_supply_changed can fix this issure
+ */
+ if (data->first_delay_count < 200) {
+ data->first_delay_count = data->first_delay_count + 1 ;
+ power_supply_changed(&data->bat);
+ }
+ }
+ mutex_unlock(&data->work_lock);
+}
+
+static int max8903_battery_get_property(struct power_supply *bat,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *di = container_of(bat,
+ struct max8903_data, bat);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ if (gpio_get_value(di->pdata->chg) == 0) {
+ di->battery_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else if (di->ta_in && gpio_get_value(di->pdata->chg) == 1) {
+ if (!di->pdata->feature_flag) {
+ if (di->percent >= 99)
+ di->battery_status = POWER_SUPPLY_STATUS_FULL;
+ else
+ di->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ di->battery_status = POWER_SUPPLY_STATUS_FULL;
+ }
+ } else if (di->usb_in && gpio_get_value(di->pdata->chg) == 1) {
+ if (!di->pdata->feature_flag) {
+ if (di->percent >= 99)
+ di->battery_status = POWER_SUPPLY_STATUS_FULL;
+ else
+ di->battery_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ di->battery_status = POWER_SUPPLY_STATUS_FULL;
+ }
+ }
+ val->intval = di->battery_status;
+ return 0;
+ default:
+ break;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = di->voltage_uV;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = HIGH_VOLT_THRESHOLD;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = LOW_VOLT_THRESHOLD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = di->percent < 0 ? 0 :
+ (di->percent > 100 ? 100 : di->percent);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ if (di->fault)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ if (di->battery_status == POWER_SUPPLY_STATUS_FULL)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (di->percent <= 15)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+static int max8903_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *data = container_of(psy,
+ struct max8903_data, psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (data->ta_in)
+ val->intval = 1;
+ data->charger_online = val->intval;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+static int max8903_get_usb_property(struct power_supply *usb,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *data = container_of(usb,
+ struct max8903_data, usb);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (data->usb_in)
+ val->intval = 1;
+ data->usb_charger_online = val->intval;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+static irqreturn_t max8903_dcin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool ta_in;
+
+ ta_in = gpio_get_value(pdata->dok) ? false : true;
+
+ if (ta_in == data->ta_in)
+ return IRQ_HANDLED;
+
+ data->ta_in = ta_in;
+ pr_info("TA(DC-IN) Charger %s.\n", ta_in ?
+ "Connected" : "Disconnected");
+ max8903_charger_update_status(data);
+ max8903_battery_update_status(data);
+ power_supply_changed(&data->psy);
+ power_supply_changed(&data->bat);
+ return IRQ_HANDLED;
+}
+static irqreturn_t max8903_usbin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool usb_in;
+ usb_in = gpio_get_value(pdata->uok) ? false : true;
+ if (usb_in == data->usb_in)
+ return IRQ_HANDLED;
+
+ data->usb_in = usb_in;
+ max8903_charger_update_status(data);
+ max8903_battery_update_status(data);
+ pr_info("USB Charger %s.\n", usb_in ?
+ "Connected" : "Disconnected");
+ power_supply_changed(&data->bat);
+ power_supply_changed(&data->usb);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_fault(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool fault;
+
+ fault = gpio_get_value(pdata->flt) ? false : true;
+
+ if (fault == data->fault)
+ return IRQ_HANDLED;
+
+ data->fault = fault;
+
+ if (fault)
+ dev_err(data->dev, "Charger suffers a fault and stops.\n");
+ else
+ dev_err(data->dev, "Charger recovered from a fault.\n");
+ max8903_charger_update_status(data);
+ max8903_battery_update_status(data);
+ power_supply_changed(&data->psy);
+ power_supply_changed(&data->bat);
+ power_supply_changed(&data->usb);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_chg(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ int chg_state;
+
+ chg_state = gpio_get_value(pdata->chg) ? false : true;
+
+ if (chg_state == data->chg_state)
+ return IRQ_HANDLED;
+
+ data->chg_state = chg_state;
+ max8903_charger_update_status(data);
+ max8903_battery_update_status(data);
+ power_supply_changed(&data->psy);
+ power_supply_changed(&data->bat);
+ power_supply_changed(&data->usb);
+ return IRQ_HANDLED;
+}
+
+static void max8903_battery_work(struct work_struct *work)
+{
+ struct max8903_data *data;
+ data = container_of(work, struct max8903_data, work.work);
+ data->interval = HZ * BATTERY_UPDATE_INTERVAL;
+
+ max8903_charger_update_status(data);
+ max8903_battery_update_status(data);
+ pr_debug("battery voltage: %4d mV\n" , data->voltage_uV);
+ pr_debug("charger online status: %d\n" , data->charger_online);
+ pr_debug("battery status : %d\n" , data->battery_status);
+ pr_debug("battery capacity percent: %3d\n" , data->percent);
+ pr_debug("data->usb_in: %x , data->ta_in: %x \n" , data->usb_in, data->ta_in);
+ /* reschedule for the next time */
+ schedule_delayed_work(&data->work, data->interval);
+}
+
+static ssize_t max8903_voltage_offset_discharger_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "read offset_discharger:%04d\n",
+ offset_discharger);
+}
+
+static ssize_t max8903_voltage_offset_discharger_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ offset_discharger = simple_strtoul(buf, NULL, 10);
+ pr_info("read offset_discharger:%04d\n", offset_discharger);
+ return count;
+}
+
+static ssize_t max8903_voltage_offset_charger_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "read offset_charger:%04d\n",
+ offset_charger);
+}
+
+static ssize_t max8903_voltage_offset_charger_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ offset_charger = simple_strtoul(buf, NULL, 10);
+ pr_info("read offset_charger:%04d\n", offset_charger);
+ return count;
+}
+
+static ssize_t max8903_voltage_offset_usb_charger_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "read offset_usb_charger:%04d\n",
+ offset_usb_charger);
+}
+
+static ssize_t max8903_voltage_offset_usb_charger_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ offset_usb_charger = simple_strtoul(buf, NULL, 10);
+ pr_info("read offset_charger:%04d\n", offset_usb_charger);
+ return count;
+}
+
+static struct device_attribute max8903_discharger_dev_attr = {
+ .attr = {
+ .name = "max8903_ctl_offset_discharger",
+ .mode = S_IRUSR | S_IWUSR,
+ },
+ .show = max8903_voltage_offset_discharger_show,
+ .store = max8903_voltage_offset_discharger_store,
+};
+
+static struct device_attribute max8903_charger_dev_attr = {
+ .attr = {
+ .name = "max8903_ctl_offset_charger",
+ .mode = S_IRUSR | S_IWUSR,
+ },
+ .show = max8903_voltage_offset_charger_show,
+ .store = max8903_voltage_offset_charger_store,
+};
+
+static struct device_attribute max8903_usb_charger_dev_attr = {
+ .attr = {
+ .name = "max8903_ctl_offset_usb_charger",
+ .mode = S_IRUSR | S_IWUSR,
+ },
+ .show = max8903_voltage_offset_usb_charger_show,
+ .store = max8903_voltage_offset_usb_charger_store,
+};
+
+static __devinit int max8903_probe(struct platform_device *pdev)
+{
+ struct max8903_data *data;
+ struct device *dev = &pdev->dev;
+ struct max8903_pdata *pdata = pdev->dev.platform_data;
+ int ret = 0;
+ int gpio = 0;
+ int ta_in = 0;
+ int usb_in = 0;
+ int retval;
+ cpu_type_flag = 0;
+ if (cpu_is_mx6q())
+ cpu_type_flag = 1;
+ if (cpu_is_mx6dl())
+ cpu_type_flag = 0;
+ data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+ data->first_delay_count = 0;
+ data->pdata = pdata;
+ data->dev = dev;
+ platform_set_drvdata(pdev, data);
+ data->usb_in = 0;
+ data->ta_in = 0;
+ if (pdata->dc_valid == false && pdata->usb_valid == false) {
+ dev_err(dev, "No valid power sources.\n");
+ printk(KERN_INFO "No valid power sources.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ if (pdata->dc_valid) {
+ if (pdata->dok && gpio_is_valid(pdata->dok)) {
+ gpio = pdata->dok; /* PULL_UPed Interrupt */
+ /* set DOK gpio input */
+ ret = gpio_request(gpio, "max8903-DOK");
+ if (ret) {
+ printk(KERN_ERR"request max8903-DOK error!!\n");
+ goto err;
+ } else {
+ gpio_direction_input(gpio);
+ }
+ ta_in = gpio_get_value(gpio) ? 0 : 1;
+ } else if (pdata->dok && gpio_is_valid(pdata->dok) && pdata->dcm_always_high) {
+ ta_in = pdata->dok; /* PULL_UPed Interrupt */
+ ta_in = gpio_get_value(gpio) ? 0 : 1;
+ } else {
+ dev_err(dev, "When DC is wired, DOK and DCM should"
+ " be wired as well."
+ " or set dcm always high\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+ if (pdata->usb_valid) {
+ if (pdata->uok && gpio_is_valid(pdata->uok)) {
+ gpio = pdata->uok;
+ /* set UOK gpio input */
+ ret = gpio_request(gpio, "max8903-UOK");
+ if (ret) {
+ printk(KERN_ERR"request max8903-UOK error!!\n");
+ goto err;
+ } else {
+ gpio_direction_input(gpio);
+ }
+ usb_in = gpio_get_value(gpio) ? 0 : 1;
+ } else {
+ dev_err(dev, "When USB is wired, UOK should be wired."
+ "as well.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+ if (pdata->chg) {
+ if (!gpio_is_valid(pdata->chg)) {
+ dev_err(dev, "Invalid pin: chg.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ /* set CHG gpio input */
+ ret = gpio_request(pdata->chg, "max8903-CHG");
+ if (ret) {
+ printk(KERN_ERR"request max8903-CHG error!!\n");
+ goto err;
+ } else {
+ gpio_direction_input(pdata->chg);
+ }
+ }
+ if (pdata->flt) {
+ if (!gpio_is_valid(pdata->flt)) {
+ dev_err(dev, "Invalid pin: flt.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ /* set FLT gpio input */
+ ret = gpio_request(pdata->flt, "max8903-FLT");
+ if (ret) {
+ printk(KERN_ERR"request max8903-FLT error!!\n");
+ goto err;
+ } else {
+ gpio_direction_input(pdata->flt);
+ }
+ }
+ if (pdata->usus) {
+ if (!gpio_is_valid(pdata->usus)) {
+ dev_err(dev, "Invalid pin: usus.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+ mutex_init(&data->work_lock);
+ data->fault = false;
+ data->ta_in = ta_in;
+ data->usb_in = usb_in;
+ data->psy.name = "max8903-ac";
+ data->psy.type = POWER_SUPPLY_TYPE_MAINS;
+ data->psy.get_property = max8903_get_property;
+ data->psy.properties = max8903_charger_props;
+ data->psy.num_properties = ARRAY_SIZE(max8903_charger_props);
+ ret = power_supply_register(dev, &data->psy);
+ if (ret) {
+ dev_err(dev, "failed: power supply register.\n");
+ goto err_psy;
+ }
+ data->usb.name = "max8903-usb";
+ data->usb.type = POWER_SUPPLY_TYPE_USB;
+ data->usb.get_property = max8903_get_usb_property;
+ data->usb.properties = max8903_charger_props;
+ data->usb.num_properties = ARRAY_SIZE(max8903_charger_props);
+ ret = power_supply_register(dev, &data->usb);
+ if (ret) {
+ dev_err(dev, "failed: power supply register.\n");
+ goto err_psy;
+ }
+ data->bat.name = "max8903-charger";
+ data->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+ data->bat.properties = max8903_battery_props;
+ data->bat.num_properties = ARRAY_SIZE(max8903_battery_props);
+ data->bat.get_property = max8903_battery_get_property;
+ data->bat.use_for_apm = 1;
+ retval = power_supply_register(&pdev->dev, &data->bat);
+ if (retval) {
+ dev_err(data->dev, "failed to register battery\n");
+ goto battery_failed;
+ }
+ INIT_DELAYED_WORK(&data->work, max8903_battery_work);
+ schedule_delayed_work(&data->work, data->interval);
+ if (pdata->dc_valid) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->dok),
+ NULL, max8903_dcin,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 DC IN", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for DC (%d)\n",
+ gpio_to_irq(pdata->dok), ret);
+ goto err_usb_irq;
+ }
+ }
+
+ if (pdata->usb_valid) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->uok),
+ NULL, max8903_usbin,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 USB IN", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for USB (%d)\n",
+ gpio_to_irq(pdata->uok), ret);
+ goto err_dc_irq;
+ }
+ }
+
+ if (pdata->flt) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->flt),
+ NULL, max8903_fault,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 Fault", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
+ gpio_to_irq(pdata->flt), ret);
+ goto err_flt_irq;
+ }
+ }
+
+ if (pdata->chg) {
+ ret = request_threaded_irq(gpio_to_irq(pdata->chg),
+ NULL, max8903_chg,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "MAX8903 Status", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for Status (%d)\n",
+ gpio_to_irq(pdata->flt), ret);
+ goto err_chg_irq;
+ }
+ }
+ ret = device_create_file(&pdev->dev, &max8903_discharger_dev_attr);
+ if (ret)
+ dev_err(&pdev->dev, "create device file failed!\n");
+ ret = device_create_file(&pdev->dev, &max8903_charger_dev_attr);
+ if (ret)
+ dev_err(&pdev->dev, "create device file failed!\n");
+ ret = device_create_file(&pdev->dev, &max8903_usb_charger_dev_attr);
+ if (ret)
+ dev_err(&pdev->dev, "create device file failed!\n");
+ if (cpu_type_flag == 1) {
+ offset_discharger = 1694;
+ offset_charger = 1900;
+ offset_usb_charger = 1685;
+ }
+ if (cpu_type_flag == 0) {
+ offset_discharger = 1464;
+ offset_charger = 1485;
+ offset_usb_charger = 1285;
+ }
+ max8903_charger_update_status(data);
+ max8903_battery_update_status(data);
+ return 0;
+err_psy:
+ power_supply_unregister(&data->psy);
+battery_failed:
+ power_supply_unregister(&data->bat);
+err_usb_irq:
+ if (pdata->usb_valid)
+ free_irq(gpio_to_irq(pdata->uok), data);
+ cancel_delayed_work(&data->work);
+err_dc_irq:
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->dok), data);
+ cancel_delayed_work(&data->work);
+err_flt_irq:
+ if (pdata->usb_valid)
+ free_irq(gpio_to_irq(pdata->uok), data);
+ cancel_delayed_work(&data->work);
+err_chg_irq:
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->dok), data);
+ cancel_delayed_work(&data->work);
+err:
+ if (pdata->uok)
+ gpio_free(pdata->uok);
+ if (pdata->dok)
+ gpio_free(pdata->dok);
+ if (pdata->flt)
+ gpio_free(pdata->flt);
+ if (pdata->chg)
+ gpio_free(pdata->chg);
+ kfree(data);
+ return ret;
+}
+
+static __devexit int max8903_remove(struct platform_device *pdev)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+ if (data) {
+ struct max8903_pdata *pdata = data->pdata;
+ if (pdata->flt)
+ free_irq(gpio_to_irq(pdata->flt), data);
+ if (pdata->usb_valid)
+ free_irq(gpio_to_irq(pdata->uok), data);
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->dok), data);
+ if (pdata->dc_valid)
+ free_irq(gpio_to_irq(pdata->chg), data);
+ cancel_delayed_work_sync(&data->work);
+ power_supply_unregister(&data->psy);
+ power_supply_unregister(&data->bat);
+ kfree(data);
+ }
+ return 0;
+}
+
+static int max8903_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+ int irq;
+ if (data) {
+ struct max8903_pdata *pdata = data->pdata;
+ if (pdata) {
+ if (pdata->dc_valid) {
+ irq = gpio_to_irq(pdata->dok);
+ enable_irq_wake(irq);
+ }
+ if (pdata->usb_valid) {
+ irq = gpio_to_irq(pdata->uok);
+ enable_irq_wake(irq);
+ }
+ cancel_delayed_work(&data->work);
+ }
+ }
+ return 0;
+}
+
+static int max8903_resume(struct platform_device *pdev)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+ bool ta_in;
+ bool usb_in;
+ int irq;
+ if (data) {
+ struct max8903_pdata *pdata = data->pdata;
+ if (pdata) {
+ ta_in = gpio_get_value(pdata->dok) ? false : true;
+ usb_in = gpio_get_value(pdata->uok) ? false : true;
+ if (ta_in != data->ta_in) {
+ data->ta_in = ta_in;
+ pr_info("TA(DC-IN) Charger %s.\n", ta_in ?
+ "Connected" : "Disconnected");
+ max8903_charger_update_status(data);
+ power_supply_changed(&data->psy);
+ }
+ if (usb_in != data->usb_in) {
+ data->usb_in = usb_in;
+ pr_info("USB Charger %s.\n", usb_in ?
+ "Connected" : "Disconnected");
+ max8903_charger_update_status(data);
+ power_supply_changed(&data->usb);
+ }
+ if (pdata->dc_valid) {
+ irq = gpio_to_irq(pdata->dok);
+ disable_irq_wake(irq);
+ }
+ if (pdata->usb_valid) {
+ irq = gpio_to_irq(pdata->uok);
+ disable_irq_wake(irq);
+ }
+ schedule_delayed_work(&data->work, BATTERY_UPDATE_INTERVAL);
+ }
+ }
+ return 0;
+
+}
+
+static struct platform_driver max8903_driver = {
+ .probe = max8903_probe,
+ .remove = __devexit_p(max8903_remove),
+ .suspend = max8903_suspend,
+ .resume = max8903_resume,
+ .driver = {
+ .name = "max8903-charger",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init max8903_init(void)
+{
+ return platform_driver_register(&max8903_driver);
+}
+module_init(max8903_init);
+
+static void __exit max8903_exit(void)
+{
+ platform_driver_unregister(&max8903_driver);
+}
+module_exit(max8903_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("Sabresd Battery Driver");
+MODULE_ALIAS("sabresd_battery");
diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c
new file mode 100644
index 00000000..b527c93b
--- /dev/null
+++ b/drivers/power/test_power.c
@@ -0,0 +1,419 @@
+/*
+ * Power supply driver for testing.
+ *
+ * Copyright 2010 Anton Vorontsov <cbouatmailru@gmail.com>
+ *
+ * Dynamic module parameter code from the Virtual Battery Driver
+ * Copyright (C) 2008 Pylone, Inc.
+ * By: Masashi YOKOTA <yokota@pylone.jp>
+ * Originally found here:
+ * http://downloads.pylone.jp/src/virtual_battery/virtual_battery-0.0.1.tar.bz2
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/vermagic.h>
+
+static int ac_online = 1;
+static int battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+static int battery_health = POWER_SUPPLY_HEALTH_GOOD;
+static int battery_present = 1; /* true */
+static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION;
+static int battery_capacity = 50;
+
+static int test_power_get_ac_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = ac_online;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int test_power_get_battery_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = "Test battery";
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "Linux";
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = UTS_RELEASE;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = battery_status;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = battery_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = battery_present;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = battery_technology;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = battery_capacity;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = 100;
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ val->intval = 3600;
+ break;
+ default:
+ pr_info("%s: some properties deliberately report errors.\n",
+ __func__);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property test_power_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property test_power_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ 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,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+};
+
+static char *test_power_ac_supplied_to[] = {
+ "test_battery",
+};
+
+static struct power_supply test_power_supplies[] = {
+ {
+ .name = "test_ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .supplied_to = test_power_ac_supplied_to,
+ .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to),
+ .properties = test_power_ac_props,
+ .num_properties = ARRAY_SIZE(test_power_ac_props),
+ .get_property = test_power_get_ac_property,
+ }, {
+ .name = "test_battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = test_power_battery_props,
+ .num_properties = ARRAY_SIZE(test_power_battery_props),
+ .get_property = test_power_get_battery_property,
+ },
+};
+
+
+static int __init test_power_init(void)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) {
+ ret = power_supply_register(NULL, &test_power_supplies[i]);
+ if (ret) {
+ pr_err("%s: failed to register %s\n", __func__,
+ test_power_supplies[i].name);
+ goto failed;
+ }
+ }
+
+ return 0;
+failed:
+ while (--i >= 0)
+ power_supply_unregister(&test_power_supplies[i]);
+ return ret;
+}
+module_init(test_power_init);
+
+static void __exit test_power_exit(void)
+{
+ int i;
+
+ /* Let's see how we handle changes... */
+ ac_online = 0;
+ battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++)
+ power_supply_changed(&test_power_supplies[i]);
+ pr_info("%s: 'changed' event sent, sleeping for 10 seconds...\n",
+ __func__);
+ ssleep(10);
+
+ for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++)
+ power_supply_unregister(&test_power_supplies[i]);
+}
+module_exit(test_power_exit);
+
+
+
+#define MAX_KEYLENGTH 256
+struct battery_property_map {
+ int value;
+ char const *key;
+};
+
+static struct battery_property_map map_ac_online[] = {
+ { 0, "on" },
+ { 1, "off" },
+ { -1, NULL },
+};
+
+static struct battery_property_map map_status[] = {
+ { POWER_SUPPLY_STATUS_CHARGING, "charging" },
+ { POWER_SUPPLY_STATUS_DISCHARGING, "discharging" },
+ { POWER_SUPPLY_STATUS_NOT_CHARGING, "not-charging" },
+ { POWER_SUPPLY_STATUS_FULL, "full" },
+ { -1, NULL },
+};
+
+static struct battery_property_map map_health[] = {
+ { POWER_SUPPLY_HEALTH_GOOD, "good" },
+ { POWER_SUPPLY_HEALTH_OVERHEAT, "overheat" },
+ { POWER_SUPPLY_HEALTH_DEAD, "dead" },
+ { POWER_SUPPLY_HEALTH_OVERVOLTAGE, "overvoltage" },
+ { POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, "failure" },
+ { -1, NULL },
+};
+
+static struct battery_property_map map_present[] = {
+ { 0, "false" },
+ { 1, "true" },
+ { -1, NULL },
+};
+
+static struct battery_property_map map_technology[] = {
+ { POWER_SUPPLY_TECHNOLOGY_NiMH, "NiMH" },
+ { POWER_SUPPLY_TECHNOLOGY_LION, "LION" },
+ { POWER_SUPPLY_TECHNOLOGY_LIPO, "LIPO" },
+ { POWER_SUPPLY_TECHNOLOGY_LiFe, "LiFe" },
+ { POWER_SUPPLY_TECHNOLOGY_NiCd, "NiCd" },
+ { POWER_SUPPLY_TECHNOLOGY_LiMn, "LiMn" },
+ { -1, NULL },
+};
+
+
+static int map_get_value(struct battery_property_map *map, const char *key,
+ int def_val)
+{
+ char buf[MAX_KEYLENGTH];
+ int cr;
+
+ strncpy(buf, key, MAX_KEYLENGTH);
+ buf[MAX_KEYLENGTH-1] = '\0';
+
+ cr = strnlen(buf, MAX_KEYLENGTH) - 1;
+ if (buf[cr] == '\n')
+ buf[cr] = '\0';
+
+ while (map->key) {
+ if (strncasecmp(map->key, buf, MAX_KEYLENGTH) == 0)
+ return map->value;
+ map++;
+ }
+
+ return def_val;
+}
+
+
+static const char *map_get_key(struct battery_property_map *map, int value,
+ const char *def_key)
+{
+ while (map->key) {
+ if (map->value == value)
+ return map->key;
+ map++;
+ }
+
+ return def_key;
+}
+
+static int param_set_ac_online(const char *key, const struct kernel_param *kp)
+{
+ ac_online = map_get_value(map_ac_online, key, ac_online);
+ power_supply_changed(&test_power_supplies[0]);
+ return 0;
+}
+
+static int param_get_ac_online(char *buffer, const struct kernel_param *kp)
+{
+ strcpy(buffer, map_get_key(map_ac_online, ac_online, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_status(const char *key,
+ const struct kernel_param *kp)
+{
+ battery_status = map_get_value(map_status, key, battery_status);
+ power_supply_changed(&test_power_supplies[1]);
+ return 0;
+}
+
+static int param_get_battery_status(char *buffer, const struct kernel_param *kp)
+{
+ strcpy(buffer, map_get_key(map_status, battery_status, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_health(const char *key,
+ const struct kernel_param *kp)
+{
+ battery_health = map_get_value(map_health, key, battery_health);
+ power_supply_changed(&test_power_supplies[1]);
+ return 0;
+}
+
+static int param_get_battery_health(char *buffer, const struct kernel_param *kp)
+{
+ strcpy(buffer, map_get_key(map_health, battery_health, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_present(const char *key,
+ const struct kernel_param *kp)
+{
+ battery_present = map_get_value(map_present, key, battery_present);
+ power_supply_changed(&test_power_supplies[0]);
+ return 0;
+}
+
+static int param_get_battery_present(char *buffer,
+ const struct kernel_param *kp)
+{
+ strcpy(buffer, map_get_key(map_present, battery_present, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_technology(const char *key,
+ const struct kernel_param *kp)
+{
+ battery_technology = map_get_value(map_technology, key,
+ battery_technology);
+ power_supply_changed(&test_power_supplies[1]);
+ return 0;
+}
+
+static int param_get_battery_technology(char *buffer,
+ const struct kernel_param *kp)
+{
+ strcpy(buffer,
+ map_get_key(map_technology, battery_technology, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_capacity(const char *key,
+ const struct kernel_param *kp)
+{
+ int tmp;
+
+ if (1 != sscanf(key, "%d", &tmp))
+ return -EINVAL;
+
+ battery_capacity = tmp;
+ power_supply_changed(&test_power_supplies[1]);
+ return 0;
+}
+
+#define param_get_battery_capacity param_get_int
+
+
+
+static struct kernel_param_ops param_ops_ac_online = {
+ .set = param_set_ac_online,
+ .get = param_get_ac_online,
+};
+
+static struct kernel_param_ops param_ops_battery_status = {
+ .set = param_set_battery_status,
+ .get = param_get_battery_status,
+};
+
+static struct kernel_param_ops param_ops_battery_present = {
+ .set = param_set_battery_present,
+ .get = param_get_battery_present,
+};
+
+static struct kernel_param_ops param_ops_battery_technology = {
+ .set = param_set_battery_technology,
+ .get = param_get_battery_technology,
+};
+
+static struct kernel_param_ops param_ops_battery_health = {
+ .set = param_set_battery_health,
+ .get = param_get_battery_health,
+};
+
+static struct kernel_param_ops param_ops_battery_capacity = {
+ .set = param_set_battery_capacity,
+ .get = param_get_battery_capacity,
+};
+
+
+#define param_check_ac_online(name, p) __param_check(name, p, void);
+#define param_check_battery_status(name, p) __param_check(name, p, void);
+#define param_check_battery_present(name, p) __param_check(name, p, void);
+#define param_check_battery_technology(name, p) __param_check(name, p, void);
+#define param_check_battery_health(name, p) __param_check(name, p, void);
+#define param_check_battery_capacity(name, p) __param_check(name, p, void);
+
+
+module_param(ac_online, ac_online, 0644);
+MODULE_PARM_DESC(ac_online, "AC charging state <on|off>");
+
+module_param(battery_status, battery_status, 0644);
+MODULE_PARM_DESC(battery_status,
+ "battery status <charging|discharging|not-charging|full>");
+
+module_param(battery_present, battery_present, 0644);
+MODULE_PARM_DESC(battery_present,
+ "battery presence state <good|overheat|dead|overvoltage|failure>");
+
+module_param(battery_technology, battery_technology, 0644);
+MODULE_PARM_DESC(battery_technology,
+ "battery technology <NiMH|LION|LIPO|LiFe|NiCd|LiMn>");
+
+module_param(battery_health, battery_health, 0644);
+MODULE_PARM_DESC(battery_health,
+ "battery health state <good|overheat|dead|overvoltage|failure>");
+
+module_param(battery_capacity, battery_capacity, 0644);
+MODULE_PARM_DESC(battery_capacity, "battery capacity (percentage)");
+
+
+MODULE_DESCRIPTION("Power supply driver for testing");
+MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/tosa_battery.c b/drivers/power/tosa_battery.c
new file mode 100644
index 00000000..53f0d352
--- /dev/null
+++ b/drivers/power/tosa_battery.c
@@ -0,0 +1,485 @@
+/*
+ * Battery and Power Management code for the Sharp SL-6000x
+ *
+ * Copyright (c) 2005 Dirk Opfer
+ * Copyright (c) 2008 Dmitry Baryshkov
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/wm97xx.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-types.h>
+#include <mach/tosa.h>
+
+static DEFINE_MUTEX(bat_lock); /* protects gpio pins */
+static struct work_struct bat_work;
+
+struct tosa_bat {
+ int status;
+ struct power_supply psy;
+ int full_chrg;
+
+ struct mutex work_lock; /* protects data */
+
+ bool (*is_present)(struct tosa_bat *bat);
+ int gpio_full;
+ int gpio_charge_off;
+
+ int technology;
+
+ int gpio_bat;
+ int adc_bat;
+ int adc_bat_divider;
+ int bat_max;
+ int bat_min;
+
+ int gpio_temp;
+ int adc_temp;
+ int adc_temp_divider;
+};
+
+static struct tosa_bat tosa_bat_main;
+static struct tosa_bat tosa_bat_jacket;
+
+static unsigned long tosa_read_bat(struct tosa_bat *bat)
+{
+ unsigned long value = 0;
+
+ if (bat->gpio_bat < 0 || bat->adc_bat < 0)
+ return 0;
+
+ mutex_lock(&bat_lock);
+ gpio_set_value(bat->gpio_bat, 1);
+ msleep(5);
+ value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy.dev->parent),
+ bat->adc_bat);
+ gpio_set_value(bat->gpio_bat, 0);
+ mutex_unlock(&bat_lock);
+
+ value = value * 1000000 / bat->adc_bat_divider;
+
+ return value;
+}
+
+static unsigned long tosa_read_temp(struct tosa_bat *bat)
+{
+ unsigned long value = 0;
+
+ if (bat->gpio_temp < 0 || bat->adc_temp < 0)
+ return 0;
+
+ mutex_lock(&bat_lock);
+ gpio_set_value(bat->gpio_temp, 1);
+ msleep(5);
+ value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy.dev->parent),
+ bat->adc_temp);
+ gpio_set_value(bat->gpio_temp, 0);
+ mutex_unlock(&bat_lock);
+
+ value = value * 10000 / bat->adc_temp_divider;
+
+ return value;
+}
+
+static int tosa_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct tosa_bat *bat = container_of(psy, struct tosa_bat, psy);
+
+ if (bat->is_present && !bat->is_present(bat)
+ && psp != POWER_SUPPLY_PROP_PRESENT) {
+ return -ENODEV;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = bat->status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = bat->technology;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = tosa_read_bat(bat);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (bat->full_chrg == -1)
+ val->intval = bat->bat_max;
+ else
+ val->intval = bat->full_chrg;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = bat->bat_max;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = bat->bat_min;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = tosa_read_temp(bat);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = bat->is_present ? bat->is_present(bat) : 1;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static bool tosa_jacket_bat_is_present(struct tosa_bat *bat)
+{
+ return gpio_get_value(TOSA_GPIO_JACKET_DETECT) == 0;
+}
+
+static void tosa_bat_external_power_changed(struct power_supply *psy)
+{
+ schedule_work(&bat_work);
+}
+
+static irqreturn_t tosa_bat_gpio_isr(int irq, void *data)
+{
+ pr_info("tosa_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq)));
+ schedule_work(&bat_work);
+ return IRQ_HANDLED;
+}
+
+static void tosa_bat_update(struct tosa_bat *bat)
+{
+ int old;
+ struct power_supply *psy = &bat->psy;
+
+ mutex_lock(&bat->work_lock);
+
+ old = bat->status;
+
+ if (bat->is_present && !bat->is_present(bat)) {
+ printk(KERN_NOTICE "%s not present\n", psy->name);
+ bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ bat->full_chrg = -1;
+ } else if (power_supply_am_i_supplied(psy)) {
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ gpio_set_value(bat->gpio_charge_off, 0);
+ mdelay(15);
+ }
+
+ if (gpio_get_value(bat->gpio_full)) {
+ if (old == POWER_SUPPLY_STATUS_CHARGING ||
+ bat->full_chrg == -1)
+ bat->full_chrg = tosa_read_bat(bat);
+
+ gpio_set_value(bat->gpio_charge_off, 1);
+ bat->status = POWER_SUPPLY_STATUS_FULL;
+ } else {
+ gpio_set_value(bat->gpio_charge_off, 0);
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ } else {
+ gpio_set_value(bat->gpio_charge_off, 1);
+ bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ if (old != bat->status)
+ power_supply_changed(psy);
+
+ mutex_unlock(&bat->work_lock);
+}
+
+static void tosa_bat_work(struct work_struct *work)
+{
+ tosa_bat_update(&tosa_bat_main);
+ tosa_bat_update(&tosa_bat_jacket);
+}
+
+
+static enum power_supply_property tosa_bat_main_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static enum power_supply_property tosa_bat_bu_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static struct tosa_bat tosa_bat_main = {
+ .status = POWER_SUPPLY_STATUS_DISCHARGING,
+ .full_chrg = -1,
+ .psy = {
+ .name = "main-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = tosa_bat_main_props,
+ .num_properties = ARRAY_SIZE(tosa_bat_main_props),
+ .get_property = tosa_bat_get_property,
+ .external_power_changed = tosa_bat_external_power_changed,
+ .use_for_apm = 1,
+ },
+
+ .gpio_full = TOSA_GPIO_BAT0_CRG,
+ .gpio_charge_off = TOSA_GPIO_CHARGE_OFF,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
+
+ .gpio_bat = TOSA_GPIO_BAT0_V_ON,
+ .adc_bat = WM97XX_AUX_ID3,
+ .adc_bat_divider = 414,
+ .bat_max = 4310000,
+ .bat_min = 1551 * 1000000 / 414,
+
+ .gpio_temp = TOSA_GPIO_BAT1_TH_ON,
+ .adc_temp = WM97XX_AUX_ID2,
+ .adc_temp_divider = 10000,
+};
+
+static struct tosa_bat tosa_bat_jacket = {
+ .status = POWER_SUPPLY_STATUS_DISCHARGING,
+ .full_chrg = -1,
+ .psy = {
+ .name = "jacket-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = tosa_bat_main_props,
+ .num_properties = ARRAY_SIZE(tosa_bat_main_props),
+ .get_property = tosa_bat_get_property,
+ .external_power_changed = tosa_bat_external_power_changed,
+ },
+
+ .is_present = tosa_jacket_bat_is_present,
+ .gpio_full = TOSA_GPIO_BAT1_CRG,
+ .gpio_charge_off = TOSA_GPIO_CHARGE_OFF_JC,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
+
+ .gpio_bat = TOSA_GPIO_BAT1_V_ON,
+ .adc_bat = WM97XX_AUX_ID3,
+ .adc_bat_divider = 414,
+ .bat_max = 4310000,
+ .bat_min = 1551 * 1000000 / 414,
+
+ .gpio_temp = TOSA_GPIO_BAT0_TH_ON,
+ .adc_temp = WM97XX_AUX_ID2,
+ .adc_temp_divider = 10000,
+};
+
+static struct tosa_bat tosa_bat_bu = {
+ .status = POWER_SUPPLY_STATUS_UNKNOWN,
+ .full_chrg = -1,
+
+ .psy = {
+ .name = "backup-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = tosa_bat_bu_props,
+ .num_properties = ARRAY_SIZE(tosa_bat_bu_props),
+ .get_property = tosa_bat_get_property,
+ .external_power_changed = tosa_bat_external_power_changed,
+ },
+
+ .gpio_full = -1,
+ .gpio_charge_off = -1,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LiMn,
+
+ .gpio_bat = TOSA_GPIO_BU_CHRG_ON,
+ .adc_bat = WM97XX_AUX_ID4,
+ .adc_bat_divider = 1266,
+
+ .gpio_temp = -1,
+ .adc_temp = -1,
+ .adc_temp_divider = -1,
+};
+
+static struct {
+ int gpio;
+ char *name;
+ bool output;
+ int value;
+} gpios[] = {
+ { TOSA_GPIO_CHARGE_OFF, "main charge off", 1, 1 },
+ { TOSA_GPIO_CHARGE_OFF_JC, "jacket charge off", 1, 1 },
+ { TOSA_GPIO_BAT_SW_ON, "battery switch", 1, 0 },
+ { TOSA_GPIO_BAT0_V_ON, "main battery", 1, 0 },
+ { TOSA_GPIO_BAT1_V_ON, "jacket battery", 1, 0 },
+ { TOSA_GPIO_BAT1_TH_ON, "main battery temp", 1, 0 },
+ { TOSA_GPIO_BAT0_TH_ON, "jacket battery temp", 1, 0 },
+ { TOSA_GPIO_BU_CHRG_ON, "backup battery", 1, 0 },
+ { TOSA_GPIO_BAT0_CRG, "main battery full", 0, 0 },
+ { TOSA_GPIO_BAT1_CRG, "jacket battery full", 0, 0 },
+ { TOSA_GPIO_BAT0_LOW, "main battery low", 0, 0 },
+ { TOSA_GPIO_BAT1_LOW, "jacket battery low", 0, 0 },
+ { TOSA_GPIO_JACKET_DETECT, "jacket detect", 0, 0 },
+};
+
+#ifdef CONFIG_PM
+static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state)
+{
+ /* flush all pending status updates */
+ flush_work_sync(&bat_work);
+ return 0;
+}
+
+static int tosa_bat_resume(struct platform_device *dev)
+{
+ /* things may have changed while we were away */
+ schedule_work(&bat_work);
+ return 0;
+}
+#else
+#define tosa_bat_suspend NULL
+#define tosa_bat_resume NULL
+#endif
+
+static int __devinit tosa_bat_probe(struct platform_device *dev)
+{
+ int ret;
+ int i;
+
+ if (!machine_is_tosa())
+ return -ENODEV;
+
+ for (i = 0; i < ARRAY_SIZE(gpios); i++) {
+ ret = gpio_request(gpios[i].gpio, gpios[i].name);
+ if (ret) {
+ i--;
+ goto err_gpio;
+ }
+
+ if (gpios[i].output)
+ ret = gpio_direction_output(gpios[i].gpio,
+ gpios[i].value);
+ else
+ ret = gpio_direction_input(gpios[i].gpio);
+
+ if (ret)
+ goto err_gpio;
+ }
+
+ mutex_init(&tosa_bat_main.work_lock);
+ mutex_init(&tosa_bat_jacket.work_lock);
+
+ INIT_WORK(&bat_work, tosa_bat_work);
+
+ ret = power_supply_register(&dev->dev, &tosa_bat_main.psy);
+ if (ret)
+ goto err_psy_reg_main;
+ ret = power_supply_register(&dev->dev, &tosa_bat_jacket.psy);
+ if (ret)
+ goto err_psy_reg_jacket;
+ ret = power_supply_register(&dev->dev, &tosa_bat_bu.psy);
+ if (ret)
+ goto err_psy_reg_bu;
+
+ ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG),
+ tosa_bat_gpio_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "main full", &tosa_bat_main);
+ if (ret)
+ goto err_req_main;
+
+ ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG),
+ tosa_bat_gpio_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "jacket full", &tosa_bat_jacket);
+ if (ret)
+ goto err_req_jacket;
+
+ ret = request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT),
+ tosa_bat_gpio_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "jacket detect", &tosa_bat_jacket);
+ if (!ret) {
+ schedule_work(&bat_work);
+ return 0;
+ }
+
+ free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket);
+err_req_jacket:
+ free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main);
+err_req_main:
+ power_supply_unregister(&tosa_bat_bu.psy);
+err_psy_reg_bu:
+ power_supply_unregister(&tosa_bat_jacket.psy);
+err_psy_reg_jacket:
+ power_supply_unregister(&tosa_bat_main.psy);
+err_psy_reg_main:
+
+ /* see comment in tosa_bat_remove */
+ cancel_work_sync(&bat_work);
+
+ i--;
+err_gpio:
+ for (; i >= 0; i--)
+ gpio_free(gpios[i].gpio);
+
+ return ret;
+}
+
+static int __devexit tosa_bat_remove(struct platform_device *dev)
+{
+ int i;
+
+ free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket);
+ free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket);
+ free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main);
+
+ power_supply_unregister(&tosa_bat_bu.psy);
+ power_supply_unregister(&tosa_bat_jacket.psy);
+ power_supply_unregister(&tosa_bat_main.psy);
+
+ /*
+ * Now cancel the bat_work. We won't get any more schedules,
+ * since all sources (isr and external_power_changed) are
+ * unregistered now.
+ */
+ cancel_work_sync(&bat_work);
+
+ for (i = ARRAY_SIZE(gpios) - 1; i >= 0; i--)
+ gpio_free(gpios[i].gpio);
+
+ return 0;
+}
+
+static struct platform_driver tosa_bat_driver = {
+ .driver.name = "wm97xx-battery",
+ .driver.owner = THIS_MODULE,
+ .probe = tosa_bat_probe,
+ .remove = __devexit_p(tosa_bat_remove),
+ .suspend = tosa_bat_suspend,
+ .resume = tosa_bat_resume,
+};
+
+static int __init tosa_bat_init(void)
+{
+ return platform_driver_register(&tosa_bat_driver);
+}
+
+static void __exit tosa_bat_exit(void)
+{
+ platform_driver_unregister(&tosa_bat_driver);
+}
+
+module_init(tosa_bat_init);
+module_exit(tosa_bat_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dmitry Baryshkov");
+MODULE_DESCRIPTION("Tosa battery driver");
+MODULE_ALIAS("platform:wm97xx-battery");
diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c
new file mode 100644
index 00000000..92c16e16
--- /dev/null
+++ b/drivers/power/twl4030_charger.c
@@ -0,0 +1,578 @@
+/*
+ * TWL4030/TPS65950 BCI (Battery Charger Interface) driver
+ *
+ * Copyright (C) 2010 Gražvydas Ignotas <notasas@gmail.com>
+ *
+ * based on twl4030_bci_battery.c by TI
+ * Copyright (C) 2008 Texas Instruments, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/i2c/twl.h>
+#include <linux/power_supply.h>
+#include <linux/notifier.h>
+#include <linux/usb/otg.h>
+
+#define TWL4030_BCIMSTATEC 0x02
+#define TWL4030_BCIICHG 0x08
+#define TWL4030_BCIVAC 0x0a
+#define TWL4030_BCIVBUS 0x0c
+#define TWL4030_BCIMFSTS4 0x10
+#define TWL4030_BCICTL1 0x23
+
+#define TWL4030_BCIAUTOWEN BIT(5)
+#define TWL4030_CONFIG_DONE BIT(4)
+#define TWL4030_BCIAUTOUSB BIT(1)
+#define TWL4030_BCIAUTOAC BIT(0)
+#define TWL4030_CGAIN BIT(5)
+#define TWL4030_USBFASTMCHG BIT(2)
+#define TWL4030_STS_VBUS BIT(7)
+#define TWL4030_STS_USB_ID BIT(2)
+
+/* BCI interrupts */
+#define TWL4030_WOVF BIT(0) /* Watchdog overflow */
+#define TWL4030_TMOVF BIT(1) /* Timer overflow */
+#define TWL4030_ICHGHIGH BIT(2) /* Battery charge current high */
+#define TWL4030_ICHGLOW BIT(3) /* Battery cc. low / FSM state change */
+#define TWL4030_ICHGEOC BIT(4) /* Battery current end-of-charge */
+#define TWL4030_TBATOR2 BIT(5) /* Battery temperature out of range 2 */
+#define TWL4030_TBATOR1 BIT(6) /* Battery temperature out of range 1 */
+#define TWL4030_BATSTS BIT(7) /* Battery status */
+
+#define TWL4030_VBATLVL BIT(0) /* VBAT level */
+#define TWL4030_VBATOV BIT(1) /* VBAT overvoltage */
+#define TWL4030_VBUSOV BIT(2) /* VBUS overvoltage */
+#define TWL4030_ACCHGOV BIT(3) /* Ac charger overvoltage */
+
+#define TWL4030_MSTATEC_USB BIT(4)
+#define TWL4030_MSTATEC_AC BIT(5)
+#define TWL4030_MSTATEC_MASK 0x0f
+#define TWL4030_MSTATEC_QUICK1 0x02
+#define TWL4030_MSTATEC_QUICK7 0x07
+#define TWL4030_MSTATEC_COMPLETE1 0x0b
+#define TWL4030_MSTATEC_COMPLETE4 0x0e
+
+static bool allow_usb;
+module_param(allow_usb, bool, 1);
+MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current");
+
+struct twl4030_bci {
+ struct device *dev;
+ struct power_supply ac;
+ struct power_supply usb;
+ struct otg_transceiver *transceiver;
+ struct notifier_block otg_nb;
+ struct work_struct work;
+ int irq_chg;
+ int irq_bci;
+
+ unsigned long event;
+};
+
+/*
+ * clear and set bits on an given register on a given module
+ */
+static int twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg)
+{
+ u8 val = 0;
+ int ret;
+
+ ret = twl_i2c_read_u8(mod_no, &val, reg);
+ if (ret)
+ return ret;
+
+ val &= ~clear;
+ val |= set;
+
+ return twl_i2c_write_u8(mod_no, val, reg);
+}
+
+static int twl4030_bci_read(u8 reg, u8 *val)
+{
+ return twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, val, reg);
+}
+
+static int twl4030_clear_set_boot_bci(u8 clear, u8 set)
+{
+ return twl4030_clear_set(TWL4030_MODULE_PM_MASTER, 0,
+ TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set,
+ TWL4030_PM_MASTER_BOOT_BCI);
+}
+
+static int twl4030bci_read_adc_val(u8 reg)
+{
+ int ret, temp;
+ u8 val;
+
+ /* read MSB */
+ ret = twl4030_bci_read(reg + 1, &val);
+ if (ret)
+ return ret;
+
+ temp = (int)(val & 0x03) << 8;
+
+ /* read LSB */
+ ret = twl4030_bci_read(reg, &val);
+ if (ret)
+ return ret;
+
+ return temp | val;
+}
+
+/*
+ * Check if VBUS power is present
+ */
+static int twl4030_bci_have_vbus(struct twl4030_bci *bci)
+{
+ int ret;
+ u8 hwsts;
+
+ ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts,
+ TWL4030_PM_MASTER_STS_HW_CONDITIONS);
+ if (ret < 0)
+ return 0;
+
+ dev_dbg(bci->dev, "check_vbus: HW_CONDITIONS %02x\n", hwsts);
+
+ /* in case we also have STS_USB_ID, VBUS is driven by TWL itself */
+ if ((hwsts & TWL4030_STS_VBUS) && !(hwsts & TWL4030_STS_USB_ID))
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Enable/Disable USB Charge funtionality.
+ */
+static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
+{
+ int ret;
+
+ if (enable) {
+ /* Check for USB charger conneted */
+ if (!twl4030_bci_have_vbus(bci))
+ return -ENODEV;
+
+ /*
+ * Until we can find out what current the device can provide,
+ * require a module param to enable USB charging.
+ */
+ if (!allow_usb) {
+ dev_warn(bci->dev, "USB charging is disabled.\n");
+ return -EACCES;
+ }
+
+ /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */
+ ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB);
+ if (ret < 0)
+ return ret;
+
+ /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */
+ ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, 0,
+ TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4);
+ } else {
+ ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0);
+ }
+
+ return ret;
+}
+
+/*
+ * Enable/Disable AC Charge funtionality.
+ */
+static int twl4030_charger_enable_ac(bool enable)
+{
+ int ret;
+
+ if (enable)
+ ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC);
+ else
+ ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC, 0);
+
+ return ret;
+}
+
+/*
+ * TWL4030 CHG_PRES (AC charger presence) events
+ */
+static irqreturn_t twl4030_charger_interrupt(int irq, void *arg)
+{
+ struct twl4030_bci *bci = arg;
+
+ dev_dbg(bci->dev, "CHG_PRES irq\n");
+ power_supply_changed(&bci->ac);
+ power_supply_changed(&bci->usb);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * TWL4030 BCI monitoring events
+ */
+static irqreturn_t twl4030_bci_interrupt(int irq, void *arg)
+{
+ struct twl4030_bci *bci = arg;
+ u8 irqs1, irqs2;
+ int ret;
+
+ ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs1,
+ TWL4030_INTERRUPTS_BCIISR1A);
+ if (ret < 0)
+ return IRQ_HANDLED;
+
+ ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs2,
+ TWL4030_INTERRUPTS_BCIISR2A);
+ if (ret < 0)
+ return IRQ_HANDLED;
+
+ dev_dbg(bci->dev, "BCI irq %02x %02x\n", irqs2, irqs1);
+
+ if (irqs1 & (TWL4030_ICHGLOW | TWL4030_ICHGEOC)) {
+ /* charger state change, inform the core */
+ power_supply_changed(&bci->ac);
+ power_supply_changed(&bci->usb);
+ }
+
+ /* various monitoring events, for now we just log them here */
+ if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1))
+ dev_warn(bci->dev, "battery temperature out of range\n");
+
+ if (irqs1 & TWL4030_BATSTS)
+ dev_crit(bci->dev, "battery disconnected\n");
+
+ if (irqs2 & TWL4030_VBATOV)
+ dev_crit(bci->dev, "VBAT overvoltage\n");
+
+ if (irqs2 & TWL4030_VBUSOV)
+ dev_crit(bci->dev, "VBUS overvoltage\n");
+
+ if (irqs2 & TWL4030_ACCHGOV)
+ dev_crit(bci->dev, "Ac charger overvoltage\n");
+
+ return IRQ_HANDLED;
+}
+
+static void twl4030_bci_usb_work(struct work_struct *data)
+{
+ struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work);
+
+ switch (bci->event) {
+ case USB_EVENT_VBUS:
+ case USB_EVENT_CHARGER:
+ twl4030_charger_enable_usb(bci, true);
+ break;
+ case USB_EVENT_NONE:
+ twl4030_charger_enable_usb(bci, false);
+ break;
+ }
+}
+
+static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
+ void *priv)
+{
+ struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, otg_nb);
+
+ dev_dbg(bci->dev, "OTG notify %lu\n", val);
+
+ bci->event = val;
+ schedule_work(&bci->work);
+
+ return NOTIFY_OK;
+}
+
+/*
+ * TI provided formulas:
+ * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85
+ * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7
+ * Here we use integer approximation of:
+ * CGAIN == 0: val * 1.6618 - 0.85
+ * CGAIN == 1: (val * 1.6618 - 0.85) * 2
+ */
+static int twl4030_charger_get_current(void)
+{
+ int curr;
+ int ret;
+ u8 bcictl1;
+
+ curr = twl4030bci_read_adc_val(TWL4030_BCIICHG);
+ if (curr < 0)
+ return curr;
+
+ ret = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
+ if (ret)
+ return ret;
+
+ ret = (curr * 16618 - 850 * 10000) / 10;
+ if (bcictl1 & TWL4030_CGAIN)
+ ret *= 2;
+
+ return ret;
+}
+
+/*
+ * Returns the main charge FSM state
+ * Or < 0 on failure.
+ */
+static int twl4030bci_state(struct twl4030_bci *bci)
+{
+ int ret;
+ u8 state;
+
+ ret = twl4030_bci_read(TWL4030_BCIMSTATEC, &state);
+ if (ret) {
+ pr_err("twl4030_bci: error reading BCIMSTATEC\n");
+ return ret;
+ }
+
+ dev_dbg(bci->dev, "state: %02x\n", state);
+
+ return state;
+}
+
+static int twl4030_bci_state_to_status(int state)
+{
+ state &= TWL4030_MSTATEC_MASK;
+ if (TWL4030_MSTATEC_QUICK1 <= state && state <= TWL4030_MSTATEC_QUICK7)
+ return POWER_SUPPLY_STATUS_CHARGING;
+ else if (TWL4030_MSTATEC_COMPLETE1 <= state &&
+ state <= TWL4030_MSTATEC_COMPLETE4)
+ return POWER_SUPPLY_STATUS_FULL;
+ else
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static int twl4030_bci_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent);
+ int is_charging;
+ int state;
+ int ret;
+
+ state = twl4030bci_state(bci);
+ if (state < 0)
+ return state;
+
+ if (psy->type == POWER_SUPPLY_TYPE_USB)
+ is_charging = state & TWL4030_MSTATEC_USB;
+ else
+ is_charging = state & TWL4030_MSTATEC_AC;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (is_charging)
+ val->intval = twl4030_bci_state_to_status(state);
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ /* charging must be active for meaningful result */
+ if (!is_charging)
+ return -ENODATA;
+ if (psy->type == POWER_SUPPLY_TYPE_USB) {
+ ret = twl4030bci_read_adc_val(TWL4030_BCIVBUS);
+ if (ret < 0)
+ return ret;
+ /* BCIVBUS uses ADCIN8, 7/1023 V/step */
+ val->intval = ret * 6843;
+ } else {
+ ret = twl4030bci_read_adc_val(TWL4030_BCIVAC);
+ if (ret < 0)
+ return ret;
+ /* BCIVAC uses ADCIN11, 10/1023 V/step */
+ val->intval = ret * 9775;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (!is_charging)
+ return -ENODATA;
+ /* current measurement is shared between AC and USB */
+ ret = twl4030_charger_get_current();
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = is_charging &&
+ twl4030_bci_state_to_status(state) !=
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property twl4030_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static int __init twl4030_bci_probe(struct platform_device *pdev)
+{
+ struct twl4030_bci *bci;
+ int ret;
+ int reg;
+
+ bci = kzalloc(sizeof(*bci), GFP_KERNEL);
+ if (bci == NULL)
+ return -ENOMEM;
+
+ bci->dev = &pdev->dev;
+ bci->irq_chg = platform_get_irq(pdev, 0);
+ bci->irq_bci = platform_get_irq(pdev, 1);
+
+ platform_set_drvdata(pdev, bci);
+
+ bci->ac.name = "twl4030_ac";
+ bci->ac.type = POWER_SUPPLY_TYPE_MAINS;
+ bci->ac.properties = twl4030_charger_props;
+ bci->ac.num_properties = ARRAY_SIZE(twl4030_charger_props);
+ bci->ac.get_property = twl4030_bci_get_property;
+
+ ret = power_supply_register(&pdev->dev, &bci->ac);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register ac: %d\n", ret);
+ goto fail_register_ac;
+ }
+
+ bci->usb.name = "twl4030_usb";
+ bci->usb.type = POWER_SUPPLY_TYPE_USB;
+ bci->usb.properties = twl4030_charger_props;
+ bci->usb.num_properties = ARRAY_SIZE(twl4030_charger_props);
+ bci->usb.get_property = twl4030_bci_get_property;
+
+ ret = power_supply_register(&pdev->dev, &bci->usb);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register usb: %d\n", ret);
+ goto fail_register_usb;
+ }
+
+ ret = request_threaded_irq(bci->irq_chg, NULL,
+ twl4030_charger_interrupt, 0, pdev->name, bci);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not request irq %d, status %d\n",
+ bci->irq_chg, ret);
+ goto fail_chg_irq;
+ }
+
+ ret = request_threaded_irq(bci->irq_bci, NULL,
+ twl4030_bci_interrupt, 0, pdev->name, bci);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not request irq %d, status %d\n",
+ bci->irq_bci, ret);
+ goto fail_bci_irq;
+ }
+
+ INIT_WORK(&bci->work, twl4030_bci_usb_work);
+
+ bci->transceiver = otg_get_transceiver();
+ if (bci->transceiver != NULL) {
+ bci->otg_nb.notifier_call = twl4030_bci_usb_ncb;
+ otg_register_notifier(bci->transceiver, &bci->otg_nb);
+ }
+
+ /* Enable interrupts now. */
+ reg = ~(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 |
+ TWL4030_TBATOR1 | TWL4030_BATSTS);
+ ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg,
+ TWL4030_INTERRUPTS_BCIIMR1A);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
+ goto fail_unmask_interrupts;
+ }
+
+ reg = ~(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
+ ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg,
+ TWL4030_INTERRUPTS_BCIIMR2A);
+ if (ret < 0)
+ dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
+
+ twl4030_charger_enable_ac(true);
+ twl4030_charger_enable_usb(bci, true);
+
+ return 0;
+
+fail_unmask_interrupts:
+ if (bci->transceiver != NULL) {
+ otg_unregister_notifier(bci->transceiver, &bci->otg_nb);
+ otg_put_transceiver(bci->transceiver);
+ }
+ free_irq(bci->irq_bci, bci);
+fail_bci_irq:
+ free_irq(bci->irq_chg, bci);
+fail_chg_irq:
+ power_supply_unregister(&bci->usb);
+fail_register_usb:
+ power_supply_unregister(&bci->ac);
+fail_register_ac:
+ platform_set_drvdata(pdev, NULL);
+ kfree(bci);
+
+ return ret;
+}
+
+static int __exit twl4030_bci_remove(struct platform_device *pdev)
+{
+ struct twl4030_bci *bci = platform_get_drvdata(pdev);
+
+ twl4030_charger_enable_ac(false);
+ twl4030_charger_enable_usb(bci, false);
+
+ /* mask interrupts */
+ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
+ TWL4030_INTERRUPTS_BCIIMR1A);
+ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
+ TWL4030_INTERRUPTS_BCIIMR2A);
+
+ if (bci->transceiver != NULL) {
+ otg_unregister_notifier(bci->transceiver, &bci->otg_nb);
+ otg_put_transceiver(bci->transceiver);
+ }
+ free_irq(bci->irq_bci, bci);
+ free_irq(bci->irq_chg, bci);
+ power_supply_unregister(&bci->usb);
+ power_supply_unregister(&bci->ac);
+ platform_set_drvdata(pdev, NULL);
+ kfree(bci);
+
+ return 0;
+}
+
+static struct platform_driver twl4030_bci_driver = {
+ .driver = {
+ .name = "twl4030_bci",
+ .owner = THIS_MODULE,
+ },
+ .remove = __exit_p(twl4030_bci_remove),
+};
+
+static int __init twl4030_bci_init(void)
+{
+ return platform_driver_probe(&twl4030_bci_driver, twl4030_bci_probe);
+}
+module_init(twl4030_bci_init);
+
+static void __exit twl4030_bci_exit(void)
+{
+ platform_driver_unregister(&twl4030_bci_driver);
+}
+module_exit(twl4030_bci_exit);
+
+MODULE_AUTHOR("Gražydas Ignotas");
+MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:twl4030_bci");
diff --git a/drivers/power/wm831x_backup.c b/drivers/power/wm831x_backup.c
new file mode 100644
index 00000000..0fd130d8
--- /dev/null
+++ b/drivers/power/wm831x_backup.c
@@ -0,0 +1,234 @@
+/*
+ * Backup battery driver for Wolfson Microelectronics wm831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * 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/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/auxadc.h>
+#include <linux/mfd/wm831x/pmu.h>
+#include <linux/mfd/wm831x/pdata.h>
+
+struct wm831x_backup {
+ struct wm831x *wm831x;
+ struct power_supply backup;
+};
+
+static int wm831x_backup_read_voltage(struct wm831x *wm831x,
+ enum wm831x_auxadc src,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = wm831x_auxadc_read_uv(wm831x, src);
+ if (ret >= 0)
+ val->intval = ret;
+
+ return ret;
+}
+
+/*********************************************************************
+ * Backup supply properties
+ *********************************************************************/
+
+static void wm831x_config_backup(struct wm831x *wm831x)
+{
+ struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
+ struct wm831x_backup_pdata *pdata;
+ int ret, reg;
+
+ if (!wm831x_pdata || !wm831x_pdata->backup) {
+ dev_warn(wm831x->dev,
+ "No backup battery charger configuration\n");
+ return;
+ }
+
+ pdata = wm831x_pdata->backup;
+
+ reg = 0;
+
+ if (pdata->charger_enable)
+ reg |= WM831X_BKUP_CHG_ENA | WM831X_BKUP_BATT_DET_ENA;
+ if (pdata->no_constant_voltage)
+ reg |= WM831X_BKUP_CHG_MODE;
+
+ switch (pdata->vlim) {
+ case 2500:
+ break;
+ case 3100:
+ reg |= WM831X_BKUP_CHG_VLIM;
+ break;
+ default:
+ dev_err(wm831x->dev, "Invalid backup voltage limit %dmV\n",
+ pdata->vlim);
+ }
+
+ switch (pdata->ilim) {
+ case 100:
+ break;
+ case 200:
+ reg |= 1;
+ break;
+ case 300:
+ reg |= 2;
+ break;
+ case 400:
+ reg |= 3;
+ break;
+ default:
+ dev_err(wm831x->dev, "Invalid backup current limit %duA\n",
+ pdata->ilim);
+ }
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret != 0) {
+ dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret);
+ return;
+ }
+
+ ret = wm831x_set_bits(wm831x, WM831X_BACKUP_CHARGER_CONTROL,
+ WM831X_BKUP_CHG_ENA_MASK |
+ WM831X_BKUP_CHG_MODE_MASK |
+ WM831X_BKUP_BATT_DET_ENA_MASK |
+ WM831X_BKUP_CHG_VLIM_MASK |
+ WM831X_BKUP_CHG_ILIM_MASK,
+ reg);
+ if (ret != 0)
+ dev_err(wm831x->dev,
+ "Failed to set backup charger config: %d\n", ret);
+
+ wm831x_reg_lock(wm831x);
+}
+
+static int wm831x_backup_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm831x_backup *devdata = dev_get_drvdata(psy->dev->parent);
+ struct wm831x *wm831x = devdata->wm831x;
+ int ret = 0;
+
+ ret = wm831x_reg_read(wm831x, WM831X_BACKUP_CHARGER_CONTROL);
+ if (ret < 0)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (ret & WM831X_BKUP_CHG_STS)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = wm831x_backup_read_voltage(wm831x, WM831X_AUX_BKUP_BATT,
+ val);
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (ret & WM831X_BKUP_CHG_STS)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm831x_backup_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static __devinit int wm831x_backup_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_backup *devdata;
+ struct power_supply *backup;
+ int ret;
+
+ devdata = kzalloc(sizeof(struct wm831x_backup), GFP_KERNEL);
+ if (devdata == NULL)
+ return -ENOMEM;
+
+ devdata->wm831x = wm831x;
+ platform_set_drvdata(pdev, devdata);
+
+ backup = &devdata->backup;
+
+ /* We ignore configuration failures since we can still read
+ * back the status without enabling the charger (which may
+ * already be enabled anyway).
+ */
+ wm831x_config_backup(wm831x);
+
+ backup->name = "wm831x-backup";
+ backup->type = POWER_SUPPLY_TYPE_BATTERY;
+ backup->properties = wm831x_backup_props;
+ backup->num_properties = ARRAY_SIZE(wm831x_backup_props);
+ backup->get_property = wm831x_backup_get_prop;
+ ret = power_supply_register(&pdev->dev, backup);
+ if (ret)
+ goto err_kmalloc;
+
+ return ret;
+
+err_kmalloc:
+ kfree(devdata);
+ return ret;
+}
+
+static __devexit int wm831x_backup_remove(struct platform_device *pdev)
+{
+ struct wm831x_backup *devdata = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&devdata->backup);
+ kfree(devdata);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_backup_driver = {
+ .probe = wm831x_backup_probe,
+ .remove = __devexit_p(wm831x_backup_remove),
+ .driver = {
+ .name = "wm831x-backup",
+ },
+};
+
+static int __init wm831x_backup_init(void)
+{
+ return platform_driver_register(&wm831x_backup_driver);
+}
+module_init(wm831x_backup_init);
+
+static void __exit wm831x_backup_exit(void)
+{
+ platform_driver_unregister(&wm831x_backup_driver);
+}
+module_exit(wm831x_backup_exit);
+
+MODULE_DESCRIPTION("Backup battery charger driver for WM831x PMICs");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-backup");
diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c
new file mode 100644
index 00000000..ddf8cf5f
--- /dev/null
+++ b/drivers/power/wm831x_power.c
@@ -0,0 +1,641 @@
+/*
+ * PMU driver for Wolfson Microelectronics wm831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * 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/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/auxadc.h>
+#include <linux/mfd/wm831x/pmu.h>
+#include <linux/mfd/wm831x/pdata.h>
+
+struct wm831x_power {
+ struct wm831x *wm831x;
+ struct power_supply wall;
+ struct power_supply usb;
+ struct power_supply battery;
+};
+
+static int wm831x_power_check_online(struct wm831x *wm831x, int supply,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & supply)
+ val->intval = 1;
+ else
+ val->intval = 0;
+
+ return 0;
+}
+
+static int wm831x_power_read_voltage(struct wm831x *wm831x,
+ enum wm831x_auxadc src,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = wm831x_auxadc_read_uv(wm831x, src);
+ if (ret >= 0)
+ val->intval = ret;
+
+ return ret;
+}
+
+/*********************************************************************
+ * WALL Power
+ *********************************************************************/
+static int wm831x_wall_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent);
+ struct wm831x *wm831x = wm831x_power->wm831x;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = wm831x_power_check_online(wm831x, WM831X_PWR_WALL, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_WALL, val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm831x_wall_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ * USB Power
+ *********************************************************************/
+static int wm831x_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent);
+ struct wm831x *wm831x = wm831x_power->wm831x;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = wm831x_power_check_online(wm831x, WM831X_PWR_USB, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_USB, val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm831x_usb_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+
+struct chg_map {
+ int val;
+ int reg_val;
+};
+
+static struct chg_map trickle_ilims[] = {
+ { 50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT },
+ { 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT },
+ { 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT },
+ { 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT },
+};
+
+static struct chg_map vsels[] = {
+ { 4050, 0 << WM831X_CHG_VSEL_SHIFT },
+ { 4100, 1 << WM831X_CHG_VSEL_SHIFT },
+ { 4150, 2 << WM831X_CHG_VSEL_SHIFT },
+ { 4200, 3 << WM831X_CHG_VSEL_SHIFT },
+};
+
+static struct chg_map fast_ilims[] = {
+ { 0, 0 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 50, 1 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 100, 2 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 150, 3 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 200, 4 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 250, 5 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 300, 6 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 350, 7 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 400, 8 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 450, 9 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 500, 10 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 600, 11 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 700, 12 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 800, 13 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 900, 14 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT },
+};
+
+static struct chg_map eoc_iterms[] = {
+ { 20, 0 << WM831X_CHG_ITERM_SHIFT },
+ { 30, 1 << WM831X_CHG_ITERM_SHIFT },
+ { 40, 2 << WM831X_CHG_ITERM_SHIFT },
+ { 50, 3 << WM831X_CHG_ITERM_SHIFT },
+ { 60, 4 << WM831X_CHG_ITERM_SHIFT },
+ { 70, 5 << WM831X_CHG_ITERM_SHIFT },
+ { 80, 6 << WM831X_CHG_ITERM_SHIFT },
+ { 90, 7 << WM831X_CHG_ITERM_SHIFT },
+};
+
+static struct chg_map chg_times[] = {
+ { 60, 0 << WM831X_CHG_TIME_SHIFT },
+ { 90, 1 << WM831X_CHG_TIME_SHIFT },
+ { 120, 2 << WM831X_CHG_TIME_SHIFT },
+ { 150, 3 << WM831X_CHG_TIME_SHIFT },
+ { 180, 4 << WM831X_CHG_TIME_SHIFT },
+ { 210, 5 << WM831X_CHG_TIME_SHIFT },
+ { 240, 6 << WM831X_CHG_TIME_SHIFT },
+ { 270, 7 << WM831X_CHG_TIME_SHIFT },
+ { 300, 8 << WM831X_CHG_TIME_SHIFT },
+ { 330, 9 << WM831X_CHG_TIME_SHIFT },
+ { 360, 10 << WM831X_CHG_TIME_SHIFT },
+ { 390, 11 << WM831X_CHG_TIME_SHIFT },
+ { 420, 12 << WM831X_CHG_TIME_SHIFT },
+ { 450, 13 << WM831X_CHG_TIME_SHIFT },
+ { 480, 14 << WM831X_CHG_TIME_SHIFT },
+ { 510, 15 << WM831X_CHG_TIME_SHIFT },
+};
+
+static void wm831x_battey_apply_config(struct wm831x *wm831x,
+ struct chg_map *map, int count, int val,
+ int *reg, const char *name,
+ const char *units)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ if (val == map[i].val)
+ break;
+ if (i == count) {
+ dev_err(wm831x->dev, "Invalid %s %d%s\n",
+ name, val, units);
+ } else {
+ *reg |= map[i].reg_val;
+ dev_dbg(wm831x->dev, "Set %s of %d%s\n", name, val, units);
+ }
+}
+
+static void wm831x_config_battery(struct wm831x *wm831x)
+{
+ struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
+ struct wm831x_battery_pdata *pdata;
+ int ret, reg1, reg2;
+
+ if (!wm831x_pdata || !wm831x_pdata->battery) {
+ dev_warn(wm831x->dev,
+ "No battery charger configuration\n");
+ return;
+ }
+
+ pdata = wm831x_pdata->battery;
+
+ reg1 = 0;
+ reg2 = 0;
+
+ if (!pdata->enable) {
+ dev_info(wm831x->dev, "Battery charger disabled\n");
+ return;
+ }
+
+ reg1 |= WM831X_CHG_ENA;
+ if (pdata->off_mask)
+ reg2 |= WM831X_CHG_OFF_MSK;
+ if (pdata->fast_enable)
+ reg1 |= WM831X_CHG_FAST;
+
+ wm831x_battey_apply_config(wm831x, trickle_ilims,
+ ARRAY_SIZE(trickle_ilims),
+ pdata->trickle_ilim, &reg2,
+ "trickle charge current limit", "mA");
+
+ wm831x_battey_apply_config(wm831x, vsels, ARRAY_SIZE(vsels),
+ pdata->vsel, &reg2,
+ "target voltage", "mV");
+
+ wm831x_battey_apply_config(wm831x, fast_ilims, ARRAY_SIZE(fast_ilims),
+ pdata->fast_ilim, &reg2,
+ "fast charge current limit", "mA");
+
+ wm831x_battey_apply_config(wm831x, eoc_iterms, ARRAY_SIZE(eoc_iterms),
+ pdata->eoc_iterm, &reg1,
+ "end of charge current threshold", "mA");
+
+ wm831x_battey_apply_config(wm831x, chg_times, ARRAY_SIZE(chg_times),
+ pdata->timeout, &reg2,
+ "charger timeout", "min");
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret != 0) {
+ dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret);
+ return;
+ }
+
+ ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_1,
+ WM831X_CHG_ENA_MASK |
+ WM831X_CHG_FAST_MASK |
+ WM831X_CHG_ITERM_MASK,
+ reg1);
+ if (ret != 0)
+ dev_err(wm831x->dev, "Failed to set charger control 1: %d\n",
+ ret);
+
+ ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_2,
+ WM831X_CHG_OFF_MSK |
+ WM831X_CHG_TIME_MASK |
+ WM831X_CHG_FAST_ILIM_MASK |
+ WM831X_CHG_TRKL_ILIM_MASK |
+ WM831X_CHG_VSEL_MASK,
+ reg2);
+ if (ret != 0)
+ dev_err(wm831x->dev, "Failed to set charger control 2: %d\n",
+ ret);
+
+ wm831x_reg_lock(wm831x);
+}
+
+static int wm831x_bat_check_status(struct wm831x *wm831x, int *status)
+{
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & WM831X_PWR_SRC_BATT) {
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS);
+ if (ret < 0)
+ return ret;
+
+ switch (ret & WM831X_CHG_STATE_MASK) {
+ case WM831X_CHG_STATE_OFF:
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case WM831X_CHG_STATE_TRICKLE:
+ case WM831X_CHG_STATE_FAST:
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+
+ default:
+ *status = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int wm831x_bat_check_type(struct wm831x *wm831x, int *type)
+{
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS);
+ if (ret < 0)
+ return ret;
+
+ switch (ret & WM831X_CHG_STATE_MASK) {
+ case WM831X_CHG_STATE_TRICKLE:
+ case WM831X_CHG_STATE_TRICKLE_OT:
+ *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case WM831X_CHG_STATE_FAST:
+ case WM831X_CHG_STATE_FAST_OT:
+ *type = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ default:
+ *type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ }
+
+ return 0;
+}
+
+static int wm831x_bat_check_health(struct wm831x *wm831x, int *health)
+{
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & WM831X_BATT_HOT_STS) {
+ *health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ return 0;
+ }
+
+ if (ret & WM831X_BATT_COLD_STS) {
+ *health = POWER_SUPPLY_HEALTH_COLD;
+ return 0;
+ }
+
+ if (ret & WM831X_BATT_OV_STS) {
+ *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ return 0;
+ }
+
+ switch (ret & WM831X_CHG_STATE_MASK) {
+ case WM831X_CHG_STATE_TRICKLE_OT:
+ case WM831X_CHG_STATE_FAST_OT:
+ *health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+ case WM831X_CHG_STATE_DEFECTIVE:
+ *health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ default:
+ *health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ }
+
+ return 0;
+}
+
+static int wm831x_bat_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev->parent);
+ struct wm831x *wm831x = wm831x_power->wm831x;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = wm831x_bat_check_status(wm831x, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = wm831x_power_check_online(wm831x, WM831X_PWR_SRC_BATT,
+ val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_BATT, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = wm831x_bat_check_health(wm831x, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = wm831x_bat_check_type(wm831x, &val->intval);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm831x_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+};
+
+static const char *wm831x_bat_irqs[] = {
+ "BATT HOT",
+ "BATT COLD",
+ "BATT FAIL",
+ "OV",
+ "END",
+ "TO",
+ "MODE",
+ "START",
+};
+
+static irqreturn_t wm831x_bat_irq(int irq, void *data)
+{
+ struct wm831x_power *wm831x_power = data;
+ struct wm831x *wm831x = wm831x_power->wm831x;
+
+ dev_dbg(wm831x->dev, "Battery status changed: %d\n", irq);
+
+ /* The battery charger is autonomous so we don't need to do
+ * anything except kick user space */
+ power_supply_changed(&wm831x_power->battery);
+
+ return IRQ_HANDLED;
+}
+
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static irqreturn_t wm831x_syslo_irq(int irq, void *data)
+{
+ struct wm831x_power *wm831x_power = data;
+ struct wm831x *wm831x = wm831x_power->wm831x;
+
+ /* Not much we can actually *do* but tell people for
+ * posterity, we're probably about to run out of power. */
+ dev_crit(wm831x->dev, "SYSVDD under voltage\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t wm831x_pwr_src_irq(int irq, void *data)
+{
+ struct wm831x_power *wm831x_power = data;
+ struct wm831x *wm831x = wm831x_power->wm831x;
+
+ dev_dbg(wm831x->dev, "Power source changed\n");
+
+ /* Just notify for everything - little harm in overnotifying. */
+ power_supply_changed(&wm831x_power->battery);
+ power_supply_changed(&wm831x_power->usb);
+ power_supply_changed(&wm831x_power->wall);
+
+ return IRQ_HANDLED;
+}
+
+static __devinit int wm831x_power_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_power *power;
+ struct power_supply *usb;
+ struct power_supply *battery;
+ struct power_supply *wall;
+ int ret, irq, i;
+
+ power = kzalloc(sizeof(struct wm831x_power), GFP_KERNEL);
+ if (power == NULL)
+ return -ENOMEM;
+
+ power->wm831x = wm831x;
+ platform_set_drvdata(pdev, power);
+
+ usb = &power->usb;
+ battery = &power->battery;
+ wall = &power->wall;
+
+ /* We ignore configuration failures since we can still read back
+ * the status without enabling the charger.
+ */
+ wm831x_config_battery(wm831x);
+
+ wall->name = "wm831x-wall";
+ wall->type = POWER_SUPPLY_TYPE_MAINS;
+ wall->properties = wm831x_wall_props;
+ wall->num_properties = ARRAY_SIZE(wm831x_wall_props);
+ wall->get_property = wm831x_wall_get_prop;
+ ret = power_supply_register(&pdev->dev, wall);
+ if (ret)
+ goto err_kmalloc;
+
+ battery->name = "wm831x-battery";
+ battery->properties = wm831x_bat_props;
+ battery->num_properties = ARRAY_SIZE(wm831x_bat_props);
+ battery->get_property = wm831x_bat_get_prop;
+ battery->use_for_apm = 1;
+ ret = power_supply_register(&pdev->dev, battery);
+ if (ret)
+ goto err_wall;
+
+ usb->name = "wm831x-usb",
+ usb->type = POWER_SUPPLY_TYPE_USB;
+ usb->properties = wm831x_usb_props;
+ usb->num_properties = ARRAY_SIZE(wm831x_usb_props);
+ usb->get_property = wm831x_usb_get_prop;
+ ret = power_supply_register(&pdev->dev, usb);
+ if (ret)
+ goto err_battery;
+
+ irq = platform_get_irq_byname(pdev, "SYSLO");
+ ret = request_threaded_irq(irq, NULL, wm831x_syslo_irq,
+ IRQF_TRIGGER_RISING, "System power low",
+ power);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n",
+ irq, ret);
+ goto err_usb;
+ }
+
+ irq = platform_get_irq_byname(pdev, "PWR SRC");
+ ret = request_threaded_irq(irq, NULL, wm831x_pwr_src_irq,
+ IRQF_TRIGGER_RISING, "Power source",
+ power);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request PWR SRC IRQ %d: %d\n",
+ irq, ret);
+ goto err_syslo;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) {
+ irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
+ ret = request_threaded_irq(irq, NULL, wm831x_bat_irq,
+ IRQF_TRIGGER_RISING,
+ wm831x_bat_irqs[i],
+ power);
+ if (ret != 0) {
+ dev_err(&pdev->dev,
+ "Failed to request %s IRQ %d: %d\n",
+ wm831x_bat_irqs[i], irq, ret);
+ goto err_bat_irq;
+ }
+ }
+
+ return ret;
+
+err_bat_irq:
+ for (; i >= 0; i--) {
+ irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
+ free_irq(irq, power);
+ }
+ irq = platform_get_irq_byname(pdev, "PWR SRC");
+ free_irq(irq, power);
+err_syslo:
+ irq = platform_get_irq_byname(pdev, "SYSLO");
+ free_irq(irq, power);
+err_usb:
+ power_supply_unregister(usb);
+err_battery:
+ power_supply_unregister(battery);
+err_wall:
+ power_supply_unregister(wall);
+err_kmalloc:
+ kfree(power);
+ return ret;
+}
+
+static __devexit int wm831x_power_remove(struct platform_device *pdev)
+{
+ struct wm831x_power *wm831x_power = platform_get_drvdata(pdev);
+ int irq, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) {
+ irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
+ free_irq(irq, wm831x_power);
+ }
+
+ irq = platform_get_irq_byname(pdev, "PWR SRC");
+ free_irq(irq, wm831x_power);
+
+ irq = platform_get_irq_byname(pdev, "SYSLO");
+ free_irq(irq, wm831x_power);
+
+ power_supply_unregister(&wm831x_power->battery);
+ power_supply_unregister(&wm831x_power->wall);
+ power_supply_unregister(&wm831x_power->usb);
+ kfree(wm831x_power);
+ return 0;
+}
+
+static struct platform_driver wm831x_power_driver = {
+ .probe = wm831x_power_probe,
+ .remove = __devexit_p(wm831x_power_remove),
+ .driver = {
+ .name = "wm831x-power",
+ },
+};
+
+static int __init wm831x_power_init(void)
+{
+ return platform_driver_register(&wm831x_power_driver);
+}
+module_init(wm831x_power_init);
+
+static void __exit wm831x_power_exit(void)
+{
+ platform_driver_unregister(&wm831x_power_driver);
+}
+module_exit(wm831x_power_exit);
+
+MODULE_DESCRIPTION("Power supply driver for WM831x PMICs");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-power");
diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c
new file mode 100644
index 00000000..0693902d
--- /dev/null
+++ b/drivers/power/wm8350_power.c
@@ -0,0 +1,539 @@
+/*
+ * Battery driver for wm8350 PMIC
+ *
+ * Copyright 2007, 2008 Wolfson Microelectronics PLC.
+ *
+ * Based on OLPC Battery Driver
+ *
+ * Copyright 2006 David Woodhouse <dwmw2@infradead.org>
+ *
+ * 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/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/wm8350/supply.h>
+#include <linux/mfd/wm8350/core.h>
+#include <linux/mfd/wm8350/comparator.h>
+
+static int wm8350_read_battery_uvolts(struct wm8350 *wm8350)
+{
+ return wm8350_read_auxadc(wm8350, WM8350_AUXADC_BATT, 0, 0)
+ * WM8350_AUX_COEFF;
+}
+
+static int wm8350_read_line_uvolts(struct wm8350 *wm8350)
+{
+ return wm8350_read_auxadc(wm8350, WM8350_AUXADC_LINE, 0, 0)
+ * WM8350_AUX_COEFF;
+}
+
+static int wm8350_read_usb_uvolts(struct wm8350 *wm8350)
+{
+ return wm8350_read_auxadc(wm8350, WM8350_AUXADC_USB, 0, 0)
+ * WM8350_AUX_COEFF;
+}
+
+#define WM8350_BATT_SUPPLY 1
+#define WM8350_USB_SUPPLY 2
+#define WM8350_LINE_SUPPLY 4
+
+static inline int wm8350_charge_time_min(struct wm8350 *wm8350, int min)
+{
+ if (!wm8350->power.rev_g_coeff)
+ return (((min - 30) / 15) & 0xf) << 8;
+ else
+ return (((min - 30) / 30) & 0xf) << 8;
+}
+
+static int wm8350_get_supplies(struct wm8350 *wm8350)
+{
+ u16 sm, ov, co, chrg;
+ int supplies = 0;
+
+ sm = wm8350_reg_read(wm8350, WM8350_STATE_MACHINE_STATUS);
+ ov = wm8350_reg_read(wm8350, WM8350_MISC_OVERRIDES);
+ co = wm8350_reg_read(wm8350, WM8350_COMPARATOR_OVERRIDES);
+ chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2);
+
+ /* USB_SM */
+ sm = (sm & WM8350_USB_SM_MASK) >> WM8350_USB_SM_SHIFT;
+
+ /* CHG_ISEL */
+ chrg &= WM8350_CHG_ISEL_MASK;
+
+ /* If the USB state machine is active then we're using that with or
+ * without battery, otherwise check for wall supply */
+ if (((sm == WM8350_USB_SM_100_SLV) ||
+ (sm == WM8350_USB_SM_500_SLV) ||
+ (sm == WM8350_USB_SM_STDBY_SLV))
+ && !(ov & WM8350_USB_LIMIT_OVRDE))
+ supplies = WM8350_USB_SUPPLY;
+ else if (((sm == WM8350_USB_SM_100_SLV) ||
+ (sm == WM8350_USB_SM_500_SLV) ||
+ (sm == WM8350_USB_SM_STDBY_SLV))
+ && (ov & WM8350_USB_LIMIT_OVRDE) && (chrg == 0))
+ supplies = WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY;
+ else if (co & WM8350_WALL_FB_OVRDE)
+ supplies = WM8350_LINE_SUPPLY;
+ else
+ supplies = WM8350_BATT_SUPPLY;
+
+ return supplies;
+}
+
+static int wm8350_charger_config(struct wm8350 *wm8350,
+ struct wm8350_charger_policy *policy)
+{
+ u16 reg, eoc_mA, fast_limit_mA;
+
+ if (!policy) {
+ dev_warn(wm8350->dev,
+ "No charger policy, charger not configured.\n");
+ return -EINVAL;
+ }
+
+ /* make sure USB fast charge current is not > 500mA */
+ if (policy->fast_limit_USB_mA > 500) {
+ dev_err(wm8350->dev, "USB fast charge > 500mA\n");
+ return -EINVAL;
+ }
+
+ eoc_mA = WM8350_CHG_EOC_mA(policy->eoc_mA);
+
+ wm8350_reg_unlock(wm8350);
+
+ reg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1)
+ & WM8350_CHG_ENA_R168;
+ wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1,
+ reg | eoc_mA | policy->trickle_start_mV |
+ WM8350_CHG_TRICKLE_TEMP_CHOKE |
+ WM8350_CHG_TRICKLE_USB_CHOKE |
+ WM8350_CHG_FAST_USB_THROTTLE);
+
+ if (wm8350_get_supplies(wm8350) & WM8350_USB_SUPPLY) {
+ fast_limit_mA =
+ WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_USB_mA);
+ wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2,
+ policy->charge_mV | policy->trickle_charge_USB_mA |
+ fast_limit_mA | wm8350_charge_time_min(wm8350,
+ policy->charge_timeout));
+
+ } else {
+ fast_limit_mA =
+ WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_mA);
+ wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2,
+ policy->charge_mV | policy->trickle_charge_mA |
+ fast_limit_mA | wm8350_charge_time_min(wm8350,
+ policy->charge_timeout));
+ }
+
+ wm8350_reg_lock(wm8350);
+ return 0;
+}
+
+static int wm8350_batt_status(struct wm8350 *wm8350)
+{
+ u16 state;
+
+ state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2);
+ state &= WM8350_CHG_STS_MASK;
+
+ switch (state) {
+ case WM8350_CHG_STS_OFF:
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ case WM8350_CHG_STS_TRICKLE:
+ case WM8350_CHG_STS_FAST:
+ return POWER_SUPPLY_STATUS_CHARGING;
+
+ default:
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+}
+
+static ssize_t charger_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(dev);
+ char *charge;
+ int state;
+
+ state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) &
+ WM8350_CHG_STS_MASK;
+ switch (state) {
+ case WM8350_CHG_STS_OFF:
+ charge = "Charger Off";
+ break;
+ case WM8350_CHG_STS_TRICKLE:
+ charge = "Trickle Charging";
+ break;
+ case WM8350_CHG_STS_FAST:
+ charge = "Fast Charging";
+ break;
+ default:
+ return 0;
+ }
+
+ return sprintf(buf, "%s\n", charge);
+}
+
+static DEVICE_ATTR(charger_state, 0444, charger_state_show, NULL);
+
+static irqreturn_t wm8350_charger_handler(int irq, void *data)
+{
+ struct wm8350 *wm8350 = data;
+ struct wm8350_power *power = &wm8350->power;
+ struct wm8350_charger_policy *policy = power->policy;
+
+ switch (irq - wm8350->irq_base) {
+ case WM8350_IRQ_CHG_BAT_FAIL:
+ dev_err(wm8350->dev, "battery failed\n");
+ break;
+ case WM8350_IRQ_CHG_TO:
+ dev_err(wm8350->dev, "charger timeout\n");
+ power_supply_changed(&power->battery);
+ break;
+
+ case WM8350_IRQ_CHG_BAT_HOT:
+ case WM8350_IRQ_CHG_BAT_COLD:
+ case WM8350_IRQ_CHG_START:
+ case WM8350_IRQ_CHG_END:
+ power_supply_changed(&power->battery);
+ break;
+
+ case WM8350_IRQ_CHG_FAST_RDY:
+ dev_dbg(wm8350->dev, "fast charger ready\n");
+ wm8350_charger_config(wm8350, policy);
+ wm8350_reg_unlock(wm8350);
+ wm8350_set_bits(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1,
+ WM8350_CHG_FAST);
+ wm8350_reg_lock(wm8350);
+ break;
+
+ case WM8350_IRQ_CHG_VBATT_LT_3P9:
+ dev_warn(wm8350->dev, "battery < 3.9V\n");
+ break;
+ case WM8350_IRQ_CHG_VBATT_LT_3P1:
+ dev_warn(wm8350->dev, "battery < 3.1V\n");
+ break;
+ case WM8350_IRQ_CHG_VBATT_LT_2P85:
+ dev_warn(wm8350->dev, "battery < 2.85V\n");
+ break;
+
+ /* Supply change. We will overnotify but it should do
+ * no harm. */
+ case WM8350_IRQ_EXT_USB_FB:
+ case WM8350_IRQ_EXT_WALL_FB:
+ wm8350_charger_config(wm8350, policy);
+ case WM8350_IRQ_EXT_BAT_FB: /* Fall through */
+ power_supply_changed(&power->battery);
+ power_supply_changed(&power->usb);
+ power_supply_changed(&power->ac);
+ break;
+
+ default:
+ dev_err(wm8350->dev, "Unknown interrupt %d\n", irq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*********************************************************************
+ * AC Power
+ *********************************************************************/
+static int wm8350_ac_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(wm8350_get_supplies(wm8350) &
+ WM8350_LINE_SUPPLY);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = wm8350_read_line_uvolts(wm8350);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property wm8350_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ * USB Power
+ *********************************************************************/
+static int wm8350_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(wm8350_get_supplies(wm8350) &
+ WM8350_USB_SUPPLY);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = wm8350_read_usb_uvolts(wm8350);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property wm8350_usb_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+
+static int wm8350_bat_check_health(struct wm8350 *wm8350)
+{
+ u16 reg;
+
+ if (wm8350_read_battery_uvolts(wm8350) < 2850000)
+ return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+
+ reg = wm8350_reg_read(wm8350, WM8350_CHARGER_OVERRIDES);
+ if (reg & WM8350_CHG_BATT_HOT_OVRDE)
+ return POWER_SUPPLY_HEALTH_OVERHEAT;
+
+ if (reg & WM8350_CHG_BATT_COLD_OVRDE)
+ return POWER_SUPPLY_HEALTH_COLD;
+
+ return POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static int wm8350_bat_get_charge_type(struct wm8350 *wm8350)
+{
+ int state;
+
+ state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) &
+ WM8350_CHG_STS_MASK;
+ switch (state) {
+ case WM8350_CHG_STS_OFF:
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+ case WM8350_CHG_STS_TRICKLE:
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ case WM8350_CHG_STS_FAST:
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ default:
+ return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+}
+
+static int wm8350_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = wm8350_batt_status(wm8350);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(wm8350_get_supplies(wm8350) &
+ WM8350_BATT_SUPPLY);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = wm8350_read_battery_uvolts(wm8350);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = wm8350_bat_check_health(wm8350);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = wm8350_bat_get_charge_type(wm8350);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm8350_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+};
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static void wm8350_init_charger(struct wm8350 *wm8350)
+{
+ /* register our interest in charger events */
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT,
+ wm8350_charger_handler, 0, "Battery hot", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD,
+ wm8350_charger_handler, 0, "Battery cold", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL,
+ wm8350_charger_handler, 0, "Battery fail", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO,
+ wm8350_charger_handler, 0,
+ "Charger timeout", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END,
+ wm8350_charger_handler, 0,
+ "Charge end", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START,
+ wm8350_charger_handler, 0,
+ "Charge start", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY,
+ wm8350_charger_handler, 0,
+ "Fast charge ready", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9,
+ wm8350_charger_handler, 0,
+ "Battery <3.9V", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1,
+ wm8350_charger_handler, 0,
+ "Battery <3.1V", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85,
+ wm8350_charger_handler, 0,
+ "Battery <2.85V", wm8350);
+
+ /* and supply change events */
+ wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB,
+ wm8350_charger_handler, 0, "USB", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB,
+ wm8350_charger_handler, 0, "Wall", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB,
+ wm8350_charger_handler, 0, "Battery", wm8350);
+}
+
+static void free_charger_irq(struct wm8350 *wm8350)
+{
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350);
+}
+
+static __devinit int wm8350_power_probe(struct platform_device *pdev)
+{
+ struct wm8350 *wm8350 = platform_get_drvdata(pdev);
+ struct wm8350_power *power = &wm8350->power;
+ struct wm8350_charger_policy *policy = power->policy;
+ struct power_supply *usb = &power->usb;
+ struct power_supply *battery = &power->battery;
+ struct power_supply *ac = &power->ac;
+ int ret;
+
+ ac->name = "wm8350-ac";
+ ac->type = POWER_SUPPLY_TYPE_MAINS;
+ ac->properties = wm8350_ac_props;
+ ac->num_properties = ARRAY_SIZE(wm8350_ac_props);
+ ac->get_property = wm8350_ac_get_prop;
+ ret = power_supply_register(&pdev->dev, ac);
+ if (ret)
+ return ret;
+
+ battery->name = "wm8350-battery";
+ battery->properties = wm8350_bat_props;
+ battery->num_properties = ARRAY_SIZE(wm8350_bat_props);
+ battery->get_property = wm8350_bat_get_property;
+ battery->use_for_apm = 1;
+ ret = power_supply_register(&pdev->dev, battery);
+ if (ret)
+ goto battery_failed;
+
+ usb->name = "wm8350-usb",
+ usb->type = POWER_SUPPLY_TYPE_USB;
+ usb->properties = wm8350_usb_props;
+ usb->num_properties = ARRAY_SIZE(wm8350_usb_props);
+ usb->get_property = wm8350_usb_get_prop;
+ ret = power_supply_register(&pdev->dev, usb);
+ if (ret)
+ goto usb_failed;
+
+ ret = device_create_file(&pdev->dev, &dev_attr_charger_state);
+ if (ret < 0)
+ dev_warn(wm8350->dev, "failed to add charge sysfs: %d\n", ret);
+ ret = 0;
+
+ wm8350_init_charger(wm8350);
+ if (wm8350_charger_config(wm8350, policy) == 0) {
+ wm8350_reg_unlock(wm8350);
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA);
+ wm8350_reg_lock(wm8350);
+ }
+
+ return ret;
+
+usb_failed:
+ power_supply_unregister(battery);
+battery_failed:
+ power_supply_unregister(ac);
+
+ return ret;
+}
+
+static __devexit int wm8350_power_remove(struct platform_device *pdev)
+{
+ struct wm8350 *wm8350 = platform_get_drvdata(pdev);
+ struct wm8350_power *power = &wm8350->power;
+
+ free_charger_irq(wm8350);
+ device_remove_file(&pdev->dev, &dev_attr_charger_state);
+ power_supply_unregister(&power->battery);
+ power_supply_unregister(&power->ac);
+ power_supply_unregister(&power->usb);
+ return 0;
+}
+
+static struct platform_driver wm8350_power_driver = {
+ .probe = wm8350_power_probe,
+ .remove = __devexit_p(wm8350_power_remove),
+ .driver = {
+ .name = "wm8350-power",
+ },
+};
+
+static int __init wm8350_power_init(void)
+{
+ return platform_driver_register(&wm8350_power_driver);
+}
+module_init(wm8350_power_init);
+
+static void __exit wm8350_power_exit(void)
+{
+ platform_driver_unregister(&wm8350_power_driver);
+}
+module_exit(wm8350_power_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Power supply driver for WM8350");
+MODULE_ALIAS("platform:wm8350-power");
diff --git a/drivers/power/wm97xx_battery.c b/drivers/power/wm97xx_battery.c
new file mode 100644
index 00000000..156559e5
--- /dev/null
+++ b/drivers/power/wm97xx_battery.c
@@ -0,0 +1,309 @@
+/*
+ * linux/drivers/power/wm97xx_battery.c
+ *
+ * Battery measurement code for WM97xx
+ *
+ * based on tosa_battery.c
+ *
+ * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.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/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/wm97xx.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+
+static DEFINE_MUTEX(bat_lock);
+static struct work_struct bat_work;
+static struct mutex work_lock;
+static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
+static enum power_supply_property *prop;
+
+static unsigned long wm97xx_read_bat(struct power_supply *bat_ps)
+{
+ struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent),
+ pdata->batt_aux) * pdata->batt_mult /
+ pdata->batt_div;
+}
+
+static unsigned long wm97xx_read_temp(struct power_supply *bat_ps)
+{
+ struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent),
+ pdata->temp_aux) * pdata->temp_mult /
+ pdata->temp_div;
+}
+
+static int wm97xx_bat_get_property(struct power_supply *bat_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = bat_status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = pdata->batt_tech;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (pdata->batt_aux >= 0)
+ val->intval = wm97xx_read_bat(bat_ps);
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ if (pdata->temp_aux >= 0)
+ val->intval = wm97xx_read_temp(bat_ps);
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (pdata->max_voltage >= 0)
+ val->intval = pdata->max_voltage;
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ if (pdata->min_voltage >= 0)
+ val->intval = pdata->min_voltage;
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps)
+{
+ schedule_work(&bat_work);
+}
+
+static void wm97xx_bat_update(struct power_supply *bat_ps)
+{
+ int old_status = bat_status;
+ struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ mutex_lock(&work_lock);
+
+ bat_status = (pdata->charge_gpio >= 0) ?
+ (gpio_get_value(pdata->charge_gpio) ?
+ POWER_SUPPLY_STATUS_DISCHARGING :
+ POWER_SUPPLY_STATUS_CHARGING) :
+ POWER_SUPPLY_STATUS_UNKNOWN;
+
+ if (old_status != bat_status) {
+ pr_debug("%s: %i -> %i\n", bat_ps->name, old_status,
+ bat_status);
+ power_supply_changed(bat_ps);
+ }
+
+ mutex_unlock(&work_lock);
+}
+
+static struct power_supply bat_ps = {
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = wm97xx_bat_get_property,
+ .external_power_changed = wm97xx_bat_external_power_changed,
+ .use_for_apm = 1,
+};
+
+static void wm97xx_bat_work(struct work_struct *work)
+{
+ wm97xx_bat_update(&bat_ps);
+}
+
+static irqreturn_t wm97xx_chrg_irq(int irq, void *data)
+{
+ schedule_work(&bat_work);
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+static int wm97xx_bat_suspend(struct device *dev)
+{
+ flush_work_sync(&bat_work);
+ return 0;
+}
+
+static int wm97xx_bat_resume(struct device *dev)
+{
+ schedule_work(&bat_work);
+ return 0;
+}
+
+static const struct dev_pm_ops wm97xx_bat_pm_ops = {
+ .suspend = wm97xx_bat_suspend,
+ .resume = wm97xx_bat_resume,
+};
+#endif
+
+static int __devinit wm97xx_bat_probe(struct platform_device *dev)
+{
+ int ret = 0;
+ int props = 1; /* POWER_SUPPLY_PROP_PRESENT */
+ int i = 0;
+ struct wm97xx_pdata *wmdata = dev->dev.platform_data;
+ struct wm97xx_batt_pdata *pdata;
+
+ if (!wmdata) {
+ dev_err(&dev->dev, "No platform data supplied\n");
+ return -EINVAL;
+ }
+
+ pdata = wmdata->batt_pdata;
+
+ if (dev->id != -1)
+ return -EINVAL;
+
+ mutex_init(&work_lock);
+
+ if (!pdata) {
+ dev_err(&dev->dev, "No platform_data supplied\n");
+ return -EINVAL;
+ }
+
+ if (gpio_is_valid(pdata->charge_gpio)) {
+ ret = gpio_request(pdata->charge_gpio, "BATT CHRG");
+ if (ret)
+ goto err;
+ ret = gpio_direction_input(pdata->charge_gpio);
+ if (ret)
+ goto err2;
+ ret = request_irq(gpio_to_irq(pdata->charge_gpio),
+ wm97xx_chrg_irq, IRQF_DISABLED,
+ "AC Detect", dev);
+ if (ret)
+ goto err2;
+ props++; /* POWER_SUPPLY_PROP_STATUS */
+ }
+
+ if (pdata->batt_tech >= 0)
+ props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */
+ if (pdata->temp_aux >= 0)
+ props++; /* POWER_SUPPLY_PROP_TEMP */
+ if (pdata->batt_aux >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */
+ if (pdata->max_voltage >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */
+ if (pdata->min_voltage >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */
+
+ prop = kzalloc(props * sizeof(*prop), GFP_KERNEL);
+ if (!prop)
+ goto err3;
+
+ prop[i++] = POWER_SUPPLY_PROP_PRESENT;
+ if (pdata->charge_gpio >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_STATUS;
+ if (pdata->batt_tech >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
+ if (pdata->temp_aux >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_TEMP;
+ if (pdata->batt_aux >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ if (pdata->max_voltage >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ if (pdata->min_voltage >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+
+ INIT_WORK(&bat_work, wm97xx_bat_work);
+
+ if (!pdata->batt_name) {
+ dev_info(&dev->dev, "Please consider setting proper battery "
+ "name in platform definition file, falling "
+ "back to name \"wm97xx-batt\"\n");
+ bat_ps.name = "wm97xx-batt";
+ } else
+ bat_ps.name = pdata->batt_name;
+
+ bat_ps.properties = prop;
+ bat_ps.num_properties = props;
+
+ ret = power_supply_register(&dev->dev, &bat_ps);
+ if (!ret)
+ schedule_work(&bat_work);
+ else
+ goto err4;
+
+ return 0;
+err4:
+ kfree(prop);
+err3:
+ if (gpio_is_valid(pdata->charge_gpio))
+ free_irq(gpio_to_irq(pdata->charge_gpio), dev);
+err2:
+ if (gpio_is_valid(pdata->charge_gpio))
+ gpio_free(pdata->charge_gpio);
+err:
+ return ret;
+}
+
+static int __devexit wm97xx_bat_remove(struct platform_device *dev)
+{
+ struct wm97xx_pdata *wmdata = dev->dev.platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ if (pdata && gpio_is_valid(pdata->charge_gpio)) {
+ free_irq(gpio_to_irq(pdata->charge_gpio), dev);
+ gpio_free(pdata->charge_gpio);
+ }
+ cancel_work_sync(&bat_work);
+ power_supply_unregister(&bat_ps);
+ kfree(prop);
+ return 0;
+}
+
+static struct platform_driver wm97xx_bat_driver = {
+ .driver = {
+ .name = "wm97xx-battery",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &wm97xx_bat_pm_ops,
+#endif
+ },
+ .probe = wm97xx_bat_probe,
+ .remove = __devexit_p(wm97xx_bat_remove),
+};
+
+static int __init wm97xx_bat_init(void)
+{
+ return platform_driver_register(&wm97xx_bat_driver);
+}
+
+static void __exit wm97xx_bat_exit(void)
+{
+ platform_driver_unregister(&wm97xx_bat_driver);
+}
+
+module_init(wm97xx_bat_init);
+module_exit(wm97xx_bat_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("WM97xx battery driver");
diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c
new file mode 100644
index 00000000..d119c38b
--- /dev/null
+++ b/drivers/power/z2_battery.c
@@ -0,0 +1,335 @@
+/*
+ * Battery measurement code for Zipit Z2
+ *
+ * Copyright (C) 2009 Peter Edwards <sweetlilmre@gmail.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/module.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/z2_battery.h>
+
+#define Z2_DEFAULT_NAME "Z2"
+
+struct z2_charger {
+ struct z2_battery_info *info;
+ int bat_status;
+ struct i2c_client *client;
+ struct power_supply batt_ps;
+ struct mutex work_lock;
+ struct work_struct bat_work;
+};
+
+static unsigned long z2_read_bat(struct z2_charger *charger)
+{
+ int data;
+ data = i2c_smbus_read_byte_data(charger->client,
+ charger->info->batt_I2C_reg);
+ if (data < 0)
+ return 0;
+
+ return data * charger->info->batt_mult / charger->info->batt_div;
+}
+
+static int z2_batt_get_property(struct power_supply *batt_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct z2_charger *charger = container_of(batt_ps, struct z2_charger,
+ batt_ps);
+ struct z2_battery_info *info = charger->info;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = charger->bat_status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = info->batt_tech;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (info->batt_I2C_reg >= 0)
+ val->intval = z2_read_bat(charger);
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (info->max_voltage >= 0)
+ val->intval = info->max_voltage;
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ if (info->min_voltage >= 0)
+ val->intval = info->min_voltage;
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void z2_batt_ext_power_changed(struct power_supply *batt_ps)
+{
+ struct z2_charger *charger = container_of(batt_ps, struct z2_charger,
+ batt_ps);
+ schedule_work(&charger->bat_work);
+}
+
+static void z2_batt_update(struct z2_charger *charger)
+{
+ int old_status = charger->bat_status;
+ struct z2_battery_info *info;
+
+ info = charger->info;
+
+ mutex_lock(&charger->work_lock);
+
+ charger->bat_status = (info->charge_gpio >= 0) ?
+ (gpio_get_value(info->charge_gpio) ?
+ POWER_SUPPLY_STATUS_CHARGING :
+ POWER_SUPPLY_STATUS_DISCHARGING) :
+ POWER_SUPPLY_STATUS_UNKNOWN;
+
+ if (old_status != charger->bat_status) {
+ pr_debug("%s: %i -> %i\n", charger->batt_ps.name, old_status,
+ charger->bat_status);
+ power_supply_changed(&charger->batt_ps);
+ }
+
+ mutex_unlock(&charger->work_lock);
+}
+
+static void z2_batt_work(struct work_struct *work)
+{
+ struct z2_charger *charger;
+ charger = container_of(work, struct z2_charger, bat_work);
+ z2_batt_update(charger);
+}
+
+static irqreturn_t z2_charge_switch_irq(int irq, void *devid)
+{
+ struct z2_charger *charger = devid;
+ schedule_work(&charger->bat_work);
+ return IRQ_HANDLED;
+}
+
+static int z2_batt_ps_init(struct z2_charger *charger, int props)
+{
+ int i = 0;
+ enum power_supply_property *prop;
+ struct z2_battery_info *info = charger->info;
+
+ if (info->charge_gpio >= 0)
+ props++; /* POWER_SUPPLY_PROP_STATUS */
+ if (info->batt_tech >= 0)
+ props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */
+ if (info->batt_I2C_reg >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */
+ if (info->max_voltage >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */
+ if (info->min_voltage >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */
+
+ prop = kzalloc(props * sizeof(*prop), GFP_KERNEL);
+ if (!prop)
+ return -ENOMEM;
+
+ prop[i++] = POWER_SUPPLY_PROP_PRESENT;
+ if (info->charge_gpio >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_STATUS;
+ if (info->batt_tech >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
+ if (info->batt_I2C_reg >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ if (info->max_voltage >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ if (info->min_voltage >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+
+ if (!info->batt_name) {
+ dev_info(&charger->client->dev,
+ "Please consider setting proper battery "
+ "name in platform definition file, falling "
+ "back to name \" Z2_DEFAULT_NAME \"\n");
+ charger->batt_ps.name = Z2_DEFAULT_NAME;
+ } else
+ charger->batt_ps.name = info->batt_name;
+
+ charger->batt_ps.properties = prop;
+ charger->batt_ps.num_properties = props;
+ charger->batt_ps.type = POWER_SUPPLY_TYPE_BATTERY;
+ charger->batt_ps.get_property = z2_batt_get_property;
+ charger->batt_ps.external_power_changed = z2_batt_ext_power_changed;
+ charger->batt_ps.use_for_apm = 1;
+
+ return 0;
+}
+
+static int __devinit z2_batt_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret = 0;
+ int props = 1; /* POWER_SUPPLY_PROP_PRESENT */
+ struct z2_charger *charger;
+ struct z2_battery_info *info = client->dev.platform_data;
+
+ if (info == NULL) {
+ dev_err(&client->dev,
+ "Please set platform device platform_data"
+ " to a valid z2_battery_info pointer!\n");
+ return -EINVAL;
+ }
+
+ charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+ if (charger == NULL)
+ return -ENOMEM;
+
+ charger->bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ charger->info = info;
+ charger->client = client;
+ i2c_set_clientdata(client, charger);
+
+ mutex_init(&charger->work_lock);
+
+ if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
+ ret = gpio_request(info->charge_gpio, "BATT CHRG");
+ if (ret)
+ goto err;
+
+ ret = gpio_direction_input(info->charge_gpio);
+ if (ret)
+ goto err2;
+
+ irq_set_irq_type(gpio_to_irq(info->charge_gpio),
+ IRQ_TYPE_EDGE_BOTH);
+ ret = request_irq(gpio_to_irq(info->charge_gpio),
+ z2_charge_switch_irq, IRQF_DISABLED,
+ "AC Detect", charger);
+ if (ret)
+ goto err3;
+ }
+
+ ret = z2_batt_ps_init(charger, props);
+ if (ret)
+ goto err3;
+
+ INIT_WORK(&charger->bat_work, z2_batt_work);
+
+ ret = power_supply_register(&client->dev, &charger->batt_ps);
+ if (ret)
+ goto err4;
+
+ schedule_work(&charger->bat_work);
+
+ return 0;
+
+err4:
+ kfree(charger->batt_ps.properties);
+err3:
+ if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
+ free_irq(gpio_to_irq(info->charge_gpio), charger);
+err2:
+ if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
+ gpio_free(info->charge_gpio);
+err:
+ kfree(charger);
+ return ret;
+}
+
+static int __devexit z2_batt_remove(struct i2c_client *client)
+{
+ struct z2_charger *charger = i2c_get_clientdata(client);
+ struct z2_battery_info *info = charger->info;
+
+ cancel_work_sync(&charger->bat_work);
+ power_supply_unregister(&charger->batt_ps);
+
+ kfree(charger->batt_ps.properties);
+ if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
+ free_irq(gpio_to_irq(info->charge_gpio), charger);
+ gpio_free(info->charge_gpio);
+ }
+
+ kfree(charger);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int z2_batt_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct z2_charger *charger = i2c_get_clientdata(client);
+
+ flush_work_sync(&charger->bat_work);
+ return 0;
+}
+
+static int z2_batt_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct z2_charger *charger = i2c_get_clientdata(client);
+
+ schedule_work(&charger->bat_work);
+ return 0;
+}
+
+static const struct dev_pm_ops z2_battery_pm_ops = {
+ .suspend = z2_batt_suspend,
+ .resume = z2_batt_resume,
+};
+
+#define Z2_BATTERY_PM_OPS (&z2_battery_pm_ops)
+
+#else
+#define Z2_BATTERY_PM_OPS (NULL)
+#endif
+
+static const struct i2c_device_id z2_batt_id[] = {
+ { "aer915", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, z2_batt_id);
+
+static struct i2c_driver z2_batt_driver = {
+ .driver = {
+ .name = "z2-battery",
+ .owner = THIS_MODULE,
+ .pm = Z2_BATTERY_PM_OPS
+ },
+ .probe = z2_batt_probe,
+ .remove = z2_batt_remove,
+ .id_table = z2_batt_id,
+};
+
+static int __init z2_batt_init(void)
+{
+ return i2c_add_driver(&z2_batt_driver);
+}
+
+static void __exit z2_batt_exit(void)
+{
+ i2c_del_driver(&z2_batt_driver);
+}
+
+module_init(z2_batt_init);
+module_exit(z2_batt_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Edwards <sweetlilmre@gmail.com>");
+MODULE_DESCRIPTION("Zipit Z2 battery driver");