summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
authorSrikant Patnaik2015-01-11 12:28:04 +0530
committerSrikant Patnaik2015-01-11 12:28:04 +0530
commit871480933a1c28f8a9fed4c4d34d06c439a7a422 (patch)
tree8718f573808810c2a1e8cb8fb6ac469093ca2784 /drivers/power
parent9d40ac5867b9aefe0722bc1f110b965ff294d30d (diff)
downloadFOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.gz
FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.tar.bz2
FOSSEE-netbook-kernel-source-871480933a1c28f8a9fed4c4d34d06c439a7a422.zip
Moved, renamed, and deleted files
The original directory structure was scattered and unorganized. Changes are basically to make it look like kernel structure.
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/Kconfig325
-rw-r--r--drivers/power/Makefile48
-rw-r--r--drivers/power/ab8500_btemp.c1124
-rw-r--r--drivers/power/ab8500_charger.c2789
-rw-r--r--drivers/power/ab8500_fg.c2637
-rw-r--r--drivers/power/abx500_chargalg.c1921
-rw-r--r--drivers/power/android_battery.c767
-rw-r--r--drivers/power/apm_power.c376
-rw-r--r--drivers/power/bq27x00_battery.c917
-rw-r--r--drivers/power/charger-manager.c1063
-rw-r--r--drivers/power/collie_battery.c390
-rw-r--r--drivers/power/da9030_battery.c595
-rw-r--r--drivers/power/da9052-battery.c655
-rw-r--r--drivers/power/ds2760_battery.c650
-rw-r--r--drivers/power/ds2780_battery.c857
-rw-r--r--drivers/power/ds2781_battery.c874
-rw-r--r--drivers/power/ds2782_battery.c410
-rw-r--r--drivers/power/gpio-charger.c193
-rw-r--r--drivers/power/intel_mid_battery.c786
-rw-r--r--drivers/power/isp1704_charger.c513
-rw-r--r--drivers/power/jz4740-battery.c449
-rw-r--r--drivers/power/lp8727_charger.c498
-rw-r--r--drivers/power/max17040_battery.c297
-rw-r--r--drivers/power/max17042_battery.c751
-rw-r--r--drivers/power/max8903_charger.c382
-rw-r--r--drivers/power/max8925_power.c530
-rw-r--r--drivers/power/max8997_charger.c208
-rw-r--r--drivers/power/max8998_charger.c211
-rw-r--r--drivers/power/olpc_battery.c637
-rw-r--r--drivers/power/pcf50633-charger.c482
-rw-r--r--drivers/power/pda_power.c515
-rw-r--r--drivers/power/pmu_battery.c216
-rw-r--r--drivers/power/power_supply.h42
-rw-r--r--drivers/power/power_supply_core.c275
-rw-r--r--drivers/power/power_supply_leds.c180
-rw-r--r--drivers/power/power_supply_sysfs.c317
-rw-r--r--drivers/power/s3c_adc_battery.c447
-rw-r--r--drivers/power/sbs-battery.c858
-rw-r--r--drivers/power/smb347-charger.c1600
-rw-r--r--drivers/power/test_power.c419
-rw-r--r--drivers/power/tosa_battery.c446
-rw-r--r--drivers/power/twl4030_charger.c578
-rw-r--r--drivers/power/wm831x_backup.c234
-rw-r--r--drivers/power/wm831x_power.c663
-rw-r--r--drivers/power/wm8350_power.c529
-rw-r--r--drivers/power/wm97xx_battery.c295
-rwxr-xr-xdrivers/power/wmt_battery/Makefile15
-rwxr-xr-xdrivers/power/wmt_battery/charger/g2214/Kconfig5
-rwxr-xr-xdrivers/power/wmt_battery/charger/g2214/Makefile7
-rwxr-xr-xdrivers/power/wmt_battery/charger/g2214/g2214_charger.c762
-rwxr-xr-xdrivers/power/wmt_battery/charger/mp2625/Kconfig5
-rwxr-xr-xdrivers/power/wmt_battery/charger/mp2625/Makefile6
-rwxr-xr-xdrivers/power/wmt_battery/charger/mp2625/mp2625_charger.c428
-rwxr-xr-xdrivers/power/wmt_battery/gauge/bq27xx/Makefile6
-rwxr-xr-xdrivers/power/wmt_battery/gauge/bq27xx/bq27x00_battery.c1011
-rw-r--r--drivers/power/wmt_battery/gauge/saradc/Makefile4
-rw-r--r--drivers/power/wmt_battery/gauge/saradc/saradc_battery.c341
-rwxr-xr-xdrivers/power/wmt_battery/gauge/sp2541/Makefile5
-rwxr-xr-xdrivers/power/wmt_battery/gauge/sp2541/sp2541_battery.c765
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/Makefile13
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_cw500_20130801_103638.h233
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_mp718_20131004_070110.h233
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_t73v_20131120_001204.h233
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms7320_20130718_200031.h233
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h233
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h233
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h233
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/global.h31
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/stdafx.h8
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/typeDefine.h462
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx.h127
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.c2307
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.h521
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.c601
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.h79
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.bbin0 -> 40924 bytes
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.h143
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.c1389
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.h127
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.c876
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.h84
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.c1196
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.h211
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_Platform.h17
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/uG31xx_Reg_Def.h667
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c782
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.h65
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.c152
-rwxr-xr-xdrivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.h43
-rwxr-xr-xdrivers/power/wmt_battery/gauge/vt1603/Makefile5
-rwxr-xr-xdrivers/power/wmt_battery/gauge/vt1603/batt_leds.c189
-rwxr-xr-xdrivers/power/wmt_battery/gauge/vt1603/batt_leds.h12
-rwxr-xr-xdrivers/power/wmt_battery/gauge/vt1603/vt1603.c513
-rwxr-xr-xdrivers/power/wmt_battery/gauge/vt1603/vt1603.h124
-rwxr-xr-xdrivers/power/wmt_battery/wmt_battery.c66
-rw-r--r--drivers/power/z2_battery.c323
96 files changed, 46073 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
new file mode 100644
index 00000000..da8bac56
--- /dev/null
+++ b/drivers/power/Kconfig
@@ -0,0 +1,325 @@
+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"
+ depends on HAS_IOMEM
+ select W1
+ select W1_SLAVE_DS2780
+ help
+ Say Y here to enable support for batteries with ds2780 chip.
+
+config BATTERY_DS2781
+ tristate "2781 battery driver"
+ depends on HAS_IOMEM
+ select W1
+ select W1_SLAVE_DS2781
+ help
+ If you enable this you will have the DS2781 battery driver support.
+
+ The battery monitor chip is used in many batteries/devices
+ as the one who is responsible for charging/discharging/monitoring
+ Li+ batteries.
+
+ If you are unsure, say N.
+
+config BATTERY_WMT
+ tristate "wmt battery driver (wonder media & others)"
+ help
+ Say Y here to enable support for batteries with wmt 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_SBS
+ tristate "SBS Compliant gas gauge"
+ depends on I2C
+ help
+ Say Y to include support for SBS battery driver for SBS-compliant
+ gas gauges.
+
+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_DA9052
+ tristate "Dialog DA9052 Battery"
+ depends on PMIC_DA9052
+ depends on BROKEN
+ help
+ Say Y here to enable support for batteries charger integrated into
+ DA9052 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_ANDROID
+ tristate "Battery driver for Android"
+ help
+ Say Y to enable generic support for battery charging according
+ to common Android policies.
+ This driver adds periodic battery level and health monitoring,
+ kernel log reporting and other debugging features, common board
+ battery file glue logic for battery/case temperature sensors,
+ etc.
+
+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 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_LP8727
+ tristate "TI/National Semiconductor LP8727 charger driver"
+ depends on I2C
+ help
+ Say Y here to enable support for LP8727 Charger Driver.
+
+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 CHARGER_MANAGER
+ bool "Battery charger manager for multiple chargers"
+ depends on REGULATOR && RTC_CLASS
+ help
+ Say Y to enable charger-manager support, which allows multiple
+ chargers attached to a battery and multiple batteries attached to a
+ system. The charger-manager also can monitor charging status in
+ runtime and in suspend-to-RAM by waking up the system periodically
+ with help of suspend_again support.
+
+config CHARGER_MAX8997
+ tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
+ depends on MFD_MAX8997 && REGULATOR_MAX8997
+ help
+ Say Y to enable support for the battery charger control sysfs and
+ platform data of MAX8997/LP3974 PMICs.
+
+config CHARGER_MAX8998
+ tristate "Maxim MAX8998/LP3974 PMIC battery charger driver"
+ depends on MFD_MAX8998 && REGULATOR_MAX8998
+ help
+ Say Y to enable support for the battery charger control sysfs and
+ platform data of MAX8998/LP3974 PMICs.
+
+config CHARGER_SMB347
+ tristate "Summit Microelectronics SMB347 Battery Charger"
+ depends on I2C
+ help
+ Say Y to include support for Summit Microelectronics SMB347
+ Battery Charger.
+
+config AB8500_BM
+ bool "AB8500 Battery Management Driver"
+ depends on AB8500_CORE && AB8500_GPADC
+ help
+ Say Y to include support for AB5500 battery management.
+
+config AB8500_BATTERY_THERM_ON_BATCTRL
+ bool "Thermistor connected on BATCTRL ADC"
+ depends on AB8500_BM
+ help
+ Say Y to enable battery temperature measurements using
+ thermistor connected on BATCTRL ADC.
+endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
new file mode 100644
index 00000000..0e4417c7
--- /dev/null
+++ b/drivers/power/Makefile
@@ -0,0 +1,48 @@
+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_WMT) += wmt_battery/
+
+obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
+obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
+obj-$(CONFIG_BATTERY_DS2781) += ds2781_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_SBS) += sbs-battery.o
+obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o
+obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
+obj-$(CONFIG_BATTERY_DA9052) += da9052-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_AB8500_BM) += ab8500_charger.o ab8500_btemp.o ab8500_fg.o abx500_chargalg.o
+obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
+obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
+obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
+obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
+obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
+obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
+obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
+obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
+obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
+obj-$(CONFIG_BATTERY_ANDROID) += android_battery.o
diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c
new file mode 100644
index 00000000..d8bb9939
--- /dev/null
+++ b/drivers/power/ab8500_btemp.c
@@ -0,0 +1,1124 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Battery temperature driver for AB8500
+ *
+ * License Terms: GNU General Public License v2
+ * Author:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@stericsson.com>
+ * Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/jiffies.h>
+
+#define VTVOUT_V 1800
+
+#define BTEMP_THERMAL_LOW_LIMIT -10
+#define BTEMP_THERMAL_MED_LIMIT 0
+#define BTEMP_THERMAL_HIGH_LIMIT_52 52
+#define BTEMP_THERMAL_HIGH_LIMIT_57 57
+#define BTEMP_THERMAL_HIGH_LIMIT_62 62
+
+#define BTEMP_BATCTRL_CURR_SRC_7UA 7
+#define BTEMP_BATCTRL_CURR_SRC_20UA 20
+
+#define to_ab8500_btemp_device_info(x) container_of((x), \
+ struct ab8500_btemp, btemp_psy);
+
+/**
+ * struct ab8500_btemp_interrupts - ab8500 interrupts
+ * @name: name of the interrupt
+ * @isr function pointer to the isr
+ */
+struct ab8500_btemp_interrupts {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct ab8500_btemp_events {
+ bool batt_rem;
+ bool btemp_high;
+ bool btemp_medhigh;
+ bool btemp_lowmed;
+ bool btemp_low;
+ bool ac_conn;
+ bool usb_conn;
+};
+
+struct ab8500_btemp_ranges {
+ int btemp_high_limit;
+ int btemp_med_limit;
+ int btemp_low_limit;
+};
+
+/**
+ * struct ab8500_btemp - ab8500 BTEMP device information
+ * @dev: Pointer to the structure device
+ * @node: List of AB8500 BTEMPs, hence prepared for reentrance
+ * @curr_source: What current source we use, in uA
+ * @bat_temp: Battery temperature in degree Celcius
+ * @prev_bat_temp Last dispatched battery temperature
+ * @parent: Pointer to the struct ab8500
+ * @gpadc: Pointer to the struct gpadc
+ * @fg: Pointer to the struct fg
+ * @pdata: Pointer to the abx500_btemp platform data
+ * @bat: Pointer to the abx500_bm platform data
+ * @btemp_psy: Structure for BTEMP specific battery properties
+ * @events: Structure for information about events triggered
+ * @btemp_ranges: Battery temperature range structure
+ * @btemp_wq: Work queue for measuring the temperature periodically
+ * @btemp_periodic_work: Work for measuring the temperature periodically
+ */
+struct ab8500_btemp {
+ struct device *dev;
+ struct list_head node;
+ int curr_source;
+ int bat_temp;
+ int prev_bat_temp;
+ struct ab8500 *parent;
+ struct ab8500_gpadc *gpadc;
+ struct ab8500_fg *fg;
+ struct abx500_btemp_platform_data *pdata;
+ struct abx500_bm_data *bat;
+ struct power_supply btemp_psy;
+ struct ab8500_btemp_events events;
+ struct ab8500_btemp_ranges btemp_ranges;
+ struct workqueue_struct *btemp_wq;
+ struct delayed_work btemp_periodic_work;
+};
+
+/* BTEMP power supply properties */
+static enum power_supply_property ab8500_btemp_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static LIST_HEAD(ab8500_btemp_list);
+
+/**
+ * ab8500_btemp_get() - returns a reference to the primary AB8500 BTEMP
+ * (i.e. the first BTEMP in the instance list)
+ */
+struct ab8500_btemp *ab8500_btemp_get(void)
+{
+ struct ab8500_btemp *btemp;
+ btemp = list_first_entry(&ab8500_btemp_list, struct ab8500_btemp, node);
+
+ return btemp;
+}
+
+/**
+ * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance
+ * @di: pointer to the ab8500_btemp structure
+ * @v_batctrl: measured batctrl voltage
+ * @inst_curr: measured instant current
+ *
+ * This function returns the battery resistance that is
+ * derived from the BATCTRL voltage.
+ * Returns value in Ohms.
+ */
+static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di,
+ int v_batctrl, int inst_curr)
+{
+ int rbs;
+
+ if (is_ab8500_1p1_or_earlier(di->parent)) {
+ /*
+ * For ABB cut1.0 and 1.1 BAT_CTRL is internally
+ * connected to 1.8V through a 450k resistor
+ */
+ return (450000 * (v_batctrl)) / (1800 - v_batctrl);
+ }
+
+ if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL) {
+ /*
+ * If the battery has internal NTC, we use the current
+ * source to calculate the resistance, 7uA or 20uA
+ */
+ rbs = (v_batctrl * 1000
+ - di->bat->gnd_lift_resistance * inst_curr)
+ / di->curr_source;
+ } else {
+ /*
+ * BAT_CTRL is internally
+ * connected to 1.8V through a 80k resistor
+ */
+ rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl);
+ }
+
+ return rbs;
+}
+
+/**
+ * ab8500_btemp_read_batctrl_voltage() - measure batctrl voltage
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * This function returns the voltage on BATCTRL. Returns value in mV.
+ */
+static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di)
+{
+ int vbtemp;
+ static int prev;
+
+ vbtemp = ab8500_gpadc_convert(di->gpadc, BAT_CTRL);
+ if (vbtemp < 0) {
+ dev_err(di->dev,
+ "%s gpadc conversion failed, using previous value",
+ __func__);
+ return prev;
+ }
+ prev = vbtemp;
+ return vbtemp;
+}
+
+/**
+ * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source
+ * @di: pointer to the ab8500_btemp structure
+ * @enable: enable or disable the current source
+ *
+ * Enable or disable the current sources for the BatCtrl AD channel
+ */
+static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
+ bool enable)
+{
+ int curr;
+ int ret = 0;
+
+ /*
+ * BATCTRL current sources are included on AB8500 cut2.0
+ * and future versions
+ */
+ if (is_ab8500_1p1_or_earlier(di->parent))
+ return 0;
+
+ /* Only do this for batteries with internal NTC */
+ if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) {
+ if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA)
+ curr = BAT_CTRL_7U_ENA;
+ else
+ curr = BAT_CTRL_20U_ENA;
+
+ dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source);
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH);
+ if (ret) {
+ dev_err(di->dev, "%s failed setting cmp_force\n",
+ __func__);
+ return ret;
+ }
+
+ /*
+ * We have to wait one 32kHz cycle before enabling
+ * the current source, since ForceBatCtrlCmpHigh needs
+ * to be written in a separate cycle
+ */
+ udelay(32);
+
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ FORCE_BAT_CTRL_CMP_HIGH | curr);
+ if (ret) {
+ dev_err(di->dev, "%s failed enabling current source\n",
+ __func__);
+ goto disable_curr_source;
+ }
+ } else if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) {
+ dev_dbg(di->dev, "Disable BATCTRL curr source\n");
+
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
+ ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling current source\n",
+ __func__);
+ goto disable_curr_source;
+ }
+
+ /* Enable Pull-Up and comparator */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA,
+ BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA);
+ if (ret) {
+ dev_err(di->dev, "%s failed enabling PU and comp\n",
+ __func__);
+ goto enable_pu_comp;
+ }
+
+ /*
+ * We have to wait one 32kHz cycle before disabling
+ * ForceBatCtrlCmpHigh since this needs to be written
+ * in a separate cycle
+ */
+ udelay(32);
+
+ /* Disable 'force comparator' */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH);
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling force comp\n",
+ __func__);
+ goto disable_force_comp;
+ }
+ }
+ return ret;
+
+ /*
+ * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time
+ * if we got an error above
+ */
+disable_curr_source:
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
+ ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling current source\n",
+ __func__);
+ return ret;
+ }
+enable_pu_comp:
+ /* Enable Pull-Up and comparator */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA,
+ BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA);
+ if (ret) {
+ dev_err(di->dev, "%s failed enabling PU and comp\n",
+ __func__);
+ return ret;
+ }
+
+disable_force_comp:
+ /*
+ * We have to wait one 32kHz cycle before disabling
+ * ForceBatCtrlCmpHigh since this needs to be written
+ * in a separate cycle
+ */
+ udelay(32);
+
+ /* Disable 'force comparator' */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH);
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling force comp\n",
+ __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * ab8500_btemp_get_batctrl_res() - get battery resistance
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * This function returns the battery pack identification resistance.
+ * Returns value in Ohms.
+ */
+static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di)
+{
+ int ret;
+ int batctrl = 0;
+ int res;
+ int inst_curr;
+ int i;
+
+ /*
+ * BATCTRL current sources are included on AB8500 cut2.0
+ * and future versions
+ */
+ ret = ab8500_btemp_curr_source_enable(di, true);
+ if (ret) {
+ dev_err(di->dev, "%s curr source enabled failed\n", __func__);
+ return ret;
+ }
+
+ if (!di->fg)
+ di->fg = ab8500_fg_get();
+ if (!di->fg) {
+ dev_err(di->dev, "No fg found\n");
+ return -EINVAL;
+ }
+
+ ret = ab8500_fg_inst_curr_start(di->fg);
+
+ if (ret) {
+ dev_err(di->dev, "Failed to start current measurement\n");
+ return ret;
+ }
+
+ /*
+ * Since there is no interrupt when current measurement is done,
+ * loop for over 250ms (250ms is one sample conversion time
+ * with 32.768 Khz RTC clock). Note that a stop time must be set
+ * since the ab8500_btemp_read_batctrl_voltage call can block and
+ * take an unknown amount of time to complete.
+ */
+ i = 0;
+
+ do {
+ batctrl += ab8500_btemp_read_batctrl_voltage(di);
+ i++;
+ msleep(20);
+ } while (!ab8500_fg_inst_curr_done(di->fg));
+ batctrl /= i;
+
+ ret = ab8500_fg_inst_curr_finalize(di->fg, &inst_curr);
+ if (ret) {
+ dev_err(di->dev, "Failed to finalize current measurement\n");
+ return ret;
+ }
+
+ res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr);
+
+ ret = ab8500_btemp_curr_source_enable(di, false);
+ if (ret) {
+ dev_err(di->dev, "%s curr source disable failed\n", __func__);
+ return ret;
+ }
+
+ dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d samples: %d\n",
+ __func__, batctrl, res, inst_curr, i);
+
+ return res;
+}
+
+/**
+ * ab8500_btemp_res_to_temp() - resistance to temperature
+ * @di: pointer to the ab8500_btemp structure
+ * @tbl: pointer to the resiatance to temperature table
+ * @tbl_size: size of the resistance to temperature table
+ * @res: resistance to calculate the temperature from
+ *
+ * This function returns the battery temperature in degrees Celcius
+ * based on the NTC resistance.
+ */
+static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di,
+ const struct abx500_res_to_temp *tbl, int tbl_size, int res)
+{
+ int i, temp;
+ /*
+ * Calculate the formula for the straight line
+ * Simple interpolation if we are within
+ * the resistance table limits, extrapolate
+ * if resistance is outside the limits.
+ */
+ if (res > tbl[0].resist)
+ i = 0;
+ else if (res <= tbl[tbl_size - 1].resist)
+ i = tbl_size - 2;
+ else {
+ i = 0;
+ while (!(res <= tbl[i].resist &&
+ res > tbl[i + 1].resist))
+ i++;
+ }
+
+ temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) *
+ (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);
+ return temp;
+}
+
+/**
+ * ab8500_btemp_measure_temp() - measure battery temperature
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * Returns battery temperature (on success) else the previous temperature
+ */
+static int ab8500_btemp_measure_temp(struct ab8500_btemp *di)
+{
+ int temp;
+ static int prev;
+ int rbat, rntc, vntc;
+ u8 id;
+
+ id = di->bat->batt_id;
+
+ if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL &&
+ id != BATTERY_UNKNOWN) {
+
+ rbat = ab8500_btemp_get_batctrl_res(di);
+ if (rbat < 0) {
+ dev_err(di->dev, "%s get batctrl res failed\n",
+ __func__);
+ /*
+ * Return out-of-range temperature so that
+ * charging is stopped
+ */
+ return BTEMP_THERMAL_LOW_LIMIT;
+ }
+
+ temp = ab8500_btemp_res_to_temp(di,
+ di->bat->bat_type[id].r_to_t_tbl,
+ di->bat->bat_type[id].n_temp_tbl_elements, rbat);
+ } else {
+ vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL);
+ if (vntc < 0) {
+ dev_err(di->dev,
+ "%s gpadc conversion failed,"
+ " using previous value\n", __func__);
+ return prev;
+ }
+ /*
+ * The PCB NTC is sourced from VTVOUT via a 230kOhm
+ * resistor.
+ */
+ rntc = 230000 * vntc / (VTVOUT_V - vntc);
+
+ temp = ab8500_btemp_res_to_temp(di,
+ di->bat->bat_type[id].r_to_t_tbl,
+ di->bat->bat_type[id].n_temp_tbl_elements, rntc);
+ prev = temp;
+ }
+ dev_dbg(di->dev, "Battery temperature is %d\n", temp);
+ return temp;
+}
+
+/**
+ * ab8500_btemp_id() - Identify the connected battery
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * This function will try to identify the battery by reading the ID
+ * resistor. Some brands use a combined ID resistor with a NTC resistor to
+ * both be able to identify and to read the temperature of it.
+ */
+static int ab8500_btemp_id(struct ab8500_btemp *di)
+{
+ int res;
+ u8 i;
+
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
+ di->bat->batt_id = BATTERY_UNKNOWN;
+
+ res = ab8500_btemp_get_batctrl_res(di);
+ if (res < 0) {
+ dev_err(di->dev, "%s get batctrl res failed\n", __func__);
+ return -ENXIO;
+ }
+
+ /* BATTERY_UNKNOWN is defined on position 0, skip it! */
+ for (i = BATTERY_UNKNOWN + 1; i < di->bat->n_btypes; i++) {
+ if ((res <= di->bat->bat_type[i].resis_high) &&
+ (res >= di->bat->bat_type[i].resis_low)) {
+ dev_dbg(di->dev, "Battery detected on %s"
+ " low %d < res %d < high: %d"
+ " index: %d\n",
+ di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL ?
+ "BATCTRL" : "BATTEMP",
+ di->bat->bat_type[i].resis_low, res,
+ di->bat->bat_type[i].resis_high, i);
+
+ di->bat->batt_id = i;
+ break;
+ }
+ }
+
+ if (di->bat->batt_id == BATTERY_UNKNOWN) {
+ dev_warn(di->dev, "Battery identified as unknown"
+ ", resistance %d Ohm\n", res);
+ return -ENXIO;
+ }
+
+ /*
+ * We only have to change current source if the
+ * detected type is Type 1, else we use the 7uA source
+ */
+ if (di->bat->adc_therm == ABx500_ADC_THERM_BATCTRL &&
+ di->bat->batt_id == 1) {
+ dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA;
+ }
+
+ return di->bat->batt_id;
+}
+
+/**
+ * ab8500_btemp_periodic_work() - Measuring the temperature periodically
+ * @work: pointer to the work_struct structure
+ *
+ * Work function for measuring the temperature periodically
+ */
+static void ab8500_btemp_periodic_work(struct work_struct *work)
+{
+ int interval;
+ struct ab8500_btemp *di = container_of(work,
+ struct ab8500_btemp, btemp_periodic_work.work);
+
+ di->bat_temp = ab8500_btemp_measure_temp(di);
+
+ if (di->bat_temp != di->prev_bat_temp) {
+ di->prev_bat_temp = di->bat_temp;
+ power_supply_changed(&di->btemp_psy);
+ }
+
+ if (di->events.ac_conn || di->events.usb_conn)
+ interval = di->bat->temp_interval_chg;
+ else
+ interval = di->bat->temp_interval_nochg;
+
+ /* Schedule a new measurement */
+ queue_delayed_work(di->btemp_wq,
+ &di->btemp_periodic_work,
+ round_jiffies(interval * HZ));
+}
+
+/**
+ * ab8500_btemp_batctrlindb_handler() - battery removal detected
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_batctrlindb_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+ dev_err(di->dev, "Battery removal detected!\n");
+
+ di->events.batt_rem = true;
+ power_supply_changed(&di->btemp_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_templow_handler() - battery temp lower than 10 degrees
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+
+ if (is_ab8500_2p0_or_earlier(di->parent)) {
+ dev_dbg(di->dev, "Ignore false btemp low irq"
+ " for ABB cut 1.0, 1.1 and 2.0\n");
+ } else {
+ dev_crit(di->dev, "Battery temperature lower than -10deg c\n");
+
+ di->events.btemp_low = true;
+ di->events.btemp_high = false;
+ di->events.btemp_medhigh = false;
+ di->events.btemp_lowmed = false;
+ power_supply_changed(&di->btemp_psy);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_temphigh_handler() - battery temp higher than max temp
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_temphigh_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+
+ dev_crit(di->dev, "Battery temperature is higher than MAX temp\n");
+
+ di->events.btemp_high = true;
+ di->events.btemp_medhigh = false;
+ di->events.btemp_lowmed = false;
+ di->events.btemp_low = false;
+ power_supply_changed(&di->btemp_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_lowmed_handler() - battery temp between low and medium
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_lowmed_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+
+ dev_dbg(di->dev, "Battery temperature is between low and medium\n");
+
+ di->events.btemp_lowmed = true;
+ di->events.btemp_medhigh = false;
+ di->events.btemp_high = false;
+ di->events.btemp_low = false;
+ power_supply_changed(&di->btemp_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_medhigh_handler() - battery temp between medium and high
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_medhigh_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+
+ dev_dbg(di->dev, "Battery temperature is between medium and high\n");
+
+ di->events.btemp_medhigh = true;
+ di->events.btemp_lowmed = false;
+ di->events.btemp_high = false;
+ di->events.btemp_low = false;
+ power_supply_changed(&di->btemp_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_periodic() - Periodic temperature measurements
+ * @di: pointer to the ab8500_btemp structure
+ * @enable: enable or disable periodic temperature measurements
+ *
+ * Starts of stops periodic temperature measurements. Periodic measurements
+ * should only be done when a charger is connected.
+ */
+static void ab8500_btemp_periodic(struct ab8500_btemp *di,
+ bool enable)
+{
+ dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n",
+ enable);
+ /*
+ * Make sure a new measurement is done directly by cancelling
+ * any pending work
+ */
+ cancel_delayed_work_sync(&di->btemp_periodic_work);
+
+ if (enable)
+ queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0);
+}
+
+/**
+ * ab8500_btemp_get_temp() - get battery temperature
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * Returns battery temperature
+ */
+static int ab8500_btemp_get_temp(struct ab8500_btemp *di)
+{
+ int temp = 0;
+
+ /*
+ * The BTEMP events are not reliabe on AB8500 cut2.0
+ * and prior versions
+ */
+ if (is_ab8500_2p0_or_earlier(di->parent)) {
+ temp = di->bat_temp * 10;
+ } else {
+ if (di->events.btemp_low) {
+ if (temp > di->btemp_ranges.btemp_low_limit)
+ temp = di->btemp_ranges.btemp_low_limit;
+ else
+ temp = di->bat_temp * 10;
+ } else if (di->events.btemp_high) {
+ if (temp < di->btemp_ranges.btemp_high_limit)
+ temp = di->btemp_ranges.btemp_high_limit;
+ else
+ temp = di->bat_temp * 10;
+ } else if (di->events.btemp_lowmed) {
+ if (temp > di->btemp_ranges.btemp_med_limit)
+ temp = di->btemp_ranges.btemp_med_limit;
+ else
+ temp = di->bat_temp * 10;
+ } else if (di->events.btemp_medhigh) {
+ if (temp < di->btemp_ranges.btemp_med_limit)
+ temp = di->btemp_ranges.btemp_med_limit;
+ else
+ temp = di->bat_temp * 10;
+ } else
+ temp = di->bat_temp * 10;
+ }
+ return temp;
+}
+
+/**
+ * ab8500_btemp_get_batctrl_temp() - get the temperature
+ * @btemp: pointer to the btemp structure
+ *
+ * Returns the batctrl temperature in millidegrees
+ */
+int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp)
+{
+ return btemp->bat_temp * 1000;
+}
+
+/**
+ * ab8500_btemp_get_property() - get the btemp properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the btemp
+ * properties by reading the sysfs files.
+ * online: presence of the battery
+ * present: presence of the battery
+ * technology: battery technology
+ * temp: battery temperature
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_btemp_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab8500_btemp *di;
+
+ di = to_ab8500_btemp_device_info(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (di->events.batt_rem)
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = di->bat->bat_type[di->bat->batt_id].name;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = ab8500_btemp_get_temp(di);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext;
+ struct ab8500_btemp *di;
+ union power_supply_propval ret;
+ int i, j;
+ bool psy_found = false;
+
+ psy = (struct power_supply *)data;
+ ext = dev_get_drvdata(dev);
+ di = to_ab8500_btemp_device_info(psy);
+
+ /*
+ * For all psy where the name of your driver
+ * appears in any supplied_to
+ */
+ for (i = 0; i < ext->num_supplicants; i++) {
+ if (!strcmp(ext->supplied_to[i], psy->name))
+ psy_found = true;
+ }
+
+ if (!psy_found)
+ return 0;
+
+ /* Go through all properties for the psy */
+ for (j = 0; j < ext->num_properties; j++) {
+ enum power_supply_property prop;
+ prop = ext->properties[j];
+
+ if (ext->get_property(ext, prop, &ret))
+ continue;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_MAINS:
+ /* AC disconnected */
+ if (!ret.intval && di->events.ac_conn) {
+ di->events.ac_conn = false;
+ }
+ /* AC connected */
+ else if (ret.intval && !di->events.ac_conn) {
+ di->events.ac_conn = true;
+ if (!di->events.usb_conn)
+ ab8500_btemp_periodic(di, true);
+ }
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ /* USB disconnected */
+ if (!ret.intval && di->events.usb_conn) {
+ di->events.usb_conn = false;
+ }
+ /* USB connected */
+ else if (ret.intval && !di->events.usb_conn) {
+ di->events.usb_conn = true;
+ if (!di->events.ac_conn)
+ ab8500_btemp_periodic(di, true);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ab8500_btemp_external_power_changed() - callback for power supply changes
+ * @psy: pointer to the structure power_supply
+ *
+ * This function is pointing to the function pointer external_power_changed
+ * of the structure power_supply.
+ * This function gets executed when there is a change in the external power
+ * supply to the btemp.
+ */
+static void ab8500_btemp_external_power_changed(struct power_supply *psy)
+{
+ struct ab8500_btemp *di = to_ab8500_btemp_device_info(psy);
+
+ class_for_each_device(power_supply_class, NULL,
+ &di->btemp_psy, ab8500_btemp_get_ext_psy_data);
+}
+
+/* ab8500 btemp driver interrupts and their respective isr */
+static struct ab8500_btemp_interrupts ab8500_btemp_irq[] = {
+ {"BAT_CTRL_INDB", ab8500_btemp_batctrlindb_handler},
+ {"BTEMP_LOW", ab8500_btemp_templow_handler},
+ {"BTEMP_HIGH", ab8500_btemp_temphigh_handler},
+ {"BTEMP_LOW_MEDIUM", ab8500_btemp_lowmed_handler},
+ {"BTEMP_MEDIUM_HIGH", ab8500_btemp_medhigh_handler},
+};
+
+#if defined(CONFIG_PM)
+static int ab8500_btemp_resume(struct platform_device *pdev)
+{
+ struct ab8500_btemp *di = platform_get_drvdata(pdev);
+
+ ab8500_btemp_periodic(di, true);
+
+ return 0;
+}
+
+static int ab8500_btemp_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ab8500_btemp *di = platform_get_drvdata(pdev);
+
+ ab8500_btemp_periodic(di, false);
+
+ return 0;
+}
+#else
+#define ab8500_btemp_suspend NULL
+#define ab8500_btemp_resume NULL
+#endif
+
+static int __devexit ab8500_btemp_remove(struct platform_device *pdev)
+{
+ struct ab8500_btemp *di = platform_get_drvdata(pdev);
+ int i, irq;
+
+ /* Disable interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
+ free_irq(irq, di);
+ }
+
+ /* Delete the work queue */
+ destroy_workqueue(di->btemp_wq);
+
+ flush_scheduled_work();
+ power_supply_unregister(&di->btemp_psy);
+ platform_set_drvdata(pdev, NULL);
+ kfree(di);
+
+ return 0;
+}
+
+static int __devinit ab8500_btemp_probe(struct platform_device *pdev)
+{
+ int irq, i, ret = 0;
+ u8 val;
+ struct abx500_bm_plat_data *plat_data;
+
+ struct ab8500_btemp *di =
+ kzalloc(sizeof(struct ab8500_btemp), GFP_KERNEL);
+ if (!di)
+ return -ENOMEM;
+
+ /* get parent data */
+ di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+ di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+ /* get btemp specific platform data */
+ plat_data = pdev->dev.platform_data;
+ di->pdata = plat_data->btemp;
+ if (!di->pdata) {
+ dev_err(di->dev, "no btemp platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ /* get battery specific platform data */
+ di->bat = plat_data->battery;
+ if (!di->bat) {
+ dev_err(di->dev, "no battery platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ /* BTEMP supply */
+ di->btemp_psy.name = "ab8500_btemp";
+ di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->btemp_psy.properties = ab8500_btemp_props;
+ di->btemp_psy.num_properties = ARRAY_SIZE(ab8500_btemp_props);
+ di->btemp_psy.get_property = ab8500_btemp_get_property;
+ di->btemp_psy.supplied_to = di->pdata->supplied_to;
+ di->btemp_psy.num_supplicants = di->pdata->num_supplicants;
+ di->btemp_psy.external_power_changed =
+ ab8500_btemp_external_power_changed;
+
+
+ /* Create a work queue for the btemp */
+ di->btemp_wq =
+ create_singlethread_workqueue("ab8500_btemp_wq");
+ if (di->btemp_wq == NULL) {
+ dev_err(di->dev, "failed to create work queue\n");
+ goto free_device_info;
+ }
+
+ /* Init work for measuring temperature periodically */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->btemp_periodic_work,
+ ab8500_btemp_periodic_work);
+
+ /* Identify the battery */
+ if (ab8500_btemp_id(di) < 0)
+ dev_warn(di->dev, "failed to identify the battery\n");
+
+ /* Set BTEMP thermal limits. Low and Med are fixed */
+ di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT;
+ di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_BTEMP_HIGH_TH, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ goto free_btemp_wq;
+ }
+ switch (val) {
+ case BTEMP_HIGH_TH_57_0:
+ case BTEMP_HIGH_TH_57_1:
+ di->btemp_ranges.btemp_high_limit =
+ BTEMP_THERMAL_HIGH_LIMIT_57;
+ break;
+ case BTEMP_HIGH_TH_52:
+ di->btemp_ranges.btemp_high_limit =
+ BTEMP_THERMAL_HIGH_LIMIT_52;
+ break;
+ case BTEMP_HIGH_TH_62:
+ di->btemp_ranges.btemp_high_limit =
+ BTEMP_THERMAL_HIGH_LIMIT_62;
+ break;
+ }
+
+ /* Register BTEMP power supply class */
+ ret = power_supply_register(di->dev, &di->btemp_psy);
+ if (ret) {
+ dev_err(di->dev, "failed to register BTEMP psy\n");
+ goto free_btemp_wq;
+ }
+
+ /* Register interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
+ ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND,
+ ab8500_btemp_irq[i].name, di);
+
+ if (ret) {
+ dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+ , ab8500_btemp_irq[i].name, irq, ret);
+ goto free_irq;
+ }
+ dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+ ab8500_btemp_irq[i].name, irq, ret);
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ /* Kick off periodic temperature measurements */
+ ab8500_btemp_periodic(di, true);
+ list_add_tail(&di->node, &ab8500_btemp_list);
+
+ return ret;
+
+free_irq:
+ power_supply_unregister(&di->btemp_psy);
+
+ /* We also have to free all successfully registered irqs */
+ for (i = i - 1; i >= 0; i--) {
+ irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
+ free_irq(irq, di);
+ }
+free_btemp_wq:
+ destroy_workqueue(di->btemp_wq);
+free_device_info:
+ kfree(di);
+
+ return ret;
+}
+
+static struct platform_driver ab8500_btemp_driver = {
+ .probe = ab8500_btemp_probe,
+ .remove = __devexit_p(ab8500_btemp_remove),
+ .suspend = ab8500_btemp_suspend,
+ .resume = ab8500_btemp_resume,
+ .driver = {
+ .name = "ab8500-btemp",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ab8500_btemp_init(void)
+{
+ return platform_driver_register(&ab8500_btemp_driver);
+}
+
+static void __exit ab8500_btemp_exit(void)
+{
+ platform_driver_unregister(&ab8500_btemp_driver);
+}
+
+subsys_initcall_sync(ab8500_btemp_init);
+module_exit(ab8500_btemp_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
+MODULE_ALIAS("platform:ab8500-btemp");
+MODULE_DESCRIPTION("AB8500 battery temperature driver");
diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c
new file mode 100644
index 00000000..e2b4accb
--- /dev/null
+++ b/drivers/power/ab8500_charger.c
@@ -0,0 +1,2789 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Charger driver for AB8500
+ *
+ * License Terms: GNU General Public License v2
+ * Author:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@stericsson.com>
+ * Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+#include <linux/kobject.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/usb/otg.h>
+
+/* Charger constants */
+#define NO_PW_CONN 0
+#define AC_PW_CONN 1
+#define USB_PW_CONN 2
+
+#define MAIN_WDOG_ENA 0x01
+#define MAIN_WDOG_KICK 0x02
+#define MAIN_WDOG_DIS 0x00
+#define CHARG_WD_KICK 0x01
+#define MAIN_CH_ENA 0x01
+#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02
+#define USB_CH_ENA 0x01
+#define USB_CHG_NO_OVERSHOOT_ENA_N 0x02
+#define MAIN_CH_DET 0x01
+#define MAIN_CH_CV_ON 0x04
+#define USB_CH_CV_ON 0x08
+#define VBUS_DET_DBNC100 0x02
+#define VBUS_DET_DBNC1 0x01
+#define OTP_ENABLE_WD 0x01
+
+#define MAIN_CH_INPUT_CURR_SHIFT 4
+#define VBUS_IN_CURR_LIM_SHIFT 4
+
+#define LED_INDICATOR_PWM_ENA 0x01
+#define LED_INDICATOR_PWM_DIS 0x00
+#define LED_IND_CUR_5MA 0x04
+#define LED_INDICATOR_PWM_DUTY_252_256 0xBF
+
+/* HW failure constants */
+#define MAIN_CH_TH_PROT 0x02
+#define VBUS_CH_NOK 0x08
+#define USB_CH_TH_PROT 0x02
+#define VBUS_OVV_TH 0x01
+#define MAIN_CH_NOK 0x01
+#define VBUS_DET 0x80
+
+/* UsbLineStatus register bit masks */
+#define AB8500_USB_LINK_STATUS 0x78
+#define AB8500_STD_HOST_SUSP 0x18
+
+/* Watchdog timeout constant */
+#define WD_TIMER 0x30 /* 4min */
+#define WD_KICK_INTERVAL (60 * HZ)
+
+/* Lowest charger voltage is 3.39V -> 0x4E */
+#define LOW_VOLT_REG 0x4E
+
+/* UsbLineStatus register - usb types */
+enum ab8500_charger_link_status {
+ USB_STAT_NOT_CONFIGURED,
+ USB_STAT_STD_HOST_NC,
+ USB_STAT_STD_HOST_C_NS,
+ USB_STAT_STD_HOST_C_S,
+ USB_STAT_HOST_CHG_NM,
+ USB_STAT_HOST_CHG_HS,
+ USB_STAT_HOST_CHG_HS_CHIRP,
+ USB_STAT_DEDICATED_CHG,
+ USB_STAT_ACA_RID_A,
+ USB_STAT_ACA_RID_B,
+ USB_STAT_ACA_RID_C_NM,
+ USB_STAT_ACA_RID_C_HS,
+ USB_STAT_ACA_RID_C_HS_CHIRP,
+ USB_STAT_HM_IDGND,
+ USB_STAT_RESERVED,
+ USB_STAT_NOT_VALID_LINK,
+};
+
+enum ab8500_usb_state {
+ AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */
+ AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */
+ AB8500_BM_USB_STATE_CONFIGURED,
+ AB8500_BM_USB_STATE_SUSPEND,
+ AB8500_BM_USB_STATE_RESUME,
+ AB8500_BM_USB_STATE_MAX,
+};
+
+/* VBUS input current limits supported in AB8500 in mA */
+#define USB_CH_IP_CUR_LVL_0P05 50
+#define USB_CH_IP_CUR_LVL_0P09 98
+#define USB_CH_IP_CUR_LVL_0P19 193
+#define USB_CH_IP_CUR_LVL_0P29 290
+#define USB_CH_IP_CUR_LVL_0P38 380
+#define USB_CH_IP_CUR_LVL_0P45 450
+#define USB_CH_IP_CUR_LVL_0P5 500
+#define USB_CH_IP_CUR_LVL_0P6 600
+#define USB_CH_IP_CUR_LVL_0P7 700
+#define USB_CH_IP_CUR_LVL_0P8 800
+#define USB_CH_IP_CUR_LVL_0P9 900
+#define USB_CH_IP_CUR_LVL_1P0 1000
+#define USB_CH_IP_CUR_LVL_1P1 1100
+#define USB_CH_IP_CUR_LVL_1P3 1300
+#define USB_CH_IP_CUR_LVL_1P4 1400
+#define USB_CH_IP_CUR_LVL_1P5 1500
+
+#define VBAT_TRESH_IP_CUR_RED 3800
+
+#define to_ab8500_charger_usb_device_info(x) container_of((x), \
+ struct ab8500_charger, usb_chg)
+#define to_ab8500_charger_ac_device_info(x) container_of((x), \
+ struct ab8500_charger, ac_chg)
+
+/**
+ * struct ab8500_charger_interrupts - ab8500 interupts
+ * @name: name of the interrupt
+ * @isr function pointer to the isr
+ */
+struct ab8500_charger_interrupts {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct ab8500_charger_info {
+ int charger_connected;
+ int charger_online;
+ int charger_voltage;
+ int cv_active;
+ bool wd_expired;
+};
+
+struct ab8500_charger_event_flags {
+ bool mainextchnotok;
+ bool main_thermal_prot;
+ bool usb_thermal_prot;
+ bool vbus_ovv;
+ bool usbchargernotok;
+ bool chgwdexp;
+ bool vbus_collapse;
+};
+
+struct ab8500_charger_usb_state {
+ bool usb_changed;
+ int usb_current;
+ enum ab8500_usb_state state;
+ spinlock_t usb_lock;
+};
+
+/**
+ * struct ab8500_charger - ab8500 Charger device information
+ * @dev: Pointer to the structure device
+ * @max_usb_in_curr: Max USB charger input current
+ * @vbus_detected: VBUS detected
+ * @vbus_detected_start:
+ * VBUS detected during startup
+ * @ac_conn: This will be true when the AC charger has been plugged
+ * @vddadc_en_ac: Indicate if VDD ADC supply is enabled because AC
+ * charger is enabled
+ * @vddadc_en_usb: Indicate if VDD ADC supply is enabled because USB
+ * charger is enabled
+ * @vbat Battery voltage
+ * @old_vbat Previously measured battery voltage
+ * @autopower Indicate if we should have automatic pwron after pwrloss
+ * @parent: Pointer to the struct ab8500
+ * @gpadc: Pointer to the struct gpadc
+ * @pdata: Pointer to the abx500_charger platform data
+ * @bat: Pointer to the abx500_bm platform data
+ * @flags: Structure for information about events triggered
+ * @usb_state: Structure for usb stack information
+ * @ac_chg: AC charger power supply
+ * @usb_chg: USB charger power supply
+ * @ac: Structure that holds the AC charger properties
+ * @usb: Structure that holds the USB charger properties
+ * @regu: Pointer to the struct regulator
+ * @charger_wq: Work queue for the IRQs and checking HW state
+ * @check_vbat_work Work for checking vbat threshold to adjust vbus current
+ * @check_hw_failure_work: Work for checking HW state
+ * @check_usbchgnotok_work: Work for checking USB charger not ok status
+ * @kick_wd_work: Work for kicking the charger watchdog in case
+ * of ABB rev 1.* due to the watchog logic bug
+ * @ac_work: Work for checking AC charger connection
+ * @detect_usb_type_work: Work for detecting the USB type connected
+ * @usb_link_status_work: Work for checking the new USB link status
+ * @usb_state_changed_work: Work for checking USB state
+ * @check_main_thermal_prot_work:
+ * Work for checking Main thermal status
+ * @check_usb_thermal_prot_work:
+ * Work for checking USB thermal status
+ */
+struct ab8500_charger {
+ struct device *dev;
+ int max_usb_in_curr;
+ bool vbus_detected;
+ bool vbus_detected_start;
+ bool ac_conn;
+ bool vddadc_en_ac;
+ bool vddadc_en_usb;
+ int vbat;
+ int old_vbat;
+ bool autopower;
+ struct ab8500 *parent;
+ struct ab8500_gpadc *gpadc;
+ struct abx500_charger_platform_data *pdata;
+ struct abx500_bm_data *bat;
+ struct ab8500_charger_event_flags flags;
+ struct ab8500_charger_usb_state usb_state;
+ struct ux500_charger ac_chg;
+ struct ux500_charger usb_chg;
+ struct ab8500_charger_info ac;
+ struct ab8500_charger_info usb;
+ struct regulator *regu;
+ struct workqueue_struct *charger_wq;
+ struct delayed_work check_vbat_work;
+ struct delayed_work check_hw_failure_work;
+ struct delayed_work check_usbchgnotok_work;
+ struct delayed_work kick_wd_work;
+ struct work_struct ac_work;
+ struct work_struct detect_usb_type_work;
+ struct work_struct usb_link_status_work;
+ struct work_struct usb_state_changed_work;
+ struct work_struct check_main_thermal_prot_work;
+ struct work_struct check_usb_thermal_prot_work;
+ struct usb_phy *usb_phy;
+ struct notifier_block nb;
+};
+
+/* AC properties */
+static enum power_supply_property ab8500_charger_ac_props[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+/* USB properties */
+static enum power_supply_property ab8500_charger_usb_props[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+/**
+ * ab8500_power_loss_handling - set how we handle powerloss.
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Magic nummbers are from STE HW department.
+ */
+static void ab8500_power_loss_handling(struct ab8500_charger *di)
+{
+ u8 reg;
+ int ret;
+
+ dev_dbg(di->dev, "Autopower : %d\n", di->autopower);
+
+ /* read the autopower register */
+ ret = abx500_get_register_interruptible(di->dev, 0x15, 0x00, &reg);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ return;
+ }
+
+ /* enable the OPT emulation registers */
+ ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ return;
+ }
+
+ if (di->autopower)
+ reg |= 0x8;
+ else
+ reg &= ~0x8;
+
+ /* write back the changed value to autopower reg */
+ ret = abx500_set_register_interruptible(di->dev, 0x15, 0x00, reg);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ return;
+ }
+
+ /* disable the set OTP registers again */
+ ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ return;
+ }
+}
+
+/**
+ * ab8500_power_supply_changed - a wrapper with local extentions for
+ * power_supply_changed
+ * @di: pointer to the ab8500_charger structure
+ * @psy: pointer to power_supply_that have changed.
+ *
+ */
+static void ab8500_power_supply_changed(struct ab8500_charger *di,
+ struct power_supply *psy)
+{
+ if (di->pdata->autopower_cfg) {
+ if (!di->usb.charger_connected &&
+ !di->ac.charger_connected &&
+ di->autopower) {
+ di->autopower = false;
+ ab8500_power_loss_handling(di);
+ } else if (!di->autopower &&
+ (di->ac.charger_connected ||
+ di->usb.charger_connected)) {
+ di->autopower = true;
+ ab8500_power_loss_handling(di);
+ }
+ }
+ power_supply_changed(psy);
+}
+
+static void ab8500_charger_set_usb_connected(struct ab8500_charger *di,
+ bool connected)
+{
+ if (connected != di->usb.charger_connected) {
+ dev_dbg(di->dev, "USB connected:%i\n", connected);
+ di->usb.charger_connected = connected;
+ sysfs_notify(&di->usb_chg.psy.dev->kobj, NULL, "present");
+ }
+}
+
+/**
+ * ab8500_charger_get_ac_voltage() - get ac charger voltage
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Returns ac charger voltage (on success)
+ */
+static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di)
+{
+ int vch;
+
+ /* Only measure voltage if the charger is connected */
+ if (di->ac.charger_connected) {
+ vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V);
+ if (vch < 0)
+ dev_err(di->dev, "%s gpadc conv failed,\n", __func__);
+ } else {
+ vch = 0;
+ }
+ return vch;
+}
+
+/**
+ * ab8500_charger_ac_cv() - check if the main charger is in CV mode
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Returns ac charger CV mode (on success) else error code
+ */
+static int ab8500_charger_ac_cv(struct ab8500_charger *di)
+{
+ u8 val;
+ int ret = 0;
+
+ /* Only check CV mode if the charger is online */
+ if (di->ac.charger_online) {
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_STATUS1_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return 0;
+ }
+
+ if (val & MAIN_CH_CV_ON)
+ ret = 1;
+ else
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_get_vbus_voltage() - get vbus voltage
+ * @di: pointer to the ab8500_charger structure
+ *
+ * This function returns the vbus voltage.
+ * Returns vbus voltage (on success)
+ */
+static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di)
+{
+ int vch;
+
+ /* Only measure voltage if the charger is connected */
+ if (di->usb.charger_connected) {
+ vch = ab8500_gpadc_convert(di->gpadc, VBUS_V);
+ if (vch < 0)
+ dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+ } else {
+ vch = 0;
+ }
+ return vch;
+}
+
+/**
+ * ab8500_charger_get_usb_current() - get usb charger current
+ * @di: pointer to the ab8500_charger structure
+ *
+ * This function returns the usb charger current.
+ * Returns usb current (on success) and error code on failure
+ */
+static int ab8500_charger_get_usb_current(struct ab8500_charger *di)
+{
+ int ich;
+
+ /* Only measure current if the charger is online */
+ if (di->usb.charger_online) {
+ ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C);
+ if (ich < 0)
+ dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+ } else {
+ ich = 0;
+ }
+ return ich;
+}
+
+/**
+ * ab8500_charger_get_ac_current() - get ac charger current
+ * @di: pointer to the ab8500_charger structure
+ *
+ * This function returns the ac charger current.
+ * Returns ac current (on success) and error code on failure.
+ */
+static int ab8500_charger_get_ac_current(struct ab8500_charger *di)
+{
+ int ich;
+
+ /* Only measure current if the charger is online */
+ if (di->ac.charger_online) {
+ ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C);
+ if (ich < 0)
+ dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+ } else {
+ ich = 0;
+ }
+ return ich;
+}
+
+/**
+ * ab8500_charger_usb_cv() - check if the usb charger is in CV mode
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Returns ac charger CV mode (on success) else error code
+ */
+static int ab8500_charger_usb_cv(struct ab8500_charger *di)
+{
+ int ret;
+ u8 val;
+
+ /* Only check CV mode if the charger is online */
+ if (di->usb.charger_online) {
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_USBCH_STAT1_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return 0;
+ }
+
+ if (val & USB_CH_CV_ON)
+ ret = 1;
+ else
+ ret = 0;
+ } else {
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_detect_chargers() - Detect the connected chargers
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Returns the type of charger connected.
+ * For USB it will not mean we can actually charge from it
+ * but that there is a USB cable connected that we have to
+ * identify. This is used during startup when we don't get
+ * interrupts of the charger detection
+ *
+ * Returns an integer value, that means,
+ * NO_PW_CONN no power supply is connected
+ * AC_PW_CONN if the AC power supply is connected
+ * USB_PW_CONN if the USB power supply is connected
+ * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected
+ */
+static int ab8500_charger_detect_chargers(struct ab8500_charger *di)
+{
+ int result = NO_PW_CONN;
+ int ret;
+ u8 val;
+
+ /* Check for AC charger */
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_STATUS1_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+
+ if (val & MAIN_CH_DET)
+ result = AC_PW_CONN;
+
+ /* Check for USB charger */
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_USBCH_STAT1_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+
+ if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100))
+ result |= USB_PW_CONN;
+
+ return result;
+}
+
+/**
+ * ab8500_charger_max_usb_curr() - get the max curr for the USB type
+ * @di: pointer to the ab8500_charger structure
+ * @link_status: the identified USB type
+ *
+ * Get the maximum current that is allowed to be drawn from the host
+ * based on the USB type.
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
+ enum ab8500_charger_link_status link_status)
+{
+ int ret = 0;
+
+ switch (link_status) {
+ case USB_STAT_STD_HOST_NC:
+ case USB_STAT_STD_HOST_C_NS:
+ case USB_STAT_STD_HOST_C_S:
+ dev_dbg(di->dev, "USB Type - Standard host is "
+ "detected through USB driver\n");
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09;
+ break;
+ case USB_STAT_HOST_CHG_HS_CHIRP:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+ break;
+ case USB_STAT_HOST_CHG_HS:
+ case USB_STAT_ACA_RID_C_HS:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9;
+ break;
+ case USB_STAT_ACA_RID_A:
+ /*
+ * Dedicated charger level minus maximum current accessory
+ * can consume (300mA). Closest level is 1100mA
+ */
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P1;
+ break;
+ case USB_STAT_ACA_RID_B:
+ /*
+ * Dedicated charger level minus 120mA (20mA for ACA and
+ * 100mA for potential accessory). Closest level is 1300mA
+ */
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3;
+ break;
+ case USB_STAT_DEDICATED_CHG:
+ case USB_STAT_HOST_CHG_NM:
+ case USB_STAT_ACA_RID_C_HS_CHIRP:
+ case USB_STAT_ACA_RID_C_NM:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5;
+ break;
+ case USB_STAT_RESERVED:
+ /*
+ * This state is used to indicate that VBUS has dropped below
+ * the detection level 4 times in a row. This is due to the
+ * charger output current is set to high making the charger
+ * voltage collapse. This have to be propagated through to
+ * chargalg. This is done using the property
+ * POWER_SUPPLY_PROP_CURRENT_AVG = 1
+ */
+ di->flags.vbus_collapse = true;
+ dev_dbg(di->dev, "USB Type - USB_STAT_RESERVED "
+ "VBUS has collapsed\n");
+ ret = -1;
+ break;
+ case USB_STAT_HM_IDGND:
+ case USB_STAT_NOT_CONFIGURED:
+ case USB_STAT_NOT_VALID_LINK:
+ dev_err(di->dev, "USB Type - Charging not allowed\n");
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+ ret = -ENXIO;
+ break;
+ default:
+ dev_err(di->dev, "USB Type - Unknown\n");
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+ ret = -ENXIO;
+ break;
+ };
+
+ dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
+ link_status, di->max_usb_in_curr);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_read_usb_type() - read the type of usb connected
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Detect the type of the plugged USB
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_charger_read_usb_type(struct ab8500_charger *di)
+{
+ int ret;
+ u8 val;
+
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINE_STAT_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+
+ /* get the USB type */
+ val = (val & AB8500_USB_LINK_STATUS) >> 3;
+ ret = ab8500_charger_max_usb_curr(di,
+ (enum ab8500_charger_link_status) val);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_detect_usb_type() - get the type of usb connected
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Detect the type of the plugged USB
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
+{
+ int i, ret;
+ u8 val;
+
+ /*
+ * On getting the VBUS rising edge detect interrupt there
+ * is a 250ms delay after which the register UsbLineStatus
+ * is filled with valid data.
+ */
+ for (i = 0; i < 10; i++) {
+ msleep(250);
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG,
+ &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINE_STAT_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+ /*
+ * Until the IT source register is read the UsbLineStatus
+ * register is not updated, hence doing the same
+ * Revisit this:
+ */
+
+ /* get the USB type */
+ val = (val & AB8500_USB_LINK_STATUS) >> 3;
+ if (val)
+ break;
+ }
+ ret = ab8500_charger_max_usb_curr(di,
+ (enum ab8500_charger_link_status) val);
+
+ return ret;
+}
+
+/*
+ * This array maps the raw hex value to charger voltage used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_charger_voltage_map[] = {
+ 3500 ,
+ 3525 ,
+ 3550 ,
+ 3575 ,
+ 3600 ,
+ 3625 ,
+ 3650 ,
+ 3675 ,
+ 3700 ,
+ 3725 ,
+ 3750 ,
+ 3775 ,
+ 3800 ,
+ 3825 ,
+ 3850 ,
+ 3875 ,
+ 3900 ,
+ 3925 ,
+ 3950 ,
+ 3975 ,
+ 4000 ,
+ 4025 ,
+ 4050 ,
+ 4060 ,
+ 4070 ,
+ 4080 ,
+ 4090 ,
+ 4100 ,
+ 4110 ,
+ 4120 ,
+ 4130 ,
+ 4140 ,
+ 4150 ,
+ 4160 ,
+ 4170 ,
+ 4180 ,
+ 4190 ,
+ 4200 ,
+ 4210 ,
+ 4220 ,
+ 4230 ,
+ 4240 ,
+ 4250 ,
+ 4260 ,
+ 4270 ,
+ 4280 ,
+ 4290 ,
+ 4300 ,
+ 4310 ,
+ 4320 ,
+ 4330 ,
+ 4340 ,
+ 4350 ,
+ 4360 ,
+ 4370 ,
+ 4380 ,
+ 4390 ,
+ 4400 ,
+ 4410 ,
+ 4420 ,
+ 4430 ,
+ 4440 ,
+ 4450 ,
+ 4460 ,
+ 4470 ,
+ 4480 ,
+ 4490 ,
+ 4500 ,
+ 4510 ,
+ 4520 ,
+ 4530 ,
+ 4540 ,
+ 4550 ,
+ 4560 ,
+ 4570 ,
+ 4580 ,
+ 4590 ,
+ 4600 ,
+};
+
+/*
+ * This array maps the raw hex value to charger current used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_charger_current_map[] = {
+ 100 ,
+ 200 ,
+ 300 ,
+ 400 ,
+ 500 ,
+ 600 ,
+ 700 ,
+ 800 ,
+ 900 ,
+ 1000 ,
+ 1100 ,
+ 1200 ,
+ 1300 ,
+ 1400 ,
+ 1500 ,
+};
+
+/*
+ * This array maps the raw hex value to VBUS input current used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_charger_vbus_in_curr_map[] = {
+ USB_CH_IP_CUR_LVL_0P05,
+ USB_CH_IP_CUR_LVL_0P09,
+ USB_CH_IP_CUR_LVL_0P19,
+ USB_CH_IP_CUR_LVL_0P29,
+ USB_CH_IP_CUR_LVL_0P38,
+ USB_CH_IP_CUR_LVL_0P45,
+ USB_CH_IP_CUR_LVL_0P5,
+ USB_CH_IP_CUR_LVL_0P6,
+ USB_CH_IP_CUR_LVL_0P7,
+ USB_CH_IP_CUR_LVL_0P8,
+ USB_CH_IP_CUR_LVL_0P9,
+ USB_CH_IP_CUR_LVL_1P0,
+ USB_CH_IP_CUR_LVL_1P1,
+ USB_CH_IP_CUR_LVL_1P3,
+ USB_CH_IP_CUR_LVL_1P4,
+ USB_CH_IP_CUR_LVL_1P5,
+};
+
+static int ab8500_voltage_to_regval(int voltage)
+{
+ int i;
+
+ /* Special case for voltage below 3.5V */
+ if (voltage < ab8500_charger_voltage_map[0])
+ return LOW_VOLT_REG;
+
+ for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) {
+ if (voltage < ab8500_charger_voltage_map[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1;
+ if (voltage == ab8500_charger_voltage_map[i])
+ return i;
+ else
+ return -1;
+}
+
+static int ab8500_current_to_regval(int curr)
+{
+ int i;
+
+ if (curr < ab8500_charger_current_map[0])
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ab8500_charger_current_map); i++) {
+ if (curr < ab8500_charger_current_map[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = ARRAY_SIZE(ab8500_charger_current_map) - 1;
+ if (curr == ab8500_charger_current_map[i])
+ return i;
+ else
+ return -1;
+}
+
+static int ab8500_vbus_in_curr_to_regval(int curr)
+{
+ int i;
+
+ if (curr < ab8500_charger_vbus_in_curr_map[0])
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ab8500_charger_vbus_in_curr_map); i++) {
+ if (curr < ab8500_charger_vbus_in_curr_map[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = ARRAY_SIZE(ab8500_charger_vbus_in_curr_map) - 1;
+ if (curr == ab8500_charger_vbus_in_curr_map[i])
+ return i;
+ else
+ return -1;
+}
+
+/**
+ * ab8500_charger_get_usb_cur() - get usb current
+ * @di: pointer to the ab8500_charger structre
+ *
+ * The usb stack provides the maximum current that can be drawn from
+ * the standard usb host. This will be in mA.
+ * This function converts current in mA to a value that can be written
+ * to the register. Returns -1 if charging is not allowed
+ */
+static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
+{
+ switch (di->usb_state.usb_current) {
+ case 100:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09;
+ break;
+ case 200:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19;
+ break;
+ case 300:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29;
+ break;
+ case 400:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38;
+ break;
+ case 500:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5;
+ break;
+ default:
+ di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05;
+ return -1;
+ break;
+ };
+ return 0;
+}
+
+/**
+ * ab8500_charger_set_vbus_in_curr() - set VBUS input current limit
+ * @di: pointer to the ab8500_charger structure
+ * @ich_in: charger input current limit
+ *
+ * Sets the current that can be drawn from the USB host
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
+ int ich_in)
+{
+ int ret;
+ int input_curr_index;
+ int min_value;
+
+ /* We should always use to lowest current limit */
+ min_value = min(di->bat->chg_params->usb_curr_max, ich_in);
+
+ switch (min_value) {
+ case 100:
+ if (di->vbat < VBAT_TRESH_IP_CUR_RED)
+ min_value = USB_CH_IP_CUR_LVL_0P05;
+ break;
+ case 500:
+ if (di->vbat < VBAT_TRESH_IP_CUR_RED)
+ min_value = USB_CH_IP_CUR_LVL_0P45;
+ break;
+ default:
+ break;
+ }
+
+ input_curr_index = ab8500_vbus_in_curr_to_regval(min_value);
+ if (input_curr_index < 0) {
+ dev_err(di->dev, "VBUS input current limit too high\n");
+ return -ENXIO;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_USBCH_IPT_CRNTLVL_REG,
+ input_curr_index << VBUS_IN_CURR_LIM_SHIFT);
+ if (ret)
+ dev_err(di->dev, "%s write failed\n", __func__);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_led_en() - turn on/off chargign led
+ * @di: pointer to the ab8500_charger structure
+ * @on: flag to turn on/off the chargign led
+ *
+ * Power ON/OFF charging LED indication
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_led_en(struct ab8500_charger *di, int on)
+{
+ int ret;
+
+ if (on) {
+ /* Power ON charging LED indicator, set LED current to 5mA */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_LED_INDICATOR_PWM_CTRL,
+ (LED_IND_CUR_5MA | LED_INDICATOR_PWM_ENA));
+ if (ret) {
+ dev_err(di->dev, "Power ON LED failed\n");
+ return ret;
+ }
+ /* LED indicator PWM duty cycle 252/256 */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_LED_INDICATOR_PWM_DUTY,
+ LED_INDICATOR_PWM_DUTY_252_256);
+ if (ret) {
+ dev_err(di->dev, "Set LED PWM duty cycle failed\n");
+ return ret;
+ }
+ } else {
+ /* Power off charging LED indicator */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_LED_INDICATOR_PWM_CTRL,
+ LED_INDICATOR_PWM_DIS);
+ if (ret) {
+ dev_err(di->dev, "Power-off LED failed\n");
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_ac_en() - enable or disable ac charging
+ * @di: pointer to the ab8500_charger structure
+ * @enable: enable/disable flag
+ * @vset: charging voltage
+ * @iset: charging current
+ *
+ * Enable/Disable AC/Mains charging and turns on/off the charging led
+ * respectively.
+ **/
+static int ab8500_charger_ac_en(struct ux500_charger *charger,
+ int enable, int vset, int iset)
+{
+ int ret;
+ int volt_index;
+ int curr_index;
+ int input_curr_index;
+ u8 overshoot = 0;
+
+ struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger);
+
+ if (enable) {
+ /* Check if AC is connected */
+ if (!di->ac.charger_connected) {
+ dev_err(di->dev, "AC charger not connected\n");
+ return -ENXIO;
+ }
+
+ /* Enable AC charging */
+ dev_dbg(di->dev, "Enable AC: %dmV %dmA\n", vset, iset);
+
+ /*
+ * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts
+ * will be triggered everytime we enable the VDD ADC supply.
+ * This will turn off charging for a short while.
+ * It can be avoided by having the supply on when
+ * there is a charger enabled. Normally the VDD ADC supply
+ * is enabled everytime a GPADC conversion is triggered. We will
+ * force it to be enabled from this driver to have
+ * the GPADC module independant of the AB8500 chargers
+ */
+ if (!di->vddadc_en_ac) {
+ regulator_enable(di->regu);
+ di->vddadc_en_ac = true;
+ }
+
+ /* Check if the requested voltage or current is valid */
+ volt_index = ab8500_voltage_to_regval(vset);
+ curr_index = ab8500_current_to_regval(iset);
+ input_curr_index = ab8500_current_to_regval(
+ di->bat->chg_params->ac_curr_max);
+ if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) {
+ dev_err(di->dev,
+ "Charger voltage or current too high, "
+ "charging not started\n");
+ return -ENXIO;
+ }
+
+ /* ChVoltLevel: maximum battery charging voltage */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_VOLT_LVL_REG, (u8) volt_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ /* MainChInputCurr: current that can be drawn from the charger*/
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_MCH_IPT_CURLVL_REG,
+ input_curr_index << MAIN_CH_INPUT_CURR_SHIFT);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ /* ChOutputCurentLevel: protected output current */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ /* Check if VBAT overshoot control should be enabled */
+ if (!di->bat->enable_overshoot)
+ overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N;
+
+ /* Enable Main Charger */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_MCH_CTRL1, MAIN_CH_ENA | overshoot);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ /* Power on charging LED indication */
+ ret = ab8500_charger_led_en(di, true);
+ if (ret < 0)
+ dev_err(di->dev, "failed to enable LED\n");
+
+ di->ac.charger_online = 1;
+ } else {
+ /* Disable AC charging */
+ if (is_ab8500_1p1_or_earlier(di->parent)) {
+ /*
+ * For ABB revision 1.0 and 1.1 there is a bug in the
+ * watchdog logic. That means we have to continously
+ * kick the charger watchdog even when no charger is
+ * connected. This is only valid once the AC charger
+ * has been enabled. This is a bug that is not handled
+ * by the algorithm and the watchdog have to be kicked
+ * by the charger driver when the AC charger
+ * is disabled
+ */
+ if (di->ac_conn) {
+ queue_delayed_work(di->charger_wq,
+ &di->kick_wd_work,
+ round_jiffies(WD_KICK_INTERVAL));
+ }
+
+ /*
+ * We can't turn off charging completely
+ * due to a bug in AB8500 cut1.
+ * If we do, charging will not start again.
+ * That is why we set the lowest voltage
+ * and current possible
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5);
+ if (ret) {
+ dev_err(di->dev,
+ "%s write failed\n", __func__);
+ return ret;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_OPT_CRNTLVL_REG, CH_OP_CUR_LVL_0P1);
+ if (ret) {
+ dev_err(di->dev,
+ "%s write failed\n", __func__);
+ return ret;
+ }
+ } else {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_MCH_CTRL1, 0);
+ if (ret) {
+ dev_err(di->dev,
+ "%s write failed\n", __func__);
+ return ret;
+ }
+ }
+
+ ret = ab8500_charger_led_en(di, false);
+ if (ret < 0)
+ dev_err(di->dev, "failed to disable LED\n");
+
+ di->ac.charger_online = 0;
+ di->ac.wd_expired = false;
+
+ /* Disable regulator if enabled */
+ if (di->vddadc_en_ac) {
+ regulator_disable(di->regu);
+ di->vddadc_en_ac = false;
+ }
+
+ dev_dbg(di->dev, "%s Disabled AC charging\n", __func__);
+ }
+ ab8500_power_supply_changed(di, &di->ac_chg.psy);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_usb_en() - enable usb charging
+ * @di: pointer to the ab8500_charger structure
+ * @enable: enable/disable flag
+ * @vset: charging voltage
+ * @ich_out: charger output current
+ *
+ * Enable/Disable USB charging and turns on/off the charging led respectively.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_usb_en(struct ux500_charger *charger,
+ int enable, int vset, int ich_out)
+{
+ int ret;
+ int volt_index;
+ int curr_index;
+ u8 overshoot = 0;
+
+ struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger);
+
+ if (enable) {
+ /* Check if USB is connected */
+ if (!di->usb.charger_connected) {
+ dev_err(di->dev, "USB charger not connected\n");
+ return -ENXIO;
+ }
+
+ /*
+ * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts
+ * will be triggered everytime we enable the VDD ADC supply.
+ * This will turn off charging for a short while.
+ * It can be avoided by having the supply on when
+ * there is a charger enabled. Normally the VDD ADC supply
+ * is enabled everytime a GPADC conversion is triggered. We will
+ * force it to be enabled from this driver to have
+ * the GPADC module independant of the AB8500 chargers
+ */
+ if (!di->vddadc_en_usb) {
+ regulator_enable(di->regu);
+ di->vddadc_en_usb = true;
+ }
+
+ /* Enable USB charging */
+ dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out);
+
+ /* Check if the requested voltage or current is valid */
+ volt_index = ab8500_voltage_to_regval(vset);
+ curr_index = ab8500_current_to_regval(ich_out);
+ if (volt_index < 0 || curr_index < 0) {
+ dev_err(di->dev,
+ "Charger voltage or current too high, "
+ "charging not started\n");
+ return -ENXIO;
+ }
+
+ /* ChVoltLevel: max voltage upto which battery can be charged */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_VOLT_LVL_REG, (u8) volt_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ /* USBChInputCurr: current that can be drawn from the usb */
+ ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
+ if (ret) {
+ dev_err(di->dev, "setting USBChInputCurr failed\n");
+ return ret;
+ }
+ /* ChOutputCurentLevel: protected output current */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ /* Check if VBAT overshoot control should be enabled */
+ if (!di->bat->enable_overshoot)
+ overshoot = USB_CHG_NO_OVERSHOOT_ENA_N;
+
+ /* Enable USB Charger */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ /* If success power on charging LED indication */
+ ret = ab8500_charger_led_en(di, true);
+ if (ret < 0)
+ dev_err(di->dev, "failed to enable LED\n");
+
+ queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ);
+
+ di->usb.charger_online = 1;
+ } else {
+ /* Disable USB charging */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL1_REG, 0);
+ if (ret) {
+ dev_err(di->dev,
+ "%s write failed\n", __func__);
+ return ret;
+ }
+
+ ret = ab8500_charger_led_en(di, false);
+ if (ret < 0)
+ dev_err(di->dev, "failed to disable LED\n");
+
+ di->usb.charger_online = 0;
+ di->usb.wd_expired = false;
+
+ /* Disable regulator if enabled */
+ if (di->vddadc_en_usb) {
+ regulator_disable(di->regu);
+ di->vddadc_en_usb = false;
+ }
+
+ dev_dbg(di->dev, "%s Disabled USB charging\n", __func__);
+
+ /* Cancel any pending Vbat check work */
+ if (delayed_work_pending(&di->check_vbat_work))
+ cancel_delayed_work(&di->check_vbat_work);
+
+ }
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_watchdog_kick() - kick charger watchdog
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Kick charger watchdog
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_watchdog_kick(struct ux500_charger *charger)
+{
+ int ret;
+ struct ab8500_charger *di;
+
+ if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
+ di = to_ab8500_charger_ac_device_info(charger);
+ else if (charger->psy.type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab8500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
+ if (ret)
+ dev_err(di->dev, "Failed to kick WD!\n");
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_update_charger_current() - update charger current
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Update the charger output current for the specified charger
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_update_charger_current(struct ux500_charger *charger,
+ int ich_out)
+{
+ int ret;
+ int curr_index;
+ struct ab8500_charger *di;
+
+ if (charger->psy.type == POWER_SUPPLY_TYPE_MAINS)
+ di = to_ab8500_charger_ac_device_info(charger);
+ else if (charger->psy.type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab8500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ curr_index = ab8500_current_to_regval(ich_out);
+ if (curr_index < 0) {
+ dev_err(di->dev,
+ "Charger current too high, "
+ "charging not started\n");
+ return -ENXIO;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_OPT_CRNTLVL_REG, (u8) curr_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ /* Reset the main and usb drop input current measurement counter */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CHARGER_CTRL,
+ 0x1);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext;
+ struct ab8500_charger *di;
+ union power_supply_propval ret;
+ int i, j;
+ bool psy_found = false;
+ struct ux500_charger *usb_chg;
+
+ usb_chg = (struct ux500_charger *)data;
+ psy = &usb_chg->psy;
+
+ di = to_ab8500_charger_usb_device_info(usb_chg);
+
+ ext = dev_get_drvdata(dev);
+
+ /* For all psy where the driver name appears in any supplied_to */
+ for (i = 0; i < ext->num_supplicants; i++) {
+ if (!strcmp(ext->supplied_to[i], psy->name))
+ psy_found = true;
+ }
+
+ if (!psy_found)
+ return 0;
+
+ /* Go through all properties for the psy */
+ for (j = 0; j < ext->num_properties; j++) {
+ enum power_supply_property prop;
+ prop = ext->properties[j];
+
+ if (ext->get_property(ext, prop, &ret))
+ continue;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ di->vbat = ret.intval / 1000;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ab8500_charger_check_vbat_work() - keep vbus current within spec
+ * @work pointer to the work_struct structure
+ *
+ * Due to a asic bug it is necessary to lower the input current to the vbus
+ * charger when charging with at some specific levels. This issue is only valid
+ * for below a certain battery voltage. This function makes sure that the
+ * the allowed current limit isn't exceeded.
+ */
+static void ab8500_charger_check_vbat_work(struct work_struct *work)
+{
+ int t = 10;
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_vbat_work.work);
+
+ class_for_each_device(power_supply_class, NULL,
+ &di->usb_chg.psy, ab8500_charger_get_ext_psy_data);
+
+ /* First run old_vbat is 0. */
+ if (di->old_vbat == 0)
+ di->old_vbat = di->vbat;
+
+ if (!((di->old_vbat <= VBAT_TRESH_IP_CUR_RED &&
+ di->vbat <= VBAT_TRESH_IP_CUR_RED) ||
+ (di->old_vbat > VBAT_TRESH_IP_CUR_RED &&
+ di->vbat > VBAT_TRESH_IP_CUR_RED))) {
+
+ dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d,"
+ " old: %d\n", di->max_usb_in_curr, di->vbat,
+ di->old_vbat);
+ ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr);
+ power_supply_changed(&di->usb_chg.psy);
+ }
+
+ di->old_vbat = di->vbat;
+
+ /*
+ * No need to check the battery voltage every second when not close to
+ * the threshold.
+ */
+ if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100) &&
+ (di->vbat > (VBAT_TRESH_IP_CUR_RED - 100)))
+ t = 1;
+
+ queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ);
+}
+
+/**
+ * ab8500_charger_check_hw_failure_work() - check main charger failure
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the main charger status
+ */
+static void ab8500_charger_check_hw_failure_work(struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_hw_failure_work.work);
+
+ /* Check if the status bits for HW failure is still active */
+ if (di->flags.mainextchnotok) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_STATUS2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if (!(reg_value & MAIN_CH_NOK)) {
+ di->flags.mainextchnotok = false;
+ ab8500_power_supply_changed(di, &di->ac_chg.psy);
+ }
+ }
+ if (di->flags.vbus_ovv) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG,
+ &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if (!(reg_value & VBUS_OVV_TH)) {
+ di->flags.vbus_ovv = false;
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+ }
+ }
+ /* If we still have a failure, schedule a new check */
+ if (di->flags.mainextchnotok || di->flags.vbus_ovv) {
+ queue_delayed_work(di->charger_wq,
+ &di->check_hw_failure_work, round_jiffies(HZ));
+ }
+}
+
+/**
+ * ab8500_charger_kick_watchdog_work() - kick the watchdog
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for kicking the charger watchdog.
+ *
+ * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+ * logic. That means we have to continously kick the charger
+ * watchdog even when no charger is connected. This is only
+ * valid once the AC charger has been enabled. This is
+ * a bug that is not handled by the algorithm and the
+ * watchdog have to be kicked by the charger driver
+ * when the AC charger is disabled
+ */
+static void ab8500_charger_kick_watchdog_work(struct work_struct *work)
+{
+ int ret;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, kick_wd_work.work);
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
+ if (ret)
+ dev_err(di->dev, "Failed to kick WD!\n");
+
+ /* Schedule a new watchdog kick */
+ queue_delayed_work(di->charger_wq,
+ &di->kick_wd_work, round_jiffies(WD_KICK_INTERVAL));
+}
+
+/**
+ * ab8500_charger_ac_work() - work to get and set main charger status
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the main charger status
+ */
+static void ab8500_charger_ac_work(struct work_struct *work)
+{
+ int ret;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, ac_work);
+
+ /*
+ * Since we can't be sure that the events are received
+ * synchronously, we have the check if the main charger is
+ * connected by reading the status register
+ */
+ ret = ab8500_charger_detect_chargers(di);
+ if (ret < 0)
+ return;
+
+ if (ret & AC_PW_CONN) {
+ di->ac.charger_connected = 1;
+ di->ac_conn = true;
+ } else {
+ di->ac.charger_connected = 0;
+ }
+
+ ab8500_power_supply_changed(di, &di->ac_chg.psy);
+ sysfs_notify(&di->ac_chg.psy.dev->kobj, NULL, "present");
+}
+
+/**
+ * ab8500_charger_detect_usb_type_work() - work to detect USB type
+ * @work: Pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
+{
+ int ret;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, detect_usb_type_work);
+
+ /*
+ * Since we can't be sure that the events are received
+ * synchronously, we have the check if is
+ * connected by reading the status register
+ */
+ ret = ab8500_charger_detect_chargers(di);
+ if (ret < 0)
+ return;
+
+ if (!(ret & USB_PW_CONN)) {
+ di->vbus_detected = 0;
+ ab8500_charger_set_usb_connected(di, false);
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+ } else {
+ di->vbus_detected = 1;
+
+ if (is_ab8500_1p1_or_earlier(di->parent)) {
+ ret = ab8500_charger_detect_usb_type(di);
+ if (!ret) {
+ ab8500_charger_set_usb_connected(di, true);
+ ab8500_power_supply_changed(di,
+ &di->usb_chg.psy);
+ }
+ } else {
+ /* For ABB cut2.0 and onwards we have an IRQ,
+ * USB_LINK_STATUS that will be triggered when the USB
+ * link status changes. The exception is USB connected
+ * during startup. Then we don't get a
+ * USB_LINK_STATUS IRQ
+ */
+ if (di->vbus_detected_start) {
+ di->vbus_detected_start = false;
+ ret = ab8500_charger_detect_usb_type(di);
+ if (!ret) {
+ ab8500_charger_set_usb_connected(di,
+ true);
+ ab8500_power_supply_changed(di,
+ &di->usb_chg.psy);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * ab8500_charger_usb_link_status_work() - work to detect USB type
+ * @work: pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+static void ab8500_charger_usb_link_status_work(struct work_struct *work)
+{
+ int ret;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, usb_link_status_work);
+
+ /*
+ * Since we can't be sure that the events are received
+ * synchronously, we have the check if is
+ * connected by reading the status register
+ */
+ ret = ab8500_charger_detect_chargers(di);
+ if (ret < 0)
+ return;
+
+ if (!(ret & USB_PW_CONN)) {
+ di->vbus_detected = 0;
+ ab8500_charger_set_usb_connected(di, false);
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+ } else {
+ di->vbus_detected = 1;
+ ret = ab8500_charger_read_usb_type(di);
+ if (!ret) {
+ /* Update maximum input current */
+ ret = ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr);
+ if (ret)
+ return;
+
+ ab8500_charger_set_usb_connected(di, true);
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+ } else if (ret == -ENXIO) {
+ /* No valid charger type detected */
+ ab8500_charger_set_usb_connected(di, false);
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+ }
+ }
+}
+
+static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
+{
+ int ret;
+ unsigned long flags;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, usb_state_changed_work);
+
+ if (!di->vbus_detected)
+ return;
+
+ spin_lock_irqsave(&di->usb_state.usb_lock, flags);
+ di->usb_state.usb_changed = false;
+ spin_unlock_irqrestore(&di->usb_state.usb_lock, flags);
+
+ /*
+ * wait for some time until you get updates from the usb stack
+ * and negotiations are completed
+ */
+ msleep(250);
+
+ if (di->usb_state.usb_changed)
+ return;
+
+ dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n",
+ __func__, di->usb_state.state, di->usb_state.usb_current);
+
+ switch (di->usb_state.state) {
+ case AB8500_BM_USB_STATE_RESET_HS:
+ case AB8500_BM_USB_STATE_RESET_FS:
+ case AB8500_BM_USB_STATE_SUSPEND:
+ case AB8500_BM_USB_STATE_MAX:
+ ab8500_charger_set_usb_connected(di, false);
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+ break;
+
+ case AB8500_BM_USB_STATE_RESUME:
+ /*
+ * when suspend->resume there should be delay
+ * of 1sec for enabling charging
+ */
+ msleep(1000);
+ /* Intentional fall through */
+ case AB8500_BM_USB_STATE_CONFIGURED:
+ /*
+ * USB is configured, enable charging with the charging
+ * input current obtained from USB driver
+ */
+ if (!ab8500_charger_get_usb_cur(di)) {
+ /* Update maximum input current */
+ ret = ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr);
+ if (ret)
+ return;
+
+ ab8500_charger_set_usb_connected(di, true);
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+ }
+ break;
+
+ default:
+ break;
+ };
+}
+
+/**
+ * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the USB charger Not OK status
+ */
+static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+ bool prev_status;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_usbchgnotok_work.work);
+
+ /* Check if the status bit for usbchargernotok is still active */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ prev_status = di->flags.usbchargernotok;
+
+ if (reg_value & VBUS_CH_NOK) {
+ di->flags.usbchargernotok = true;
+ /* Check again in 1sec */
+ queue_delayed_work(di->charger_wq,
+ &di->check_usbchgnotok_work, HZ);
+ } else {
+ di->flags.usbchargernotok = false;
+ di->flags.vbus_collapse = false;
+ }
+
+ if (prev_status != di->flags.usbchargernotok)
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+}
+
+/**
+ * ab8500_charger_check_main_thermal_prot_work() - check main thermal status
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the Main thermal prot status
+ */
+static void ab8500_charger_check_main_thermal_prot_work(
+ struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_main_thermal_prot_work);
+
+ /* Check if the status bit for main_thermal_prot is still active */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_STATUS2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if (reg_value & MAIN_CH_TH_PROT)
+ di->flags.main_thermal_prot = true;
+ else
+ di->flags.main_thermal_prot = false;
+
+ ab8500_power_supply_changed(di, &di->ac_chg.psy);
+}
+
+/**
+ * ab8500_charger_check_usb_thermal_prot_work() - check usb thermal status
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the USB thermal prot status
+ */
+static void ab8500_charger_check_usb_thermal_prot_work(
+ struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_usb_thermal_prot_work);
+
+ /* Check if the status bit for usb_thermal_prot is still active */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if (reg_value & USB_CH_TH_PROT)
+ di->flags.usb_thermal_prot = true;
+ else
+ di->flags.usb_thermal_prot = false;
+
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+}
+
+/**
+ * ab8500_charger_mainchunplugdet_handler() - main charger unplugged
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Main charger unplugged\n");
+ queue_work(di->charger_wq, &di->ac_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainchplugdet_handler() - main charger plugged
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Main charger plugged\n");
+ queue_work(di->charger_wq, &di->ac_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainextchnotok_handler() - main charger not ok
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainextchnotok_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Main charger not ok\n");
+ di->flags.mainextchnotok = true;
+ ab8500_power_supply_changed(di, &di->ac_chg.psy);
+
+ /* Schedule a new HW failure check */
+ queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainchthprotr_handler() - Die temp is above main charger
+ * thermal protection threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchthprotr_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev,
+ "Die temp above Main charger thermal protection threshold\n");
+ queue_work(di->charger_wq, &di->check_main_thermal_prot_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainchthprotf_handler() - Die temp is below main charger
+ * thermal protection threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev,
+ "Die temp ok for Main charger thermal protection threshold\n");
+ queue_work(di->charger_wq, &di->check_main_thermal_prot_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_vbusdetf_handler() - VBUS falling detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "VBUS falling detected\n");
+ queue_work(di->charger_wq, &di->detect_usb_type_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_vbusdetr_handler() - VBUS rising detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ di->vbus_detected = true;
+ dev_dbg(di->dev, "VBUS rising detected\n");
+ queue_work(di->charger_wq, &di->detect_usb_type_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usblinkstatus_handler() - USB link status has changed
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usblinkstatus_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "USB link status changed\n");
+
+ queue_work(di->charger_wq, &di->usb_link_status_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usbchthprotr_handler() - Die temp is above usb charger
+ * thermal protection threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usbchthprotr_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev,
+ "Die temp above USB charger thermal protection threshold\n");
+ queue_work(di->charger_wq, &di->check_usb_thermal_prot_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usbchthprotf_handler() - Die temp is below usb charger
+ * thermal protection threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev,
+ "Die temp ok for USB charger thermal protection threshold\n");
+ queue_work(di->charger_wq, &di->check_usb_thermal_prot_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Not allowed USB charger detected\n");
+ queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_chwdexp_handler() - Charger watchdog expired
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Charger watchdog expired\n");
+
+ /*
+ * The charger that was online when the watchdog expired
+ * needs to be restarted for charging to start again
+ */
+ if (di->ac.charger_online) {
+ di->ac.wd_expired = true;
+ ab8500_power_supply_changed(di, &di->ac_chg.psy);
+ }
+ if (di->usb.charger_online) {
+ di->usb.wd_expired = true;
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_vbusovv_handler() - VBUS overvoltage detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbusovv_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "VBUS overvoltage detected\n");
+ di->flags.vbus_ovv = true;
+ ab8500_power_supply_changed(di, &di->usb_chg.psy);
+
+ /* Schedule a new HW failure check */
+ queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_ac_get_property() - get the ac/mains properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the ac/mains
+ * properties by reading the sysfs files.
+ * AC/Mains properties are online, present and voltage.
+ * online: ac/mains charging is in progress or not
+ * present: presence of the ac/mains
+ * voltage: AC/Mains voltage
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab8500_charger *di;
+
+ di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy));
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (di->flags.mainextchnotok)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (di->ac.wd_expired || di->usb.wd_expired)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else if (di->flags.main_thermal_prot)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = di->ac.charger_online;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = di->ac.charger_connected;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ di->ac.charger_voltage = ab8500_charger_get_ac_voltage(di);
+ val->intval = di->ac.charger_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ /*
+ * This property is used to indicate when CV mode is entered
+ * for the AC charger
+ */
+ di->ac.cv_active = ab8500_charger_ac_cv(di);
+ val->intval = di->ac.cv_active;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = ab8500_charger_get_ac_current(di) * 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * ab8500_charger_usb_get_property() - get the usb properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the usb
+ * properties by reading the sysfs files.
+ * USB properties are online, present and voltage.
+ * online: usb charging is in progress or not
+ * present: presence of the usb
+ * voltage: vbus voltage
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab8500_charger *di;
+
+ di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy));
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (di->flags.usbchargernotok)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (di->ac.wd_expired || di->usb.wd_expired)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else if (di->flags.usb_thermal_prot)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (di->flags.vbus_ovv)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = di->usb.charger_online;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = di->usb.charger_connected;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ di->usb.charger_voltage = ab8500_charger_get_vbus_voltage(di);
+ val->intval = di->usb.charger_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ /*
+ * This property is used to indicate when CV mode is entered
+ * for the USB charger
+ */
+ di->usb.cv_active = ab8500_charger_usb_cv(di);
+ val->intval = di->usb.cv_active;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = ab8500_charger_get_usb_current(di) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ /*
+ * This property is used to indicate when VBUS has collapsed
+ * due to too high output current from the USB charger
+ */
+ if (di->flags.vbus_collapse)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * ab8500_charger_init_hw_registers() - Set up charger related registers
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Set up charger OVV, watchdog and maximum voltage registers as well as
+ * charging of the backup battery
+ */
+static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
+{
+ int ret = 0;
+
+ /* Setup maximum charger current and voltage for ABB cut2.0 */
+ if (!is_ab8500_1p1_or_earlier(di->parent)) {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to set CH_VOLT_LVL_MAX_REG\n");
+ goto out;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_OPT_CRNTLVL_MAX_REG, CH_OP_CUR_LVL_1P6);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to set CH_OPT_CRNTLVL_MAX_REG\n");
+ goto out;
+ }
+ }
+
+ /* VBUS OVV set to 6.3V and enable automatic current limitiation */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL2_REG,
+ VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA);
+ if (ret) {
+ dev_err(di->dev, "failed to set VBUS OVV\n");
+ goto out;
+ }
+
+ /* Enable main watchdog in OTP */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_OTP_EMUL, AB8500_OTP_CONF_15, OTP_ENABLE_WD);
+ if (ret) {
+ dev_err(di->dev, "failed to enable main WD in OTP\n");
+ goto out;
+ }
+
+ /* Enable main watchdog */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
+ AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA);
+ if (ret) {
+ dev_err(di->dev, "faile to enable main watchdog\n");
+ goto out;
+ }
+
+ /*
+ * Due to internal synchronisation, Enable and Kick watchdog bits
+ * cannot be enabled in a single write.
+ * A minimum delay of 2*32 kHz period (62.5µs) must be inserted
+ * between writing Enable then Kick bits.
+ */
+ udelay(63);
+
+ /* Kick main watchdog */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
+ AB8500_MAIN_WDOG_CTRL_REG,
+ (MAIN_WDOG_ENA | MAIN_WDOG_KICK));
+ if (ret) {
+ dev_err(di->dev, "failed to kick main watchdog\n");
+ goto out;
+ }
+
+ /* Disable main watchdog */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
+ AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS);
+ if (ret) {
+ dev_err(di->dev, "failed to disable main watchdog\n");
+ goto out;
+ }
+
+ /* Set watchdog timeout */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_WD_TIMER_REG, WD_TIMER);
+ if (ret) {
+ dev_err(di->dev, "failed to set charger watchdog timeout\n");
+ goto out;
+ }
+
+ /* Backup battery voltage and current */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_BACKUP_CHG_REG,
+ di->bat->bkup_bat_v |
+ di->bat->bkup_bat_i);
+ if (ret) {
+ dev_err(di->dev, "failed to setup backup battery charging\n");
+ goto out;
+ }
+
+ /* Enable backup battery charging */
+ abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_RTC, AB8500_RTC_CTRL_REG,
+ RTC_BUP_CH_ENA, RTC_BUP_CH_ENA);
+ if (ret < 0)
+ dev_err(di->dev, "%s mask and set failed\n", __func__);
+
+out:
+ return ret;
+}
+
+/*
+ * ab8500 charger driver interrupts and their respective isr
+ */
+static struct ab8500_charger_interrupts ab8500_charger_irq[] = {
+ {"MAIN_CH_UNPLUG_DET", ab8500_charger_mainchunplugdet_handler},
+ {"MAIN_CHARGE_PLUG_DET", ab8500_charger_mainchplugdet_handler},
+ {"MAIN_EXT_CH_NOT_OK", ab8500_charger_mainextchnotok_handler},
+ {"MAIN_CH_TH_PROT_R", ab8500_charger_mainchthprotr_handler},
+ {"MAIN_CH_TH_PROT_F", ab8500_charger_mainchthprotf_handler},
+ {"VBUS_DET_F", ab8500_charger_vbusdetf_handler},
+ {"VBUS_DET_R", ab8500_charger_vbusdetr_handler},
+ {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler},
+ {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler},
+ {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler},
+ {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler},
+ {"VBUS_OVV", ab8500_charger_vbusovv_handler},
+ {"CH_WD_EXP", ab8500_charger_chwdexp_handler},
+};
+
+static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
+ unsigned long event, void *power)
+{
+ struct ab8500_charger *di =
+ container_of(nb, struct ab8500_charger, nb);
+ enum ab8500_usb_state bm_usb_state;
+ unsigned mA = *((unsigned *)power);
+
+ if (event != USB_EVENT_VBUS) {
+ dev_dbg(di->dev, "not a standard host, returning\n");
+ return NOTIFY_DONE;
+ }
+
+ /* TODO: State is fabricate here. See if charger really needs USB
+ * state or if mA is enough
+ */
+ if ((di->usb_state.usb_current == 2) && (mA > 2))
+ bm_usb_state = AB8500_BM_USB_STATE_RESUME;
+ else if (mA == 0)
+ bm_usb_state = AB8500_BM_USB_STATE_RESET_HS;
+ else if (mA == 2)
+ bm_usb_state = AB8500_BM_USB_STATE_SUSPEND;
+ else if (mA >= 8) /* 8, 100, 500 */
+ bm_usb_state = AB8500_BM_USB_STATE_CONFIGURED;
+ else /* Should never occur */
+ bm_usb_state = AB8500_BM_USB_STATE_RESET_FS;
+
+ dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n",
+ __func__, bm_usb_state, mA);
+
+ spin_lock(&di->usb_state.usb_lock);
+ di->usb_state.usb_changed = true;
+ spin_unlock(&di->usb_state.usb_lock);
+
+ di->usb_state.state = bm_usb_state;
+ di->usb_state.usb_current = mA;
+
+ queue_work(di->charger_wq, &di->usb_state_changed_work);
+
+ return NOTIFY_OK;
+}
+
+#if defined(CONFIG_PM)
+static int ab8500_charger_resume(struct platform_device *pdev)
+{
+ int ret;
+ struct ab8500_charger *di = platform_get_drvdata(pdev);
+
+ /*
+ * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+ * logic. That means we have to continously kick the charger
+ * watchdog even when no charger is connected. This is only
+ * valid once the AC charger has been enabled. This is
+ * a bug that is not handled by the algorithm and the
+ * watchdog have to be kicked by the charger driver
+ * when the AC charger is disabled
+ */
+ if (di->ac_conn && is_ab8500_1p1_or_earlier(di->parent)) {
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
+ if (ret)
+ dev_err(di->dev, "Failed to kick WD!\n");
+
+ /* If not already pending start a new timer */
+ if (!delayed_work_pending(
+ &di->kick_wd_work)) {
+ queue_delayed_work(di->charger_wq, &di->kick_wd_work,
+ round_jiffies(WD_KICK_INTERVAL));
+ }
+ }
+
+ /* If we still have a HW failure, schedule a new check */
+ if (di->flags.mainextchnotok || di->flags.vbus_ovv) {
+ queue_delayed_work(di->charger_wq,
+ &di->check_hw_failure_work, 0);
+ }
+
+ return 0;
+}
+
+static int ab8500_charger_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ab8500_charger *di = platform_get_drvdata(pdev);
+
+ /* Cancel any pending HW failure check */
+ if (delayed_work_pending(&di->check_hw_failure_work))
+ cancel_delayed_work(&di->check_hw_failure_work);
+
+ return 0;
+}
+#else
+#define ab8500_charger_suspend NULL
+#define ab8500_charger_resume NULL
+#endif
+
+static int __devexit ab8500_charger_remove(struct platform_device *pdev)
+{
+ struct ab8500_charger *di = platform_get_drvdata(pdev);
+ int i, irq, ret;
+
+ /* Disable AC charging */
+ ab8500_charger_ac_en(&di->ac_chg, false, 0, 0);
+
+ /* Disable USB charging */
+ ab8500_charger_usb_en(&di->usb_chg, false, 0, 0);
+
+ /* Disable interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+ free_irq(irq, di);
+ }
+
+ /* disable the regulator */
+ regulator_put(di->regu);
+
+ /* Backup battery voltage and current disable */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0);
+ if (ret < 0)
+ dev_err(di->dev, "%s mask and set failed\n", __func__);
+
+ usb_unregister_notifier(di->usb_phy, &di->nb);
+ usb_put_transceiver(di->usb_phy);
+
+ /* Delete the work queue */
+ destroy_workqueue(di->charger_wq);
+
+ flush_scheduled_work();
+ power_supply_unregister(&di->usb_chg.psy);
+ power_supply_unregister(&di->ac_chg.psy);
+ platform_set_drvdata(pdev, NULL);
+ kfree(di);
+
+ return 0;
+}
+
+static int __devinit ab8500_charger_probe(struct platform_device *pdev)
+{
+ int irq, i, charger_status, ret = 0;
+ struct abx500_bm_plat_data *plat_data;
+
+ struct ab8500_charger *di =
+ kzalloc(sizeof(struct ab8500_charger), GFP_KERNEL);
+ if (!di)
+ return -ENOMEM;
+
+ /* get parent data */
+ di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+ di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+ /* initialize lock */
+ spin_lock_init(&di->usb_state.usb_lock);
+
+ /* get charger specific platform data */
+ plat_data = pdev->dev.platform_data;
+ di->pdata = plat_data->charger;
+
+ if (!di->pdata) {
+ dev_err(di->dev, "no charger platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ /* get battery specific platform data */
+ di->bat = plat_data->battery;
+ if (!di->bat) {
+ dev_err(di->dev, "no battery platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ di->autopower = false;
+
+ /* AC supply */
+ /* power_supply base class */
+ di->ac_chg.psy.name = "ab8500_ac";
+ di->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS;
+ di->ac_chg.psy.properties = ab8500_charger_ac_props;
+ di->ac_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_ac_props);
+ di->ac_chg.psy.get_property = ab8500_charger_ac_get_property;
+ di->ac_chg.psy.supplied_to = di->pdata->supplied_to;
+ di->ac_chg.psy.num_supplicants = di->pdata->num_supplicants;
+ /* ux500_charger sub-class */
+ di->ac_chg.ops.enable = &ab8500_charger_ac_en;
+ di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
+ di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current;
+ di->ac_chg.max_out_volt = ab8500_charger_voltage_map[
+ ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
+ di->ac_chg.max_out_curr = ab8500_charger_current_map[
+ ARRAY_SIZE(ab8500_charger_current_map) - 1];
+
+ /* USB supply */
+ /* power_supply base class */
+ di->usb_chg.psy.name = "ab8500_usb";
+ di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB;
+ di->usb_chg.psy.properties = ab8500_charger_usb_props;
+ di->usb_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_usb_props);
+ di->usb_chg.psy.get_property = ab8500_charger_usb_get_property;
+ di->usb_chg.psy.supplied_to = di->pdata->supplied_to;
+ di->usb_chg.psy.num_supplicants = di->pdata->num_supplicants;
+ /* ux500_charger sub-class */
+ di->usb_chg.ops.enable = &ab8500_charger_usb_en;
+ di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
+ di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current;
+ di->usb_chg.max_out_volt = ab8500_charger_voltage_map[
+ ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
+ di->usb_chg.max_out_curr = ab8500_charger_current_map[
+ ARRAY_SIZE(ab8500_charger_current_map) - 1];
+
+
+ /* Create a work queue for the charger */
+ di->charger_wq =
+ create_singlethread_workqueue("ab8500_charger_wq");
+ if (di->charger_wq == NULL) {
+ dev_err(di->dev, "failed to create work queue\n");
+ goto free_device_info;
+ }
+
+ /* Init work for HW failure check */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->check_hw_failure_work,
+ ab8500_charger_check_hw_failure_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&di->check_usbchgnotok_work,
+ ab8500_charger_check_usbchargernotok_work);
+
+ /*
+ * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+ * logic. That means we have to continously kick the charger
+ * watchdog even when no charger is connected. This is only
+ * valid once the AC charger has been enabled. This is
+ * a bug that is not handled by the algorithm and the
+ * watchdog have to be kicked by the charger driver
+ * when the AC charger is disabled
+ */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->kick_wd_work,
+ ab8500_charger_kick_watchdog_work);
+
+ INIT_DELAYED_WORK_DEFERRABLE(&di->check_vbat_work,
+ ab8500_charger_check_vbat_work);
+
+ /* Init work for charger detection */
+ INIT_WORK(&di->usb_link_status_work,
+ ab8500_charger_usb_link_status_work);
+ INIT_WORK(&di->ac_work, ab8500_charger_ac_work);
+ INIT_WORK(&di->detect_usb_type_work,
+ ab8500_charger_detect_usb_type_work);
+
+ INIT_WORK(&di->usb_state_changed_work,
+ ab8500_charger_usb_state_changed_work);
+
+ /* Init work for checking HW status */
+ INIT_WORK(&di->check_main_thermal_prot_work,
+ ab8500_charger_check_main_thermal_prot_work);
+ INIT_WORK(&di->check_usb_thermal_prot_work,
+ ab8500_charger_check_usb_thermal_prot_work);
+
+ /*
+ * VDD ADC supply needs to be enabled from this driver when there
+ * is a charger connected to avoid erroneous BTEMP_HIGH/LOW
+ * interrupts during charging
+ */
+ di->regu = regulator_get(di->dev, "vddadc");
+ if (IS_ERR(di->regu)) {
+ ret = PTR_ERR(di->regu);
+ dev_err(di->dev, "failed to get vddadc regulator\n");
+ goto free_charger_wq;
+ }
+
+
+ /* Initialize OVV, and other registers */
+ ret = ab8500_charger_init_hw_registers(di);
+ if (ret) {
+ dev_err(di->dev, "failed to initialize ABB registers\n");
+ goto free_regulator;
+ }
+
+ /* Register AC charger class */
+ ret = power_supply_register(di->dev, &di->ac_chg.psy);
+ if (ret) {
+ dev_err(di->dev, "failed to register AC charger\n");
+ goto free_regulator;
+ }
+
+ /* Register USB charger class */
+ ret = power_supply_register(di->dev, &di->usb_chg.psy);
+ if (ret) {
+ dev_err(di->dev, "failed to register USB charger\n");
+ goto free_ac;
+ }
+
+ di->usb_phy = usb_get_transceiver();
+ if (!di->usb_phy) {
+ dev_err(di->dev, "failed to get usb transceiver\n");
+ ret = -EINVAL;
+ goto free_usb;
+ }
+ di->nb.notifier_call = ab8500_charger_usb_notifier_call;
+ ret = usb_register_notifier(di->usb_phy, &di->nb);
+ if (ret) {
+ dev_err(di->dev, "failed to register usb notifier\n");
+ goto put_usb_phy;
+ }
+
+ /* Identify the connected charger types during startup */
+ charger_status = ab8500_charger_detect_chargers(di);
+ if (charger_status & AC_PW_CONN) {
+ di->ac.charger_connected = 1;
+ di->ac_conn = true;
+ ab8500_power_supply_changed(di, &di->ac_chg.psy);
+ sysfs_notify(&di->ac_chg.psy.dev->kobj, NULL, "present");
+ }
+
+ if (charger_status & USB_PW_CONN) {
+ dev_dbg(di->dev, "VBUS Detect during startup\n");
+ di->vbus_detected = true;
+ di->vbus_detected_start = true;
+ queue_work(di->charger_wq,
+ &di->detect_usb_type_work);
+ }
+
+ /* Register interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+ ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND,
+ ab8500_charger_irq[i].name, di);
+
+ if (ret != 0) {
+ dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+ , ab8500_charger_irq[i].name, irq, ret);
+ goto free_irq;
+ }
+ dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+ ab8500_charger_irq[i].name, irq, ret);
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ return ret;
+
+free_irq:
+ usb_unregister_notifier(di->usb_phy, &di->nb);
+
+ /* We also have to free all successfully registered irqs */
+ for (i = i - 1; i >= 0; i--) {
+ irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+ free_irq(irq, di);
+ }
+put_usb_phy:
+ usb_put_transceiver(di->usb_phy);
+free_usb:
+ power_supply_unregister(&di->usb_chg.psy);
+free_ac:
+ power_supply_unregister(&di->ac_chg.psy);
+free_regulator:
+ regulator_put(di->regu);
+free_charger_wq:
+ destroy_workqueue(di->charger_wq);
+free_device_info:
+ kfree(di);
+
+ return ret;
+}
+
+static struct platform_driver ab8500_charger_driver = {
+ .probe = ab8500_charger_probe,
+ .remove = __devexit_p(ab8500_charger_remove),
+ .suspend = ab8500_charger_suspend,
+ .resume = ab8500_charger_resume,
+ .driver = {
+ .name = "ab8500-charger",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ab8500_charger_init(void)
+{
+ return platform_driver_register(&ab8500_charger_driver);
+}
+
+static void __exit ab8500_charger_exit(void)
+{
+ platform_driver_unregister(&ab8500_charger_driver);
+}
+
+subsys_initcall_sync(ab8500_charger_init);
+module_exit(ab8500_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
+MODULE_ALIAS("platform:ab8500-charger");
+MODULE_DESCRIPTION("AB8500 charger management driver");
diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c
new file mode 100644
index 00000000..c22f2f05
--- /dev/null
+++ b/drivers/power/ab8500_fg.c
@@ -0,0 +1,2637 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2012
+ *
+ * Main and Back-up battery management driver.
+ *
+ * Note: Backup battery management is required in case of Li-Ion battery and not
+ * for capacitive battery. HREF boards have capacitive battery and hence backup
+ * battery management is not used and the supported code is available in this
+ * driver.
+ *
+ * License Terms: GNU General Public License v2
+ * Author:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@stericsson.com>
+ * Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/kobject.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500.h>
+#include <linux/slab.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/delay.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/mfd/abx500.h>
+#include <linux/time.h>
+#include <linux/completion.h>
+
+#define MILLI_TO_MICRO 1000
+#define FG_LSB_IN_MA 1627
+#define QLSB_NANO_AMP_HOURS_X10 1129
+#define INS_CURR_TIMEOUT (3 * HZ)
+
+#define SEC_TO_SAMPLE(S) (S * 4)
+
+#define NBR_AVG_SAMPLES 20
+
+#define LOW_BAT_CHECK_INTERVAL (2 * HZ)
+
+#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */
+#define BATT_OK_MIN 2360 /* mV */
+#define BATT_OK_INCREMENT 50 /* mV */
+#define BATT_OK_MAX_NR_INCREMENTS 0xE
+
+/* FG constants */
+#define BATT_OVV 0x01
+
+#define interpolate(x, x1, y1, x2, y2) \
+ ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1))));
+
+#define to_ab8500_fg_device_info(x) container_of((x), \
+ struct ab8500_fg, fg_psy);
+
+/**
+ * struct ab8500_fg_interrupts - ab8500 fg interupts
+ * @name: name of the interrupt
+ * @isr function pointer to the isr
+ */
+struct ab8500_fg_interrupts {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+enum ab8500_fg_discharge_state {
+ AB8500_FG_DISCHARGE_INIT,
+ AB8500_FG_DISCHARGE_INITMEASURING,
+ AB8500_FG_DISCHARGE_INIT_RECOVERY,
+ AB8500_FG_DISCHARGE_RECOVERY,
+ AB8500_FG_DISCHARGE_READOUT_INIT,
+ AB8500_FG_DISCHARGE_READOUT,
+ AB8500_FG_DISCHARGE_WAKEUP,
+};
+
+static char *discharge_state[] = {
+ "DISCHARGE_INIT",
+ "DISCHARGE_INITMEASURING",
+ "DISCHARGE_INIT_RECOVERY",
+ "DISCHARGE_RECOVERY",
+ "DISCHARGE_READOUT_INIT",
+ "DISCHARGE_READOUT",
+ "DISCHARGE_WAKEUP",
+};
+
+enum ab8500_fg_charge_state {
+ AB8500_FG_CHARGE_INIT,
+ AB8500_FG_CHARGE_READOUT,
+};
+
+static char *charge_state[] = {
+ "CHARGE_INIT",
+ "CHARGE_READOUT",
+};
+
+enum ab8500_fg_calibration_state {
+ AB8500_FG_CALIB_INIT,
+ AB8500_FG_CALIB_WAIT,
+ AB8500_FG_CALIB_END,
+};
+
+struct ab8500_fg_avg_cap {
+ int avg;
+ int samples[NBR_AVG_SAMPLES];
+ __kernel_time_t time_stamps[NBR_AVG_SAMPLES];
+ int pos;
+ int nbr_samples;
+ int sum;
+};
+
+struct ab8500_fg_battery_capacity {
+ int max_mah_design;
+ int max_mah;
+ int mah;
+ int permille;
+ int level;
+ int prev_mah;
+ int prev_percent;
+ int prev_level;
+ int user_mah;
+};
+
+struct ab8500_fg_flags {
+ bool fg_enabled;
+ bool conv_done;
+ bool charging;
+ bool fully_charged;
+ bool force_full;
+ bool low_bat_delay;
+ bool low_bat;
+ bool bat_ovv;
+ bool batt_unknown;
+ bool calibrate;
+ bool user_cap;
+ bool batt_id_received;
+};
+
+struct inst_curr_result_list {
+ struct list_head list;
+ int *result;
+};
+
+/**
+ * struct ab8500_fg - ab8500 FG device information
+ * @dev: Pointer to the structure device
+ * @node: a list of AB8500 FGs, hence prepared for reentrance
+ * @irq holds the CCEOC interrupt number
+ * @vbat: Battery voltage in mV
+ * @vbat_nom: Nominal battery voltage in mV
+ * @inst_curr: Instantenous battery current in mA
+ * @avg_curr: Average battery current in mA
+ * @bat_temp battery temperature
+ * @fg_samples: Number of samples used in the FG accumulation
+ * @accu_charge: Accumulated charge from the last conversion
+ * @recovery_cnt: Counter for recovery mode
+ * @high_curr_cnt: Counter for high current mode
+ * @init_cnt: Counter for init mode
+ * @recovery_needed: Indicate if recovery is needed
+ * @high_curr_mode: Indicate if we're in high current mode
+ * @init_capacity: Indicate if initial capacity measuring should be done
+ * @turn_off_fg: True if fg was off before current measurement
+ * @calib_state State during offset calibration
+ * @discharge_state: Current discharge state
+ * @charge_state: Current charge state
+ * @ab8500_fg_complete Completion struct used for the instant current reading
+ * @flags: Structure for information about events triggered
+ * @bat_cap: Structure for battery capacity specific parameters
+ * @avg_cap: Average capacity filter
+ * @parent: Pointer to the struct ab8500
+ * @gpadc: Pointer to the struct gpadc
+ * @pdata: Pointer to the abx500_fg platform data
+ * @bat: Pointer to the abx500_bm platform data
+ * @fg_psy: Structure that holds the FG specific battery properties
+ * @fg_wq: Work queue for running the FG algorithm
+ * @fg_periodic_work: Work to run the FG algorithm periodically
+ * @fg_low_bat_work: Work to check low bat condition
+ * @fg_reinit_work Work used to reset and reinitialise the FG algorithm
+ * @fg_work: Work to run the FG algorithm instantly
+ * @fg_acc_cur_work: Work to read the FG accumulator
+ * @fg_check_hw_failure_work: Work for checking HW state
+ * @cc_lock: Mutex for locking the CC
+ * @fg_kobject: Structure of type kobject
+ */
+struct ab8500_fg {
+ struct device *dev;
+ struct list_head node;
+ int irq;
+ int vbat;
+ int vbat_nom;
+ int inst_curr;
+ int avg_curr;
+ int bat_temp;
+ int fg_samples;
+ int accu_charge;
+ int recovery_cnt;
+ int high_curr_cnt;
+ int init_cnt;
+ bool recovery_needed;
+ bool high_curr_mode;
+ bool init_capacity;
+ bool turn_off_fg;
+ enum ab8500_fg_calibration_state calib_state;
+ enum ab8500_fg_discharge_state discharge_state;
+ enum ab8500_fg_charge_state charge_state;
+ struct completion ab8500_fg_complete;
+ struct ab8500_fg_flags flags;
+ struct ab8500_fg_battery_capacity bat_cap;
+ struct ab8500_fg_avg_cap avg_cap;
+ struct ab8500 *parent;
+ struct ab8500_gpadc *gpadc;
+ struct abx500_fg_platform_data *pdata;
+ struct abx500_bm_data *bat;
+ struct power_supply fg_psy;
+ struct workqueue_struct *fg_wq;
+ struct delayed_work fg_periodic_work;
+ struct delayed_work fg_low_bat_work;
+ struct delayed_work fg_reinit_work;
+ struct work_struct fg_work;
+ struct work_struct fg_acc_cur_work;
+ struct delayed_work fg_check_hw_failure_work;
+ struct mutex cc_lock;
+ struct kobject fg_kobject;
+};
+static LIST_HEAD(ab8500_fg_list);
+
+/**
+ * ab8500_fg_get() - returns a reference to the primary AB8500 fuel gauge
+ * (i.e. the first fuel gauge in the instance list)
+ */
+struct ab8500_fg *ab8500_fg_get(void)
+{
+ struct ab8500_fg *fg;
+
+ if (list_empty(&ab8500_fg_list))
+ return NULL;
+
+ fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node);
+ return fg;
+}
+
+/* Main battery properties */
+static enum power_supply_property ab8500_fg_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+};
+
+/*
+ * This array maps the raw hex value to lowbat voltage used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_fg_lowbat_voltage_map[] = {
+ 2300 ,
+ 2325 ,
+ 2350 ,
+ 2375 ,
+ 2400 ,
+ 2425 ,
+ 2450 ,
+ 2475 ,
+ 2500 ,
+ 2525 ,
+ 2550 ,
+ 2575 ,
+ 2600 ,
+ 2625 ,
+ 2650 ,
+ 2675 ,
+ 2700 ,
+ 2725 ,
+ 2750 ,
+ 2775 ,
+ 2800 ,
+ 2825 ,
+ 2850 ,
+ 2875 ,
+ 2900 ,
+ 2925 ,
+ 2950 ,
+ 2975 ,
+ 3000 ,
+ 3025 ,
+ 3050 ,
+ 3075 ,
+ 3100 ,
+ 3125 ,
+ 3150 ,
+ 3175 ,
+ 3200 ,
+ 3225 ,
+ 3250 ,
+ 3275 ,
+ 3300 ,
+ 3325 ,
+ 3350 ,
+ 3375 ,
+ 3400 ,
+ 3425 ,
+ 3450 ,
+ 3475 ,
+ 3500 ,
+ 3525 ,
+ 3550 ,
+ 3575 ,
+ 3600 ,
+ 3625 ,
+ 3650 ,
+ 3675 ,
+ 3700 ,
+ 3725 ,
+ 3750 ,
+ 3775 ,
+ 3800 ,
+ 3825 ,
+ 3850 ,
+ 3850 ,
+};
+
+static u8 ab8500_volt_to_regval(int voltage)
+{
+ int i;
+
+ if (voltage < ab8500_fg_lowbat_voltage_map[0])
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ab8500_fg_lowbat_voltage_map); i++) {
+ if (voltage < ab8500_fg_lowbat_voltage_map[i])
+ return (u8) i - 1;
+ }
+
+ /* If not captured above, return index of last element */
+ return (u8) ARRAY_SIZE(ab8500_fg_lowbat_voltage_map) - 1;
+}
+
+/**
+ * ab8500_fg_is_low_curr() - Low or high current mode
+ * @di: pointer to the ab8500_fg structure
+ * @curr: the current to base or our decision on
+ *
+ * Low current mode if the current consumption is below a certain threshold
+ */
+static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr)
+{
+ /*
+ * We want to know if we're in low current mode
+ */
+ if (curr > -di->bat->fg_params->high_curr_threshold)
+ return true;
+ else
+ return false;
+}
+
+/**
+ * ab8500_fg_add_cap_sample() - Add capacity to average filter
+ * @di: pointer to the ab8500_fg structure
+ * @sample: the capacity in mAh to add to the filter
+ *
+ * A capacity is added to the filter and a new mean capacity is calculated and
+ * returned
+ */
+static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample)
+{
+ struct timespec ts;
+ struct ab8500_fg_avg_cap *avg = &di->avg_cap;
+
+ getnstimeofday(&ts);
+
+ do {
+ avg->sum += sample - avg->samples[avg->pos];
+ avg->samples[avg->pos] = sample;
+ avg->time_stamps[avg->pos] = ts.tv_sec;
+ avg->pos++;
+
+ if (avg->pos == NBR_AVG_SAMPLES)
+ avg->pos = 0;
+
+ if (avg->nbr_samples < NBR_AVG_SAMPLES)
+ avg->nbr_samples++;
+
+ /*
+ * Check the time stamp for each sample. If too old,
+ * replace with latest sample
+ */
+ } while (ts.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]);
+
+ avg->avg = avg->sum / avg->nbr_samples;
+
+ return avg->avg;
+}
+
+/**
+ * ab8500_fg_clear_cap_samples() - Clear average filter
+ * @di: pointer to the ab8500_fg structure
+ *
+ * The capacity filter is is reset to zero.
+ */
+static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di)
+{
+ int i;
+ struct ab8500_fg_avg_cap *avg = &di->avg_cap;
+
+ avg->pos = 0;
+ avg->nbr_samples = 0;
+ avg->sum = 0;
+ avg->avg = 0;
+
+ for (i = 0; i < NBR_AVG_SAMPLES; i++) {
+ avg->samples[i] = 0;
+ avg->time_stamps[i] = 0;
+ }
+}
+
+/**
+ * ab8500_fg_fill_cap_sample() - Fill average filter
+ * @di: pointer to the ab8500_fg structure
+ * @sample: the capacity in mAh to fill the filter with
+ *
+ * The capacity filter is filled with a capacity in mAh
+ */
+static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample)
+{
+ int i;
+ struct timespec ts;
+ struct ab8500_fg_avg_cap *avg = &di->avg_cap;
+
+ getnstimeofday(&ts);
+
+ for (i = 0; i < NBR_AVG_SAMPLES; i++) {
+ avg->samples[i] = sample;
+ avg->time_stamps[i] = ts.tv_sec;
+ }
+
+ avg->pos = 0;
+ avg->nbr_samples = NBR_AVG_SAMPLES;
+ avg->sum = sample * NBR_AVG_SAMPLES;
+ avg->avg = sample;
+}
+
+/**
+ * ab8500_fg_coulomb_counter() - enable coulomb counter
+ * @di: pointer to the ab8500_fg structure
+ * @enable: enable/disable
+ *
+ * Enable/Disable coulomb counter.
+ * On failure returns negative value.
+ */
+static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable)
+{
+ int ret = 0;
+ mutex_lock(&di->cc_lock);
+ if (enable) {
+ /* To be able to reprogram the number of samples, we have to
+ * first stop the CC and then enable it again */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, 0x00);
+ if (ret)
+ goto cc_err;
+
+ /* Program the samples */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU,
+ di->fg_samples);
+ if (ret)
+ goto cc_err;
+
+ /* Start the CC */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG,
+ (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+ if (ret)
+ goto cc_err;
+
+ di->flags.fg_enabled = true;
+ } else {
+ /* Clear any pending read requests */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0);
+ if (ret)
+ goto cc_err;
+
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU_CTRL, 0);
+ if (ret)
+ goto cc_err;
+
+ /* Stop the CC */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, 0);
+ if (ret)
+ goto cc_err;
+
+ di->flags.fg_enabled = false;
+
+ }
+ dev_dbg(di->dev, " CC enabled: %d Samples: %d\n",
+ enable, di->fg_samples);
+
+ mutex_unlock(&di->cc_lock);
+
+ return ret;
+cc_err:
+ dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__);
+ mutex_unlock(&di->cc_lock);
+ return ret;
+}
+
+/**
+ * ab8500_fg_inst_curr_start() - start battery instantaneous current
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns 0 or error code
+ * Note: This is part "one" and has to be called before
+ * ab8500_fg_inst_curr_finalize()
+ */
+ int ab8500_fg_inst_curr_start(struct ab8500_fg *di)
+{
+ u8 reg_val;
+ int ret;
+
+ mutex_lock(&di->cc_lock);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, &reg_val);
+ if (ret < 0)
+ goto fail;
+
+ if (!(reg_val & CC_PWR_UP_ENA)) {
+ dev_dbg(di->dev, "%s Enable FG\n", __func__);
+ di->turn_off_fg = true;
+
+ /* Program the samples */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU,
+ SEC_TO_SAMPLE(10));
+ if (ret)
+ goto fail;
+
+ /* Start the CC */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG,
+ (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+ if (ret)
+ goto fail;
+ } else {
+ di->turn_off_fg = false;
+ }
+
+ /* Return and WFI */
+ INIT_COMPLETION(di->ab8500_fg_complete);
+ enable_irq(di->irq);
+
+ /* Note: cc_lock is still locked */
+ return 0;
+fail:
+ mutex_unlock(&di->cc_lock);
+ return ret;
+}
+
+/**
+ * ab8500_fg_inst_curr_done() - check if fg conversion is done
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns 1 if conversion done, 0 if still waiting
+ */
+int ab8500_fg_inst_curr_done(struct ab8500_fg *di)
+{
+ return completion_done(&di->ab8500_fg_complete);
+}
+
+/**
+ * ab8500_fg_inst_curr_finalize() - battery instantaneous current
+ * @di: pointer to the ab8500_fg structure
+ * @res: battery instantenous current(on success)
+ *
+ * Returns 0 or an error code
+ * Note: This is part "two" and has to be called at earliest 250 ms
+ * after ab8500_fg_inst_curr_start()
+ */
+int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res)
+{
+ u8 low, high;
+ int val;
+ int ret;
+ int timeout;
+
+ if (!completion_done(&di->ab8500_fg_complete)) {
+ timeout = wait_for_completion_timeout(&di->ab8500_fg_complete,
+ INS_CURR_TIMEOUT);
+ dev_dbg(di->dev, "Finalize time: %d ms\n",
+ ((INS_CURR_TIMEOUT - timeout) * 1000) / HZ);
+ if (!timeout) {
+ ret = -ETIME;
+ disable_irq(di->irq);
+ dev_err(di->dev, "completion timed out [%d]\n",
+ __LINE__);
+ goto fail;
+ }
+ }
+
+ disable_irq(di->irq);
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ READ_REQ, READ_REQ);
+
+ /* 100uS between read request and read is needed */
+ usleep_range(100, 100);
+
+ /* Read CC Sample conversion value Low and high */
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_SMPL_CNVL_REG, &low);
+ if (ret < 0)
+ goto fail;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_SMPL_CNVH_REG, &high);
+ if (ret < 0)
+ goto fail;
+
+ /*
+ * negative value for Discharging
+ * convert 2's compliment into decimal
+ */
+ if (high & 0x10)
+ val = (low | (high << 8) | 0xFFFFE000);
+ else
+ val = (low | (high << 8));
+
+ /*
+ * Convert to unit value in mA
+ * Full scale input voltage is
+ * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA
+ * Given a 250ms conversion cycle time the LSB corresponds
+ * to 112.9 nAh. Convert to current by dividing by the conversion
+ * time in hours (250ms = 1 / (3600 * 4)h)
+ * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+ */
+ val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) /
+ (1000 * di->bat->fg_res);
+
+ if (di->turn_off_fg) {
+ dev_dbg(di->dev, "%s Disable FG\n", __func__);
+
+ /* Clear any pending read requests */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0);
+ if (ret)
+ goto fail;
+
+ /* Stop the CC */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, 0);
+ if (ret)
+ goto fail;
+ }
+ mutex_unlock(&di->cc_lock);
+ (*res) = val;
+
+ return 0;
+fail:
+ mutex_unlock(&di->cc_lock);
+ return ret;
+}
+
+/**
+ * ab8500_fg_inst_curr_blocking() - battery instantaneous current
+ * @di: pointer to the ab8500_fg structure
+ * @res: battery instantenous current(on success)
+ *
+ * Returns 0 else error code
+ */
+int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di)
+{
+ int ret;
+ int res = 0;
+
+ ret = ab8500_fg_inst_curr_start(di);
+ if (ret) {
+ dev_err(di->dev, "Failed to initialize fg_inst\n");
+ return 0;
+ }
+
+ ret = ab8500_fg_inst_curr_finalize(di, &res);
+ if (ret) {
+ dev_err(di->dev, "Failed to finalize fg_inst\n");
+ return 0;
+ }
+
+ return res;
+}
+
+/**
+ * ab8500_fg_acc_cur_work() - average battery current
+ * @work: pointer to the work_struct structure
+ *
+ * Updated the average battery current obtained from the
+ * coulomb counter.
+ */
+static void ab8500_fg_acc_cur_work(struct work_struct *work)
+{
+ int val;
+ int ret;
+ u8 low, med, high;
+
+ struct ab8500_fg *di = container_of(work,
+ struct ab8500_fg, fg_acc_cur_work);
+
+ mutex_lock(&di->cc_lock);
+ ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_NCOV_ACCU_CTRL, RD_NCONV_ACCU_REQ);
+ if (ret)
+ goto exit;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_NCOV_ACCU_LOW, &low);
+ if (ret < 0)
+ goto exit;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_NCOV_ACCU_MED, &med);
+ if (ret < 0)
+ goto exit;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_NCOV_ACCU_HIGH, &high);
+ if (ret < 0)
+ goto exit;
+
+ /* Check for sign bit in case of negative value, 2's compliment */
+ if (high & 0x10)
+ val = (low | (med << 8) | (high << 16) | 0xFFE00000);
+ else
+ val = (low | (med << 8) | (high << 16));
+
+ /*
+ * Convert to uAh
+ * Given a 250ms conversion cycle time the LSB corresponds
+ * to 112.9 nAh.
+ * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+ */
+ di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10) /
+ (100 * di->bat->fg_res);
+
+ /*
+ * Convert to unit value in mA
+ * Full scale input voltage is
+ * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA
+ * Given a 250ms conversion cycle time the LSB corresponds
+ * to 112.9 nAh. Convert to current by dividing by the conversion
+ * time in hours (= samples / (3600 * 4)h)
+ * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+ */
+ di->avg_curr = (val * QLSB_NANO_AMP_HOURS_X10 * 36) /
+ (1000 * di->bat->fg_res * (di->fg_samples / 4));
+
+ di->flags.conv_done = true;
+
+ mutex_unlock(&di->cc_lock);
+
+ queue_work(di->fg_wq, &di->fg_work);
+
+ return;
+exit:
+ dev_err(di->dev,
+ "Failed to read or write gas gauge registers\n");
+ mutex_unlock(&di->cc_lock);
+ queue_work(di->fg_wq, &di->fg_work);
+}
+
+/**
+ * ab8500_fg_bat_voltage() - get battery voltage
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns battery voltage(on success) else error code
+ */
+static int ab8500_fg_bat_voltage(struct ab8500_fg *di)
+{
+ int vbat;
+ static int prev;
+
+ vbat = ab8500_gpadc_convert(di->gpadc, MAIN_BAT_V);
+ if (vbat < 0) {
+ dev_err(di->dev,
+ "%s gpadc conversion failed, using previous value\n",
+ __func__);
+ return prev;
+ }
+
+ prev = vbat;
+ return vbat;
+}
+
+/**
+ * ab8500_fg_volt_to_capacity() - Voltage based capacity
+ * @di: pointer to the ab8500_fg structure
+ * @voltage: The voltage to convert to a capacity
+ *
+ * Returns battery capacity in per mille based on voltage
+ */
+static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage)
+{
+ int i, tbl_size;
+ struct abx500_v_to_cap *tbl;
+ int cap = 0;
+
+ tbl = di->bat->bat_type[di->bat->batt_id].v_to_cap_tbl,
+ tbl_size = di->bat->bat_type[di->bat->batt_id].n_v_cap_tbl_elements;
+
+ for (i = 0; i < tbl_size; ++i) {
+ if (voltage > tbl[i].voltage)
+ break;
+ }
+
+ if ((i > 0) && (i < tbl_size)) {
+ cap = interpolate(voltage,
+ tbl[i].voltage,
+ tbl[i].capacity * 10,
+ tbl[i-1].voltage,
+ tbl[i-1].capacity * 10);
+ } else if (i == 0) {
+ cap = 1000;
+ } else {
+ cap = 0;
+ }
+
+ dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille",
+ __func__, voltage, cap);
+
+ return cap;
+}
+
+/**
+ * ab8500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns battery capacity based on battery voltage that is not compensated
+ * for the voltage drop due to the load
+ */
+static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di)
+{
+ di->vbat = ab8500_fg_bat_voltage(di);
+ return ab8500_fg_volt_to_capacity(di, di->vbat);
+}
+
+/**
+ * ab8500_fg_battery_resistance() - Returns the battery inner resistance
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns battery inner resistance added with the fuel gauge resistor value
+ * to get the total resistance in the whole link from gnd to bat+ node.
+ */
+static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
+{
+ int i, tbl_size;
+ struct batres_vs_temp *tbl;
+ int resist = 0;
+
+ tbl = di->bat->bat_type[di->bat->batt_id].batres_tbl;
+ tbl_size = di->bat->bat_type[di->bat->batt_id].n_batres_tbl_elements;
+
+ for (i = 0; i < tbl_size; ++i) {
+ if (di->bat_temp / 10 > tbl[i].temp)
+ break;
+ }
+
+ if ((i > 0) && (i < tbl_size)) {
+ resist = interpolate(di->bat_temp / 10,
+ tbl[i].temp,
+ tbl[i].resist,
+ tbl[i-1].temp,
+ tbl[i-1].resist);
+ } else if (i == 0) {
+ resist = tbl[0].resist;
+ } else {
+ resist = tbl[tbl_size - 1].resist;
+ }
+
+ dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d"
+ " fg resistance %d, total: %d (mOhm)\n",
+ __func__, di->bat_temp, resist, di->bat->fg_res / 10,
+ (di->bat->fg_res / 10) + resist);
+
+ /* fg_res variable is in 0.1mOhm */
+ resist += di->bat->fg_res / 10;
+
+ return resist;
+}
+
+/**
+ * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns battery capacity based on battery voltage that is load compensated
+ * for the voltage drop
+ */
+static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di)
+{
+ int vbat_comp, res;
+ int i = 0;
+ int vbat = 0;
+
+ ab8500_fg_inst_curr_start(di);
+
+ do {
+ vbat += ab8500_fg_bat_voltage(di);
+ i++;
+ msleep(5);
+ } while (!ab8500_fg_inst_curr_done(di));
+
+ ab8500_fg_inst_curr_finalize(di, &di->inst_curr);
+
+ di->vbat = vbat / i;
+ res = ab8500_fg_battery_resistance(di);
+
+ /* Use Ohms law to get the load compensated voltage */
+ vbat_comp = di->vbat - (di->inst_curr * res) / 1000;
+
+ dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, "
+ "R: %dmOhm, Current: %dmA Vbat Samples: %d\n",
+ __func__, di->vbat, vbat_comp, res, di->inst_curr, i);
+
+ return ab8500_fg_volt_to_capacity(di, vbat_comp);
+}
+
+/**
+ * ab8500_fg_convert_mah_to_permille() - Capacity in mAh to permille
+ * @di: pointer to the ab8500_fg structure
+ * @cap_mah: capacity in mAh
+ *
+ * Converts capacity in mAh to capacity in permille
+ */
+static int ab8500_fg_convert_mah_to_permille(struct ab8500_fg *di, int cap_mah)
+{
+ return (cap_mah * 1000) / di->bat_cap.max_mah_design;
+}
+
+/**
+ * ab8500_fg_convert_permille_to_mah() - Capacity in permille to mAh
+ * @di: pointer to the ab8500_fg structure
+ * @cap_pm: capacity in permille
+ *
+ * Converts capacity in permille to capacity in mAh
+ */
+static int ab8500_fg_convert_permille_to_mah(struct ab8500_fg *di, int cap_pm)
+{
+ return cap_pm * di->bat_cap.max_mah_design / 1000;
+}
+
+/**
+ * ab8500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh
+ * @di: pointer to the ab8500_fg structure
+ * @cap_mah: capacity in mAh
+ *
+ * Converts capacity in mAh to capacity in uWh
+ */
+static int ab8500_fg_convert_mah_to_uwh(struct ab8500_fg *di, int cap_mah)
+{
+ u64 div_res;
+ u32 div_rem;
+
+ div_res = ((u64) cap_mah) * ((u64) di->vbat_nom);
+ div_rem = do_div(div_res, 1000);
+
+ /* Make sure to round upwards if necessary */
+ if (div_rem >= 1000 / 2)
+ div_res++;
+
+ return (int) div_res;
+}
+
+/**
+ * ab8500_fg_calc_cap_charging() - Calculate remaining capacity while charging
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Return the capacity in mAh based on previous calculated capcity and the FG
+ * accumulator register value. The filter is filled with this capacity
+ */
+static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di)
+{
+ dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n",
+ __func__,
+ di->bat_cap.mah,
+ di->accu_charge);
+
+ /* Capacity should not be less than 0 */
+ if (di->bat_cap.mah + di->accu_charge > 0)
+ di->bat_cap.mah += di->accu_charge;
+ else
+ di->bat_cap.mah = 0;
+ /*
+ * We force capacity to 100% once when the algorithm
+ * reports that it's full.
+ */
+ if (di->bat_cap.mah >= di->bat_cap.max_mah_design ||
+ di->flags.force_full) {
+ di->bat_cap.mah = di->bat_cap.max_mah_design;
+ }
+
+ ab8500_fg_fill_cap_sample(di, di->bat_cap.mah);
+ di->bat_cap.permille =
+ ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+
+ /* We need to update battery voltage and inst current when charging */
+ di->vbat = ab8500_fg_bat_voltage(di);
+ di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+ return di->bat_cap.mah;
+}
+
+/**
+ * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage
+ * @di: pointer to the ab8500_fg structure
+ * @comp: if voltage should be load compensated before capacity calc
+ *
+ * Return the capacity in mAh based on the battery voltage. The voltage can
+ * either be load compensated or not. This value is added to the filter and a
+ * new mean value is calculated and returned.
+ */
+static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp)
+{
+ int permille, mah;
+
+ if (comp)
+ permille = ab8500_fg_load_comp_volt_to_capacity(di);
+ else
+ permille = ab8500_fg_uncomp_volt_to_capacity(di);
+
+ mah = ab8500_fg_convert_permille_to_mah(di, permille);
+
+ di->bat_cap.mah = ab8500_fg_add_cap_sample(di, mah);
+ di->bat_cap.permille =
+ ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+
+ return di->bat_cap.mah;
+}
+
+/**
+ * ab8500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Return the capacity in mAh based on previous calculated capcity and the FG
+ * accumulator register value. This value is added to the filter and a
+ * new mean value is calculated and returned.
+ */
+static int ab8500_fg_calc_cap_discharge_fg(struct ab8500_fg *di)
+{
+ int permille_volt, permille;
+
+ dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n",
+ __func__,
+ di->bat_cap.mah,
+ di->accu_charge);
+
+ /* Capacity should not be less than 0 */
+ if (di->bat_cap.mah + di->accu_charge > 0)
+ di->bat_cap.mah += di->accu_charge;
+ else
+ di->bat_cap.mah = 0;
+
+ if (di->bat_cap.mah >= di->bat_cap.max_mah_design)
+ di->bat_cap.mah = di->bat_cap.max_mah_design;
+
+ /*
+ * Check against voltage based capacity. It can not be lower
+ * than what the uncompensated voltage says
+ */
+ permille = ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+ permille_volt = ab8500_fg_uncomp_volt_to_capacity(di);
+
+ if (permille < permille_volt) {
+ di->bat_cap.permille = permille_volt;
+ di->bat_cap.mah = ab8500_fg_convert_permille_to_mah(di,
+ di->bat_cap.permille);
+
+ dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n",
+ __func__,
+ permille,
+ permille_volt);
+
+ ab8500_fg_fill_cap_sample(di, di->bat_cap.mah);
+ } else {
+ ab8500_fg_fill_cap_sample(di, di->bat_cap.mah);
+ di->bat_cap.permille =
+ ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+ }
+
+ return di->bat_cap.mah;
+}
+
+/**
+ * ab8500_fg_capacity_level() - Get the battery capacity level
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Get the battery capacity level based on the capacity in percent
+ */
+static int ab8500_fg_capacity_level(struct ab8500_fg *di)
+{
+ int ret, percent;
+
+ percent = di->bat_cap.permille / 10;
+
+ if (percent <= di->bat->cap_levels->critical ||
+ di->flags.low_bat)
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ else if (percent <= di->bat->cap_levels->low)
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else if (percent <= di->bat->cap_levels->normal)
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ else if (percent <= di->bat->cap_levels->high)
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+ else
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+
+ return ret;
+}
+
+/**
+ * ab8500_fg_check_capacity_limits() - Check if capacity has changed
+ * @di: pointer to the ab8500_fg structure
+ * @init: capacity is allowed to go up in init mode
+ *
+ * Check if capacity or capacity limit has changed and notify the system
+ * about it using the power_supply framework
+ */
+static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
+{
+ bool changed = false;
+
+ di->bat_cap.level = ab8500_fg_capacity_level(di);
+
+ if (di->bat_cap.level != di->bat_cap.prev_level) {
+ /*
+ * We do not allow reported capacity level to go up
+ * unless we're charging or if we're in init
+ */
+ if (!(!di->flags.charging && di->bat_cap.level >
+ di->bat_cap.prev_level) || init) {
+ dev_dbg(di->dev, "level changed from %d to %d\n",
+ di->bat_cap.prev_level,
+ di->bat_cap.level);
+ di->bat_cap.prev_level = di->bat_cap.level;
+ changed = true;
+ } else {
+ dev_dbg(di->dev, "level not allowed to go up "
+ "since no charger is connected: %d to %d\n",
+ di->bat_cap.prev_level,
+ di->bat_cap.level);
+ }
+ }
+
+ /*
+ * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate
+ * shutdown
+ */
+ if (di->flags.low_bat) {
+ dev_dbg(di->dev, "Battery low, set capacity to 0\n");
+ di->bat_cap.prev_percent = 0;
+ di->bat_cap.permille = 0;
+ di->bat_cap.prev_mah = 0;
+ di->bat_cap.mah = 0;
+ changed = true;
+ } else if (di->flags.fully_charged) {
+ /*
+ * We report 100% if algorithm reported fully charged
+ * unless capacity drops too much
+ */
+ if (di->flags.force_full) {
+ di->bat_cap.prev_percent = di->bat_cap.permille / 10;
+ di->bat_cap.prev_mah = di->bat_cap.mah;
+ } else if (!di->flags.force_full &&
+ di->bat_cap.prev_percent !=
+ (di->bat_cap.permille) / 10 &&
+ (di->bat_cap.permille / 10) <
+ di->bat->fg_params->maint_thres) {
+ dev_dbg(di->dev,
+ "battery reported full "
+ "but capacity dropping: %d\n",
+ di->bat_cap.permille / 10);
+ di->bat_cap.prev_percent = di->bat_cap.permille / 10;
+ di->bat_cap.prev_mah = di->bat_cap.mah;
+
+ changed = true;
+ }
+ } else if (di->bat_cap.prev_percent != di->bat_cap.permille / 10) {
+ if (di->bat_cap.permille / 10 == 0) {
+ /*
+ * We will not report 0% unless we've got
+ * the LOW_BAT IRQ, no matter what the FG
+ * algorithm says.
+ */
+ di->bat_cap.prev_percent = 1;
+ di->bat_cap.permille = 1;
+ di->bat_cap.prev_mah = 1;
+ di->bat_cap.mah = 1;
+
+ changed = true;
+ } else if (!(!di->flags.charging &&
+ (di->bat_cap.permille / 10) >
+ di->bat_cap.prev_percent) || init) {
+ /*
+ * We do not allow reported capacity to go up
+ * unless we're charging or if we're in init
+ */
+ dev_dbg(di->dev,
+ "capacity changed from %d to %d (%d)\n",
+ di->bat_cap.prev_percent,
+ di->bat_cap.permille / 10,
+ di->bat_cap.permille);
+ di->bat_cap.prev_percent = di->bat_cap.permille / 10;
+ di->bat_cap.prev_mah = di->bat_cap.mah;
+
+ changed = true;
+ } else {
+ dev_dbg(di->dev, "capacity not allowed to go up since "
+ "no charger is connected: %d to %d (%d)\n",
+ di->bat_cap.prev_percent,
+ di->bat_cap.permille / 10,
+ di->bat_cap.permille);
+ }
+ }
+
+ if (changed) {
+ power_supply_changed(&di->fg_psy);
+ if (di->flags.fully_charged && di->flags.force_full) {
+ dev_dbg(di->dev, "Battery full, notifying.\n");
+ di->flags.force_full = false;
+ sysfs_notify(&di->fg_kobject, NULL, "charge_full");
+ }
+ sysfs_notify(&di->fg_kobject, NULL, "charge_now");
+ }
+}
+
+static void ab8500_fg_charge_state_to(struct ab8500_fg *di,
+ enum ab8500_fg_charge_state new_state)
+{
+ dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n",
+ di->charge_state,
+ charge_state[di->charge_state],
+ new_state,
+ charge_state[new_state]);
+
+ di->charge_state = new_state;
+}
+
+static void ab8500_fg_discharge_state_to(struct ab8500_fg *di,
+ enum ab8500_fg_discharge_state new_state)
+{
+ dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n",
+ di->discharge_state,
+ discharge_state[di->discharge_state],
+ new_state,
+ discharge_state[new_state]);
+
+ di->discharge_state = new_state;
+}
+
+/**
+ * ab8500_fg_algorithm_charging() - FG algorithm for when charging
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Battery capacity calculation state machine for when we're charging
+ */
+static void ab8500_fg_algorithm_charging(struct ab8500_fg *di)
+{
+ /*
+ * If we change to discharge mode
+ * we should start with recovery
+ */
+ if (di->discharge_state != AB8500_FG_DISCHARGE_INIT_RECOVERY)
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_INIT_RECOVERY);
+
+ switch (di->charge_state) {
+ case AB8500_FG_CHARGE_INIT:
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bat->fg_params->accu_charging);
+
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT);
+
+ break;
+
+ case AB8500_FG_CHARGE_READOUT:
+ /*
+ * Read the FG and calculate the new capacity
+ */
+ mutex_lock(&di->cc_lock);
+ if (!di->flags.conv_done) {
+ /* Wasn't the CC IRQ that got us here */
+ mutex_unlock(&di->cc_lock);
+ dev_dbg(di->dev, "%s CC conv not done\n",
+ __func__);
+
+ break;
+ }
+ di->flags.conv_done = false;
+ mutex_unlock(&di->cc_lock);
+
+ ab8500_fg_calc_cap_charging(di);
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* Check capacity limits */
+ ab8500_fg_check_capacity_limits(di, false);
+}
+
+static void force_capacity(struct ab8500_fg *di)
+{
+ int cap;
+
+ ab8500_fg_clear_cap_samples(di);
+ cap = di->bat_cap.user_mah;
+ if (cap > di->bat_cap.max_mah_design) {
+ dev_dbg(di->dev, "Remaining cap %d can't be bigger than total"
+ " %d\n", cap, di->bat_cap.max_mah_design);
+ cap = di->bat_cap.max_mah_design;
+ }
+ ab8500_fg_fill_cap_sample(di, di->bat_cap.user_mah);
+ di->bat_cap.permille = ab8500_fg_convert_mah_to_permille(di, cap);
+ di->bat_cap.mah = cap;
+ ab8500_fg_check_capacity_limits(di, true);
+}
+
+static bool check_sysfs_capacity(struct ab8500_fg *di)
+{
+ int cap, lower, upper;
+ int cap_permille;
+
+ cap = di->bat_cap.user_mah;
+
+ cap_permille = ab8500_fg_convert_mah_to_permille(di,
+ di->bat_cap.user_mah);
+
+ lower = di->bat_cap.permille - di->bat->fg_params->user_cap_limit * 10;
+ upper = di->bat_cap.permille + di->bat->fg_params->user_cap_limit * 10;
+
+ if (lower < 0)
+ lower = 0;
+ /* 1000 is permille, -> 100 percent */
+ if (upper > 1000)
+ upper = 1000;
+
+ dev_dbg(di->dev, "Capacity limits:"
+ " (Lower: %d User: %d Upper: %d) [user: %d, was: %d]\n",
+ lower, cap_permille, upper, cap, di->bat_cap.mah);
+
+ /* If within limits, use the saved capacity and exit estimation...*/
+ if (cap_permille > lower && cap_permille < upper) {
+ dev_dbg(di->dev, "OK! Using users cap %d uAh now\n", cap);
+ force_capacity(di);
+ return true;
+ }
+ dev_dbg(di->dev, "Capacity from user out of limits, ignoring");
+ return false;
+}
+
+/**
+ * ab8500_fg_algorithm_discharging() - FG algorithm for when discharging
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Battery capacity calculation state machine for when we're discharging
+ */
+static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
+{
+ int sleep_time;
+
+ /* If we change to charge mode we should start with init */
+ if (di->charge_state != AB8500_FG_CHARGE_INIT)
+ ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
+
+ switch (di->discharge_state) {
+ case AB8500_FG_DISCHARGE_INIT:
+ /* We use the FG IRQ to work on */
+ di->init_cnt = 0;
+ di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_INITMEASURING);
+
+ /* Intentional fallthrough */
+ case AB8500_FG_DISCHARGE_INITMEASURING:
+ /*
+ * Discard a number of samples during startup.
+ * After that, use compensated voltage for a few
+ * samples to get an initial capacity.
+ * Then go to READOUT
+ */
+ sleep_time = di->bat->fg_params->init_timer;
+
+ /* Discard the first [x] seconds */
+ if (di->init_cnt >
+ di->bat->fg_params->init_discard_time) {
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+
+ ab8500_fg_check_capacity_limits(di, true);
+ }
+
+ di->init_cnt += sleep_time;
+ if (di->init_cnt > di->bat->fg_params->init_total_time)
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT_INIT);
+
+ break;
+
+ case AB8500_FG_DISCHARGE_INIT_RECOVERY:
+ di->recovery_cnt = 0;
+ di->recovery_needed = true;
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_RECOVERY);
+
+ /* Intentional fallthrough */
+
+ case AB8500_FG_DISCHARGE_RECOVERY:
+ sleep_time = di->bat->fg_params->recovery_sleep_timer;
+
+ /*
+ * We should check the power consumption
+ * If low, go to READOUT (after x min) or
+ * RECOVERY_SLEEP if time left.
+ * If high, go to READOUT
+ */
+ di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+ if (ab8500_fg_is_low_curr(di, di->inst_curr)) {
+ if (di->recovery_cnt >
+ di->bat->fg_params->recovery_total_time) {
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bat->fg_params->accu_high_curr);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT);
+ di->recovery_needed = false;
+ } else {
+ queue_delayed_work(di->fg_wq,
+ &di->fg_periodic_work,
+ sleep_time * HZ);
+ }
+ di->recovery_cnt += sleep_time;
+ } else {
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bat->fg_params->accu_high_curr);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT);
+ }
+ break;
+
+ case AB8500_FG_DISCHARGE_READOUT_INIT:
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bat->fg_params->accu_high_curr);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT);
+ break;
+
+ case AB8500_FG_DISCHARGE_READOUT:
+ di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+ if (ab8500_fg_is_low_curr(di, di->inst_curr)) {
+ /* Detect mode change */
+ if (di->high_curr_mode) {
+ di->high_curr_mode = false;
+ di->high_curr_cnt = 0;
+ }
+
+ if (di->recovery_needed) {
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_RECOVERY);
+
+ queue_delayed_work(di->fg_wq,
+ &di->fg_periodic_work, 0);
+
+ break;
+ }
+
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+ } else {
+ mutex_lock(&di->cc_lock);
+ if (!di->flags.conv_done) {
+ /* Wasn't the CC IRQ that got us here */
+ mutex_unlock(&di->cc_lock);
+ dev_dbg(di->dev, "%s CC conv not done\n",
+ __func__);
+
+ break;
+ }
+ di->flags.conv_done = false;
+ mutex_unlock(&di->cc_lock);
+
+ /* Detect mode change */
+ if (!di->high_curr_mode) {
+ di->high_curr_mode = true;
+ di->high_curr_cnt = 0;
+ }
+
+ di->high_curr_cnt +=
+ di->bat->fg_params->accu_high_curr;
+ if (di->high_curr_cnt >
+ di->bat->fg_params->high_curr_time)
+ di->recovery_needed = true;
+
+ ab8500_fg_calc_cap_discharge_fg(di);
+ }
+
+ ab8500_fg_check_capacity_limits(di, false);
+
+ break;
+
+ case AB8500_FG_DISCHARGE_WAKEUP:
+ ab8500_fg_coulomb_counter(di, true);
+ di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bat->fg_params->accu_high_curr);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT);
+
+ ab8500_fg_check_capacity_limits(di, false);
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * ab8500_fg_algorithm_calibrate() - Internal columb counter offset calibration
+ * @di: pointer to the ab8500_fg structure
+ *
+ */
+static void ab8500_fg_algorithm_calibrate(struct ab8500_fg *di)
+{
+ int ret;
+
+ switch (di->calib_state) {
+ case AB8500_FG_CALIB_INIT:
+ dev_dbg(di->dev, "Calibration ongoing...\n");
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ CC_INT_CAL_N_AVG_MASK, CC_INT_CAL_SAMPLES_8);
+ if (ret < 0)
+ goto err;
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ CC_INTAVGOFFSET_ENA, CC_INTAVGOFFSET_ENA);
+ if (ret < 0)
+ goto err;
+ di->calib_state = AB8500_FG_CALIB_WAIT;
+ break;
+ case AB8500_FG_CALIB_END:
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ CC_MUXOFFSET, CC_MUXOFFSET);
+ if (ret < 0)
+ goto err;
+ di->flags.calibrate = false;
+ dev_dbg(di->dev, "Calibration done...\n");
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ break;
+ case AB8500_FG_CALIB_WAIT:
+ dev_dbg(di->dev, "Calibration WFI\n");
+ default:
+ break;
+ }
+ return;
+err:
+ /* Something went wrong, don't calibrate then */
+ dev_err(di->dev, "failed to calibrate the CC\n");
+ di->flags.calibrate = false;
+ di->calib_state = AB8500_FG_CALIB_INIT;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+}
+
+/**
+ * ab8500_fg_algorithm() - Entry point for the FG algorithm
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Entry point for the battery capacity calculation state machine
+ */
+static void ab8500_fg_algorithm(struct ab8500_fg *di)
+{
+ if (di->flags.calibrate)
+ ab8500_fg_algorithm_calibrate(di);
+ else {
+ if (di->flags.charging)
+ ab8500_fg_algorithm_charging(di);
+ else
+ ab8500_fg_algorithm_discharging(di);
+ }
+
+ dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d "
+ "%d %d %d %d %d %d %d\n",
+ di->bat_cap.max_mah_design,
+ di->bat_cap.mah,
+ di->bat_cap.permille,
+ di->bat_cap.level,
+ di->bat_cap.prev_mah,
+ di->bat_cap.prev_percent,
+ di->bat_cap.prev_level,
+ di->vbat,
+ di->inst_curr,
+ di->avg_curr,
+ di->accu_charge,
+ di->flags.charging,
+ di->charge_state,
+ di->discharge_state,
+ di->high_curr_mode,
+ di->recovery_needed);
+}
+
+/**
+ * ab8500_fg_periodic_work() - Run the FG state machine periodically
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for periodic work
+ */
+static void ab8500_fg_periodic_work(struct work_struct *work)
+{
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+ fg_periodic_work.work);
+
+ if (di->init_capacity) {
+ /* A dummy read that will return 0 */
+ di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+ /* Get an initial capacity calculation */
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+ ab8500_fg_check_capacity_limits(di, true);
+ di->init_capacity = false;
+
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ } else if (di->flags.user_cap) {
+ if (check_sysfs_capacity(di)) {
+ ab8500_fg_check_capacity_limits(di, true);
+ if (di->flags.charging)
+ ab8500_fg_charge_state_to(di,
+ AB8500_FG_CHARGE_INIT);
+ else
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT_INIT);
+ }
+ di->flags.user_cap = false;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ } else
+ ab8500_fg_algorithm(di);
+
+}
+
+/**
+ * ab8500_fg_check_hw_failure_work() - Check OVV_BAT condition
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the OVV_BAT condition
+ */
+static void ab8500_fg_check_hw_failure_work(struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+ fg_check_hw_failure_work.work);
+
+ /*
+ * If we have had a battery over-voltage situation,
+ * check ovv-bit to see if it should be reset.
+ */
+ if (di->flags.bat_ovv) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_STAT_REG,
+ &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if ((reg_value & BATT_OVV) != BATT_OVV) {
+ dev_dbg(di->dev, "Battery recovered from OVV\n");
+ di->flags.bat_ovv = false;
+ power_supply_changed(&di->fg_psy);
+ return;
+ }
+
+ /* Not yet recovered from ovv, reschedule this test */
+ queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work,
+ round_jiffies(HZ));
+ }
+}
+
+/**
+ * ab8500_fg_low_bat_work() - Check LOW_BAT condition
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the LOW_BAT condition
+ */
+static void ab8500_fg_low_bat_work(struct work_struct *work)
+{
+ int vbat;
+
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+ fg_low_bat_work.work);
+
+ vbat = ab8500_fg_bat_voltage(di);
+
+ /* Check if LOW_BAT still fulfilled */
+ if (vbat < di->bat->fg_params->lowbat_threshold) {
+ di->flags.low_bat = true;
+ dev_warn(di->dev, "Battery voltage still LOW\n");
+
+ /*
+ * We need to re-schedule this check to be able to detect
+ * if the voltage increases again during charging
+ */
+ queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
+ round_jiffies(LOW_BAT_CHECK_INTERVAL));
+ } else {
+ di->flags.low_bat = false;
+ dev_warn(di->dev, "Battery voltage OK again\n");
+ }
+
+ /* This is needed to dispatch LOW_BAT */
+ ab8500_fg_check_capacity_limits(di, false);
+
+ /* Set this flag to check if LOW_BAT IRQ still occurs */
+ di->flags.low_bat_delay = false;
+}
+
+/**
+ * ab8500_fg_battok_calc - calculate the bit pattern corresponding
+ * to the target voltage.
+ * @di: pointer to the ab8500_fg structure
+ * @target target voltage
+ *
+ * Returns bit pattern closest to the target voltage
+ * valid return values are 0-14. (0-BATT_OK_MAX_NR_INCREMENTS)
+ */
+
+static int ab8500_fg_battok_calc(struct ab8500_fg *di, int target)
+{
+ if (target > BATT_OK_MIN +
+ (BATT_OK_INCREMENT * BATT_OK_MAX_NR_INCREMENTS))
+ return BATT_OK_MAX_NR_INCREMENTS;
+ if (target < BATT_OK_MIN)
+ return 0;
+ return (target - BATT_OK_MIN) / BATT_OK_INCREMENT;
+}
+
+/**
+ * ab8500_fg_battok_init_hw_register - init battok levels
+ * @di: pointer to the ab8500_fg structure
+ *
+ */
+
+static int ab8500_fg_battok_init_hw_register(struct ab8500_fg *di)
+{
+ int selected;
+ int sel0;
+ int sel1;
+ int cbp_sel0;
+ int cbp_sel1;
+ int ret;
+ int new_val;
+
+ sel0 = di->bat->fg_params->battok_falling_th_sel0;
+ sel1 = di->bat->fg_params->battok_raising_th_sel1;
+
+ cbp_sel0 = ab8500_fg_battok_calc(di, sel0);
+ cbp_sel1 = ab8500_fg_battok_calc(di, sel1);
+
+ selected = BATT_OK_MIN + cbp_sel0 * BATT_OK_INCREMENT;
+
+ if (selected != sel0)
+ dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n",
+ sel0, selected, cbp_sel0);
+
+ selected = BATT_OK_MIN + cbp_sel1 * BATT_OK_INCREMENT;
+
+ if (selected != sel1)
+ dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n",
+ sel1, selected, cbp_sel1);
+
+ new_val = cbp_sel0 | (cbp_sel1 << 4);
+
+ dev_dbg(di->dev, "using: %x %d %d\n", new_val, cbp_sel0, cbp_sel1);
+ ret = abx500_set_register_interruptible(di->dev, AB8500_SYS_CTRL2_BLOCK,
+ AB8500_BATT_OK_REG, new_val);
+ return ret;
+}
+
+/**
+ * ab8500_fg_instant_work() - Run the FG state machine instantly
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for instant work
+ */
+static void ab8500_fg_instant_work(struct work_struct *work)
+{
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg, fg_work);
+
+ ab8500_fg_algorithm(di);
+}
+
+/**
+ * ab8500_fg_cc_data_end_handler() - isr to get battery avg current.
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_cc_data_end_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+ complete(&di->ab8500_fg_complete);
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_cc_convend_handler() - isr to get battery avg current.
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_cc_int_calib_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+ di->calib_state = AB8500_FG_CALIB_END;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_cc_convend_handler() - isr to get battery avg current.
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_cc_convend_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+
+ queue_work(di->fg_wq, &di->fg_acc_cur_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_batt_ovv_handler() - Battery OVV occured
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+
+ dev_dbg(di->dev, "Battery OVV\n");
+ di->flags.bat_ovv = true;
+ power_supply_changed(&di->fg_psy);
+
+ /* Schedule a new HW failure check */
+ queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_lowbatf_handler() - Battery voltage is below LOW threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+
+ if (!di->flags.low_bat_delay) {
+ dev_warn(di->dev, "Battery voltage is below LOW threshold\n");
+ di->flags.low_bat_delay = true;
+ /*
+ * Start a timer to check LOW_BAT again after some time
+ * This is done to avoid shutdown on single voltage dips
+ */
+ queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
+ round_jiffies(LOW_BAT_CHECK_INTERVAL));
+ }
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_get_property() - get the fg properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the
+ * fg properties by reading the sysfs files.
+ * voltage_now: battery voltage
+ * current_now: battery instant current
+ * current_avg: battery average current
+ * charge_full_design: capacity where battery is considered full
+ * charge_now: battery capacity in nAh
+ * capacity: capacity in percent
+ * capacity_level: capacity level
+ *
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_fg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab8500_fg *di;
+
+ di = to_ab8500_fg_device_info(psy);
+
+ /*
+ * If battery is identified as unknown and charging of unknown
+ * batteries is disabled, we always report 100% capacity and
+ * capacity level UNKNOWN, since we can't calculate
+ * remaining capacity
+ */
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (di->flags.bat_ovv)
+ val->intval = BATT_OVV_VALUE * 1000;
+ else
+ val->intval = di->vbat * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = di->inst_curr * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = di->avg_curr * 1000;
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ val->intval = ab8500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.max_mah_design);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ val->intval = ab8500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.max_mah);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+ di->flags.batt_id_received)
+ val->intval = ab8500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.max_mah);
+ else
+ val->intval = ab8500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.prev_mah);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = di->bat_cap.max_mah_design;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = di->bat_cap.max_mah;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+ di->flags.batt_id_received)
+ val->intval = di->bat_cap.max_mah;
+ else
+ val->intval = di->bat_cap.prev_mah;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+ di->flags.batt_id_received)
+ val->intval = 100;
+ else
+ val->intval = di->bat_cap.prev_percent;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ if (di->flags.batt_unknown && !di->bat->chg_unknown_bat &&
+ di->flags.batt_id_received)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+ else
+ val->intval = di->bat_cap.prev_level;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext;
+ struct ab8500_fg *di;
+ union power_supply_propval ret;
+ int i, j;
+ bool psy_found = false;
+
+ psy = (struct power_supply *)data;
+ ext = dev_get_drvdata(dev);
+ di = to_ab8500_fg_device_info(psy);
+
+ /*
+ * For all psy where the name of your driver
+ * appears in any supplied_to
+ */
+ for (i = 0; i < ext->num_supplicants; i++) {
+ if (!strcmp(ext->supplied_to[i], psy->name))
+ psy_found = true;
+ }
+
+ if (!psy_found)
+ return 0;
+
+ /* Go through all properties for the psy */
+ for (j = 0; j < ext->num_properties; j++) {
+ enum power_supply_property prop;
+ prop = ext->properties[j];
+
+ if (ext->get_property(ext, prop, &ret))
+ continue;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ switch (ret.intval) {
+ case POWER_SUPPLY_STATUS_UNKNOWN:
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ if (!di->flags.charging)
+ break;
+ di->flags.charging = false;
+ di->flags.fully_charged = false;
+ queue_work(di->fg_wq, &di->fg_work);
+ break;
+ case POWER_SUPPLY_STATUS_FULL:
+ if (di->flags.fully_charged)
+ break;
+ di->flags.fully_charged = true;
+ di->flags.force_full = true;
+ /* Save current capacity as maximum */
+ di->bat_cap.max_mah = di->bat_cap.mah;
+ queue_work(di->fg_wq, &di->fg_work);
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ if (di->flags.charging)
+ break;
+ di->flags.charging = true;
+ di->flags.fully_charged = false;
+ queue_work(di->fg_wq, &di->fg_work);
+ break;
+ };
+ default:
+ break;
+ };
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ if (!di->flags.batt_id_received) {
+ const struct abx500_battery_type *b;
+
+ b = &(di->bat->bat_type[di->bat->batt_id]);
+
+ di->flags.batt_id_received = true;
+
+ di->bat_cap.max_mah_design =
+ MILLI_TO_MICRO *
+ b->charge_full_design;
+
+ di->bat_cap.max_mah =
+ di->bat_cap.max_mah_design;
+
+ di->vbat_nom = b->nominal_voltage;
+ }
+
+ if (ret.intval)
+ di->flags.batt_unknown = false;
+ else
+ di->flags.batt_unknown = true;
+ break;
+ default:
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ if (di->flags.batt_id_received)
+ di->bat_temp = ret.intval;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ab8500_fg_init_hw_registers() - Set up FG related registers
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Set up battery OVV, low battery voltage registers
+ */
+static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
+{
+ int ret;
+
+ /* Set VBAT OVV threshold */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_BATT_OVV,
+ BATT_OVV_TH_4P75,
+ BATT_OVV_TH_4P75);
+ if (ret) {
+ dev_err(di->dev, "failed to set BATT_OVV\n");
+ goto out;
+ }
+
+ /* Enable VBAT OVV detection */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_BATT_OVV,
+ BATT_OVV_ENA,
+ BATT_OVV_ENA);
+ if (ret) {
+ dev_err(di->dev, "failed to enable BATT_OVV\n");
+ goto out;
+ }
+
+ /* Low Battery Voltage */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
+ AB8500_LOW_BAT_REG,
+ ab8500_volt_to_regval(
+ di->bat->fg_params->lowbat_threshold) << 1 |
+ LOW_BAT_ENABLE);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ goto out;
+ }
+
+ /* Battery OK threshold */
+ ret = ab8500_fg_battok_init_hw_register(di);
+ if (ret) {
+ dev_err(di->dev, "BattOk init write failed.\n");
+ goto out;
+ }
+out:
+ return ret;
+}
+
+/**
+ * ab8500_fg_external_power_changed() - callback for power supply changes
+ * @psy: pointer to the structure power_supply
+ *
+ * This function is the entry point of the pointer external_power_changed
+ * of the structure power_supply.
+ * This function gets executed when there is a change in any external power
+ * supply that this driver needs to be notified of.
+ */
+static void ab8500_fg_external_power_changed(struct power_supply *psy)
+{
+ struct ab8500_fg *di = to_ab8500_fg_device_info(psy);
+
+ class_for_each_device(power_supply_class, NULL,
+ &di->fg_psy, ab8500_fg_get_ext_psy_data);
+}
+
+/**
+ * abab8500_fg_reinit_work() - work to reset the FG algorithm
+ * @work: pointer to the work_struct structure
+ *
+ * Used to reset the current battery capacity to be able to
+ * retrigger a new voltage base capacity calculation. For
+ * test and verification purpose.
+ */
+static void ab8500_fg_reinit_work(struct work_struct *work)
+{
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+ fg_reinit_work.work);
+
+ if (di->flags.calibrate == false) {
+ dev_dbg(di->dev, "Resetting FG state machine to init.\n");
+ ab8500_fg_clear_cap_samples(di);
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+ ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
+ ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT);
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+
+ } else {
+ dev_err(di->dev, "Residual offset calibration ongoing "
+ "retrying..\n");
+ /* Wait one second until next try*/
+ queue_delayed_work(di->fg_wq, &di->fg_reinit_work,
+ round_jiffies(1));
+ }
+}
+
+/**
+ * ab8500_fg_reinit() - forces FG algorithm to reinitialize with current values
+ *
+ * This function can be used to force the FG algorithm to recalculate a new
+ * voltage based battery capacity.
+ */
+void ab8500_fg_reinit(void)
+{
+ struct ab8500_fg *di = ab8500_fg_get();
+ /* User won't be notified if a null pointer returned. */
+ if (di != NULL)
+ queue_delayed_work(di->fg_wq, &di->fg_reinit_work, 0);
+}
+
+/* Exposure to the sysfs interface */
+
+struct ab8500_fg_sysfs_entry {
+ struct attribute attr;
+ ssize_t (*show)(struct ab8500_fg *, char *);
+ ssize_t (*store)(struct ab8500_fg *, const char *, size_t);
+};
+
+static ssize_t charge_full_show(struct ab8500_fg *di, char *buf)
+{
+ return sprintf(buf, "%d\n", di->bat_cap.max_mah);
+}
+
+static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf,
+ size_t count)
+{
+ unsigned long charge_full;
+ ssize_t ret = -EINVAL;
+
+ ret = strict_strtoul(buf, 10, &charge_full);
+
+ dev_dbg(di->dev, "Ret %zd charge_full %lu", ret, charge_full);
+
+ if (!ret) {
+ di->bat_cap.max_mah = (int) charge_full;
+ ret = count;
+ }
+ return ret;
+}
+
+static ssize_t charge_now_show(struct ab8500_fg *di, char *buf)
+{
+ return sprintf(buf, "%d\n", di->bat_cap.prev_mah);
+}
+
+static ssize_t charge_now_store(struct ab8500_fg *di, const char *buf,
+ size_t count)
+{
+ unsigned long charge_now;
+ ssize_t ret;
+
+ ret = strict_strtoul(buf, 10, &charge_now);
+
+ dev_dbg(di->dev, "Ret %zd charge_now %lu was %d",
+ ret, charge_now, di->bat_cap.prev_mah);
+
+ if (!ret) {
+ di->bat_cap.user_mah = (int) charge_now;
+ di->flags.user_cap = true;
+ ret = count;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ }
+ return ret;
+}
+
+static struct ab8500_fg_sysfs_entry charge_full_attr =
+ __ATTR(charge_full, 0644, charge_full_show, charge_full_store);
+
+static struct ab8500_fg_sysfs_entry charge_now_attr =
+ __ATTR(charge_now, 0644, charge_now_show, charge_now_store);
+
+static ssize_t
+ab8500_fg_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ struct ab8500_fg_sysfs_entry *entry;
+ struct ab8500_fg *di;
+
+ entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr);
+ di = container_of(kobj, struct ab8500_fg, fg_kobject);
+
+ if (!entry->show)
+ return -EIO;
+
+ return entry->show(di, buf);
+}
+static ssize_t
+ab8500_fg_store(struct kobject *kobj, struct attribute *attr, const char *buf,
+ size_t count)
+{
+ struct ab8500_fg_sysfs_entry *entry;
+ struct ab8500_fg *di;
+
+ entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr);
+ di = container_of(kobj, struct ab8500_fg, fg_kobject);
+
+ if (!entry->store)
+ return -EIO;
+
+ return entry->store(di, buf, count);
+}
+
+static const struct sysfs_ops ab8500_fg_sysfs_ops = {
+ .show = ab8500_fg_show,
+ .store = ab8500_fg_store,
+};
+
+static struct attribute *ab8500_fg_attrs[] = {
+ &charge_full_attr.attr,
+ &charge_now_attr.attr,
+ NULL,
+};
+
+static struct kobj_type ab8500_fg_ktype = {
+ .sysfs_ops = &ab8500_fg_sysfs_ops,
+ .default_attrs = ab8500_fg_attrs,
+};
+
+/**
+ * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry
+ * @di: pointer to the struct ab8500_chargalg
+ *
+ * This function removes the entry in sysfs.
+ */
+static void ab8500_fg_sysfs_exit(struct ab8500_fg *di)
+{
+ kobject_del(&di->fg_kobject);
+}
+
+/**
+ * ab8500_chargalg_sysfs_init() - init of sysfs entry
+ * @di: pointer to the struct ab8500_chargalg
+ *
+ * This function adds an entry in sysfs.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_fg_sysfs_init(struct ab8500_fg *di)
+{
+ int ret = 0;
+
+ ret = kobject_init_and_add(&di->fg_kobject,
+ &ab8500_fg_ktype,
+ NULL, "battery");
+ if (ret < 0)
+ dev_err(di->dev, "failed to create sysfs entry\n");
+
+ return ret;
+}
+/* Exposure to the sysfs interface <<END>> */
+
+#if defined(CONFIG_PM)
+static int ab8500_fg_resume(struct platform_device *pdev)
+{
+ struct ab8500_fg *di = platform_get_drvdata(pdev);
+
+ /*
+ * Change state if we're not charging. If we're charging we will wake
+ * up on the FG IRQ
+ */
+ if (!di->flags.charging) {
+ ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP);
+ queue_work(di->fg_wq, &di->fg_work);
+ }
+
+ return 0;
+}
+
+static int ab8500_fg_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ab8500_fg *di = platform_get_drvdata(pdev);
+
+ flush_delayed_work(&di->fg_periodic_work);
+
+ /*
+ * If the FG is enabled we will disable it before going to suspend
+ * only if we're not charging
+ */
+ if (di->flags.fg_enabled && !di->flags.charging)
+ ab8500_fg_coulomb_counter(di, false);
+
+ return 0;
+}
+#else
+#define ab8500_fg_suspend NULL
+#define ab8500_fg_resume NULL
+#endif
+
+static int __devexit ab8500_fg_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct ab8500_fg *di = platform_get_drvdata(pdev);
+
+ list_del(&di->node);
+
+ /* Disable coulomb counter */
+ ret = ab8500_fg_coulomb_counter(di, false);
+ if (ret)
+ dev_err(di->dev, "failed to disable coulomb counter\n");
+
+ destroy_workqueue(di->fg_wq);
+ ab8500_fg_sysfs_exit(di);
+
+ flush_scheduled_work();
+ power_supply_unregister(&di->fg_psy);
+ platform_set_drvdata(pdev, NULL);
+ kfree(di);
+ return ret;
+}
+
+/* ab8500 fg driver interrupts and their respective isr */
+static struct ab8500_fg_interrupts ab8500_fg_irq[] = {
+ {"NCONV_ACCU", ab8500_fg_cc_convend_handler},
+ {"BATT_OVV", ab8500_fg_batt_ovv_handler},
+ {"LOW_BAT_F", ab8500_fg_lowbatf_handler},
+ {"CC_INT_CALIB", ab8500_fg_cc_int_calib_handler},
+ {"CCEOC", ab8500_fg_cc_data_end_handler},
+};
+
+static int __devinit ab8500_fg_probe(struct platform_device *pdev)
+{
+ int i, irq;
+ int ret = 0;
+ struct abx500_bm_plat_data *plat_data;
+
+ struct ab8500_fg *di =
+ kzalloc(sizeof(struct ab8500_fg), GFP_KERNEL);
+ if (!di)
+ return -ENOMEM;
+
+ mutex_init(&di->cc_lock);
+
+ /* get parent data */
+ di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+ di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+ /* get fg specific platform data */
+ plat_data = pdev->dev.platform_data;
+ di->pdata = plat_data->fg;
+ if (!di->pdata) {
+ dev_err(di->dev, "no fg platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ /* get battery specific platform data */
+ di->bat = plat_data->battery;
+ if (!di->bat) {
+ dev_err(di->dev, "no battery platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ di->fg_psy.name = "ab8500_fg";
+ di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->fg_psy.properties = ab8500_fg_props;
+ di->fg_psy.num_properties = ARRAY_SIZE(ab8500_fg_props);
+ di->fg_psy.get_property = ab8500_fg_get_property;
+ di->fg_psy.supplied_to = di->pdata->supplied_to;
+ di->fg_psy.num_supplicants = di->pdata->num_supplicants;
+ di->fg_psy.external_power_changed = ab8500_fg_external_power_changed;
+
+ di->bat_cap.max_mah_design = MILLI_TO_MICRO *
+ di->bat->bat_type[di->bat->batt_id].charge_full_design;
+
+ di->bat_cap.max_mah = di->bat_cap.max_mah_design;
+
+ di->vbat_nom = di->bat->bat_type[di->bat->batt_id].nominal_voltage;
+
+ di->init_capacity = true;
+
+ ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
+ ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT);
+
+ /* Create a work queue for running the FG algorithm */
+ di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq");
+ if (di->fg_wq == NULL) {
+ dev_err(di->dev, "failed to create work queue\n");
+ goto free_device_info;
+ }
+
+ /* Init work for running the fg algorithm instantly */
+ INIT_WORK(&di->fg_work, ab8500_fg_instant_work);
+
+ /* Init work for getting the battery accumulated current */
+ INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work);
+
+ /* Init work for reinitialising the fg algorithm */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->fg_reinit_work,
+ ab8500_fg_reinit_work);
+
+ /* Work delayed Queue to run the state machine */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->fg_periodic_work,
+ ab8500_fg_periodic_work);
+
+ /* Work to check low battery condition */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->fg_low_bat_work,
+ ab8500_fg_low_bat_work);
+
+ /* Init work for HW failure check */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->fg_check_hw_failure_work,
+ ab8500_fg_check_hw_failure_work);
+
+ /* Initialize OVV, and other registers */
+ ret = ab8500_fg_init_hw_registers(di);
+ if (ret) {
+ dev_err(di->dev, "failed to initialize registers\n");
+ goto free_inst_curr_wq;
+ }
+
+ /* Consider battery unknown until we're informed otherwise */
+ di->flags.batt_unknown = true;
+ di->flags.batt_id_received = false;
+
+ /* Register FG power supply class */
+ ret = power_supply_register(di->dev, &di->fg_psy);
+ if (ret) {
+ dev_err(di->dev, "failed to register FG psy\n");
+ goto free_inst_curr_wq;
+ }
+
+ di->fg_samples = SEC_TO_SAMPLE(di->bat->fg_params->init_timer);
+ ab8500_fg_coulomb_counter(di, true);
+
+ /* Initialize completion used to notify completion of inst current */
+ init_completion(&di->ab8500_fg_complete);
+
+ /* Register interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name);
+ ret = request_threaded_irq(irq, NULL, ab8500_fg_irq[i].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND,
+ ab8500_fg_irq[i].name, di);
+
+ if (ret != 0) {
+ dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+ , ab8500_fg_irq[i].name, irq, ret);
+ goto free_irq;
+ }
+ dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+ ab8500_fg_irq[i].name, irq, ret);
+ }
+ di->irq = platform_get_irq_byname(pdev, "CCEOC");
+ disable_irq(di->irq);
+
+ platform_set_drvdata(pdev, di);
+
+ ret = ab8500_fg_sysfs_init(di);
+ if (ret) {
+ dev_err(di->dev, "failed to create sysfs entry\n");
+ goto free_irq;
+ }
+
+ /* Calibrate the fg first time */
+ di->flags.calibrate = true;
+ di->calib_state = AB8500_FG_CALIB_INIT;
+
+ /* Use room temp as default value until we get an update from driver. */
+ di->bat_temp = 210;
+
+ /* Run the FG algorithm */
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+
+ list_add_tail(&di->node, &ab8500_fg_list);
+
+ return ret;
+
+free_irq:
+ power_supply_unregister(&di->fg_psy);
+
+ /* We also have to free all successfully registered irqs */
+ for (i = i - 1; i >= 0; i--) {
+ irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name);
+ free_irq(irq, di);
+ }
+free_inst_curr_wq:
+ destroy_workqueue(di->fg_wq);
+free_device_info:
+ kfree(di);
+
+ return ret;
+}
+
+static struct platform_driver ab8500_fg_driver = {
+ .probe = ab8500_fg_probe,
+ .remove = __devexit_p(ab8500_fg_remove),
+ .suspend = ab8500_fg_suspend,
+ .resume = ab8500_fg_resume,
+ .driver = {
+ .name = "ab8500-fg",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ab8500_fg_init(void)
+{
+ return platform_driver_register(&ab8500_fg_driver);
+}
+
+static void __exit ab8500_fg_exit(void)
+{
+ platform_driver_unregister(&ab8500_fg_driver);
+}
+
+subsys_initcall_sync(ab8500_fg_init);
+module_exit(ab8500_fg_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
+MODULE_ALIAS("platform:ab8500-fg");
+MODULE_DESCRIPTION("AB8500 Fuel Gauge driver");
diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c
new file mode 100644
index 00000000..804b88c7
--- /dev/null
+++ b/drivers/power/abx500_chargalg.c
@@ -0,0 +1,1921 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Charging algorithm driver for abx500 variants
+ *
+ * License Terms: GNU General Public License v2
+ * Authors:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@stericsson.com>
+ * Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/kobject.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+
+/* Watchdog kick interval */
+#define CHG_WD_INTERVAL (6 * HZ)
+
+/* End-of-charge criteria counter */
+#define EOC_COND_CNT 10
+
+/* Recharge criteria counter */
+#define RCH_COND_CNT 3
+
+#define to_abx500_chargalg_device_info(x) container_of((x), \
+ struct abx500_chargalg, chargalg_psy);
+
+enum abx500_chargers {
+ NO_CHG,
+ AC_CHG,
+ USB_CHG,
+};
+
+struct abx500_chargalg_charger_info {
+ enum abx500_chargers conn_chg;
+ enum abx500_chargers prev_conn_chg;
+ enum abx500_chargers online_chg;
+ enum abx500_chargers prev_online_chg;
+ enum abx500_chargers charger_type;
+ bool usb_chg_ok;
+ bool ac_chg_ok;
+ int usb_volt;
+ int usb_curr;
+ int ac_volt;
+ int ac_curr;
+ int usb_vset;
+ int usb_iset;
+ int ac_vset;
+ int ac_iset;
+};
+
+struct abx500_chargalg_suspension_status {
+ bool suspended_change;
+ bool ac_suspended;
+ bool usb_suspended;
+};
+
+struct abx500_chargalg_battery_data {
+ int temp;
+ int volt;
+ int avg_curr;
+ int inst_curr;
+ int percent;
+};
+
+enum abx500_chargalg_states {
+ STATE_HANDHELD_INIT,
+ STATE_HANDHELD,
+ STATE_CHG_NOT_OK_INIT,
+ STATE_CHG_NOT_OK,
+ STATE_HW_TEMP_PROTECT_INIT,
+ STATE_HW_TEMP_PROTECT,
+ STATE_NORMAL_INIT,
+ STATE_NORMAL,
+ STATE_WAIT_FOR_RECHARGE_INIT,
+ STATE_WAIT_FOR_RECHARGE,
+ STATE_MAINTENANCE_A_INIT,
+ STATE_MAINTENANCE_A,
+ STATE_MAINTENANCE_B_INIT,
+ STATE_MAINTENANCE_B,
+ STATE_TEMP_UNDEROVER_INIT,
+ STATE_TEMP_UNDEROVER,
+ STATE_TEMP_LOWHIGH_INIT,
+ STATE_TEMP_LOWHIGH,
+ STATE_SUSPENDED_INIT,
+ STATE_SUSPENDED,
+ STATE_OVV_PROTECT_INIT,
+ STATE_OVV_PROTECT,
+ STATE_SAFETY_TIMER_EXPIRED_INIT,
+ STATE_SAFETY_TIMER_EXPIRED,
+ STATE_BATT_REMOVED_INIT,
+ STATE_BATT_REMOVED,
+ STATE_WD_EXPIRED_INIT,
+ STATE_WD_EXPIRED,
+};
+
+static const char *states[] = {
+ "HANDHELD_INIT",
+ "HANDHELD",
+ "CHG_NOT_OK_INIT",
+ "CHG_NOT_OK",
+ "HW_TEMP_PROTECT_INIT",
+ "HW_TEMP_PROTECT",
+ "NORMAL_INIT",
+ "NORMAL",
+ "WAIT_FOR_RECHARGE_INIT",
+ "WAIT_FOR_RECHARGE",
+ "MAINTENANCE_A_INIT",
+ "MAINTENANCE_A",
+ "MAINTENANCE_B_INIT",
+ "MAINTENANCE_B",
+ "TEMP_UNDEROVER_INIT",
+ "TEMP_UNDEROVER",
+ "TEMP_LOWHIGH_INIT",
+ "TEMP_LOWHIGH",
+ "SUSPENDED_INIT",
+ "SUSPENDED",
+ "OVV_PROTECT_INIT",
+ "OVV_PROTECT",
+ "SAFETY_TIMER_EXPIRED_INIT",
+ "SAFETY_TIMER_EXPIRED",
+ "BATT_REMOVED_INIT",
+ "BATT_REMOVED",
+ "WD_EXPIRED_INIT",
+ "WD_EXPIRED",
+};
+
+struct abx500_chargalg_events {
+ bool batt_unknown;
+ bool mainextchnotok;
+ bool batt_ovv;
+ bool batt_rem;
+ bool btemp_underover;
+ bool btemp_lowhigh;
+ bool main_thermal_prot;
+ bool usb_thermal_prot;
+ bool main_ovv;
+ bool vbus_ovv;
+ bool usbchargernotok;
+ bool safety_timer_expired;
+ bool maintenance_timer_expired;
+ bool ac_wd_expired;
+ bool usb_wd_expired;
+ bool ac_cv_active;
+ bool usb_cv_active;
+ bool vbus_collapsed;
+};
+
+/**
+ * struct abx500_charge_curr_maximization - Charger maximization parameters
+ * @original_iset: the non optimized/maximised charger current
+ * @current_iset: the charging current used at this moment
+ * @test_delta_i: the delta between the current we want to charge and the
+ current that is really going into the battery
+ * @condition_cnt: number of iterations needed before a new charger current
+ is set
+ * @max_current: maximum charger current
+ * @wait_cnt: to avoid too fast current step down in case of charger
+ * voltage collapse, we insert this delay between step
+ * down
+ * @level: tells in how many steps the charging current has been
+ increased
+ */
+struct abx500_charge_curr_maximization {
+ int original_iset;
+ int current_iset;
+ int test_delta_i;
+ int condition_cnt;
+ int max_current;
+ int wait_cnt;
+ u8 level;
+};
+
+enum maxim_ret {
+ MAXIM_RET_NOACTION,
+ MAXIM_RET_CHANGE,
+ MAXIM_RET_IBAT_TOO_HIGH,
+};
+
+/**
+ * struct abx500_chargalg - abx500 Charging algorithm device information
+ * @dev: pointer to the structure device
+ * @charge_status: battery operating status
+ * @eoc_cnt: counter used to determine end-of_charge
+ * @rch_cnt: counter used to determine start of recharge
+ * @maintenance_chg: indicate if maintenance charge is active
+ * @t_hyst_norm temperature hysteresis when the temperature has been
+ * over or under normal limits
+ * @t_hyst_lowhigh temperature hysteresis when the temperature has been
+ * over or under the high or low limits
+ * @charge_state: current state of the charging algorithm
+ * @ccm charging current maximization parameters
+ * @chg_info: information about connected charger types
+ * @batt_data: data of the battery
+ * @susp_status: current charger suspension status
+ * @pdata: pointer to the abx500_chargalg platform data
+ * @bat: pointer to the abx500_bm platform data
+ * @chargalg_psy: structure that holds the battery properties exposed by
+ * the charging algorithm
+ * @events: structure for information about events triggered
+ * @chargalg_wq: work queue for running the charging algorithm
+ * @chargalg_periodic_work: work to run the charging algorithm periodically
+ * @chargalg_wd_work: work to kick the charger watchdog periodically
+ * @chargalg_work: work to run the charging algorithm instantly
+ * @safety_timer: charging safety timer
+ * @maintenance_timer: maintenance charging timer
+ * @chargalg_kobject: structure of type kobject
+ */
+struct abx500_chargalg {
+ struct device *dev;
+ int charge_status;
+ int eoc_cnt;
+ int rch_cnt;
+ bool maintenance_chg;
+ int t_hyst_norm;
+ int t_hyst_lowhigh;
+ enum abx500_chargalg_states charge_state;
+ struct abx500_charge_curr_maximization ccm;
+ struct abx500_chargalg_charger_info chg_info;
+ struct abx500_chargalg_battery_data batt_data;
+ struct abx500_chargalg_suspension_status susp_status;
+ struct abx500_chargalg_platform_data *pdata;
+ struct abx500_bm_data *bat;
+ struct power_supply chargalg_psy;
+ struct ux500_charger *ac_chg;
+ struct ux500_charger *usb_chg;
+ struct abx500_chargalg_events events;
+ struct workqueue_struct *chargalg_wq;
+ struct delayed_work chargalg_periodic_work;
+ struct delayed_work chargalg_wd_work;
+ struct work_struct chargalg_work;
+ struct timer_list safety_timer;
+ struct timer_list maintenance_timer;
+ struct kobject chargalg_kobject;
+};
+
+/* Main battery properties */
+static enum power_supply_property abx500_chargalg_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+/**
+ * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer
+ * @data: pointer to the abx500_chargalg structure
+ *
+ * This function gets called when the safety timer for the charger
+ * expires
+ */
+static void abx500_chargalg_safety_timer_expired(unsigned long data)
+{
+ struct abx500_chargalg *di = (struct abx500_chargalg *) data;
+ dev_err(di->dev, "Safety timer expired\n");
+ di->events.safety_timer_expired = true;
+
+ /* Trigger execution of the algorithm instantly */
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_maintenance_timer_expired() - Expiration of
+ * the maintenance timer
+ * @i: pointer to the abx500_chargalg structure
+ *
+ * This function gets called when the maintenence timer
+ * expires
+ */
+static void abx500_chargalg_maintenance_timer_expired(unsigned long data)
+{
+
+ struct abx500_chargalg *di = (struct abx500_chargalg *) data;
+ dev_dbg(di->dev, "Maintenance timer expired\n");
+ di->events.maintenance_timer_expired = true;
+
+ /* Trigger execution of the algorithm instantly */
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_state_to() - Change charge state
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function gets called when a charge state change should occur
+ */
+static void abx500_chargalg_state_to(struct abx500_chargalg *di,
+ enum abx500_chargalg_states state)
+{
+ dev_dbg(di->dev,
+ "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n",
+ di->charge_state == state ? "NO" : "YES",
+ di->charge_state,
+ states[di->charge_state],
+ state,
+ states[state]);
+
+ di->charge_state = state;
+}
+
+/**
+ * abx500_chargalg_check_charger_connection() - Check charger connection change
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function will check if there is a change in the charger connection
+ * and change charge state accordingly. AC has precedence over USB.
+ */
+static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di)
+{
+ if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg ||
+ di->susp_status.suspended_change) {
+ /*
+ * Charger state changed or suspension
+ * has changed since last update
+ */
+ if ((di->chg_info.conn_chg & AC_CHG) &&
+ !di->susp_status.ac_suspended) {
+ dev_dbg(di->dev, "Charging source is AC\n");
+ if (di->chg_info.charger_type != AC_CHG) {
+ di->chg_info.charger_type = AC_CHG;
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ }
+ } else if ((di->chg_info.conn_chg & USB_CHG) &&
+ !di->susp_status.usb_suspended) {
+ dev_dbg(di->dev, "Charging source is USB\n");
+ di->chg_info.charger_type = USB_CHG;
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ } else if (di->chg_info.conn_chg &&
+ (di->susp_status.ac_suspended ||
+ di->susp_status.usb_suspended)) {
+ dev_dbg(di->dev, "Charging is suspended\n");
+ di->chg_info.charger_type = NO_CHG;
+ abx500_chargalg_state_to(di, STATE_SUSPENDED_INIT);
+ } else {
+ dev_dbg(di->dev, "Charging source is OFF\n");
+ di->chg_info.charger_type = NO_CHG;
+ abx500_chargalg_state_to(di, STATE_HANDHELD_INIT);
+ }
+ di->chg_info.prev_conn_chg = di->chg_info.conn_chg;
+ di->susp_status.suspended_change = false;
+ }
+ return di->chg_info.conn_chg;
+}
+
+/**
+ * abx500_chargalg_start_safety_timer() - Start charging safety timer
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The safety timer is used to avoid overcharging of old or bad batteries.
+ * There are different timers for AC and USB
+ */
+static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)
+{
+ unsigned long timer_expiration = 0;
+
+ switch (di->chg_info.charger_type) {
+ case AC_CHG:
+ timer_expiration =
+ round_jiffies(jiffies +
+ (di->bat->main_safety_tmr_h * 3600 * HZ));
+ break;
+
+ case USB_CHG:
+ timer_expiration =
+ round_jiffies(jiffies +
+ (di->bat->usb_safety_tmr_h * 3600 * HZ));
+ break;
+
+ default:
+ dev_err(di->dev, "Unknown charger to charge from\n");
+ break;
+ }
+
+ di->events.safety_timer_expired = false;
+ di->safety_timer.expires = timer_expiration;
+ if (!timer_pending(&di->safety_timer))
+ add_timer(&di->safety_timer);
+ else
+ mod_timer(&di->safety_timer, timer_expiration);
+}
+
+/**
+ * abx500_chargalg_stop_safety_timer() - Stop charging safety timer
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The safety timer is stopped whenever the NORMAL state is exited
+ */
+static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di)
+{
+ di->events.safety_timer_expired = false;
+ del_timer(&di->safety_timer);
+}
+
+/**
+ * abx500_chargalg_start_maintenance_timer() - Start charging maintenance timer
+ * @di: pointer to the abx500_chargalg structure
+ * @duration: duration of ther maintenance timer in hours
+ *
+ * The maintenance timer is used to maintain the charge in the battery once
+ * the battery is considered full. These timers are chosen to match the
+ * discharge curve of the battery
+ */
+static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di,
+ int duration)
+{
+ unsigned long timer_expiration;
+
+ /* Convert from hours to jiffies */
+ timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ));
+
+ di->events.maintenance_timer_expired = false;
+ di->maintenance_timer.expires = timer_expiration;
+ if (!timer_pending(&di->maintenance_timer))
+ add_timer(&di->maintenance_timer);
+ else
+ mod_timer(&di->maintenance_timer, timer_expiration);
+}
+
+/**
+ * abx500_chargalg_stop_maintenance_timer() - Stop maintenance timer
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The maintenance timer is stopped whenever maintenance ends or when another
+ * state is entered
+ */
+static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di)
+{
+ di->events.maintenance_timer_expired = false;
+ del_timer(&di->maintenance_timer);
+}
+
+/**
+ * abx500_chargalg_kick_watchdog() - Kick charger watchdog
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The charger watchdog have to be kicked periodically whenever the charger is
+ * on, else the ABB will reset the system
+ */
+static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di)
+{
+ /* Check if charger exists and kick watchdog if charging */
+ if (di->ac_chg && di->ac_chg->ops.kick_wd &&
+ di->chg_info.online_chg & AC_CHG)
+ return di->ac_chg->ops.kick_wd(di->ac_chg);
+ else if (di->usb_chg && di->usb_chg->ops.kick_wd &&
+ di->chg_info.online_chg & USB_CHG)
+ return di->usb_chg->ops.kick_wd(di->usb_chg);
+
+ return -ENXIO;
+}
+
+/**
+ * abx500_chargalg_ac_en() - Turn on/off the AC charger
+ * @di: pointer to the abx500_chargalg structure
+ * @enable: charger on/off
+ * @vset: requested charger output voltage
+ * @iset: requested charger output current
+ *
+ * The AC charger will be turned on/off with the requested charge voltage and
+ * current
+ */
+static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable,
+ int vset, int iset)
+{
+ if (!di->ac_chg || !di->ac_chg->ops.enable)
+ return -ENXIO;
+
+ /* Select maximum of what both the charger and the battery supports */
+ if (di->ac_chg->max_out_volt)
+ vset = min(vset, di->ac_chg->max_out_volt);
+ if (di->ac_chg->max_out_curr)
+ iset = min(iset, di->ac_chg->max_out_curr);
+
+ di->chg_info.ac_iset = iset;
+ di->chg_info.ac_vset = vset;
+
+ return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset);
+}
+
+/**
+ * abx500_chargalg_usb_en() - Turn on/off the USB charger
+ * @di: pointer to the abx500_chargalg structure
+ * @enable: charger on/off
+ * @vset: requested charger output voltage
+ * @iset: requested charger output current
+ *
+ * The USB charger will be turned on/off with the requested charge voltage and
+ * current
+ */
+static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable,
+ int vset, int iset)
+{
+ if (!di->usb_chg || !di->usb_chg->ops.enable)
+ return -ENXIO;
+
+ /* Select maximum of what both the charger and the battery supports */
+ if (di->usb_chg->max_out_volt)
+ vset = min(vset, di->usb_chg->max_out_volt);
+ if (di->usb_chg->max_out_curr)
+ iset = min(iset, di->usb_chg->max_out_curr);
+
+ di->chg_info.usb_iset = iset;
+ di->chg_info.usb_vset = vset;
+
+ return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset);
+}
+
+/**
+ * abx500_chargalg_update_chg_curr() - Update charger current
+ * @di: pointer to the abx500_chargalg structure
+ * @iset: requested charger output current
+ *
+ * The charger output current will be updated for the charger
+ * that is currently in use
+ */
+static int abx500_chargalg_update_chg_curr(struct abx500_chargalg *di,
+ int iset)
+{
+ /* Check if charger exists and update current if charging */
+ if (di->ac_chg && di->ac_chg->ops.update_curr &&
+ di->chg_info.charger_type & AC_CHG) {
+ /*
+ * Select maximum of what both the charger
+ * and the battery supports
+ */
+ if (di->ac_chg->max_out_curr)
+ iset = min(iset, di->ac_chg->max_out_curr);
+
+ di->chg_info.ac_iset = iset;
+
+ return di->ac_chg->ops.update_curr(di->ac_chg, iset);
+ } else if (di->usb_chg && di->usb_chg->ops.update_curr &&
+ di->chg_info.charger_type & USB_CHG) {
+ /*
+ * Select maximum of what both the charger
+ * and the battery supports
+ */
+ if (di->usb_chg->max_out_curr)
+ iset = min(iset, di->usb_chg->max_out_curr);
+
+ di->chg_info.usb_iset = iset;
+
+ return di->usb_chg->ops.update_curr(di->usb_chg, iset);
+ }
+
+ return -ENXIO;
+}
+
+/**
+ * abx500_chargalg_stop_charging() - Stop charging
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function is called from any state where charging should be stopped.
+ * All charging is disabled and all status parameters and timers are changed
+ * accordingly
+ */
+static void abx500_chargalg_stop_charging(struct abx500_chargalg *di)
+{
+ abx500_chargalg_ac_en(di, false, 0, 0);
+ abx500_chargalg_usb_en(di, false, 0, 0);
+ abx500_chargalg_stop_safety_timer(di);
+ abx500_chargalg_stop_maintenance_timer(di);
+ di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ di->maintenance_chg = false;
+ cancel_delayed_work(&di->chargalg_wd_work);
+ power_supply_changed(&di->chargalg_psy);
+}
+
+/**
+ * abx500_chargalg_hold_charging() - Pauses charging
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function is called in the case where maintenance charging has been
+ * disabled and instead a battery voltage mode is entered to check when the
+ * battery voltage has reached a certain recharge voltage
+ */
+static void abx500_chargalg_hold_charging(struct abx500_chargalg *di)
+{
+ abx500_chargalg_ac_en(di, false, 0, 0);
+ abx500_chargalg_usb_en(di, false, 0, 0);
+ abx500_chargalg_stop_safety_timer(di);
+ abx500_chargalg_stop_maintenance_timer(di);
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ di->maintenance_chg = false;
+ cancel_delayed_work(&di->chargalg_wd_work);
+ power_supply_changed(&di->chargalg_psy);
+}
+
+/**
+ * abx500_chargalg_start_charging() - Start the charger
+ * @di: pointer to the abx500_chargalg structure
+ * @vset: requested charger output voltage
+ * @iset: requested charger output current
+ *
+ * A charger will be enabled depending on the requested charger type that was
+ * detected previously.
+ */
+static void abx500_chargalg_start_charging(struct abx500_chargalg *di,
+ int vset, int iset)
+{
+ switch (di->chg_info.charger_type) {
+ case AC_CHG:
+ dev_dbg(di->dev,
+ "AC parameters: Vset %d, Ich %d\n", vset, iset);
+ abx500_chargalg_usb_en(di, false, 0, 0);
+ abx500_chargalg_ac_en(di, true, vset, iset);
+ break;
+
+ case USB_CHG:
+ dev_dbg(di->dev,
+ "USB parameters: Vset %d, Ich %d\n", vset, iset);
+ abx500_chargalg_ac_en(di, false, 0, 0);
+ abx500_chargalg_usb_en(di, true, vset, iset);
+ break;
+
+ default:
+ dev_err(di->dev, "Unknown charger to charge from\n");
+ break;
+ }
+}
+
+/**
+ * abx500_chargalg_check_temp() - Check battery temperature ranges
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The battery temperature is checked against the predefined limits and the
+ * charge state is changed accordingly
+ */
+static void abx500_chargalg_check_temp(struct abx500_chargalg *di)
+{
+ if (di->batt_data.temp > (di->bat->temp_low + di->t_hyst_norm) &&
+ di->batt_data.temp < (di->bat->temp_high - di->t_hyst_norm)) {
+ /* Temp OK! */
+ di->events.btemp_underover = false;
+ di->events.btemp_lowhigh = false;
+ di->t_hyst_norm = 0;
+ di->t_hyst_lowhigh = 0;
+ } else {
+ if (((di->batt_data.temp >= di->bat->temp_high) &&
+ (di->batt_data.temp <
+ (di->bat->temp_over - di->t_hyst_lowhigh))) ||
+ ((di->batt_data.temp >
+ (di->bat->temp_under + di->t_hyst_lowhigh)) &&
+ (di->batt_data.temp <= di->bat->temp_low))) {
+ /* TEMP minor!!!!! */
+ di->events.btemp_underover = false;
+ di->events.btemp_lowhigh = true;
+ di->t_hyst_norm = di->bat->temp_hysteresis;
+ di->t_hyst_lowhigh = 0;
+ } else if (di->batt_data.temp <= di->bat->temp_under ||
+ di->batt_data.temp >= di->bat->temp_over) {
+ /* TEMP major!!!!! */
+ di->events.btemp_underover = true;
+ di->events.btemp_lowhigh = false;
+ di->t_hyst_norm = 0;
+ di->t_hyst_lowhigh = di->bat->temp_hysteresis;
+ } else {
+ /* Within hysteresis */
+ dev_dbg(di->dev, "Within hysteresis limit temp: %d "
+ "hyst_lowhigh %d, hyst normal %d\n",
+ di->batt_data.temp, di->t_hyst_lowhigh,
+ di->t_hyst_norm);
+ }
+ }
+}
+
+/**
+ * abx500_chargalg_check_charger_voltage() - Check charger voltage
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * Charger voltage is checked against maximum limit
+ */
+static void abx500_chargalg_check_charger_voltage(struct abx500_chargalg *di)
+{
+ if (di->chg_info.usb_volt > di->bat->chg_params->usb_volt_max)
+ di->chg_info.usb_chg_ok = false;
+ else
+ di->chg_info.usb_chg_ok = true;
+
+ if (di->chg_info.ac_volt > di->bat->chg_params->ac_volt_max)
+ di->chg_info.ac_chg_ok = false;
+ else
+ di->chg_info.ac_chg_ok = true;
+
+}
+
+/**
+ * abx500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * End-of-charge criteria is fulfilled when the battery voltage is above a
+ * certain limit and the battery current is below a certain limit for a
+ * predefined number of consecutive seconds. If true, the battery is full
+ */
+static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di)
+{
+ if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING &&
+ di->charge_state == STATE_NORMAL &&
+ !di->maintenance_chg && (di->batt_data.volt >=
+ di->bat->bat_type[di->bat->batt_id].termination_vol ||
+ di->events.usb_cv_active || di->events.ac_cv_active) &&
+ di->batt_data.avg_curr <
+ di->bat->bat_type[di->bat->batt_id].termination_curr &&
+ di->batt_data.avg_curr > 0) {
+ if (++di->eoc_cnt >= EOC_COND_CNT) {
+ di->eoc_cnt = 0;
+ di->charge_status = POWER_SUPPLY_STATUS_FULL;
+ di->maintenance_chg = true;
+ dev_dbg(di->dev, "EOC reached!\n");
+ power_supply_changed(&di->chargalg_psy);
+ } else {
+ dev_dbg(di->dev,
+ " EOC limit reached for the %d"
+ " time, out of %d before EOC\n",
+ di->eoc_cnt,
+ EOC_COND_CNT);
+ }
+ } else {
+ di->eoc_cnt = 0;
+ }
+}
+
+static void init_maxim_chg_curr(struct abx500_chargalg *di)
+{
+ di->ccm.original_iset =
+ di->bat->bat_type[di->bat->batt_id].normal_cur_lvl;
+ di->ccm.current_iset =
+ di->bat->bat_type[di->bat->batt_id].normal_cur_lvl;
+ di->ccm.test_delta_i = di->bat->maxi->charger_curr_step;
+ di->ccm.max_current = di->bat->maxi->chg_curr;
+ di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ di->ccm.level = 0;
+}
+
+/**
+ * abx500_chargalg_chg_curr_maxim - increases the charger current to
+ * compensate for the system load
+ * @di pointer to the abx500_chargalg structure
+ *
+ * This maximization function is used to raise the charger current to get the
+ * battery current as close to the optimal value as possible. The battery
+ * current during charging is affected by the system load
+ */
+static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di)
+{
+ int delta_i;
+
+ if (!di->bat->maxi->ena_maxi)
+ return MAXIM_RET_NOACTION;
+
+ delta_i = di->ccm.original_iset - di->batt_data.inst_curr;
+
+ if (di->events.vbus_collapsed) {
+ dev_dbg(di->dev, "Charger voltage has collapsed %d\n",
+ di->ccm.wait_cnt);
+ if (di->ccm.wait_cnt == 0) {
+ dev_dbg(di->dev, "lowering current\n");
+ di->ccm.wait_cnt++;
+ di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ di->ccm.max_current =
+ di->ccm.current_iset - di->ccm.test_delta_i;
+ di->ccm.current_iset = di->ccm.max_current;
+ di->ccm.level--;
+ return MAXIM_RET_CHANGE;
+ } else {
+ dev_dbg(di->dev, "waiting\n");
+ /* Let's go in here twice before lowering curr again */
+ di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3;
+ return MAXIM_RET_NOACTION;
+ }
+ }
+
+ di->ccm.wait_cnt = 0;
+
+ if ((di->batt_data.inst_curr > di->ccm.original_iset)) {
+ dev_dbg(di->dev, " Maximization Ibat (%dmA) too high"
+ " (limit %dmA) (current iset: %dmA)!\n",
+ di->batt_data.inst_curr, di->ccm.original_iset,
+ di->ccm.current_iset);
+
+ if (di->ccm.current_iset == di->ccm.original_iset)
+ return MAXIM_RET_NOACTION;
+
+ di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ di->ccm.current_iset = di->ccm.original_iset;
+ di->ccm.level = 0;
+
+ return MAXIM_RET_IBAT_TOO_HIGH;
+ }
+
+ if (delta_i > di->ccm.test_delta_i &&
+ (di->ccm.current_iset + di->ccm.test_delta_i) <
+ di->ccm.max_current) {
+ if (di->ccm.condition_cnt-- == 0) {
+ /* Increse the iset with cco.test_delta_i */
+ di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ di->ccm.current_iset += di->ccm.test_delta_i;
+ di->ccm.level++;
+ dev_dbg(di->dev, " Maximization needed, increase"
+ " with %d mA to %dmA (Optimal ibat: %d)"
+ " Level %d\n",
+ di->ccm.test_delta_i,
+ di->ccm.current_iset,
+ di->ccm.original_iset,
+ di->ccm.level);
+ return MAXIM_RET_CHANGE;
+ } else {
+ return MAXIM_RET_NOACTION;
+ }
+ } else {
+ di->ccm.condition_cnt = di->bat->maxi->wait_cycles;
+ return MAXIM_RET_NOACTION;
+ }
+}
+
+static void handle_maxim_chg_curr(struct abx500_chargalg *di)
+{
+ enum maxim_ret ret;
+ int result;
+
+ ret = abx500_chargalg_chg_curr_maxim(di);
+ switch (ret) {
+ case MAXIM_RET_CHANGE:
+ result = abx500_chargalg_update_chg_curr(di,
+ di->ccm.current_iset);
+ if (result)
+ dev_err(di->dev, "failed to set chg curr\n");
+ break;
+ case MAXIM_RET_IBAT_TOO_HIGH:
+ result = abx500_chargalg_update_chg_curr(di,
+ di->bat->bat_type[di->bat->batt_id].normal_cur_lvl);
+ if (result)
+ dev_err(di->dev, "failed to set chg curr\n");
+ break;
+
+ case MAXIM_RET_NOACTION:
+ default:
+ /* Do nothing..*/
+ break;
+ }
+}
+
+static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext;
+ struct abx500_chargalg *di;
+ union power_supply_propval ret;
+ int i, j;
+ bool psy_found = false;
+
+ psy = (struct power_supply *)data;
+ ext = dev_get_drvdata(dev);
+ di = to_abx500_chargalg_device_info(psy);
+ /* For all psy where the driver name appears in any supplied_to */
+ for (i = 0; i < ext->num_supplicants; i++) {
+ if (!strcmp(ext->supplied_to[i], psy->name))
+ psy_found = true;
+ }
+ if (!psy_found)
+ return 0;
+
+ /* Go through all properties for the psy */
+ for (j = 0; j < ext->num_properties; j++) {
+ enum power_supply_property prop;
+ prop = ext->properties[j];
+
+ /* Initialize chargers if not already done */
+ if (!di->ac_chg &&
+ ext->type == POWER_SUPPLY_TYPE_MAINS)
+ di->ac_chg = psy_to_ux500_charger(ext);
+ else if (!di->usb_chg &&
+ ext->type == POWER_SUPPLY_TYPE_USB)
+ di->usb_chg = psy_to_ux500_charger(ext);
+
+ if (ext->get_property(ext, prop, &ret))
+ continue;
+ switch (prop) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ /* Battery present */
+ if (ret.intval)
+ di->events.batt_rem = false;
+ /* Battery removed */
+ else
+ di->events.batt_rem = true;
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ /* AC disconnected */
+ if (!ret.intval &&
+ (di->chg_info.conn_chg & AC_CHG)) {
+ di->chg_info.prev_conn_chg =
+ di->chg_info.conn_chg;
+ di->chg_info.conn_chg &= ~AC_CHG;
+ }
+ /* AC connected */
+ else if (ret.intval &&
+ !(di->chg_info.conn_chg & AC_CHG)) {
+ di->chg_info.prev_conn_chg =
+ di->chg_info.conn_chg;
+ di->chg_info.conn_chg |= AC_CHG;
+ }
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ /* USB disconnected */
+ if (!ret.intval &&
+ (di->chg_info.conn_chg & USB_CHG)) {
+ di->chg_info.prev_conn_chg =
+ di->chg_info.conn_chg;
+ di->chg_info.conn_chg &= ~USB_CHG;
+ }
+ /* USB connected */
+ else if (ret.intval &&
+ !(di->chg_info.conn_chg & USB_CHG)) {
+ di->chg_info.prev_conn_chg =
+ di->chg_info.conn_chg;
+ di->chg_info.conn_chg |= USB_CHG;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ /* AC offline */
+ if (!ret.intval &&
+ (di->chg_info.online_chg & AC_CHG)) {
+ di->chg_info.prev_online_chg =
+ di->chg_info.online_chg;
+ di->chg_info.online_chg &= ~AC_CHG;
+ }
+ /* AC online */
+ else if (ret.intval &&
+ !(di->chg_info.online_chg & AC_CHG)) {
+ di->chg_info.prev_online_chg =
+ di->chg_info.online_chg;
+ di->chg_info.online_chg |= AC_CHG;
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_wd_work, 0);
+ }
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ /* USB offline */
+ if (!ret.intval &&
+ (di->chg_info.online_chg & USB_CHG)) {
+ di->chg_info.prev_online_chg =
+ di->chg_info.online_chg;
+ di->chg_info.online_chg &= ~USB_CHG;
+ }
+ /* USB online */
+ else if (ret.intval &&
+ !(di->chg_info.online_chg & USB_CHG)) {
+ di->chg_info.prev_online_chg =
+ di->chg_info.online_chg;
+ di->chg_info.online_chg |= USB_CHG;
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_wd_work, 0);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ switch (ret.intval) {
+ case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
+ di->events.mainextchnotok = true;
+ di->events.main_thermal_prot = false;
+ di->events.main_ovv = false;
+ di->events.ac_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_DEAD:
+ di->events.ac_wd_expired = true;
+ di->events.mainextchnotok = false;
+ di->events.main_ovv = false;
+ di->events.main_thermal_prot = false;
+ break;
+ case POWER_SUPPLY_HEALTH_COLD:
+ case POWER_SUPPLY_HEALTH_OVERHEAT:
+ di->events.main_thermal_prot = true;
+ di->events.mainextchnotok = false;
+ di->events.main_ovv = false;
+ di->events.ac_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
+ di->events.main_ovv = true;
+ di->events.mainextchnotok = false;
+ di->events.main_thermal_prot = false;
+ di->events.ac_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_GOOD:
+ di->events.main_thermal_prot = false;
+ di->events.mainextchnotok = false;
+ di->events.main_ovv = false;
+ di->events.ac_wd_expired = false;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_TYPE_USB:
+ switch (ret.intval) {
+ case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
+ di->events.usbchargernotok = true;
+ di->events.usb_thermal_prot = false;
+ di->events.vbus_ovv = false;
+ di->events.usb_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_DEAD:
+ di->events.usb_wd_expired = true;
+ di->events.usbchargernotok = false;
+ di->events.usb_thermal_prot = false;
+ di->events.vbus_ovv = false;
+ break;
+ case POWER_SUPPLY_HEALTH_COLD:
+ case POWER_SUPPLY_HEALTH_OVERHEAT:
+ di->events.usb_thermal_prot = true;
+ di->events.usbchargernotok = false;
+ di->events.vbus_ovv = false;
+ di->events.usb_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
+ di->events.vbus_ovv = true;
+ di->events.usbchargernotok = false;
+ di->events.usb_thermal_prot = false;
+ di->events.usb_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_GOOD:
+ di->events.usbchargernotok = false;
+ di->events.usb_thermal_prot = false;
+ di->events.vbus_ovv = false;
+ di->events.usb_wd_expired = false;
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ di->batt_data.volt = ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ di->chg_info.ac_volt = ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ di->chg_info.usb_volt = ret.intval / 1000;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_MAINS:
+ /* AVG is used to indicate when we are
+ * in CV mode */
+ if (ret.intval)
+ di->events.ac_cv_active = true;
+ else
+ di->events.ac_cv_active = false;
+
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ /* AVG is used to indicate when we are
+ * in CV mode */
+ if (ret.intval)
+ di->events.usb_cv_active = true;
+ else
+ di->events.usb_cv_active = false;
+
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ if (ret.intval)
+ di->events.batt_unknown = false;
+ else
+ di->events.batt_unknown = true;
+
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ di->batt_data.temp = ret.intval / 10;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_MAINS:
+ di->chg_info.ac_curr =
+ ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ di->chg_info.usb_curr =
+ ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_BATTERY:
+ di->batt_data.inst_curr = ret.intval / 1000;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ switch (ext->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ di->batt_data.avg_curr = ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ if (ret.intval)
+ di->events.vbus_collapsed = true;
+ else
+ di->events.vbus_collapsed = false;
+ break;
+ default:
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ di->batt_data.percent = ret.intval;
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * abx500_chargalg_external_power_changed() - callback for power supply changes
+ * @psy: pointer to the structure power_supply
+ *
+ * This function is the entry point of the pointer external_power_changed
+ * of the structure power_supply.
+ * This function gets executed when there is a change in any external power
+ * supply that this driver needs to be notified of.
+ */
+static void abx500_chargalg_external_power_changed(struct power_supply *psy)
+{
+ struct abx500_chargalg *di = to_abx500_chargalg_device_info(psy);
+
+ /*
+ * Trigger execution of the algorithm instantly and read
+ * all power_supply properties there instead
+ */
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_algorithm() - Main function for the algorithm
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This is the main control function for the charging algorithm.
+ * It is called periodically or when something happens that will
+ * trigger a state change
+ */
+static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
+{
+ int charger_status;
+
+ /* Collect data from all power_supply class devices */
+ class_for_each_device(power_supply_class, NULL,
+ &di->chargalg_psy, abx500_chargalg_get_ext_psy_data);
+
+ abx500_chargalg_end_of_charge(di);
+ abx500_chargalg_check_temp(di);
+ abx500_chargalg_check_charger_voltage(di);
+
+ charger_status = abx500_chargalg_check_charger_connection(di);
+ /*
+ * First check if we have a charger connected.
+ * Also we don't allow charging of unknown batteries if configured
+ * this way
+ */
+ if (!charger_status ||
+ (di->events.batt_unknown && !di->bat->chg_unknown_bat)) {
+ if (di->charge_state != STATE_HANDHELD) {
+ di->events.safety_timer_expired = false;
+ abx500_chargalg_state_to(di, STATE_HANDHELD_INIT);
+ }
+ }
+
+ /* If suspended, we should not continue checking the flags */
+ else if (di->charge_state == STATE_SUSPENDED_INIT ||
+ di->charge_state == STATE_SUSPENDED) {
+ /* We don't do anything here, just don,t continue */
+ }
+
+ /* Safety timer expiration */
+ else if (di->events.safety_timer_expired) {
+ if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED)
+ abx500_chargalg_state_to(di,
+ STATE_SAFETY_TIMER_EXPIRED_INIT);
+ }
+ /*
+ * Check if any interrupts has occured
+ * that will prevent us from charging
+ */
+
+ /* Battery removed */
+ else if (di->events.batt_rem) {
+ if (di->charge_state != STATE_BATT_REMOVED)
+ abx500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT);
+ }
+ /* Main or USB charger not ok. */
+ else if (di->events.mainextchnotok || di->events.usbchargernotok) {
+ /*
+ * If vbus_collapsed is set, we have to lower the charger
+ * current, which is done in the normal state below
+ */
+ if (di->charge_state != STATE_CHG_NOT_OK &&
+ !di->events.vbus_collapsed)
+ abx500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT);
+ }
+ /* VBUS, Main or VBAT OVV. */
+ else if (di->events.vbus_ovv ||
+ di->events.main_ovv ||
+ di->events.batt_ovv ||
+ !di->chg_info.usb_chg_ok ||
+ !di->chg_info.ac_chg_ok) {
+ if (di->charge_state != STATE_OVV_PROTECT)
+ abx500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT);
+ }
+ /* USB Thermal, stop charging */
+ else if (di->events.main_thermal_prot ||
+ di->events.usb_thermal_prot) {
+ if (di->charge_state != STATE_HW_TEMP_PROTECT)
+ abx500_chargalg_state_to(di,
+ STATE_HW_TEMP_PROTECT_INIT);
+ }
+ /* Battery temp over/under */
+ else if (di->events.btemp_underover) {
+ if (di->charge_state != STATE_TEMP_UNDEROVER)
+ abx500_chargalg_state_to(di,
+ STATE_TEMP_UNDEROVER_INIT);
+ }
+ /* Watchdog expired */
+ else if (di->events.ac_wd_expired ||
+ di->events.usb_wd_expired) {
+ if (di->charge_state != STATE_WD_EXPIRED)
+ abx500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT);
+ }
+ /* Battery temp high/low */
+ else if (di->events.btemp_lowhigh) {
+ if (di->charge_state != STATE_TEMP_LOWHIGH)
+ abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT);
+ }
+
+ dev_dbg(di->dev,
+ "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d "
+ "State %s Active_chg %d Chg_status %d AC %d USB %d "
+ "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d "
+ "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n",
+ di->batt_data.volt,
+ di->batt_data.avg_curr,
+ di->batt_data.inst_curr,
+ di->batt_data.temp,
+ di->batt_data.percent,
+ di->maintenance_chg,
+ states[di->charge_state],
+ di->chg_info.charger_type,
+ di->charge_status,
+ di->chg_info.conn_chg & AC_CHG,
+ di->chg_info.conn_chg & USB_CHG,
+ di->chg_info.online_chg & AC_CHG,
+ di->chg_info.online_chg & USB_CHG,
+ di->events.ac_cv_active,
+ di->events.usb_cv_active,
+ di->chg_info.ac_curr,
+ di->chg_info.usb_curr,
+ di->chg_info.ac_vset,
+ di->chg_info.ac_iset,
+ di->chg_info.usb_vset,
+ di->chg_info.usb_iset);
+
+ switch (di->charge_state) {
+ case STATE_HANDHELD_INIT:
+ abx500_chargalg_stop_charging(di);
+ di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ abx500_chargalg_state_to(di, STATE_HANDHELD);
+ /* Intentional fallthrough */
+
+ case STATE_HANDHELD:
+ break;
+
+ case STATE_SUSPENDED_INIT:
+ if (di->susp_status.ac_suspended)
+ abx500_chargalg_ac_en(di, false, 0, 0);
+ if (di->susp_status.usb_suspended)
+ abx500_chargalg_usb_en(di, false, 0, 0);
+ abx500_chargalg_stop_safety_timer(di);
+ abx500_chargalg_stop_maintenance_timer(di);
+ di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ di->maintenance_chg = false;
+ abx500_chargalg_state_to(di, STATE_SUSPENDED);
+ power_supply_changed(&di->chargalg_psy);
+ /* Intentional fallthrough */
+
+ case STATE_SUSPENDED:
+ /* CHARGING is suspended */
+ break;
+
+ case STATE_BATT_REMOVED_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_BATT_REMOVED);
+ /* Intentional fallthrough */
+
+ case STATE_BATT_REMOVED:
+ if (!di->events.batt_rem)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_HW_TEMP_PROTECT_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT);
+ /* Intentional fallthrough */
+
+ case STATE_HW_TEMP_PROTECT:
+ if (!di->events.main_thermal_prot &&
+ !di->events.usb_thermal_prot)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_OVV_PROTECT_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_OVV_PROTECT);
+ /* Intentional fallthrough */
+
+ case STATE_OVV_PROTECT:
+ if (!di->events.vbus_ovv &&
+ !di->events.main_ovv &&
+ !di->events.batt_ovv &&
+ di->chg_info.usb_chg_ok &&
+ di->chg_info.ac_chg_ok)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_CHG_NOT_OK_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_CHG_NOT_OK);
+ /* Intentional fallthrough */
+
+ case STATE_CHG_NOT_OK:
+ if (!di->events.mainextchnotok &&
+ !di->events.usbchargernotok)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_SAFETY_TIMER_EXPIRED_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED);
+ /* Intentional fallthrough */
+
+ case STATE_SAFETY_TIMER_EXPIRED:
+ /* We exit this state when charger is removed */
+ break;
+
+ case STATE_NORMAL_INIT:
+ abx500_chargalg_start_charging(di,
+ di->bat->bat_type[di->bat->batt_id].normal_vol_lvl,
+ di->bat->bat_type[di->bat->batt_id].normal_cur_lvl);
+ abx500_chargalg_state_to(di, STATE_NORMAL);
+ abx500_chargalg_start_safety_timer(di);
+ abx500_chargalg_stop_maintenance_timer(di);
+ init_maxim_chg_curr(di);
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ di->eoc_cnt = 0;
+ di->maintenance_chg = false;
+ power_supply_changed(&di->chargalg_psy);
+
+ break;
+
+ case STATE_NORMAL:
+ handle_maxim_chg_curr(di);
+ if (di->charge_status == POWER_SUPPLY_STATUS_FULL &&
+ di->maintenance_chg) {
+ if (di->bat->no_maintenance)
+ abx500_chargalg_state_to(di,
+ STATE_WAIT_FOR_RECHARGE_INIT);
+ else
+ abx500_chargalg_state_to(di,
+ STATE_MAINTENANCE_A_INIT);
+ }
+ break;
+
+ /* This state will be used when the maintenance state is disabled */
+ case STATE_WAIT_FOR_RECHARGE_INIT:
+ abx500_chargalg_hold_charging(di);
+ abx500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE);
+ di->rch_cnt = RCH_COND_CNT;
+ /* Intentional fallthrough */
+
+ case STATE_WAIT_FOR_RECHARGE:
+ if (di->batt_data.volt <=
+ di->bat->bat_type[di->bat->batt_id].recharge_vol) {
+ if (di->rch_cnt-- == 0)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ } else
+ di->rch_cnt = RCH_COND_CNT;
+ break;
+
+ case STATE_MAINTENANCE_A_INIT:
+ abx500_chargalg_stop_safety_timer(di);
+ abx500_chargalg_start_maintenance_timer(di,
+ di->bat->bat_type[
+ di->bat->batt_id].maint_a_chg_timer_h);
+ abx500_chargalg_start_charging(di,
+ di->bat->bat_type[
+ di->bat->batt_id].maint_a_vol_lvl,
+ di->bat->bat_type[
+ di->bat->batt_id].maint_a_cur_lvl);
+ abx500_chargalg_state_to(di, STATE_MAINTENANCE_A);
+ power_supply_changed(&di->chargalg_psy);
+ /* Intentional fallthrough*/
+
+ case STATE_MAINTENANCE_A:
+ if (di->events.maintenance_timer_expired) {
+ abx500_chargalg_stop_maintenance_timer(di);
+ abx500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT);
+ }
+ break;
+
+ case STATE_MAINTENANCE_B_INIT:
+ abx500_chargalg_start_maintenance_timer(di,
+ di->bat->bat_type[
+ di->bat->batt_id].maint_b_chg_timer_h);
+ abx500_chargalg_start_charging(di,
+ di->bat->bat_type[
+ di->bat->batt_id].maint_b_vol_lvl,
+ di->bat->bat_type[
+ di->bat->batt_id].maint_b_cur_lvl);
+ abx500_chargalg_state_to(di, STATE_MAINTENANCE_B);
+ power_supply_changed(&di->chargalg_psy);
+ /* Intentional fallthrough*/
+
+ case STATE_MAINTENANCE_B:
+ if (di->events.maintenance_timer_expired) {
+ abx500_chargalg_stop_maintenance_timer(di);
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ }
+ break;
+
+ case STATE_TEMP_LOWHIGH_INIT:
+ abx500_chargalg_start_charging(di,
+ di->bat->bat_type[
+ di->bat->batt_id].low_high_vol_lvl,
+ di->bat->bat_type[
+ di->bat->batt_id].low_high_cur_lvl);
+ abx500_chargalg_stop_maintenance_timer(di);
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH);
+ power_supply_changed(&di->chargalg_psy);
+ /* Intentional fallthrough */
+
+ case STATE_TEMP_LOWHIGH:
+ if (!di->events.btemp_lowhigh)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_WD_EXPIRED_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_WD_EXPIRED);
+ /* Intentional fallthrough */
+
+ case STATE_WD_EXPIRED:
+ if (!di->events.ac_wd_expired &&
+ !di->events.usb_wd_expired)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_TEMP_UNDEROVER_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_TEMP_UNDEROVER);
+ /* Intentional fallthrough */
+
+ case STATE_TEMP_UNDEROVER:
+ if (!di->events.btemp_underover)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+ }
+
+ /* Start charging directly if the new state is a charge state */
+ if (di->charge_state == STATE_NORMAL_INIT ||
+ di->charge_state == STATE_MAINTENANCE_A_INIT ||
+ di->charge_state == STATE_MAINTENANCE_B_INIT)
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_periodic_work() - Periodic work for the algorithm
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for the charging algorithm
+ */
+static void abx500_chargalg_periodic_work(struct work_struct *work)
+{
+ struct abx500_chargalg *di = container_of(work,
+ struct abx500_chargalg, chargalg_periodic_work.work);
+
+ abx500_chargalg_algorithm(di);
+
+ /*
+ * If a charger is connected then the battery has to be monitored
+ * frequently, else the work can be delayed.
+ */
+ if (di->chg_info.conn_chg)
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_periodic_work,
+ di->bat->interval_charging * HZ);
+ else
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_periodic_work,
+ di->bat->interval_not_charging * HZ);
+}
+
+/**
+ * abx500_chargalg_wd_work() - periodic work to kick the charger watchdog
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for kicking the charger watchdog
+ */
+static void abx500_chargalg_wd_work(struct work_struct *work)
+{
+ int ret;
+ struct abx500_chargalg *di = container_of(work,
+ struct abx500_chargalg, chargalg_wd_work.work);
+
+ dev_dbg(di->dev, "abx500_chargalg_wd_work\n");
+
+ ret = abx500_chargalg_kick_watchdog(di);
+ if (ret < 0)
+ dev_err(di->dev, "failed to kick watchdog\n");
+
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_wd_work, CHG_WD_INTERVAL);
+}
+
+/**
+ * abx500_chargalg_work() - Work to run the charging algorithm instantly
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for calling the charging algorithm
+ */
+static void abx500_chargalg_work(struct work_struct *work)
+{
+ struct abx500_chargalg *di = container_of(work,
+ struct abx500_chargalg, chargalg_work);
+
+ abx500_chargalg_algorithm(di);
+}
+
+/**
+ * abx500_chargalg_get_property() - get the chargalg properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the
+ * chargalg properties by reading the sysfs files.
+ * status: charging/discharging/full/unknown
+ * health: health of the battery
+ * Returns error code in case of failure else 0 on success
+ */
+static int abx500_chargalg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct abx500_chargalg *di;
+
+ di = to_abx500_chargalg_device_info(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = di->charge_status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (di->events.batt_ovv) {
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ } else if (di->events.btemp_underover) {
+ if (di->batt_data.temp <= di->bat->temp_under)
+ val->intval = POWER_SUPPLY_HEALTH_COLD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ } else {
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Exposure to the sysfs interface */
+
+/**
+ * abx500_chargalg_sysfs_charger() - sysfs store operations
+ * @kobj: pointer to the struct kobject
+ * @attr: pointer to the struct attribute
+ * @buf: buffer that holds the parameter passed from userspace
+ * @length: length of the parameter passed
+ *
+ * Returns length of the buffer(input taken from user space) on success
+ * else error code on failure
+ * The operation to be performed on passing the parameters from the user space.
+ */
+static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj,
+ struct attribute *attr, const char *buf, size_t length)
+{
+ struct abx500_chargalg *di = container_of(kobj,
+ struct abx500_chargalg, chargalg_kobject);
+ long int param;
+ int ac_usb;
+ int ret;
+ char entry = *attr->name;
+
+ switch (entry) {
+ case 'c':
+ ret = strict_strtol(buf, 10, &param);
+ if (ret < 0)
+ return ret;
+
+ ac_usb = param;
+ switch (ac_usb) {
+ case 0:
+ /* Disable charging */
+ di->susp_status.ac_suspended = true;
+ di->susp_status.usb_suspended = true;
+ di->susp_status.suspended_change = true;
+ /* Trigger a state change */
+ queue_work(di->chargalg_wq,
+ &di->chargalg_work);
+ break;
+ case 1:
+ /* Enable AC Charging */
+ di->susp_status.ac_suspended = false;
+ di->susp_status.suspended_change = true;
+ /* Trigger a state change */
+ queue_work(di->chargalg_wq,
+ &di->chargalg_work);
+ break;
+ case 2:
+ /* Enable USB charging */
+ di->susp_status.usb_suspended = false;
+ di->susp_status.suspended_change = true;
+ /* Trigger a state change */
+ queue_work(di->chargalg_wq,
+ &di->chargalg_work);
+ break;
+ default:
+ dev_info(di->dev, "Wrong input\n"
+ "Enter 0. Disable AC/USB Charging\n"
+ "1. Enable AC charging\n"
+ "2. Enable USB Charging\n");
+ };
+ break;
+ };
+ return strlen(buf);
+}
+
+static struct attribute abx500_chargalg_en_charger = \
+{
+ .name = "chargalg",
+ .mode = S_IWUGO,
+};
+
+static struct attribute *abx500_chargalg_chg[] = {
+ &abx500_chargalg_en_charger,
+ NULL
+};
+
+static const struct sysfs_ops abx500_chargalg_sysfs_ops = {
+ .store = abx500_chargalg_sysfs_charger,
+};
+
+static struct kobj_type abx500_chargalg_ktype = {
+ .sysfs_ops = &abx500_chargalg_sysfs_ops,
+ .default_attrs = abx500_chargalg_chg,
+};
+
+/**
+ * abx500_chargalg_sysfs_exit() - de-init of sysfs entry
+ * @di: pointer to the struct abx500_chargalg
+ *
+ * This function removes the entry in sysfs.
+ */
+static void abx500_chargalg_sysfs_exit(struct abx500_chargalg *di)
+{
+ kobject_del(&di->chargalg_kobject);
+}
+
+/**
+ * abx500_chargalg_sysfs_init() - init of sysfs entry
+ * @di: pointer to the struct abx500_chargalg
+ *
+ * This function adds an entry in sysfs.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int abx500_chargalg_sysfs_init(struct abx500_chargalg *di)
+{
+ int ret = 0;
+
+ ret = kobject_init_and_add(&di->chargalg_kobject,
+ &abx500_chargalg_ktype,
+ NULL, "abx500_chargalg");
+ if (ret < 0)
+ dev_err(di->dev, "failed to create sysfs entry\n");
+
+ return ret;
+}
+/* Exposure to the sysfs interface <<END>> */
+
+#if defined(CONFIG_PM)
+static int abx500_chargalg_resume(struct platform_device *pdev)
+{
+ struct abx500_chargalg *di = platform_get_drvdata(pdev);
+
+ /* Kick charger watchdog if charging (any charger online) */
+ if (di->chg_info.online_chg)
+ queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0);
+
+ /*
+ * Run the charging algorithm directly to be sure we don't
+ * do it too seldom
+ */
+ queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
+
+ return 0;
+}
+
+static int abx500_chargalg_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct abx500_chargalg *di = platform_get_drvdata(pdev);
+
+ if (di->chg_info.online_chg)
+ cancel_delayed_work_sync(&di->chargalg_wd_work);
+
+ cancel_delayed_work_sync(&di->chargalg_periodic_work);
+
+ return 0;
+}
+#else
+#define abx500_chargalg_suspend NULL
+#define abx500_chargalg_resume NULL
+#endif
+
+static int __devexit abx500_chargalg_remove(struct platform_device *pdev)
+{
+ struct abx500_chargalg *di = platform_get_drvdata(pdev);
+
+ /* sysfs interface to enable/disbale charging from user space */
+ abx500_chargalg_sysfs_exit(di);
+
+ /* Delete the work queue */
+ destroy_workqueue(di->chargalg_wq);
+
+ flush_scheduled_work();
+ power_supply_unregister(&di->chargalg_psy);
+ platform_set_drvdata(pdev, NULL);
+ kfree(di);
+
+ return 0;
+}
+
+static int __devinit abx500_chargalg_probe(struct platform_device *pdev)
+{
+ struct abx500_bm_plat_data *plat_data;
+ int ret = 0;
+
+ struct abx500_chargalg *di =
+ kzalloc(sizeof(struct abx500_chargalg), GFP_KERNEL);
+ if (!di)
+ return -ENOMEM;
+
+ /* get device struct */
+ di->dev = &pdev->dev;
+
+ plat_data = pdev->dev.platform_data;
+ di->pdata = plat_data->chargalg;
+ di->bat = plat_data->battery;
+
+ /* chargalg supply */
+ di->chargalg_psy.name = "abx500_chargalg";
+ di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->chargalg_psy.properties = abx500_chargalg_props;
+ di->chargalg_psy.num_properties = ARRAY_SIZE(abx500_chargalg_props);
+ di->chargalg_psy.get_property = abx500_chargalg_get_property;
+ di->chargalg_psy.supplied_to = di->pdata->supplied_to;
+ di->chargalg_psy.num_supplicants = di->pdata->num_supplicants;
+ di->chargalg_psy.external_power_changed =
+ abx500_chargalg_external_power_changed;
+
+ /* Initilialize safety timer */
+ init_timer(&di->safety_timer);
+ di->safety_timer.function = abx500_chargalg_safety_timer_expired;
+ di->safety_timer.data = (unsigned long) di;
+
+ /* Initilialize maintenance timer */
+ init_timer(&di->maintenance_timer);
+ di->maintenance_timer.function =
+ abx500_chargalg_maintenance_timer_expired;
+ di->maintenance_timer.data = (unsigned long) di;
+
+ /* Create a work queue for the chargalg */
+ di->chargalg_wq =
+ create_singlethread_workqueue("abx500_chargalg_wq");
+ if (di->chargalg_wq == NULL) {
+ dev_err(di->dev, "failed to create work queue\n");
+ goto free_device_info;
+ }
+
+ /* Init work for chargalg */
+ INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_periodic_work,
+ abx500_chargalg_periodic_work);
+ INIT_DELAYED_WORK_DEFERRABLE(&di->chargalg_wd_work,
+ abx500_chargalg_wd_work);
+
+ /* Init work for chargalg */
+ INIT_WORK(&di->chargalg_work, abx500_chargalg_work);
+
+ /* To detect charger at startup */
+ di->chg_info.prev_conn_chg = -1;
+
+ /* Register chargalg power supply class */
+ ret = power_supply_register(di->dev, &di->chargalg_psy);
+ if (ret) {
+ dev_err(di->dev, "failed to register chargalg psy\n");
+ goto free_chargalg_wq;
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ /* sysfs interface to enable/disable charging from user space */
+ ret = abx500_chargalg_sysfs_init(di);
+ if (ret) {
+ dev_err(di->dev, "failed to create sysfs entry\n");
+ goto free_psy;
+ }
+
+ /* Run the charging algorithm */
+ queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
+
+ dev_info(di->dev, "probe success\n");
+ return ret;
+
+free_psy:
+ power_supply_unregister(&di->chargalg_psy);
+free_chargalg_wq:
+ destroy_workqueue(di->chargalg_wq);
+free_device_info:
+ kfree(di);
+
+ return ret;
+}
+
+static struct platform_driver abx500_chargalg_driver = {
+ .probe = abx500_chargalg_probe,
+ .remove = __devexit_p(abx500_chargalg_remove),
+ .suspend = abx500_chargalg_suspend,
+ .resume = abx500_chargalg_resume,
+ .driver = {
+ .name = "abx500-chargalg",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init abx500_chargalg_init(void)
+{
+ return platform_driver_register(&abx500_chargalg_driver);
+}
+
+static void __exit abx500_chargalg_exit(void)
+{
+ platform_driver_unregister(&abx500_chargalg_driver);
+}
+
+module_init(abx500_chargalg_init);
+module_exit(abx500_chargalg_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
+MODULE_ALIAS("platform:abx500-chargalg");
+MODULE_DESCRIPTION("abx500 battery charging algorithm");
diff --git a/drivers/power/android_battery.c b/drivers/power/android_battery.c
new file mode 100644
index 00000000..e0cefafb
--- /dev/null
+++ b/drivers/power/android_battery.c
@@ -0,0 +1,767 @@
+/*
+ * android_battery.c
+ * Android Battery Driver
+ *
+ * Copyright (C) 2012 Google, Inc.
+ * Copyright (C) 2012 Samsung Electronics
+ *
+ * Based on work by himihee.seo@samsung.com, ms925.kim@samsung.com, and
+ * joshua.chang@samsung.com.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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.
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/wakelock.h>
+#include <linux/workqueue.h>
+#include <linux/alarmtimer.h>
+#include <linux/timer.h>
+#include <linux/mutex.h>
+#include <linux/debugfs.h>
+#include <linux/platform_data/android_battery.h>
+
+#define FAST_POLL (1 * 60)
+#define SLOW_POLL (10 * 60)
+
+struct android_bat_data {
+ struct android_bat_platform_data *pdata;
+ struct android_bat_callbacks callbacks;
+
+ struct device *dev;
+
+ struct power_supply psy_bat;
+ struct power_supply psy_usb;
+ struct power_supply psy_ac;
+
+ struct wake_lock monitor_wake_lock;
+ struct wake_lock charger_wake_lock;
+
+ int charge_source;
+
+ int batt_temp;
+ int batt_current;
+ unsigned int batt_health;
+ unsigned int batt_vcell;
+ unsigned int batt_soc;
+ unsigned int charging_status;
+ bool recharging;
+ unsigned long charging_start_time;
+
+ struct workqueue_struct *monitor_wqueue;
+ struct work_struct monitor_work;
+ struct work_struct charger_work;
+
+ struct alarm monitor_alarm;
+ ktime_t last_poll;
+
+ struct dentry *debugfs_entry;
+};
+
+static char *supply_list[] = {
+ "android-battery",
+};
+
+static enum power_supply_property android_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static enum power_supply_property android_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static DEFINE_MUTEX(android_bat_state_lock);
+
+static void android_bat_update_data(struct android_bat_data *battery);
+static int android_bat_enable_charging(struct android_bat_data *battery,
+ bool enable);
+
+static char *charge_source_str(int charge_source)
+{
+ switch (charge_source) {
+ case CHARGE_SOURCE_NONE:
+ return "none";
+ case CHARGE_SOURCE_AC:
+ return "ac";
+ case CHARGE_SOURCE_USB:
+ return "usb";
+ default:
+ break;
+ }
+
+ return "?";
+}
+
+static int android_bat_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct android_bat_data *battery =
+ container_of(ps, struct android_bat_data, psy_bat);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = battery->charging_status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = battery->batt_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = battery->batt_temp;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ android_bat_update_data(battery);
+ val->intval = battery->batt_vcell;
+ if (val->intval == -1)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = battery->batt_soc;
+ if (val->intval == -1)
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ android_bat_update_data(battery);
+ val->intval = battery->batt_current;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int android_usb_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct android_bat_data *battery = container_of(ps,
+ struct android_bat_data, psy_usb);
+
+ if (psp != POWER_SUPPLY_PROP_ONLINE)
+ return -EINVAL;
+
+ val->intval = (battery->charge_source == CHARGE_SOURCE_USB);
+
+ return 0;
+}
+
+static int android_ac_get_property(struct power_supply *ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct android_bat_data *battery = container_of(ps,
+ struct android_bat_data, psy_ac);
+
+ if (psp != POWER_SUPPLY_PROP_ONLINE)
+ return -EINVAL;
+
+ val->intval = (battery->charge_source == CHARGE_SOURCE_AC);
+
+ return 0;
+}
+
+static void android_bat_get_temp(struct android_bat_data *battery)
+{
+ int batt_temp = 42; /* 4.2C */
+ int health = battery->batt_health;
+
+ if (battery->pdata->get_temperature)
+ battery->pdata->get_temperature(&batt_temp);
+
+ if (battery->charge_source != CHARGE_SOURCE_NONE) {
+ if (batt_temp >= battery->pdata->temp_high_threshold) {
+ if (health != POWER_SUPPLY_HEALTH_OVERHEAT &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) {
+ pr_info("battery overheat (%d>=%d), " \
+ "charging unavailable\n",
+ batt_temp,
+ battery->pdata->temp_high_threshold);
+ battery->batt_health =
+ POWER_SUPPLY_HEALTH_OVERHEAT;
+ }
+ } else if (batt_temp <= battery->pdata->temp_high_recovery &&
+ batt_temp >= battery->pdata->temp_low_recovery) {
+ if (health == POWER_SUPPLY_HEALTH_OVERHEAT ||
+ health == POWER_SUPPLY_HEALTH_COLD) {
+ pr_info("battery recovery (%d,%d~%d)," \
+ "charging available\n",
+ batt_temp,
+ battery->pdata->temp_low_recovery,
+ battery->pdata->temp_high_recovery);
+ battery->batt_health =
+ POWER_SUPPLY_HEALTH_GOOD;
+ }
+ } else if (batt_temp <= battery->pdata->temp_low_threshold) {
+ if (health != POWER_SUPPLY_HEALTH_COLD &&
+ health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) {
+ pr_info("battery cold (%d <= %d)," \
+ "charging unavailable\n",
+ batt_temp,
+ battery->pdata->temp_low_threshold);
+ battery->batt_health =
+ POWER_SUPPLY_HEALTH_COLD;
+ }
+ }
+ }
+
+ battery->batt_temp = batt_temp;
+}
+
+/*
+ * android_bat_state_lock not held, may call back into
+ * android_bat_charge_source_changed. Gathering data here can be
+ * non-atomic; updating our state based on the data may need to be
+ * atomic.
+ */
+
+static void android_bat_update_data(struct android_bat_data *battery)
+{
+ int ret;
+ int v;
+
+ if (battery->pdata->poll_charge_source)
+ battery->charge_source = battery->pdata->poll_charge_source();
+
+ if (battery->pdata->get_voltage_now) {
+ ret = battery->pdata->get_voltage_now();
+ battery->batt_vcell = ret >= 0 ? ret : 4242000;
+ }
+
+ if (battery->pdata->get_capacity) {
+ ret = battery->pdata->get_capacity();
+ battery->batt_soc = ret >= 0 ? ret : 42;
+ }
+
+ if (battery->pdata->get_current_now) {
+ ret = battery->pdata->get_current_now(&v);
+
+ if (!ret)
+ battery->batt_current = v;
+ }
+
+ android_bat_get_temp(battery);
+}
+
+static void android_bat_set_charge_time(struct android_bat_data *battery,
+ bool enable)
+{
+ if (enable && !battery->charging_start_time) {
+ struct timespec cur_time;
+
+ get_monotonic_boottime(&cur_time);
+ /* record start time for charge timeout timer */
+ battery->charging_start_time = cur_time.tv_sec;
+ } else if (!enable) {
+ /* clear charge timeout timer */
+ battery->charging_start_time = 0;
+ }
+}
+
+static int android_bat_enable_charging(struct android_bat_data *battery,
+ bool enable)
+{
+ if (enable && (battery->batt_health != POWER_SUPPLY_HEALTH_GOOD)) {
+ battery->charging_status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ return -EPERM;
+ }
+
+ if (enable) {
+ if (battery->pdata && battery->pdata->set_charging_current)
+ battery->pdata->set_charging_current
+ (battery->charge_source);
+ }
+
+ if (battery->pdata && battery->pdata->set_charging_enable)
+ battery->pdata->set_charging_enable(enable);
+
+ android_bat_set_charge_time(battery, enable);
+ pr_info("battery: enable=%d charger: %s\n", enable,
+ charge_source_str(battery->charge_source));
+ return 0;
+}
+
+static bool android_bat_charge_timeout(struct android_bat_data *battery,
+ unsigned long timeout)
+{
+ struct timespec cur_time;
+
+ if (!battery->charging_start_time)
+ return 0;
+
+ get_monotonic_boottime(&cur_time);
+ pr_debug("%s: Start time: %ld, End time: %ld, current time: %ld\n",
+ __func__, battery->charging_start_time,
+ battery->charging_start_time + timeout,
+ cur_time.tv_sec);
+ return cur_time.tv_sec >= battery->charging_start_time + timeout;
+}
+
+static void android_bat_charging_timer(struct android_bat_data *battery)
+{
+ if (!battery->charging_start_time &&
+ battery->charging_status == POWER_SUPPLY_STATUS_CHARGING) {
+ android_bat_enable_charging(battery, true);
+ battery->recharging = true;
+ pr_debug("%s: charge status charging but timer is expired\n",
+ __func__);
+ } else if (battery->charging_start_time == 0) {
+ pr_debug("%s: charging_start_time never initialized\n",
+ __func__);
+ return;
+ }
+
+ if (android_bat_charge_timeout(
+ battery,
+ battery->recharging ? battery->pdata->recharging_time :
+ battery->pdata->full_charging_time)) {
+ android_bat_enable_charging(battery, false);
+ if (battery->batt_vcell >
+ battery->pdata->recharging_voltage &&
+ battery->batt_soc == 100)
+ battery->charging_status =
+ POWER_SUPPLY_STATUS_FULL;
+ battery->recharging = false;
+ battery->charging_start_time = 0;
+ pr_info("battery: charging timer expired\n");
+ }
+
+ return;
+}
+
+static void android_bat_charge_source_changed(struct android_bat_callbacks *ptr,
+ int charge_source)
+{
+ struct android_bat_data *battery =
+ container_of(ptr, struct android_bat_data, callbacks);
+
+ wake_lock(&battery->charger_wake_lock);
+ mutex_lock(&android_bat_state_lock);
+ battery->charge_source = charge_source;
+
+ pr_info("battery: charge source type was changed: %s\n",
+ charge_source_str(battery->charge_source));
+
+ mutex_unlock(&android_bat_state_lock);
+ queue_work(battery->monitor_wqueue, &battery->charger_work);
+}
+
+static void android_bat_set_full_status(struct android_bat_callbacks *ptr)
+{
+ struct android_bat_data *battery =
+ container_of(ptr, struct android_bat_data, callbacks);
+
+ mutex_lock(&android_bat_state_lock);
+ pr_info("battery: battery full\n");
+ battery->charging_status = POWER_SUPPLY_STATUS_FULL;
+ android_bat_enable_charging(battery, false);
+ battery->recharging = false;
+ mutex_unlock(&android_bat_state_lock);
+ power_supply_changed(&battery->psy_bat);
+}
+
+static void android_bat_charger_work(struct work_struct *work)
+{
+ struct android_bat_data *battery =
+ container_of(work, struct android_bat_data, charger_work);
+
+ mutex_lock(&android_bat_state_lock);
+
+ switch (battery->charge_source) {
+ case CHARGE_SOURCE_NONE:
+ battery->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ android_bat_enable_charging(battery, false);
+ battery->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ battery->recharging = false;
+ battery->charging_start_time = 0;
+ break;
+ case CHARGE_SOURCE_USB:
+ case CHARGE_SOURCE_AC:
+ /*
+ * If charging status indicates a charger was already
+ * connected prior to this and the status is something
+ * other than charging ("full" or "not-charging"), leave
+ * the status alone.
+ */
+ if (battery->charging_status ==
+ POWER_SUPPLY_STATUS_DISCHARGING ||
+ battery->charging_status == POWER_SUPPLY_STATUS_UNKNOWN)
+ battery->charging_status = POWER_SUPPLY_STATUS_CHARGING;
+
+ /*
+ * Don't re-enable charging if the battery is full and we
+ * are not actively re-charging it, or if "not-charging"
+ * status is set.
+ */
+ if (!((battery->charging_status == POWER_SUPPLY_STATUS_FULL
+ && !battery->recharging) || battery->charging_status ==
+ POWER_SUPPLY_STATUS_NOT_CHARGING))
+ android_bat_enable_charging(battery, true);
+
+ break;
+ default:
+ pr_err("%s: Invalid charger type\n", __func__);
+ break;
+ }
+
+ mutex_unlock(&android_bat_state_lock);
+ wake_lock_timeout(&battery->charger_wake_lock, HZ * 2);
+ power_supply_changed(&battery->psy_ac);
+ power_supply_changed(&battery->psy_usb);
+}
+
+
+static void android_bat_monitor_set_alarm(struct android_bat_data *battery,
+ int seconds)
+{
+ alarm_start(&battery->monitor_alarm,
+ ktime_add(battery->last_poll, ktime_set(seconds, 0)));
+}
+
+static void android_bat_monitor_work(struct work_struct *work)
+{
+ struct android_bat_data *battery =
+ container_of(work, struct android_bat_data, monitor_work);
+ struct timespec cur_time;
+
+ wake_lock(&battery->monitor_wake_lock);
+ android_bat_update_data(battery);
+ mutex_lock(&android_bat_state_lock);
+
+ switch (battery->charging_status) {
+ case POWER_SUPPLY_STATUS_FULL:
+ if (battery->batt_vcell < battery->pdata->recharging_voltage &&
+ !battery->recharging) {
+ battery->recharging = true;
+ android_bat_enable_charging(battery, true);
+ pr_info("battery: start recharging, v=%d\n",
+ battery->batt_vcell/1000);
+ }
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ switch (battery->batt_health) {
+ case POWER_SUPPLY_HEALTH_OVERHEAT:
+ case POWER_SUPPLY_HEALTH_COLD:
+ case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
+ case POWER_SUPPLY_HEALTH_DEAD:
+ case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
+ battery->charging_status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ android_bat_enable_charging(battery, false);
+
+ pr_info("battery: Not charging, health=%d\n",
+ battery->batt_health);
+ break;
+ default:
+ break;
+ }
+ break;
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ if (battery->batt_health == POWER_SUPPLY_HEALTH_GOOD) {
+ pr_info("battery: battery health recovered\n");
+ if (battery->charge_source != CHARGE_SOURCE_NONE) {
+ android_bat_enable_charging(battery, true);
+ battery->charging_status
+ = POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ battery->charging_status
+ = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ }
+ break;
+ default:
+ pr_err("%s: Undefined battery status: %d\n", __func__,
+ battery->charging_status);
+ break;
+ }
+
+ android_bat_charging_timer(battery);
+ get_monotonic_boottime(&cur_time);
+ pr_info("battery: l=%d v=%d c=%d temp=%s%ld.%ld h=%d st=%d%s ct=%lu type=%s\n",
+ battery->batt_soc, battery->batt_vcell/1000,
+ battery->batt_current, battery->batt_temp < 0 ? "-" : "",
+ abs(battery->batt_temp / 10), abs(battery->batt_temp % 10),
+ battery->batt_health, battery->charging_status,
+ battery->recharging ? "r" : "",
+ battery->charging_start_time ?
+ cur_time.tv_sec - battery->charging_start_time : 0,
+ charge_source_str(battery->charge_source));
+ mutex_unlock(&android_bat_state_lock);
+ power_supply_changed(&battery->psy_bat);
+ battery->last_poll = ktime_get_boottime();
+ android_bat_monitor_set_alarm(battery, FAST_POLL);
+ wake_unlock(&battery->monitor_wake_lock);
+ return;
+}
+
+static enum alarmtimer_restart android_bat_monitor_alarm(
+ struct alarm *alarm, ktime_t now)
+{
+ struct android_bat_data *battery =
+ container_of(alarm, struct android_bat_data, monitor_alarm);
+
+ wake_lock(&battery->monitor_wake_lock);
+ queue_work(battery->monitor_wqueue, &battery->monitor_work);
+ return ALARMTIMER_NORESTART;
+}
+
+static int android_power_debug_dump(struct seq_file *s, void *unused)
+{
+ struct android_bat_data *battery = s->private;
+ struct timespec cur_time;
+
+ android_bat_update_data(battery);
+ get_monotonic_boottime(&cur_time);
+ mutex_lock(&android_bat_state_lock);
+ seq_printf(s, "l=%d v=%d c=%d temp=%s%ld.%ld h=%d st=%d%s ct=%lu type=%s\n",
+ battery->batt_soc, battery->batt_vcell/1000,
+ battery->batt_current, battery->batt_temp < 0 ? "-" : "",
+ abs(battery->batt_temp / 10), abs(battery->batt_temp % 10),
+ battery->batt_health, battery->charging_status,
+ battery->recharging ? "r" : "",
+ battery->charging_start_time ?
+ cur_time.tv_sec - battery->charging_start_time : 0,
+ charge_source_str(battery->charge_source));
+ mutex_unlock(&android_bat_state_lock);
+ return 0;
+}
+
+static int android_power_debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, android_power_debug_dump, inode->i_private);
+}
+
+static const struct file_operations android_power_debug_fops = {
+ .open = android_power_debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static __devinit int android_bat_probe(struct platform_device *pdev)
+{
+ struct android_bat_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct android_bat_data *battery;
+ int ret = 0;
+
+ dev_info(&pdev->dev, "Android Battery Driver\n");
+ battery = kzalloc(sizeof(*battery), GFP_KERNEL);
+ if (!battery)
+ return -ENOMEM;
+
+ battery->pdata = pdata;
+ if (!battery->pdata) {
+ pr_err("%s : No platform data\n", __func__);
+ ret = -EINVAL;
+ goto err_pdata;
+ }
+
+ battery->dev = &pdev->dev;
+ platform_set_drvdata(pdev, battery);
+ battery->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+
+ battery->psy_bat.name = "android-battery",
+ battery->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
+ battery->psy_bat.properties = android_battery_props,
+ battery->psy_bat.num_properties = ARRAY_SIZE(android_battery_props),
+ battery->psy_bat.get_property = android_bat_get_property,
+
+ battery->psy_usb.name = "android-usb",
+ battery->psy_usb.type = POWER_SUPPLY_TYPE_USB,
+ battery->psy_usb.supplied_to = supply_list,
+ battery->psy_usb.num_supplicants = ARRAY_SIZE(supply_list),
+ battery->psy_usb.properties = android_power_props,
+ battery->psy_usb.num_properties = ARRAY_SIZE(android_power_props),
+ battery->psy_usb.get_property = android_usb_get_property,
+
+ battery->psy_ac.name = "android-ac",
+ battery->psy_ac.type = POWER_SUPPLY_TYPE_MAINS,
+ battery->psy_ac.supplied_to = supply_list,
+ battery->psy_ac.num_supplicants = ARRAY_SIZE(supply_list),
+ battery->psy_ac.properties = android_power_props,
+ battery->psy_ac.num_properties = ARRAY_SIZE(android_power_props),
+ battery->psy_ac.get_property = android_ac_get_property;
+
+ battery->batt_vcell = -1;
+ battery->batt_soc = -1;
+
+ wake_lock_init(&battery->monitor_wake_lock, WAKE_LOCK_SUSPEND,
+ "android-battery-monitor");
+ wake_lock_init(&battery->charger_wake_lock, WAKE_LOCK_SUSPEND,
+ "android-chargerdetect");
+
+ ret = power_supply_register(&pdev->dev, &battery->psy_bat);
+ if (ret) {
+ dev_err(battery->dev, "%s: failed to register psy_bat\n",
+ __func__);
+ goto err_psy_bat_reg;
+ }
+
+ ret = power_supply_register(&pdev->dev, &battery->psy_usb);
+ if (ret) {
+ dev_err(battery->dev, "%s: failed to register psy_usb\n",
+ __func__);
+ goto err_psy_usb_reg;
+ }
+
+ ret = power_supply_register(&pdev->dev, &battery->psy_ac);
+ if (ret) {
+ dev_err(battery->dev, "%s: failed to register psy_ac\n",
+ __func__);
+ goto err_psy_ac_reg;
+ }
+
+ battery->monitor_wqueue =
+ alloc_workqueue(dev_name(&pdev->dev), WQ_FREEZABLE, 1);
+ if (!battery->monitor_wqueue) {
+ dev_err(battery->dev, "%s: fail to create workqueue\n",
+ __func__);
+ goto err_wq;
+ }
+
+ INIT_WORK(&battery->monitor_work, android_bat_monitor_work);
+ INIT_WORK(&battery->charger_work, android_bat_charger_work);
+
+ battery->callbacks.charge_source_changed =
+ android_bat_charge_source_changed;
+ battery->callbacks.battery_set_full =
+ android_bat_set_full_status;
+ if (battery->pdata && battery->pdata->register_callbacks)
+ battery->pdata->register_callbacks(&battery->callbacks);
+
+ /* get initial charger status */
+ if (battery->pdata->poll_charge_source)
+ battery->charge_source = battery->pdata->poll_charge_source();
+
+ wake_lock(&battery->charger_wake_lock);
+ queue_work(battery->monitor_wqueue, &battery->charger_work);
+
+ wake_lock(&battery->monitor_wake_lock);
+ battery->last_poll = ktime_get_boottime();
+ alarm_init(&battery->monitor_alarm, ALARM_BOOTTIME,
+ android_bat_monitor_alarm);
+ queue_work(battery->monitor_wqueue, &battery->monitor_work);
+
+ battery->debugfs_entry =
+ debugfs_create_file("android-power", S_IRUGO, NULL,
+ battery, &android_power_debug_fops);
+ if (!battery->debugfs_entry)
+ pr_err("failed to create android-power debugfs entry\n");
+
+ return 0;
+
+err_wq:
+ power_supply_unregister(&battery->psy_ac);
+err_psy_ac_reg:
+ power_supply_unregister(&battery->psy_usb);
+err_psy_usb_reg:
+ power_supply_unregister(&battery->psy_bat);
+err_psy_bat_reg:
+ wake_lock_destroy(&battery->monitor_wake_lock);
+ wake_lock_destroy(&battery->charger_wake_lock);
+err_pdata:
+ kfree(battery);
+
+ return ret;
+}
+
+static int __devexit android_bat_remove(struct platform_device *pdev)
+{
+ struct android_bat_data *battery = platform_get_drvdata(pdev);
+
+ alarm_cancel(&battery->monitor_alarm);
+ flush_workqueue(battery->monitor_wqueue);
+ destroy_workqueue(battery->monitor_wqueue);
+ power_supply_unregister(&battery->psy_bat);
+ wake_lock_destroy(&battery->monitor_wake_lock);
+ wake_lock_destroy(&battery->charger_wake_lock);
+ debugfs_remove(battery->debugfs_entry);
+ kfree(battery);
+ return 0;
+}
+
+static int android_bat_suspend(struct device *dev)
+{
+ struct android_bat_data *battery = dev_get_drvdata(dev);
+
+ cancel_work_sync(&battery->monitor_work);
+ android_bat_monitor_set_alarm(
+ battery,
+ battery->charge_source == CHARGE_SOURCE_NONE ?
+ SLOW_POLL : FAST_POLL);
+ return 0;
+}
+
+static void android_bat_resume(struct device *dev)
+{
+ struct android_bat_data *battery = dev_get_drvdata(dev);
+
+ android_bat_monitor_set_alarm(battery, FAST_POLL);
+ return;
+}
+
+static const struct dev_pm_ops android_bat_pm_ops = {
+ .prepare = android_bat_suspend,
+ .complete = android_bat_resume,
+};
+
+static struct platform_driver android_bat_driver = {
+ .driver = {
+ .name = "android-battery",
+ .owner = THIS_MODULE,
+ .pm = &android_bat_pm_ops,
+ },
+ .probe = android_bat_probe,
+ .remove = __devexit_p(android_bat_remove),
+};
+
+static int __init android_bat_init(void)
+{
+ return platform_driver_register(&android_bat_driver);
+}
+
+static void __exit android_bat_exit(void)
+{
+ platform_driver_unregister(&android_bat_driver);
+}
+
+late_initcall(android_bat_init);
+module_exit(android_bat_exit);
+
+MODULE_DESCRIPTION("Android battery driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/apm_power.c b/drivers/power/apm_power.c
new file mode 100644
index 00000000..39763015
--- /dev/null
+++ b/drivers/power/apm_power.c
@@ -0,0 +1,376 @@
+/*
+ * 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/device.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/bq27x00_battery.c b/drivers/power/bq27x00_battery.c
new file mode 100644
index 00000000..222ccd87
--- /dev/null
+++ b/drivers/power/bq27x00_battery.c
@@ -0,0 +1,917 @@
+/*
+ * 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 capacity */
+#define BQ27x00_REG_LMD 0x12 /* Last measured discharge */
+#define BQ27x00_REG_CYCT 0x2A /* Cycle count total */
+#define BQ27x00_REG_AE 0x22 /* Available energy */
+
+#define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */
+#define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */
+#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */
+#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */
+#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */
+#define BQ27000_FLAG_FC BIT(5)
+#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */
+
+#define BQ27500_REG_SOC 0x2C
+#define BQ27500_REG_DCAP 0x3C /* Design capacity */
+#define BQ27500_FLAG_DSC BIT(0)
+#define BQ27500_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */
+#define BQ27500_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */
+#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 energy;
+ int flags;
+};
+
+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_CAPACITY_LEVEL,
+ 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_dbg(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_dbg(di->dev, "error reading charge register %02x: %d\n",
+ reg, charge);
+ 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_dbg(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 Available energy in µWh
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_energy(struct bq27x00_device_info *di)
+{
+ int ae;
+
+ ae = bq27x00_read(di, BQ27x00_REG_AE, false);
+ if (ae < 0) {
+ dev_dbg(di->dev, "error reading available energy\n");
+ return ae;
+ }
+
+ if (di->chip == BQ27500)
+ ae *= 1000;
+ else
+ ae = ae * 29200 / BQ27000_RS;
+
+ return ae;
+}
+
+/*
+ * Return the battery temperature in tenths of degree Celsius
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di)
+{
+ int temp;
+
+ temp = bq27x00_read(di, BQ27x00_REG_TEMP, false);
+ if (temp < 0) {
+ dev_err(di->dev, "error reading temperature\n");
+ return temp;
+ }
+
+ if (di->chip == BQ27500)
+ temp -= 2731;
+ else
+ temp = ((temp * 5) - 5463) / 2;
+
+ return temp;
+}
+
+/*
+ * 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_dbg(di->dev, "error reading time 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) {
+ if (!is_bq27500 && (cache.flags & BQ27000_FLAG_CI)) {
+ dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n");
+ cache.capacity = -ENODATA;
+ cache.energy = -ENODATA;
+ cache.time_to_empty = -ENODATA;
+ cache.time_to_empty_avg = -ENODATA;
+ cache.time_to_full = -ENODATA;
+ cache.charge_full = -ENODATA;
+ } else {
+ cache.capacity = bq27x00_battery_read_rsoc(di);
+ cache.energy = bq27x00_battery_read_energy(di);
+ 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.temperature = bq27x00_battery_read_temperature(di);
+ cache.cycle_count = bq27x00_battery_read_cyct(di);
+
+ /* We only have to read charge design full once */
+ if (di->charge_design_full <= 0)
+ di->charge_design_full = bq27x00_battery_read_ilmd(di);
+ }
+
+ if (memcmp(&di->cache, &cache, sizeof(cache)) != 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 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;
+ int flags;
+
+ curr = bq27x00_read(di, BQ27x00_REG_AI, false);
+ if (curr < 0) {
+ dev_err(di->dev, "error reading current\n");
+ return curr;
+ }
+
+ if (di->chip == BQ27500) {
+ /* bq27500 returns signed value */
+ val->intval = (int)((s16)curr) * 1000;
+ } else {
+ flags = bq27x00_read(di, BQ27x00_REG_FLAGS, false);
+ if (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;
+}
+
+static int bq27x00_battery_capacity_level(struct bq27x00_device_info *di,
+ union power_supply_propval *val)
+{
+ int level;
+
+ if (di->chip == BQ27500) {
+ if (di->cache.flags & BQ27500_FLAG_FC)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (di->cache.flags & BQ27500_FLAG_SOC1)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else if (di->cache.flags & BQ27500_FLAG_SOCF)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ else
+ level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ } else {
+ if (di->cache.flags & BQ27000_FLAG_FC)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (di->cache.flags & BQ27000_FLAG_EDV1)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else if (di->cache.flags & BQ27000_FLAG_EDVF)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ else
+ level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ }
+
+ val->intval = level;
+
+ 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) {
+ dev_err(di->dev, "error reading voltage\n");
+ return volt;
+ }
+
+ val->intval = volt * 1000;
+
+ 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_CAPACITY_LEVEL:
+ ret = bq27x00_battery_capacity_level(di, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = bq27x00_simple_value(di->cache.temperature, 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_simple_value(di->cache.energy, 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)
+{
+ /*
+ * power_supply_unregister call bq27x00_battery_get_property which
+ * call bq27x00_battery_poll.
+ * Make sure that bq27x00_battery_poll will not call
+ * schedule_delayed_work again after unregister (which cause OOPS).
+ */
+ poll_interval = 0;
+
+ 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/charger-manager.c b/drivers/power/charger-manager.c
new file mode 100644
index 00000000..9eca9f1f
--- /dev/null
+++ b/drivers/power/charger-manager.c
@@ -0,0 +1,1063 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * This driver enables to monitor battery health and control charger
+ * during suspend-to-mem.
+ * Charger manager depends on other devices. register this later than
+ * the depending devices.
+ *
+ * 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/io.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/power/charger-manager.h>
+#include <linux/regulator/consumer.h>
+
+/*
+ * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
+ * delayed works so that we can run delayed works with CM_JIFFIES_SMALL
+ * without any delays.
+ */
+#define CM_JIFFIES_SMALL (2)
+
+/* If y is valid (> 0) and smaller than x, do x = y */
+#define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x))
+
+/*
+ * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking
+ * rtc alarm. It should be 2 or larger
+ */
+#define CM_RTC_SMALL (2)
+
+#define UEVENT_BUF_SIZE 32
+
+static LIST_HEAD(cm_list);
+static DEFINE_MUTEX(cm_list_mtx);
+
+/* About in-suspend (suspend-again) monitoring */
+static struct rtc_device *rtc_dev;
+/*
+ * Backup RTC alarm
+ * Save the wakeup alarm before entering suspend-to-RAM
+ */
+static struct rtc_wkalrm rtc_wkalarm_save;
+/* Backup RTC alarm time in terms of seconds since 01-01-1970 00:00:00 */
+static unsigned long rtc_wkalarm_save_time;
+static bool cm_suspended;
+static bool cm_rtc_set;
+static unsigned long cm_suspend_duration_ms;
+
+/* Global charger-manager description */
+static struct charger_global_desc *g_desc; /* init with setup_charger_manager */
+
+/**
+ * is_batt_present - See if the battery presents in place.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_batt_present(struct charger_manager *cm)
+{
+ union power_supply_propval val;
+ bool present = false;
+ int i, ret;
+
+ switch (cm->desc->battery_present) {
+ case CM_FUEL_GAUGE:
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_PRESENT, &val);
+ if (ret == 0 && val.intval)
+ present = true;
+ break;
+ case CM_CHARGER_STAT:
+ for (i = 0; cm->charger_stat[i]; i++) {
+ ret = cm->charger_stat[i]->get_property(
+ cm->charger_stat[i],
+ POWER_SUPPLY_PROP_PRESENT, &val);
+ if (ret == 0 && val.intval) {
+ present = true;
+ break;
+ }
+ }
+ break;
+ }
+
+ return present;
+}
+
+/**
+ * is_ext_pwr_online - See if an external power source is attached to charge
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Returns true if at least one of the chargers of the battery has an external
+ * power source attached to charge the battery regardless of whether it is
+ * actually charging or not.
+ */
+static bool is_ext_pwr_online(struct charger_manager *cm)
+{
+ union power_supply_propval val;
+ bool online = false;
+ int i, ret;
+
+ /* If at least one of them has one, it's yes. */
+ for (i = 0; cm->charger_stat[i]; i++) {
+ ret = cm->charger_stat[i]->get_property(
+ cm->charger_stat[i],
+ POWER_SUPPLY_PROP_ONLINE, &val);
+ if (ret == 0 && val.intval) {
+ online = true;
+ break;
+ }
+ }
+
+ return online;
+}
+
+/**
+ * get_batt_uV - Get the voltage level of the battery
+ * @cm: the Charger Manager representing the battery.
+ * @uV: the voltage level returned.
+ *
+ * Returns 0 if there is no error.
+ * Returns a negative value on error.
+ */
+static int get_batt_uV(struct charger_manager *cm, int *uV)
+{
+ union power_supply_propval val;
+ int ret;
+
+ if (!cm->fuel_gauge)
+ return -ENODEV;
+
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
+ if (ret)
+ return ret;
+
+ *uV = val.intval;
+ return 0;
+}
+
+/**
+ * is_charging - Returns true if the battery is being charged.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_charging(struct charger_manager *cm)
+{
+ int i, ret;
+ bool charging = false;
+ union power_supply_propval val;
+
+ /* If there is no battery, it cannot be charged */
+ if (!is_batt_present(cm))
+ return false;
+
+ /* If at least one of the charger is charging, return yes */
+ for (i = 0; cm->charger_stat[i]; i++) {
+ /* 1. The charger sholuld not be DISABLED */
+ if (cm->emergency_stop)
+ continue;
+ if (!cm->charger_enabled)
+ continue;
+
+ /* 2. The charger should be online (ext-power) */
+ ret = cm->charger_stat[i]->get_property(
+ cm->charger_stat[i],
+ POWER_SUPPLY_PROP_ONLINE, &val);
+ if (ret) {
+ dev_warn(cm->dev, "Cannot read ONLINE value from %s.\n",
+ cm->desc->psy_charger_stat[i]);
+ continue;
+ }
+ if (val.intval == 0)
+ continue;
+
+ /*
+ * 3. The charger should not be FULL, DISCHARGING,
+ * or NOT_CHARGING.
+ */
+ ret = cm->charger_stat[i]->get_property(
+ cm->charger_stat[i],
+ POWER_SUPPLY_PROP_STATUS, &val);
+ if (ret) {
+ dev_warn(cm->dev, "Cannot read STATUS value from %s.\n",
+ cm->desc->psy_charger_stat[i]);
+ continue;
+ }
+ if (val.intval == POWER_SUPPLY_STATUS_FULL ||
+ val.intval == POWER_SUPPLY_STATUS_DISCHARGING ||
+ val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING)
+ continue;
+
+ /* Then, this is charging. */
+ charging = true;
+ break;
+ }
+
+ return charging;
+}
+
+/**
+ * is_polling_required - Return true if need to continue polling for this CM.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_polling_required(struct charger_manager *cm)
+{
+ switch (cm->desc->polling_mode) {
+ case CM_POLL_DISABLE:
+ return false;
+ case CM_POLL_ALWAYS:
+ return true;
+ case CM_POLL_EXTERNAL_POWER_ONLY:
+ return is_ext_pwr_online(cm);
+ case CM_POLL_CHARGING_ONLY:
+ return is_charging(cm);
+ default:
+ dev_warn(cm->dev, "Incorrect polling_mode (%d)\n",
+ cm->desc->polling_mode);
+ }
+
+ return false;
+}
+
+/**
+ * try_charger_enable - Enable/Disable chargers altogether
+ * @cm: the Charger Manager representing the battery.
+ * @enable: true: enable / false: disable
+ *
+ * Note that Charger Manager keeps the charger enabled regardless whether
+ * the charger is charging or not (because battery is full or no external
+ * power source exists) except when CM needs to disable chargers forcibly
+ * bacause of emergency causes; when the battery is overheated or too cold.
+ */
+static int try_charger_enable(struct charger_manager *cm, bool enable)
+{
+ int err = 0, i;
+ struct charger_desc *desc = cm->desc;
+
+ /* Ignore if it's redundent command */
+ if (enable == cm->charger_enabled)
+ return 0;
+
+ if (enable) {
+ if (cm->emergency_stop)
+ return -EAGAIN;
+ err = regulator_bulk_enable(desc->num_charger_regulators,
+ desc->charger_regulators);
+ } else {
+ /*
+ * Abnormal battery state - Stop charging forcibly,
+ * even if charger was enabled at the other places
+ */
+ err = regulator_bulk_disable(desc->num_charger_regulators,
+ desc->charger_regulators);
+
+ for (i = 0; i < desc->num_charger_regulators; i++) {
+ if (regulator_is_enabled(
+ desc->charger_regulators[i].consumer)) {
+ regulator_force_disable(
+ desc->charger_regulators[i].consumer);
+ dev_warn(cm->dev,
+ "Disable regulator(%s) forcibly.\n",
+ desc->charger_regulators[i].supply);
+ }
+ }
+ }
+
+ if (!err)
+ cm->charger_enabled = enable;
+
+ return err;
+}
+
+/**
+ * uevent_notify - Let users know something has changed.
+ * @cm: the Charger Manager representing the battery.
+ * @event: the event string.
+ *
+ * If @event is null, it implies that uevent_notify is called
+ * by resume function. When called in the resume function, cm_suspended
+ * should be already reset to false in order to let uevent_notify
+ * notify the recent event during the suspend to users. While
+ * suspended, uevent_notify does not notify users, but tracks
+ * events so that uevent_notify can notify users later after resumed.
+ */
+static void uevent_notify(struct charger_manager *cm, const char *event)
+{
+ static char env_str[UEVENT_BUF_SIZE + 1] = "";
+ static char env_str_save[UEVENT_BUF_SIZE + 1] = "";
+
+ if (cm_suspended) {
+ /* Nothing in suspended-event buffer */
+ if (env_str_save[0] == 0) {
+ if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
+ return; /* status not changed */
+ strncpy(env_str_save, event, UEVENT_BUF_SIZE);
+ return;
+ }
+
+ if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE))
+ return; /* Duplicated. */
+ strncpy(env_str_save, event, UEVENT_BUF_SIZE);
+ return;
+ }
+
+ if (event == NULL) {
+ /* No messages pending */
+ if (!env_str_save[0])
+ return;
+
+ strncpy(env_str, env_str_save, UEVENT_BUF_SIZE);
+ kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
+ env_str_save[0] = 0;
+
+ return;
+ }
+
+ /* status not changed */
+ if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
+ return;
+
+ /* save the status and notify the update */
+ strncpy(env_str, event, UEVENT_BUF_SIZE);
+ kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
+
+ dev_info(cm->dev, event);
+}
+
+/**
+ * _cm_monitor - Monitor the temperature and return true for exceptions.
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Returns true if there is an event to notify for the battery.
+ * (True if the status of "emergency_stop" changes)
+ */
+static bool _cm_monitor(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ int temp = desc->temperature_out_of_range(&cm->last_temp_mC);
+
+ dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n",
+ cm->last_temp_mC / 1000, cm->last_temp_mC % 1000);
+
+ /* It has been stopped or charging already */
+ if (!!temp == !!cm->emergency_stop)
+ return false;
+
+ if (temp) {
+ cm->emergency_stop = temp;
+ if (!try_charger_enable(cm, false)) {
+ if (temp > 0)
+ uevent_notify(cm, "OVERHEAT");
+ else
+ uevent_notify(cm, "COLD");
+ }
+ } else {
+ cm->emergency_stop = 0;
+ if (!try_charger_enable(cm, true))
+ uevent_notify(cm, "CHARGING");
+ }
+
+ return true;
+}
+
+/**
+ * cm_monitor - Monitor every battery.
+ *
+ * Returns true if there is an event to notify from any of the batteries.
+ * (True if the status of "emergency_stop" changes)
+ */
+static bool cm_monitor(void)
+{
+ bool stop = false;
+ struct charger_manager *cm;
+
+ mutex_lock(&cm_list_mtx);
+
+ list_for_each_entry(cm, &cm_list, entry) {
+ if (_cm_monitor(cm))
+ stop = true;
+ }
+
+ mutex_unlock(&cm_list_mtx);
+
+ return stop;
+}
+
+static int charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct charger_manager *cm = container_of(psy,
+ struct charger_manager, charger_psy);
+ struct charger_desc *desc = cm->desc;
+ int ret = 0;
+ int uV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (is_charging(cm))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (is_ext_pwr_online(cm))
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (cm->emergency_stop > 0)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (cm->emergency_stop < 0)
+ val->intval = POWER_SUPPLY_HEALTH_COLD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (is_batt_present(cm))
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = get_batt_uV(cm, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CURRENT_NOW, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ /* in thenth of centigrade */
+ if (cm->last_temp_mC == INT_MIN)
+ desc->temperature_out_of_range(&cm->last_temp_mC);
+ val->intval = cm->last_temp_mC / 100;
+ if (!desc->measure_battery_temp)
+ ret = -ENODEV;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+ /* in thenth of centigrade */
+ if (cm->last_temp_mC == INT_MIN)
+ desc->temperature_out_of_range(&cm->last_temp_mC);
+ val->intval = cm->last_temp_mC / 100;
+ if (desc->measure_battery_temp)
+ ret = -ENODEV;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (!cm->fuel_gauge) {
+ ret = -ENODEV;
+ break;
+ }
+
+ if (!is_batt_present(cm)) {
+ /* There is no battery. Assume 100% */
+ val->intval = 100;
+ break;
+ }
+
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CAPACITY, val);
+ if (ret)
+ break;
+
+ if (val->intval > 100) {
+ val->intval = 100;
+ break;
+ }
+ if (val->intval < 0)
+ val->intval = 0;
+
+ /* Do not adjust SOC when charging: voltage is overrated */
+ if (is_charging(cm))
+ break;
+
+ /*
+ * If the capacity value is inconsistent, calibrate it base on
+ * the battery voltage values and the thresholds given as desc
+ */
+ ret = get_batt_uV(cm, &uV);
+ if (ret) {
+ /* Voltage information not available. No calibration */
+ ret = 0;
+ break;
+ }
+
+ if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV &&
+ !is_charging(cm)) {
+ val->intval = 100;
+ break;
+ }
+
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (is_ext_pwr_online(cm))
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ if (cm->fuel_gauge) {
+ if (cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CHARGE_FULL, val) == 0)
+ break;
+ }
+
+ if (is_ext_pwr_online(cm)) {
+ /* Not full if it's charging. */
+ if (is_charging(cm)) {
+ val->intval = 0;
+ break;
+ }
+ /*
+ * Full if it's powered but not charging andi
+ * not forced stop by emergency
+ */
+ if (!cm->emergency_stop) {
+ val->intval = 1;
+ break;
+ }
+ }
+
+ /* Full if it's over the fullbatt voltage */
+ ret = get_batt_uV(cm, &uV);
+ if (!ret && desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV &&
+ !is_charging(cm)) {
+ val->intval = 1;
+ break;
+ }
+
+ /* Full if the cap is 100 */
+ if (cm->fuel_gauge) {
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CAPACITY, val);
+ if (!ret && val->intval >= 100 && !is_charging(cm)) {
+ val->intval = 1;
+ break;
+ }
+ }
+
+ val->intval = 0;
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ if (is_charging(cm)) {
+ ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ val);
+ if (ret) {
+ val->intval = 1;
+ ret = 0;
+ } else {
+ /* If CHARGE_NOW is supplied, use it */
+ val->intval = (val->intval > 0) ?
+ val->intval : 1;
+ }
+ } else {
+ val->intval = 0;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+#define NUM_CHARGER_PSY_OPTIONAL (4)
+static enum power_supply_property default_charger_props[] = {
+ /* Guaranteed to provide */
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ /*
+ * Optional properties are:
+ * POWER_SUPPLY_PROP_CHARGE_NOW,
+ * POWER_SUPPLY_PROP_CURRENT_NOW,
+ * POWER_SUPPLY_PROP_TEMP, and
+ * POWER_SUPPLY_PROP_TEMP_AMBIENT,
+ */
+};
+
+static struct power_supply psy_default = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = default_charger_props,
+ .num_properties = ARRAY_SIZE(default_charger_props),
+ .get_property = charger_get_property,
+};
+
+/**
+ * cm_setup_timer - For in-suspend monitoring setup wakeup alarm
+ * for suspend_again.
+ *
+ * Returns true if the alarm is set for Charger Manager to use.
+ * Returns false if
+ * cm_setup_timer fails to set an alarm,
+ * cm_setup_timer does not need to set an alarm for Charger Manager,
+ * or an alarm previously configured is to be used.
+ */
+static bool cm_setup_timer(void)
+{
+ struct charger_manager *cm;
+ unsigned int wakeup_ms = UINT_MAX;
+ bool ret = false;
+
+ mutex_lock(&cm_list_mtx);
+
+ list_for_each_entry(cm, &cm_list, entry) {
+ /* Skip if polling is not required for this CM */
+ if (!is_polling_required(cm) && !cm->emergency_stop)
+ continue;
+ if (cm->desc->polling_interval_ms == 0)
+ continue;
+ CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms);
+ }
+
+ mutex_unlock(&cm_list_mtx);
+
+ if (wakeup_ms < UINT_MAX && wakeup_ms > 0) {
+ pr_info("Charger Manager wakeup timer: %u ms.\n", wakeup_ms);
+ if (rtc_dev) {
+ struct rtc_wkalrm tmp;
+ unsigned long time, now;
+ unsigned long add = DIV_ROUND_UP(wakeup_ms, 1000);
+
+ /*
+ * Set alarm with the polling interval (wakeup_ms)
+ * except when rtc_wkalarm_save comes first.
+ * However, the alarm time should be NOW +
+ * CM_RTC_SMALL or later.
+ */
+ tmp.enabled = 1;
+ rtc_read_time(rtc_dev, &tmp.time);
+ rtc_tm_to_time(&tmp.time, &now);
+ if (add < CM_RTC_SMALL)
+ add = CM_RTC_SMALL;
+ time = now + add;
+
+ ret = true;
+
+ if (rtc_wkalarm_save.enabled &&
+ rtc_wkalarm_save_time &&
+ rtc_wkalarm_save_time < time) {
+ if (rtc_wkalarm_save_time < now + CM_RTC_SMALL)
+ time = now + CM_RTC_SMALL;
+ else
+ time = rtc_wkalarm_save_time;
+
+ /* The timer is not appointed by CM */
+ ret = false;
+ }
+
+ pr_info("Waking up after %lu secs.\n",
+ time - now);
+
+ rtc_time_to_tm(time, &tmp.time);
+ rtc_set_alarm(rtc_dev, &tmp);
+ cm_suspend_duration_ms += wakeup_ms;
+ return ret;
+ }
+ }
+
+ if (rtc_dev)
+ rtc_set_alarm(rtc_dev, &rtc_wkalarm_save);
+ return false;
+}
+
+/**
+ * cm_suspend_again - Determine whether suspend again or not
+ *
+ * Returns true if the system should be suspended again
+ * Returns false if the system should be woken up
+ */
+bool cm_suspend_again(void)
+{
+ struct charger_manager *cm;
+ bool ret = false;
+
+ if (!g_desc || !g_desc->rtc_only_wakeup || !g_desc->rtc_only_wakeup() ||
+ !cm_rtc_set)
+ return false;
+
+ if (cm_monitor())
+ goto out;
+
+ ret = true;
+ mutex_lock(&cm_list_mtx);
+ list_for_each_entry(cm, &cm_list, entry) {
+ if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) ||
+ cm->status_save_batt != is_batt_present(cm)) {
+ ret = false;
+ break;
+ }
+ }
+ mutex_unlock(&cm_list_mtx);
+
+ cm_rtc_set = cm_setup_timer();
+out:
+ /* It's about the time when the non-CM appointed timer goes off */
+ if (rtc_wkalarm_save.enabled) {
+ unsigned long now;
+ struct rtc_time tmp;
+
+ rtc_read_time(rtc_dev, &tmp);
+ rtc_tm_to_time(&tmp, &now);
+
+ if (rtc_wkalarm_save_time &&
+ now + CM_RTC_SMALL >= rtc_wkalarm_save_time)
+ return false;
+ }
+ return ret;
+}
+EXPORT_SYMBOL_GPL(cm_suspend_again);
+
+/**
+ * setup_charger_manager - initialize charger_global_desc data
+ * @gd: pointer to instance of charger_global_desc
+ */
+int setup_charger_manager(struct charger_global_desc *gd)
+{
+ if (!gd)
+ return -EINVAL;
+
+ if (rtc_dev)
+ rtc_class_close(rtc_dev);
+ rtc_dev = NULL;
+ g_desc = NULL;
+
+ if (!gd->rtc_only_wakeup) {
+ pr_err("The callback rtc_only_wakeup is not given.\n");
+ return -EINVAL;
+ }
+
+ if (gd->rtc_name) {
+ rtc_dev = rtc_class_open(gd->rtc_name);
+ if (IS_ERR_OR_NULL(rtc_dev)) {
+ rtc_dev = NULL;
+ /* Retry at probe. RTC may be not registered yet */
+ }
+ } else {
+ pr_warn("No wakeup timer is given for charger manager."
+ "In-suspend monitoring won't work.\n");
+ }
+
+ g_desc = gd;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(setup_charger_manager);
+
+static int charger_manager_probe(struct platform_device *pdev)
+{
+ struct charger_desc *desc = dev_get_platdata(&pdev->dev);
+ struct charger_manager *cm;
+ int ret = 0, i = 0;
+ union power_supply_propval val;
+
+ if (g_desc && !rtc_dev && g_desc->rtc_name) {
+ rtc_dev = rtc_class_open(g_desc->rtc_name);
+ if (IS_ERR_OR_NULL(rtc_dev)) {
+ rtc_dev = NULL;
+ dev_err(&pdev->dev, "Cannot get RTC %s.\n",
+ g_desc->rtc_name);
+ ret = -ENODEV;
+ goto err_alloc;
+ }
+ }
+
+ if (!desc) {
+ dev_err(&pdev->dev, "No platform data (desc) found.\n");
+ ret = -ENODEV;
+ goto err_alloc;
+ }
+
+ cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL);
+ if (!cm) {
+ dev_err(&pdev->dev, "Cannot allocate memory.\n");
+ ret = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Basic Values. Unspecified are Null or 0 */
+ cm->dev = &pdev->dev;
+ cm->desc = kzalloc(sizeof(struct charger_desc), GFP_KERNEL);
+ if (!cm->desc) {
+ dev_err(&pdev->dev, "Cannot allocate memory.\n");
+ ret = -ENOMEM;
+ goto err_alloc_desc;
+ }
+ memcpy(cm->desc, desc, sizeof(struct charger_desc));
+ cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */
+
+ if (!desc->charger_regulators || desc->num_charger_regulators < 1) {
+ ret = -EINVAL;
+ dev_err(&pdev->dev, "charger_regulators undefined.\n");
+ goto err_no_charger;
+ }
+
+ if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) {
+ dev_err(&pdev->dev, "No power supply defined.\n");
+ ret = -EINVAL;
+ goto err_no_charger_stat;
+ }
+
+ /* Counting index only */
+ while (desc->psy_charger_stat[i])
+ i++;
+
+ cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1),
+ GFP_KERNEL);
+ if (!cm->charger_stat) {
+ ret = -ENOMEM;
+ goto err_no_charger_stat;
+ }
+
+ for (i = 0; desc->psy_charger_stat[i]; i++) {
+ cm->charger_stat[i] = power_supply_get_by_name(
+ desc->psy_charger_stat[i]);
+ if (!cm->charger_stat[i]) {
+ dev_err(&pdev->dev, "Cannot find power supply "
+ "\"%s\"\n",
+ desc->psy_charger_stat[i]);
+ ret = -ENODEV;
+ goto err_chg_stat;
+ }
+ }
+
+ cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
+ if (!cm->fuel_gauge) {
+ dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
+ desc->psy_fuel_gauge);
+ ret = -ENODEV;
+ goto err_chg_stat;
+ }
+
+ if (desc->polling_interval_ms == 0 ||
+ msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) {
+ dev_err(&pdev->dev, "polling_interval_ms is too small\n");
+ ret = -EINVAL;
+ goto err_chg_stat;
+ }
+
+ if (!desc->temperature_out_of_range) {
+ dev_err(&pdev->dev, "there is no temperature_out_of_range\n");
+ ret = -EINVAL;
+ goto err_chg_stat;
+ }
+
+ platform_set_drvdata(pdev, cm);
+
+ memcpy(&cm->charger_psy, &psy_default, sizeof(psy_default));
+
+ if (!desc->psy_name) {
+ strncpy(cm->psy_name_buf, psy_default.name, PSY_NAME_MAX);
+ } else {
+ strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX);
+ }
+ cm->charger_psy.name = cm->psy_name_buf;
+
+ /* Allocate for psy properties because they may vary */
+ cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property)
+ * (ARRAY_SIZE(default_charger_props) +
+ NUM_CHARGER_PSY_OPTIONAL),
+ GFP_KERNEL);
+ if (!cm->charger_psy.properties) {
+ dev_err(&pdev->dev, "Cannot allocate for psy properties.\n");
+ ret = -ENOMEM;
+ goto err_chg_stat;
+ }
+ memcpy(cm->charger_psy.properties, default_charger_props,
+ sizeof(enum power_supply_property) *
+ ARRAY_SIZE(default_charger_props));
+ cm->charger_psy.num_properties = psy_default.num_properties;
+
+ /* Find which optional psy-properties are available */
+ if (!cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CHARGE_NOW, &val)) {
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_CHARGE_NOW;
+ cm->charger_psy.num_properties++;
+ }
+ if (!cm->fuel_gauge->get_property(cm->fuel_gauge,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val)) {
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_CURRENT_NOW;
+ cm->charger_psy.num_properties++;
+ }
+
+ if (desc->measure_battery_temp) {
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_TEMP;
+ cm->charger_psy.num_properties++;
+ } else {
+ cm->charger_psy.properties[cm->charger_psy.num_properties] =
+ POWER_SUPPLY_PROP_TEMP_AMBIENT;
+ cm->charger_psy.num_properties++;
+ }
+
+ ret = power_supply_register(NULL, &cm->charger_psy);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot register charger-manager with"
+ " name \"%s\".\n", cm->charger_psy.name);
+ goto err_register;
+ }
+
+ ret = regulator_bulk_get(&pdev->dev, desc->num_charger_regulators,
+ desc->charger_regulators);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot get charger regulators.\n");
+ goto err_bulk_get;
+ }
+
+ ret = try_charger_enable(cm, true);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot enable charger regulators\n");
+ goto err_chg_enable;
+ }
+
+ /* Add to the list */
+ mutex_lock(&cm_list_mtx);
+ list_add(&cm->entry, &cm_list);
+ mutex_unlock(&cm_list_mtx);
+
+ return 0;
+
+err_chg_enable:
+ regulator_bulk_free(desc->num_charger_regulators,
+ desc->charger_regulators);
+err_bulk_get:
+ power_supply_unregister(&cm->charger_psy);
+err_register:
+ kfree(cm->charger_psy.properties);
+err_chg_stat:
+ kfree(cm->charger_stat);
+err_no_charger_stat:
+err_no_charger:
+ kfree(cm->desc);
+err_alloc_desc:
+ kfree(cm);
+err_alloc:
+ return ret;
+}
+
+static int __devexit charger_manager_remove(struct platform_device *pdev)
+{
+ struct charger_manager *cm = platform_get_drvdata(pdev);
+ struct charger_desc *desc = cm->desc;
+
+ /* Remove from the list */
+ mutex_lock(&cm_list_mtx);
+ list_del(&cm->entry);
+ mutex_unlock(&cm_list_mtx);
+
+ regulator_bulk_free(desc->num_charger_regulators,
+ desc->charger_regulators);
+ power_supply_unregister(&cm->charger_psy);
+ kfree(cm->charger_psy.properties);
+ kfree(cm->charger_stat);
+ kfree(cm->desc);
+ kfree(cm);
+
+ return 0;
+}
+
+static const struct platform_device_id charger_manager_id[] = {
+ { "charger-manager", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, charger_manager_id);
+
+static int cm_suspend_prepare(struct device *dev)
+{
+ struct charger_manager *cm = dev_get_drvdata(dev);
+
+ if (!cm_suspended) {
+ if (rtc_dev) {
+ struct rtc_time tmp;
+ unsigned long now;
+
+ rtc_read_alarm(rtc_dev, &rtc_wkalarm_save);
+ rtc_read_time(rtc_dev, &tmp);
+
+ if (rtc_wkalarm_save.enabled) {
+ rtc_tm_to_time(&rtc_wkalarm_save.time,
+ &rtc_wkalarm_save_time);
+ rtc_tm_to_time(&tmp, &now);
+ if (now > rtc_wkalarm_save_time)
+ rtc_wkalarm_save_time = 0;
+ } else {
+ rtc_wkalarm_save_time = 0;
+ }
+ }
+ cm_suspended = true;
+ }
+
+ cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm);
+ cm->status_save_batt = is_batt_present(cm);
+
+ if (!cm_rtc_set) {
+ cm_suspend_duration_ms = 0;
+ cm_rtc_set = cm_setup_timer();
+ }
+
+ return 0;
+}
+
+static void cm_suspend_complete(struct device *dev)
+{
+ struct charger_manager *cm = dev_get_drvdata(dev);
+
+ if (cm_suspended) {
+ if (rtc_dev) {
+ struct rtc_wkalrm tmp;
+
+ rtc_read_alarm(rtc_dev, &tmp);
+ rtc_wkalarm_save.pending = tmp.pending;
+ rtc_set_alarm(rtc_dev, &rtc_wkalarm_save);
+ }
+ cm_suspended = false;
+ cm_rtc_set = false;
+ }
+
+ uevent_notify(cm, NULL);
+}
+
+static const struct dev_pm_ops charger_manager_pm = {
+ .prepare = cm_suspend_prepare,
+ .complete = cm_suspend_complete,
+};
+
+static struct platform_driver charger_manager_driver = {
+ .driver = {
+ .name = "charger-manager",
+ .owner = THIS_MODULE,
+ .pm = &charger_manager_pm,
+ },
+ .probe = charger_manager_probe,
+ .remove = __devexit_p(charger_manager_remove),
+ .id_table = charger_manager_id,
+};
+
+static int __init charger_manager_init(void)
+{
+ return platform_driver_register(&charger_manager_driver);
+}
+late_initcall(charger_manager_init);
+
+static void __exit charger_manager_cleanup(void)
+{
+ platform_driver_unregister(&charger_manager_driver);
+}
+module_exit(charger_manager_cleanup);
+
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_DESCRIPTION("Charger Manager");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/collie_battery.c b/drivers/power/collie_battery.c
new file mode 100644
index 00000000..74c6b23a
--- /dev/null
+++ b/drivers/power/collie_battery.c
@@ -0,0 +1,390 @@
+/*
+ * 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\n");
+ 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 gpio collie_batt_gpios[] = {
+ { COLLIE_GPIO_CO, GPIOF_IN, "main battery full" },
+ { COLLIE_GPIO_MAIN_BAT_LOW, GPIOF_IN, "main battery low" },
+ { COLLIE_GPIO_CHARGE_ON, GPIOF_OUT_INIT_LOW, "main charge on" },
+ { COLLIE_GPIO_MBAT_ON, GPIOF_OUT_INIT_LOW, "main battery" },
+ { COLLIE_GPIO_TMP_ON, GPIOF_OUT_INIT_LOW, "main battery temp" },
+ { COLLIE_GPIO_BBAT_ON, GPIOF_OUT_INIT_LOW, "backup battery" },
+};
+
+#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;
+
+ if (!machine_is_collie())
+ return -ENODEV;
+
+ ucb = dev->ucb;
+
+ ret = gpio_request_array(collie_batt_gpios,
+ ARRAY_SIZE(collie_batt_gpios));
+ if (ret)
+ return ret;
+
+ 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);
+ gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
+ return ret;
+}
+
+static void __devexit collie_bat_remove(struct ucb1x00_dev *dev)
+{
+ 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);
+ gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
+}
+
+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..3fd3e95d
--- /dev/null
+++ b/drivers/power/da9030_battery.c
@@ -0,0 +1,595 @@
+/*
+ * 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,
+};
+
+module_platform_driver(da903x_battery_driver);
+
+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..a5f6a0ec
--- /dev/null
+++ b/drivers/power/da9052-battery.c
@@ -0,0 +1,655 @@
+/*
+ * Batttery Driver for Dialog DA9052 PMICs
+ *
+ * Copyright(c) 2011 Dialog Semiconductor Ltd.
+ *
+ * Author: David Dajun Chen <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/delay.h>
+#include <linux/freezer.h>
+#include <linux/fs.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include <linux/mfd/da9052/da9052.h>
+#include <linux/mfd/da9052/pdata.h>
+#include <linux/mfd/da9052/reg.h>
+
+/* STATIC CONFIGURATION */
+#define DA9052_BAT_CUTOFF_VOLT 2800
+#define DA9052_BAT_TSH 62000
+#define DA9052_BAT_LOW_CAP 4
+#define DA9052_AVG_SZ 4
+#define DA9052_VC_TBL_SZ 68
+#define DA9052_VC_TBL_REF_SZ 3
+
+#define DA9052_ISET_USB_MASK 0x0F
+#define DA9052_CHG_USB_ILIM_MASK 0x40
+#define DA9052_CHG_LIM_COLS 16
+
+#define DA9052_MEAN(x, y) ((x + y) / 2)
+
+enum charger_type_enum {
+ DA9052_NOCHARGER = 1,
+ DA9052_CHARGER,
+};
+
+static const u16 da9052_chg_current_lim[2][DA9052_CHG_LIM_COLS] = {
+ {70, 80, 90, 100, 110, 120, 400, 450,
+ 500, 550, 600, 650, 700, 900, 1100, 1300},
+ {80, 90, 100, 110, 120, 400, 450, 500,
+ 550, 600, 800, 1000, 1200, 1400, 1600, 1800},
+};
+
+static const u16 vc_tbl_ref[3] = {10, 25, 40};
+/* Lookup table for voltage vs capacity */
+static u32 const vc_tbl[3][68][2] = {
+ /* For temperature 10 degree Celsius */
+ {
+ {4082, 100}, {4036, 98},
+ {4020, 96}, {4008, 95},
+ {3997, 93}, {3983, 91},
+ {3964, 90}, {3943, 88},
+ {3926, 87}, {3912, 85},
+ {3900, 84}, {3890, 82},
+ {3881, 80}, {3873, 79},
+ {3865, 77}, {3857, 76},
+ {3848, 74}, {3839, 73},
+ {3829, 71}, {3820, 70},
+ {3811, 68}, {3802, 67},
+ {3794, 65}, {3785, 64},
+ {3778, 62}, {3770, 61},
+ {3763, 59}, {3756, 58},
+ {3750, 56}, {3744, 55},
+ {3738, 53}, {3732, 52},
+ {3727, 50}, {3722, 49},
+ {3717, 47}, {3712, 46},
+ {3708, 44}, {3703, 43},
+ {3700, 41}, {3696, 40},
+ {3693, 38}, {3691, 37},
+ {3688, 35}, {3686, 34},
+ {3683, 32}, {3681, 31},
+ {3678, 29}, {3675, 28},
+ {3672, 26}, {3669, 25},
+ {3665, 23}, {3661, 22},
+ {3656, 21}, {3651, 19},
+ {3645, 18}, {3639, 16},
+ {3631, 15}, {3622, 13},
+ {3611, 12}, {3600, 10},
+ {3587, 9}, {3572, 7},
+ {3548, 6}, {3503, 5},
+ {3420, 3}, {3268, 2},
+ {2992, 1}, {2746, 0}
+ },
+ /* For temperature 25 degree Celsius */
+ {
+ {4102, 100}, {4065, 98},
+ {4048, 96}, {4034, 95},
+ {4021, 93}, {4011, 92},
+ {4001, 90}, {3986, 88},
+ {3968, 87}, {3952, 85},
+ {3938, 84}, {3926, 82},
+ {3916, 81}, {3908, 79},
+ {3900, 77}, {3892, 76},
+ {3883, 74}, {3874, 73},
+ {3864, 71}, {3855, 70},
+ {3846, 68}, {3836, 67},
+ {3827, 65}, {3819, 64},
+ {3810, 62}, {3801, 61},
+ {3793, 59}, {3786, 58},
+ {3778, 56}, {3772, 55},
+ {3765, 53}, {3759, 52},
+ {3754, 50}, {3748, 49},
+ {3743, 47}, {3738, 46},
+ {3733, 44}, {3728, 43},
+ {3724, 41}, {3720, 40},
+ {3716, 38}, {3712, 37},
+ {3709, 35}, {3706, 34},
+ {3703, 33}, {3701, 31},
+ {3698, 30}, {3696, 28},
+ {3693, 27}, {3690, 25},
+ {3687, 24}, {3683, 22},
+ {3680, 21}, {3675, 19},
+ {3671, 18}, {3666, 17},
+ {3660, 15}, {3654, 14},
+ {3647, 12}, {3639, 11},
+ {3630, 9}, {3621, 8},
+ {3613, 6}, {3606, 5},
+ {3597, 4}, {3582, 2},
+ {3546, 1}, {2747, 0}
+ },
+ /* For temperature 40 degree Celsius */
+ {
+ {4114, 100}, {4081, 98},
+ {4065, 96}, {4050, 95},
+ {4036, 93}, {4024, 92},
+ {4013, 90}, {4002, 88},
+ {3990, 87}, {3976, 85},
+ {3962, 84}, {3950, 82},
+ {3939, 81}, {3930, 79},
+ {3921, 77}, {3912, 76},
+ {3902, 74}, {3893, 73},
+ {3883, 71}, {3874, 70},
+ {3865, 68}, {3856, 67},
+ {3847, 65}, {3838, 64},
+ {3829, 62}, {3820, 61},
+ {3812, 59}, {3803, 58},
+ {3795, 56}, {3787, 55},
+ {3780, 53}, {3773, 52},
+ {3767, 50}, {3761, 49},
+ {3756, 47}, {3751, 46},
+ {3746, 44}, {3741, 43},
+ {3736, 41}, {3732, 40},
+ {3728, 38}, {3724, 37},
+ {3720, 35}, {3716, 34},
+ {3713, 33}, {3710, 31},
+ {3707, 30}, {3704, 28},
+ {3701, 27}, {3698, 25},
+ {3695, 24}, {3691, 22},
+ {3686, 21}, {3681, 19},
+ {3676, 18}, {3671, 17},
+ {3666, 15}, {3661, 14},
+ {3655, 12}, {3648, 11},
+ {3640, 9}, {3632, 8},
+ {3622, 6}, {3616, 5},
+ {3611, 4}, {3604, 2},
+ {3594, 1}, {2747, 0}
+ }
+};
+
+struct da9052_battery {
+ struct da9052 *da9052;
+ struct power_supply psy;
+ struct notifier_block nb;
+ int charger_type;
+ int status;
+ int health;
+};
+
+static inline int volt_reg_to_mV(int value)
+{
+ return ((value * 1000) / 512) + 2500;
+}
+
+static inline int ichg_reg_to_mA(int value)
+{
+ return (value * 3900) / 1000;
+}
+
+static int da9052_read_chgend_current(struct da9052_battery *bat,
+ int *current_mA)
+{
+ int ret;
+
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING)
+ return -EINVAL;
+
+ ret = da9052_reg_read(bat->da9052, DA9052_ICHG_END_REG);
+ if (ret < 0)
+ return ret;
+
+ *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGEND_ICHGEND);
+
+ return 0;
+}
+
+static int da9052_read_chg_current(struct da9052_battery *bat, int *current_mA)
+{
+ int ret;
+
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING)
+ return -EINVAL;
+
+ ret = da9052_reg_read(bat->da9052, DA9052_ICHG_AV_REG);
+ if (ret < 0)
+ return ret;
+
+ *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGAV_ICHGAV);
+
+ return 0;
+}
+
+static int da9052_bat_check_status(struct da9052_battery *bat, int *status)
+{
+ u8 v[2] = {0, 0};
+ u8 bat_status;
+ u8 chg_end;
+ int ret;
+ int chg_current;
+ int chg_end_current;
+ bool dcinsel;
+ bool dcindet;
+ bool vbussel;
+ bool vbusdet;
+ bool dc;
+ bool vbus;
+
+ ret = da9052_group_read(bat->da9052, DA9052_STATUS_A_REG, 2, v);
+ if (ret < 0)
+ return ret;
+
+ bat_status = v[0];
+ chg_end = v[1];
+
+ dcinsel = bat_status & DA9052_STATUSA_DCINSEL;
+ dcindet = bat_status & DA9052_STATUSA_DCINDET;
+ vbussel = bat_status & DA9052_STATUSA_VBUSSEL;
+ vbusdet = bat_status & DA9052_STATUSA_VBUSDET;
+ dc = dcinsel && dcindet;
+ vbus = vbussel && vbusdet;
+
+ /* Preference to WALL(DCIN) charger unit */
+ if (dc || vbus) {
+ bat->charger_type = DA9052_CHARGER;
+
+ /* If charging end flag is set and Charging current is greater
+ * than charging end limit then battery is charging
+ */
+ if ((chg_end & DA9052_STATUSB_CHGEND) != 0) {
+ ret = da9052_read_chg_current(bat, &chg_current);
+ if (ret < 0)
+ return ret;
+ ret = da9052_read_chgend_current(bat, &chg_end_current);
+ if (ret < 0)
+ return ret;
+
+ if (chg_current >= chg_end_current)
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ /* If Charging end flag is cleared then battery is
+ * charging
+ */
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ } else if (dcindet || vbusdet) {
+ bat->charger_type = DA9052_CHARGER;
+ bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ bat->charger_type = DA9052_NOCHARGER;
+ bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ if (status != NULL)
+ *status = bat->status;
+ return 0;
+}
+
+static int da9052_bat_read_volt(struct da9052_battery *bat, int *volt_mV)
+{
+ int volt;
+
+ volt = da9052_adc_manual_read(bat->da9052, DA9052_ADC_MAN_MUXSEL_VBAT);
+ if (volt < 0)
+ return volt;
+
+ *volt_mV = volt_reg_to_mV(volt);
+
+ return 0;
+}
+
+static int da9052_bat_check_presence(struct da9052_battery *bat, int *illegal)
+{
+ int bat_temp;
+
+ bat_temp = da9052_adc_read_temp(bat->da9052);
+ if (bat_temp < 0)
+ return bat_temp;
+
+ if (bat_temp > DA9052_BAT_TSH)
+ *illegal = 1;
+ else
+ *illegal = 0;
+
+ return 0;
+}
+
+static int da9052_bat_interpolate(int vbat_lower, int vbat_upper,
+ int level_lower, int level_upper,
+ int bat_voltage)
+{
+ int tmp;
+
+ tmp = ((level_upper - level_lower) * 1000) / (vbat_upper - vbat_lower);
+ tmp = level_lower + (((bat_voltage - vbat_lower) * tmp) / 1000);
+
+ return tmp;
+}
+
+unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp)
+{
+ int i;
+
+ if (adc_temp <= vc_tbl_ref[0])
+ return 0;
+
+ if (adc_temp > vc_tbl_ref[DA9052_VC_TBL_REF_SZ - 1])
+ return DA9052_VC_TBL_REF_SZ - 1;
+
+ for (i = 0; i < DA9052_VC_TBL_REF_SZ; i++) {
+ if ((adc_temp > vc_tbl_ref[i]) &&
+ (adc_temp <= DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1])))
+ return i;
+ if ((adc_temp > DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1]))
+ && (adc_temp <= vc_tbl_ref[i]))
+ return i + 1;
+ }
+}
+
+static int da9052_bat_read_capacity(struct da9052_battery *bat, int *capacity)
+{
+ int adc_temp;
+ int bat_voltage;
+ int vbat_lower;
+ int vbat_upper;
+ int level_upper;
+ int level_lower;
+ int ret;
+ int flag;
+ int i = 0;
+ int j;
+
+ ret = da9052_bat_read_volt(bat, &bat_voltage);
+ if (ret < 0)
+ return ret;
+
+ adc_temp = da9052_adc_read_temp(bat->da9052);
+ if (adc_temp < 0)
+ return adc_temp;
+
+ i = da9052_determine_vc_tbl_index(adc_temp);
+
+ if (bat_voltage >= vc_tbl[i][0][0]) {
+ *capacity = 100;
+ return 0;
+ }
+ if (bat_voltage <= vc_tbl[i][DA9052_VC_TBL_SZ - 1][0]) {
+ *capacity = 0;
+ return 0;
+ }
+ flag = 0;
+
+ for (j = 0; j < (DA9052_VC_TBL_SZ-1); j++) {
+ if ((bat_voltage <= vc_tbl[i][j][0]) &&
+ (bat_voltage >= vc_tbl[i][j + 1][0])) {
+ vbat_upper = vc_tbl[i][j][0];
+ vbat_lower = vc_tbl[i][j + 1][0];
+ level_upper = vc_tbl[i][j][1];
+ level_lower = vc_tbl[i][j + 1][1];
+ flag = 1;
+ break;
+ }
+ }
+ if (!flag)
+ return -EIO;
+
+ *capacity = da9052_bat_interpolate(vbat_lower, vbat_upper, level_lower,
+ level_upper, bat_voltage);
+
+ return 0;
+}
+
+static int da9052_bat_check_health(struct da9052_battery *bat, int *health)
+{
+ int ret;
+ int bat_illegal;
+ int capacity;
+
+ ret = da9052_bat_check_presence(bat, &bat_illegal);
+ if (ret < 0)
+ return ret;
+
+ if (bat_illegal) {
+ bat->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ return 0;
+ }
+
+ if (bat->health != POWER_SUPPLY_HEALTH_OVERHEAT) {
+ ret = da9052_bat_read_capacity(bat, &capacity);
+ if (ret < 0)
+ return ret;
+ if (capacity < DA9052_BAT_LOW_CAP)
+ bat->health = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ bat->health = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ *health = bat->health;
+
+ return 0;
+}
+
+static irqreturn_t da9052_bat_irq(int irq, void *data)
+{
+ struct da9052_battery *bat = data;
+
+ irq -= bat->da9052->irq_base;
+
+ if (irq == DA9052_IRQ_CHGEND)
+ bat->status = POWER_SUPPLY_STATUS_FULL;
+ else
+ da9052_bat_check_status(bat, NULL);
+
+ if (irq == DA9052_IRQ_CHGEND || irq == DA9052_IRQ_DCIN ||
+ irq == DA9052_IRQ_VBUS || irq == DA9052_IRQ_TBAT) {
+ power_supply_changed(&bat->psy);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int da9052_USB_current_notifier(struct notifier_block *nb,
+ unsigned long events, void *data)
+{
+ u8 row;
+ u8 col;
+ int *current_mA = data;
+ int ret;
+ struct da9052_battery *bat = container_of(nb, struct da9052_battery,
+ nb);
+
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING)
+ return -EPERM;
+
+ ret = da9052_reg_read(bat->da9052, DA9052_CHGBUCK_REG);
+ if (ret & DA9052_CHG_USB_ILIM_MASK)
+ return -EPERM;
+
+ if (bat->da9052->chip_id == DA9052)
+ row = 0;
+ else
+ row = 1;
+
+ if (*current_mA < da9052_chg_current_lim[row][0] ||
+ *current_mA > da9052_chg_current_lim[row][DA9052_CHG_LIM_COLS - 1])
+ return -EINVAL;
+
+ for (col = 0; col <= DA9052_CHG_LIM_COLS - 1 ; col++) {
+ if (*current_mA <= da9052_chg_current_lim[row][col])
+ break;
+ }
+
+ return da9052_reg_update(bat->da9052, DA9052_ISET_REG,
+ DA9052_ISET_USB_MASK, col);
+}
+
+static int da9052_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret;
+ int illegal;
+ struct da9052_battery *bat = container_of(psy, struct da9052_battery,
+ psy);
+
+ ret = da9052_bat_check_presence(bat, &illegal);
+ if (ret < 0)
+ return ret;
+
+ if (illegal && psp != POWER_SUPPLY_PROP_PRESENT)
+ return -ENODEV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = da9052_bat_check_status(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval =
+ (bat->charger_type == DA9052_NOCHARGER) ? 0 : 1;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = da9052_bat_check_presence(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = da9052_bat_check_health(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = DA9052_BAT_CUTOFF_VOLT * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ ret = da9052_bat_read_volt(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = da9052_read_chg_current(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = da9052_bat_read_capacity(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = da9052_adc_read_temp(bat->da9052);
+ ret = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+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_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 struct power_supply template_battery = {
+ .name = "da9052-bat",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = da9052_bat_props,
+ .num_properties = ARRAY_SIZE(da9052_bat_props),
+ .get_property = da9052_bat_get_property,
+};
+
+static const char *const da9052_bat_irqs[] = {
+ "BATT TEMP",
+ "DCIN DET",
+ "DCIN REM",
+ "VBUS DET",
+ "VBUS REM",
+ "CHG END",
+};
+
+static s32 __devinit da9052_bat_probe(struct platform_device *pdev)
+{
+ struct da9052_pdata *pdata;
+ struct da9052_battery *bat;
+ int ret;
+ int irq;
+ int i;
+
+ bat = kzalloc(sizeof(struct da9052_battery), GFP_KERNEL);
+ if (!bat)
+ return -ENOMEM;
+
+ bat->da9052 = dev_get_drvdata(pdev->dev.parent);
+ bat->psy = template_battery;
+ bat->charger_type = DA9052_NOCHARGER;
+ bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ bat->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ bat->nb.notifier_call = da9052_USB_current_notifier;
+
+ pdata = bat->da9052->dev->platform_data;
+ if (pdata != NULL && pdata->use_for_apm)
+ bat->psy.use_for_apm = pdata->use_for_apm;
+ else
+ bat->psy.use_for_apm = 1;
+
+ for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) {
+ irq = platform_get_irq_byname(pdev, da9052_bat_irqs[i]);
+ ret = request_threaded_irq(bat->da9052->irq_base + irq,
+ NULL, da9052_bat_irq,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ da9052_bat_irqs[i], bat);
+ if (ret != 0) {
+ dev_err(bat->da9052->dev,
+ "DA9052 failed to request %s IRQ %d: %d\n",
+ da9052_bat_irqs[i], irq, ret);
+ goto err;
+ }
+ }
+
+ ret = power_supply_register(&pdev->dev, &bat->psy);
+ if (ret)
+ goto err;
+
+ platform_set_drvdata(pdev, bat);
+ return 0;
+
+err:
+ for (; i >= 0; i--) {
+ irq = platform_get_irq_byname(pdev, da9052_bat_irqs[i]);
+ free_irq(bat->da9052->irq_base + irq, bat);
+ }
+ kfree(bat);
+ return ret;
+}
+static int __devexit da9052_bat_remove(struct platform_device *pdev)
+{
+ int i;
+ int irq;
+ struct da9052_battery *bat = platform_get_drvdata(pdev);
+
+ for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) {
+ irq = platform_get_irq_byname(pdev, da9052_bat_irqs[i]);
+ free_irq(bat->da9052->irq_base + irq, bat);
+ }
+ power_supply_unregister(&bat->psy);
+ kfree(bat);
+
+ return 0;
+}
+
+static struct platform_driver da9052_bat_driver = {
+ .probe = da9052_bat_probe,
+ .remove = __devexit_p(da9052_bat_remove),
+ .driver = {
+ .name = "da9052-bat",
+ .owner = THIS_MODULE,
+ },
+};
+module_platform_driver(da9052_bat_driver);
+
+MODULE_DESCRIPTION("DA9052 BAT Device Driver");
+MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9052-bat");
diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c
new file mode 100644
index 00000000..076e211a
--- /dev/null
+++ b/drivers/power/ds2760_battery.c
@@ -0,0 +1,650 @@
+/*
+ * 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 bool 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 */
+#ifdef CONFIG_MACH_H4700
+ 0,
+ 3600, /* HP iPAQ hx4700 3.7V 3600mAh (359114-001) */
+#endif
+};
+
+/* 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,
+};
+
+module_platform_driver(ds2760_battery_driver);
+
+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..de31cae1
--- /dev/null
+++ b/drivers/power/ds2780_battery.c
@@ -0,0 +1,857 @@
+/*
+ * 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;
+}
+
+static struct platform_driver ds2780_battery_driver = {
+ .driver = {
+ .name = "ds2780-battery",
+ },
+ .probe = ds2780_battery_probe,
+ .remove = __devexit_p(ds2780_battery_remove),
+};
+
+module_platform_driver(ds2780_battery_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Clifton Barnes <cabarnes@indesign-llc.com>");
+MODULE_DESCRIPTION("Maxim/Dallas DS2780 Stand-Alone Fuel Gauage IC driver");
+MODULE_ALIAS("platform:ds2780-battery");
diff --git a/drivers/power/ds2781_battery.c b/drivers/power/ds2781_battery.c
new file mode 100644
index 00000000..ca0d653d
--- /dev/null
+++ b/drivers/power/ds2781_battery.c
@@ -0,0 +1,874 @@
+/*
+ * 1-wire client/driver for the Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC
+ *
+ * Author: Renata Sayakhova <renata@oktetlabs.ru>
+ *
+ * Based on ds2780_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_ds2781.h"
+
+/* Current unit measurement in uA for a 1 milli-ohm sense resistor */
+#define DS2781_CURRENT_UNITS 1563
+/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */
+#define DS2781_CHARGE_UNITS 6250
+/* Number of bytes in user EEPROM space */
+#define DS2781_USER_EEPROM_SIZE (DS2781_EEPROM_BLOCK0_END - \
+ DS2781_EEPROM_BLOCK0_START + 1)
+/* Number of bytes in parameter EEPROM space */
+#define DS2781_PARAM_EEPROM_SIZE (DS2781_EEPROM_BLOCK1_END - \
+ DS2781_EEPROM_BLOCK1_START + 1)
+
+struct ds2781_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[] = "DS2781";
+static const char manufacturer[] = "Maxim/Dallas";
+
+static inline struct ds2781_device_info *
+to_ds2781_device_info(struct power_supply *psy)
+{
+ return container_of(psy, struct ds2781_device_info, bat);
+}
+
+static inline struct power_supply *to_power_supply(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static inline int ds2781_battery_io(struct ds2781_device_info *dev_info,
+ char *buf, int addr, size_t count, int io)
+{
+ if (dev_info->mutex_holder == current)
+ return w1_ds2781_io_nolock(dev_info->w1_dev, buf, addr,
+ count, io);
+ else
+ return w1_ds2781_io(dev_info->w1_dev, buf, addr, count, io);
+}
+
+int w1_ds2781_read(struct ds2781_device_info *dev_info, char *buf,
+ int addr, size_t count)
+{
+ return ds2781_battery_io(dev_info, buf, addr, count, 0);
+}
+
+static inline int ds2781_read8(struct ds2781_device_info *dev_info, u8 *val,
+ int addr)
+{
+ return ds2781_battery_io(dev_info, val, addr, sizeof(u8), 0);
+}
+
+static int ds2781_read16(struct ds2781_device_info *dev_info, s16 *val,
+ int addr)
+{
+ int ret;
+ u8 raw[2];
+
+ ret = ds2781_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 ds2781_read_block(struct ds2781_device_info *dev_info,
+ u8 *val, int addr, size_t count)
+{
+ return ds2781_battery_io(dev_info, val, addr, count, 0);
+}
+
+static inline int ds2781_write(struct ds2781_device_info *dev_info, u8 *val,
+ int addr, size_t count)
+{
+ return ds2781_battery_io(dev_info, val, addr, count, 1);
+}
+
+static inline int ds2781_store_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_COPY_DATA);
+}
+
+static inline int ds2781_recall_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_RECALL_DATA);
+}
+
+static int ds2781_save_eeprom(struct ds2781_device_info *dev_info, int reg)
+{
+ int ret;
+
+ ret = ds2781_store_eeprom(dev_info->w1_dev, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_recall_eeprom(dev_info->w1_dev, reg);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* Set sense resistor value in mhos */
+static int ds2781_set_sense_register(struct ds2781_device_info *dev_info,
+ u8 conductance)
+{
+ int ret;
+
+ ret = ds2781_write(dev_info, &conductance,
+ DS2781_RSNSP, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return ds2781_save_eeprom(dev_info, DS2781_RSNSP);
+}
+
+/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */
+static int ds2781_get_rsgain_register(struct ds2781_device_info *dev_info,
+ u16 *rsgain)
+{
+ return ds2781_read16(dev_info, rsgain, DS2781_RSGAIN_MSB);
+}
+
+/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */
+static int ds2781_set_rsgain_register(struct ds2781_device_info *dev_info,
+ u16 rsgain)
+{
+ int ret;
+ u8 raw[] = {rsgain >> 8, rsgain & 0xFF};
+
+ ret = ds2781_write(dev_info, raw,
+ DS2781_RSGAIN_MSB, sizeof(raw));
+ if (ret < 0)
+ return ret;
+
+ return ds2781_save_eeprom(dev_info, DS2781_RSGAIN_MSB);
+}
+
+static int ds2781_get_voltage(struct ds2781_device_info *dev_info,
+ int *voltage_uV)
+{
+ int ret;
+ char val[2];
+ int voltage_raw;
+
+ ret = w1_ds2781_read(dev_info, val, DS2781_VOLT_MSB, 2 * sizeof(u8));
+ if (ret < 0)
+ return ret;
+ /*
+ * 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
+ */
+ voltage_raw = (val[0] << 3) |
+ (val[1] >> 5);
+
+ /* DS2781 reports voltage in units of 9.76mV, but the battery class
+ * reports in units of uV, so convert by multiplying by 9760. */
+ *voltage_uV = voltage_raw * 9760;
+
+ return 0;
+}
+
+static int ds2781_get_temperature(struct ds2781_device_info *dev_info,
+ int *temp)
+{
+ int ret;
+ char val[2];
+ int temp_raw;
+
+ ret = w1_ds2781_read(dev_info, val, DS2781_TEMP_MSB, 2 * sizeof(u8));
+ if (ret < 0)
+ return ret;
+ /*
+ * 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
+ */
+ temp_raw = ((val[0]) << 3) |
+ (val[1] >> 5);
+ *temp = temp_raw + (temp_raw / 4);
+
+ return 0;
+}
+
+static int ds2781_get_current(struct ds2781_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 = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP);
+ 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 = DS2781_CURRENT_MSB;
+ else if (type == CURRENT_AVG)
+ reg_msb = DS2781_IAVG_MSB;
+ 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 = ds2781_read16(dev_info, &current_raw, reg_msb);
+ if (ret < 0)
+ return ret;
+
+ *current_uA = current_raw * (DS2781_CURRENT_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2781_get_accumulated_current(struct ds2781_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 = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP);
+ 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;
+
+ /*
+ * 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 = ds2781_read16(dev_info, &current_raw, DS2781_ACR_MSB);
+ if (ret < 0)
+ return ret;
+
+ *accumulated_current = current_raw * (DS2781_CHARGE_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2781_get_capacity(struct ds2781_device_info *dev_info,
+ int *capacity)
+{
+ int ret;
+ u8 raw;
+
+ ret = ds2781_read8(dev_info, &raw, DS2781_RARC);
+ if (ret < 0)
+ return ret;
+
+ *capacity = raw;
+ return 0;
+}
+
+static int ds2781_get_status(struct ds2781_device_info *dev_info, int *status)
+{
+ int ret, current_uA, capacity;
+
+ ret = ds2781_get_current(dev_info, CURRENT_NOW, &current_uA);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_get_capacity(dev_info, &capacity);
+ if (ret < 0)
+ return ret;
+
+ if (power_supply_am_i_supplied(&dev_info->bat)) {
+ if (capacity == 100)
+ *status = POWER_SUPPLY_STATUS_FULL;
+ else if (current_uA > 50000)
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ return 0;
+}
+
+static int ds2781_get_charge_now(struct ds2781_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 = ds2781_read16(dev_info, &charge_raw, DS2781_RAAC_MSB);
+ if (ret < 0)
+ return ret;
+
+ *charge_now = charge_raw * 1600;
+ return 0;
+}
+
+static int ds2781_get_control_register(struct ds2781_device_info *dev_info,
+ u8 *control_reg)
+{
+ return ds2781_read8(dev_info, control_reg, DS2781_CONTROL);
+}
+
+static int ds2781_set_control_register(struct ds2781_device_info *dev_info,
+ u8 control_reg)
+{
+ int ret;
+
+ ret = ds2781_write(dev_info, &control_reg,
+ DS2781_CONTROL, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return ds2781_save_eeprom(dev_info, DS2781_CONTROL);
+}
+
+static int ds2781_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = ds2781_get_voltage(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = ds2781_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 = ds2781_get_current(dev_info, CURRENT_NOW, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = ds2781_get_current(dev_info, CURRENT_AVG, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = ds2781_get_status(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = ds2781_get_capacity(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = ds2781_get_accumulated_current(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = ds2781_get_charge_now(dev_info, &val->intval);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property ds2781_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 ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ /* Get power mode */
+ ret = ds2781_get_control_register(dev_info, &control_reg);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n",
+ !!(control_reg & DS2781_CONTROL_PMOD));
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ /* Set power mode */
+ ret = ds2781_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 |= DS2781_CONTROL_PMOD;
+ else
+ control_reg &= ~DS2781_CONTROL_PMOD;
+
+ ret = ds2781_set_control_register(dev_info, control_reg);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = ds2781_read8(dev_info, &sense_resistor, DS2781_RSNSP);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(buf, "%d\n", sense_resistor);
+ return ret;
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_set_sense_register(dev_info, new_setting);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = ds2781_get_rsgain_register(dev_info, &rsgain);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", rsgain);
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_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 = ds2781_set_rsgain_register(dev_info, new_setting);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = ds2781_read8(dev_info, &sfr, DS2781_SFR);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(buf, "%d\n", sfr & DS2781_SFR_PIOSC);
+ return ret;
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_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 = ds2781_write(dev_info, &new_setting,
+ DS2781_SFR, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ count = min_t(loff_t, count,
+ DS2781_EEPROM_BLOCK1_END -
+ DS2781_EEPROM_BLOCK1_START + 1 - off);
+
+ return ds2781_read_block(dev_info, buf,
+ DS2781_EEPROM_BLOCK1_START + off, count);
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+ int ret;
+
+ count = min_t(loff_t, count,
+ DS2781_EEPROM_BLOCK1_END -
+ DS2781_EEPROM_BLOCK1_START + 1 - off);
+
+ ret = ds2781_write(dev_info, buf,
+ DS2781_EEPROM_BLOCK1_START + off, count);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK1_START);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static struct bin_attribute ds2781_param_eeprom_bin_attr = {
+ .attr = {
+ .name = "param_eeprom",
+ .mode = S_IRUGO | S_IWUSR,
+ },
+ .size = DS2781_EEPROM_BLOCK1_END - DS2781_EEPROM_BLOCK1_START + 1,
+ .read = ds2781_read_param_eeprom_bin,
+ .write = ds2781_write_param_eeprom_bin,
+};
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ count = min_t(loff_t, count,
+ DS2781_EEPROM_BLOCK0_END -
+ DS2781_EEPROM_BLOCK0_START + 1 - off);
+
+ return ds2781_read_block(dev_info, buf,
+ DS2781_EEPROM_BLOCK0_START + off, count);
+
+}
+
+static ssize_t ds2781_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 ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+ int ret;
+
+ count = min_t(loff_t, count,
+ DS2781_EEPROM_BLOCK0_END -
+ DS2781_EEPROM_BLOCK0_START + 1 - off);
+
+ ret = ds2781_write(dev_info, buf,
+ DS2781_EEPROM_BLOCK0_START + off, count);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK0_START);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static struct bin_attribute ds2781_user_eeprom_bin_attr = {
+ .attr = {
+ .name = "user_eeprom",
+ .mode = S_IRUGO | S_IWUSR,
+ },
+ .size = DS2781_EEPROM_BLOCK0_END - DS2781_EEPROM_BLOCK0_START + 1,
+ .read = ds2781_read_user_eeprom_bin,
+ .write = ds2781_write_user_eeprom_bin,
+};
+
+static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2781_get_pmod_enabled,
+ ds2781_set_pmod_enabled);
+static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR,
+ ds2781_get_sense_resistor_value, ds2781_set_sense_resistor_value);
+static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2781_get_rsgain_setting,
+ ds2781_set_rsgain_setting);
+static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2781_get_pio_pin,
+ ds2781_set_pio_pin);
+
+
+static struct attribute *ds2781_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 ds2781_attr_group = {
+ .attrs = ds2781_attributes,
+};
+
+static int __devinit ds2781_battery_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct ds2781_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 = ds2781_battery_props;
+ dev_info->bat.num_properties = ARRAY_SIZE(ds2781_battery_props);
+ dev_info->bat.get_property = ds2781_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, &ds2781_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,
+ &ds2781_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,
+ &ds2781_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,
+ &ds2781_param_eeprom_bin_attr);
+fail_remove_group:
+ sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2781_attr_group);
+fail_unregister:
+ power_supply_unregister(&dev_info->bat);
+fail_free_info:
+ kfree(dev_info);
+fail:
+ return ret;
+}
+
+static int __devexit ds2781_battery_remove(struct platform_device *pdev)
+{
+ struct ds2781_device_info *dev_info = platform_get_drvdata(pdev);
+
+ dev_info->mutex_holder = current;
+
+ /* remove attributes */
+ sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2781_attr_group);
+
+ power_supply_unregister(&dev_info->bat);
+
+ kfree(dev_info);
+ return 0;
+}
+
+static struct platform_driver ds2781_battery_driver = {
+ .driver = {
+ .name = "ds2781-battery",
+ },
+ .probe = ds2781_battery_probe,
+ .remove = __devexit_p(ds2781_battery_remove),
+};
+
+static int __init ds2781_battery_init(void)
+{
+ return platform_driver_register(&ds2781_battery_driver);
+}
+
+static void __exit ds2781_battery_exit(void)
+{
+ platform_driver_unregister(&ds2781_battery_driver);
+}
+
+module_init(ds2781_battery_init);
+module_exit(ds2781_battery_exit);
+
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Renata Sayakhova <renata@oktetlabs.ru>");
+MODULE_DESCRIPTION("Maxim/Dallas DS2781 Stand-Alone Fuel Gauage IC driver");
+MODULE_ALIAS("platform:ds2781-battery");
+
diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c
new file mode 100644
index 00000000..6bb6e2f5
--- /dev/null
+++ b/drivers/power/ds2782_battery.c
@@ -0,0 +1,410 @@
+/*
+ * I2C client/driver for the Maxim/Dallas DS2782 Stand-Alone Fuel Gauge IC
+ *
+ * Copyright (C) 2009 Bluewater Systems Ltd
+ *
+ * Author: Ryan Mallon
+ *
+ * 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,
+};
+module_i2c_driver(ds278x_battery_driver);
+
+MODULE_AUTHOR("Ryan Mallon");
+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..8672c917
--- /dev/null
+++ b/drivers/power/gpio-charger.c
@@ -0,0 +1,193 @@
+/*
+ * 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 < 0)
+ 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,
+ },
+};
+
+module_platform_driver(gpio_charger_driver);
+
+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..d0964970
--- /dev/null
+++ b/drivers/power/intel_mid_battery.c
@@ -0,0 +1,786 @@
+/*
+ * 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 0x86
+
+#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_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;
+ if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) {
+ /* PMIC will change charging current automatically */
+ pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT);
+ }
+ }
+ }
+
+ /* 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),
+};
+
+module_platform_driver(platform_pmic_battery_driver);
+
+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..39eb50f3
--- /dev/null
+++ b/drivers/power/isp1704_charger.c
@@ -0,0 +1,513 @@
+/*
+ * 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 usb_phy *phy;
+ 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;
+};
+
+static inline int isp1704_read(struct isp1704_charger *isp, u32 reg)
+{
+ return usb_phy_io_read(isp->phy, reg);
+}
+
+static inline int isp1704_write(struct isp1704_charger *isp, u32 val, u32 reg)
+{
+ return usb_phy_io_write(isp->phy, val, reg);
+}
+
+/*
+ * 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 && 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 = isp1704_read(isp, ULPI_FUNC_CTRL);
+ otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL);
+
+ /* disable pulldowns */
+ reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN;
+ isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg);
+
+ /* full speed */
+ isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_XCVRSEL_MASK);
+ isp1704_write(isp, 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;
+ isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg);
+ usleep_range(1000, 2000);
+
+ reg = isp1704_read(isp, ULPI_DEBUG);
+ if ((reg & 3) != 3)
+ type = POWER_SUPPLY_TYPE_USB_CDP;
+
+ /* recover original state */
+ isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl);
+ isp1704_write(isp, 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 = isp1704_read(isp, ULPI_FUNC_CTRL);
+ r |= ULPI_FUNC_CTRL_RESET;
+ isp1704_write(isp, ULPI_FUNC_CTRL, r);
+ usleep_range(1000, 2000);
+
+ /* Set normal mode */
+ r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK);
+ isp1704_write(isp, ULPI_FUNC_CTRL, r);
+
+ /* Clear the DP and DM pull-down bits */
+ r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN;
+ isp1704_write(isp, 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;
+ isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r);
+ usleep_range(1000, 2000);
+
+ /* Read the line state */
+ if (!isp1704_read(isp, ULPI_DEBUG)) {
+ /* Disable strong pull-up on DP (1.5K) */
+ isp1704_write(isp, 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 */
+ isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),
+ ISP1704_PWR_CTRL_DP_WKPU_EN);
+
+ /* Disable strong pull-up on DP (1.5K) */
+ isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_TERMSELECT);
+
+ /* Enable weak pull-down resistor on DM */
+ isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL),
+ ULPI_OTG_CTRL_DM_PULLDOWN);
+
+ /* It's a charger if the line states are clear */
+ if (!(isp1704_read(isp, ULPI_DEBUG)))
+ ret = 1;
+
+ /* Disable weak pull-up resistor on DP */
+ isp1704_write(isp, 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 = isp1704_read(isp, ISP1704_PWR_CTRL);
+
+ /* set SW control bit in PWR_CTRL register */
+ isp1704_write(isp, ISP1704_PWR_CTRL,
+ ISP1704_PWR_CTRL_SWCTRL);
+
+ /* enable manual charger detection */
+ isp1704_write(isp, 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 (isp1704_read(isp, ISP1704_PWR_CTRL)
+ & ISP1704_PWR_CTRL_VDAT_DET) {
+ ret = isp1704_charger_verify(isp);
+ break;
+ }
+ } while (!time_after(jiffies, timeout) && isp->online);
+
+ /* recover original state */
+ isp1704_write(isp, 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->phy->otg->gadget)
+ usb_gadget_connect(isp->phy->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->phy->otg->gadget)
+ usb_gadget_disconnect(isp->phy->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 = isp1704_write(isp, ULPI_SCRATCH, 0xaa);
+ if (ret < 0)
+ return ret;
+
+ ret = isp1704_read(isp, ULPI_SCRATCH);
+ if (ret < 0)
+ return ret;
+
+ if (ret != 0xaa)
+ return -ENODEV;
+
+ /* Verify the product and vendor id matches */
+ vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW);
+ vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8;
+ if (vendor != NXP_VENDOR_ID)
+ return -ENODEV;
+
+ product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW);
+ product |= isp1704_read(isp, 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->phy = usb_get_transceiver();
+ if (!isp->phy)
+ 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 usb notifications to be
+ * made atomically in the future.
+ */
+ INIT_WORK(&isp->work, isp1704_charger_work);
+
+ isp->nb.notifier_call = isp1704_notifier_call;
+
+ ret = usb_register_notifier(isp->phy, &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->phy->otg->gadget)
+ usb_gadget_disconnect(isp->phy->otg->gadget);
+
+ /* Detect charger if VBUS is valid (the cable was already plugged). */
+ ret = isp1704_read(isp, ULPI_USB_INT_STS);
+ isp1704_charger_set_power(isp, 0);
+ if ((ret & ULPI_INT_VBUS_VALID) && !isp->phy->otg->default_a) {
+ isp->event = USB_EVENT_VBUS;
+ schedule_work(&isp->work);
+ }
+
+ return 0;
+fail2:
+ power_supply_unregister(&isp->psy);
+fail1:
+ usb_put_transceiver(isp->phy);
+fail0:
+ kfree(isp);
+
+ dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
+
+ isp1704_charger_set_power(isp, 0);
+ return ret;
+}
+
+static int __devexit isp1704_charger_remove(struct platform_device *pdev)
+{
+ struct isp1704_charger *isp = platform_get_drvdata(pdev);
+
+ usb_unregister_notifier(isp->phy, &isp->nb);
+ power_supply_unregister(&isp->psy);
+ usb_put_transceiver(isp->phy);
+ 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),
+};
+
+module_platform_driver(isp1704_charger_driver);
+
+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..8dbc7bfa
--- /dev/null
+++ b/drivers/power/jz4740-battery.c
@@ -0,0 +1,449 @@
+/*
+ * 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)
+{
+ 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,
+ },
+};
+
+module_platform_driver(jz_battery_driver);
+
+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/lp8727_charger.c b/drivers/power/lp8727_charger.c
new file mode 100644
index 00000000..d8b75780
--- /dev/null
+++ b/drivers/power/lp8727_charger.c
@@ -0,0 +1,498 @@
+/*
+ * Driver for LP8727 Micro/Mini USB IC with integrated charger
+ *
+ * Copyright (C) 2011 Texas Instruments
+ * Copyright (C) 2011 National Semiconductor
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * 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/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/power_supply.h>
+#include <linux/lp8727.h>
+
+#define DEBOUNCE_MSEC 270
+
+/* Registers */
+#define CTRL1 0x1
+#define CTRL2 0x2
+#define SWCTRL 0x3
+#define INT1 0x4
+#define INT2 0x5
+#define STATUS1 0x6
+#define STATUS2 0x7
+#define CHGCTRL2 0x9
+
+/* CTRL1 register */
+#define CP_EN (1 << 0)
+#define ADC_EN (1 << 1)
+#define ID200_EN (1 << 4)
+
+/* CTRL2 register */
+#define CHGDET_EN (1 << 1)
+#define INT_EN (1 << 6)
+
+/* SWCTRL register */
+#define SW_DM1_DM (0x0 << 0)
+#define SW_DM1_U1 (0x1 << 0)
+#define SW_DM1_HiZ (0x7 << 0)
+#define SW_DP2_DP (0x0 << 3)
+#define SW_DP2_U2 (0x1 << 3)
+#define SW_DP2_HiZ (0x7 << 3)
+
+/* INT1 register */
+#define IDNO (0xF << 0)
+#define VBUS (1 << 4)
+
+/* STATUS1 register */
+#define CHGSTAT (3 << 4)
+#define CHPORT (1 << 6)
+#define DCPORT (1 << 7)
+
+/* STATUS2 register */
+#define TEMP_STAT (3 << 5)
+
+enum lp8727_dev_id {
+ ID_NONE,
+ ID_TA,
+ ID_DEDICATED_CHG,
+ ID_USB_CHG,
+ ID_USB_DS,
+ ID_MAX,
+};
+
+enum lp8727_chg_stat {
+ PRECHG,
+ CC,
+ CV,
+ EOC,
+};
+
+struct lp8727_psy {
+ struct power_supply ac;
+ struct power_supply usb;
+ struct power_supply batt;
+};
+
+struct lp8727_chg {
+ struct device *dev;
+ struct i2c_client *client;
+ struct mutex xfer_lock;
+ struct delayed_work work;
+ struct workqueue_struct *irqthread;
+ struct lp8727_platform_data *pdata;
+ struct lp8727_psy *psy;
+ struct lp8727_chg_param *chg_parm;
+ enum lp8727_dev_id devid;
+};
+
+static int lp8727_read_bytes(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len)
+{
+ s32 ret;
+
+ mutex_lock(&pchg->xfer_lock);
+ ret = i2c_smbus_read_i2c_block_data(pchg->client, reg, len, data);
+ mutex_unlock(&pchg->xfer_lock);
+
+ return (ret != len) ? -EIO : 0;
+}
+
+static inline int lp8727_read_byte(struct lp8727_chg *pchg, u8 reg, u8 *data)
+{
+ return lp8727_read_bytes(pchg, reg, data, 1);
+}
+
+static int lp8727_write_byte(struct lp8727_chg *pchg, u8 reg, u8 data)
+{
+ int ret;
+
+ mutex_lock(&pchg->xfer_lock);
+ ret = i2c_smbus_write_byte_data(pchg->client, reg, data);
+ mutex_unlock(&pchg->xfer_lock);
+
+ return ret;
+}
+
+static int lp8727_is_charger_attached(const char *name, int id)
+{
+ if (name) {
+ if (!strcmp(name, "ac"))
+ return (id == ID_TA || id == ID_DEDICATED_CHG) ? 1 : 0;
+ else if (!strcmp(name, "usb"))
+ return (id == ID_USB_CHG) ? 1 : 0;
+ }
+
+ return (id >= ID_TA && id <= ID_USB_CHG) ? 1 : 0;
+}
+
+static int lp8727_init_device(struct lp8727_chg *pchg)
+{
+ u8 val;
+ int ret;
+
+ val = ID200_EN | ADC_EN | CP_EN;
+ ret = lp8727_write_byte(pchg, CTRL1, val);
+ if (ret)
+ return ret;
+
+ val = INT_EN | CHGDET_EN;
+ ret = lp8727_write_byte(pchg, CTRL2, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int lp8727_is_dedicated_charger(struct lp8727_chg *pchg)
+{
+ u8 val;
+ lp8727_read_byte(pchg, STATUS1, &val);
+ return val & DCPORT;
+}
+
+static int lp8727_is_usb_charger(struct lp8727_chg *pchg)
+{
+ u8 val;
+ lp8727_read_byte(pchg, STATUS1, &val);
+ return val & CHPORT;
+}
+
+static void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw)
+{
+ lp8727_write_byte(pchg, SWCTRL, sw);
+}
+
+static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin)
+{
+ u8 devid = ID_NONE;
+ u8 swctrl = SW_DM1_HiZ | SW_DP2_HiZ;
+
+ switch (id) {
+ case 0x5:
+ devid = ID_TA;
+ pchg->chg_parm = &pchg->pdata->ac;
+ break;
+ case 0xB:
+ if (lp8727_is_dedicated_charger(pchg)) {
+ pchg->chg_parm = &pchg->pdata->ac;
+ devid = ID_DEDICATED_CHG;
+ } else if (lp8727_is_usb_charger(pchg)) {
+ pchg->chg_parm = &pchg->pdata->usb;
+ devid = ID_USB_CHG;
+ swctrl = SW_DM1_DM | SW_DP2_DP;
+ } else if (vbusin) {
+ devid = ID_USB_DS;
+ swctrl = SW_DM1_DM | SW_DP2_DP;
+ }
+ break;
+ default:
+ devid = ID_NONE;
+ pchg->chg_parm = NULL;
+ break;
+ }
+
+ pchg->devid = devid;
+ lp8727_ctrl_switch(pchg, swctrl);
+}
+
+static void lp8727_enable_chgdet(struct lp8727_chg *pchg)
+{
+ u8 val;
+
+ lp8727_read_byte(pchg, CTRL2, &val);
+ val |= CHGDET_EN;
+ lp8727_write_byte(pchg, CTRL2, val);
+}
+
+static void lp8727_delayed_func(struct work_struct *_work)
+{
+ u8 intstat[2], idno, vbus;
+ struct lp8727_chg *pchg =
+ container_of(_work, struct lp8727_chg, work.work);
+
+ if (lp8727_read_bytes(pchg, INT1, intstat, 2)) {
+ dev_err(pchg->dev, "can not read INT registers\n");
+ return;
+ }
+
+ idno = intstat[0] & IDNO;
+ vbus = intstat[0] & VBUS;
+
+ lp8727_id_detection(pchg, idno, vbus);
+ lp8727_enable_chgdet(pchg);
+
+ power_supply_changed(&pchg->psy->ac);
+ power_supply_changed(&pchg->psy->usb);
+ power_supply_changed(&pchg->psy->batt);
+}
+
+static irqreturn_t lp8727_isr_func(int irq, void *ptr)
+{
+ struct lp8727_chg *pchg = ptr;
+ unsigned long delay = msecs_to_jiffies(DEBOUNCE_MSEC);
+
+ queue_delayed_work(pchg->irqthread, &pchg->work, delay);
+
+ return IRQ_HANDLED;
+}
+
+static int lp8727_intr_config(struct lp8727_chg *pchg)
+{
+ INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func);
+
+ pchg->irqthread = create_singlethread_workqueue("lp8727-irqthd");
+ if (!pchg->irqthread) {
+ dev_err(pchg->dev, "can not create thread for lp8727\n");
+ return -ENOMEM;
+ }
+
+ return request_threaded_irq(pchg->client->irq,
+ NULL,
+ lp8727_isr_func,
+ IRQF_TRIGGER_FALLING,
+ "lp8727_irq",
+ pchg);
+}
+
+static enum power_supply_property lp8727_charger_prop[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property lp8727_battery_prop[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static char *battery_supplied_to[] = {
+ "main_batt",
+};
+
+static int lp8727_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent);
+
+ if (psp == POWER_SUPPLY_PROP_ONLINE)
+ val->intval = lp8727_is_charger_attached(psy->name,
+ pchg->devid);
+
+ return 0;
+}
+
+static int lp8727_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent);
+ u8 read;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (lp8727_is_charger_attached(psy->name, pchg->devid)) {
+ lp8727_read_byte(pchg, STATUS1, &read);
+ if (((read & CHGSTAT) >> 4) == EOC)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ lp8727_read_byte(pchg, STATUS2, &read);
+ read = (read & TEMP_STAT) >> 5;
+ if (read >= 0x1 && read <= 0x3)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (pchg->pdata->get_batt_present)
+ val->intval = pchg->pdata->get_batt_present();
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (pchg->pdata->get_batt_level)
+ val->intval = pchg->pdata->get_batt_level();
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (pchg->pdata->get_batt_capacity)
+ val->intval = pchg->pdata->get_batt_capacity();
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ if (pchg->pdata->get_batt_temp)
+ val->intval = pchg->pdata->get_batt_temp();
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void lp8727_charger_changed(struct power_supply *psy)
+{
+ struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent);
+ u8 val;
+ u8 eoc_level, ichg;
+
+ if (lp8727_is_charger_attached(psy->name, pchg->devid)) {
+ if (pchg->chg_parm) {
+ eoc_level = pchg->chg_parm->eoc_level;
+ ichg = pchg->chg_parm->ichg;
+ val = (ichg << 4) | eoc_level;
+ lp8727_write_byte(pchg, CHGCTRL2, val);
+ }
+ }
+}
+
+static int lp8727_register_psy(struct lp8727_chg *pchg)
+{
+ struct lp8727_psy *psy;
+
+ psy = kzalloc(sizeof(*psy), GFP_KERNEL);
+ if (!psy)
+ goto err_mem;
+
+ pchg->psy = psy;
+
+ psy->ac.name = "ac";
+ psy->ac.type = POWER_SUPPLY_TYPE_MAINS;
+ psy->ac.properties = lp8727_charger_prop;
+ psy->ac.num_properties = ARRAY_SIZE(lp8727_charger_prop);
+ psy->ac.get_property = lp8727_charger_get_property;
+ psy->ac.supplied_to = battery_supplied_to;
+ psy->ac.num_supplicants = ARRAY_SIZE(battery_supplied_to);
+
+ if (power_supply_register(pchg->dev, &psy->ac))
+ goto err_psy;
+
+ psy->usb.name = "usb";
+ psy->usb.type = POWER_SUPPLY_TYPE_USB;
+ psy->usb.properties = lp8727_charger_prop;
+ psy->usb.num_properties = ARRAY_SIZE(lp8727_charger_prop);
+ psy->usb.get_property = lp8727_charger_get_property;
+ psy->usb.supplied_to = battery_supplied_to;
+ psy->usb.num_supplicants = ARRAY_SIZE(battery_supplied_to);
+
+ if (power_supply_register(pchg->dev, &psy->usb))
+ goto err_psy;
+
+ psy->batt.name = "main_batt";
+ psy->batt.type = POWER_SUPPLY_TYPE_BATTERY;
+ psy->batt.properties = lp8727_battery_prop;
+ psy->batt.num_properties = ARRAY_SIZE(lp8727_battery_prop);
+ psy->batt.get_property = lp8727_battery_get_property;
+ psy->batt.external_power_changed = lp8727_charger_changed;
+
+ if (power_supply_register(pchg->dev, &psy->batt))
+ goto err_psy;
+
+ return 0;
+
+err_mem:
+ return -ENOMEM;
+err_psy:
+ kfree(psy);
+ return -EPERM;
+}
+
+static void lp8727_unregister_psy(struct lp8727_chg *pchg)
+{
+ struct lp8727_psy *psy = pchg->psy;
+
+ if (!psy)
+ return;
+
+ power_supply_unregister(&psy->ac);
+ power_supply_unregister(&psy->usb);
+ power_supply_unregister(&psy->batt);
+ kfree(psy);
+}
+
+static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id)
+{
+ struct lp8727_chg *pchg;
+ int ret;
+
+ if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
+ return -EIO;
+
+ pchg = kzalloc(sizeof(*pchg), GFP_KERNEL);
+ if (!pchg)
+ return -ENOMEM;
+
+ pchg->client = cl;
+ pchg->dev = &cl->dev;
+ pchg->pdata = cl->dev.platform_data;
+ i2c_set_clientdata(cl, pchg);
+
+ mutex_init(&pchg->xfer_lock);
+
+ ret = lp8727_init_device(pchg);
+ if (ret) {
+ dev_err(pchg->dev, "i2c communication err: %d", ret);
+ goto error;
+ }
+
+ ret = lp8727_intr_config(pchg);
+ if (ret) {
+ dev_err(pchg->dev, "irq handler err: %d", ret);
+ goto error;
+ }
+
+ ret = lp8727_register_psy(pchg);
+ if (ret) {
+ dev_err(pchg->dev, "power supplies register err: %d", ret);
+ goto error;
+ }
+
+ return 0;
+
+error:
+ kfree(pchg);
+ return ret;
+}
+
+static int __devexit lp8727_remove(struct i2c_client *cl)
+{
+ struct lp8727_chg *pchg = i2c_get_clientdata(cl);
+
+ lp8727_unregister_psy(pchg);
+ free_irq(pchg->client->irq, pchg);
+ flush_workqueue(pchg->irqthread);
+ destroy_workqueue(pchg->irqthread);
+ kfree(pchg);
+ return 0;
+}
+
+static const struct i2c_device_id lp8727_ids[] = {
+ {"lp8727", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lp8727_ids);
+
+static struct i2c_driver lp8727_driver = {
+ .driver = {
+ .name = "lp8727",
+ },
+ .probe = lp8727_probe,
+ .remove = __devexit_p(lp8727_remove),
+ .id_table = lp8727_ids,
+};
+module_i2c_driver(lp8727_driver);
+
+MODULE_DESCRIPTION("TI/National Semiconductor LP8727 charger driver");
+MODULE_AUTHOR("Woogyom Kim <milo.kim@ti.com>, "
+ "Daniel Jeong <daniel.jeong@ti.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c
new file mode 100644
index 00000000..c284143c
--- /dev/null
+++ b/drivers/power/max17040_battery.c
@@ -0,0 +1,297 @@
+/*
+ * 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,
+};
+module_i2c_driver(max17040_i2c_driver);
+
+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..04620c2c
--- /dev/null
+++ b/drivers/power/max17042_battery.c
@@ -0,0 +1,751 @@
+/*
+ * 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/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/mod_devicetable.h>
+#include <linux/power_supply.h>
+#include <linux/power/max17042_battery.h>
+#include <linux/of.h>
+
+/* Status register bits */
+#define STATUS_POR_BIT (1 << 1)
+#define STATUS_BST_BIT (1 << 3)
+#define STATUS_VMN_BIT (1 << 8)
+#define STATUS_TMN_BIT (1 << 9)
+#define STATUS_SMN_BIT (1 << 10)
+#define STATUS_BI_BIT (1 << 11)
+#define STATUS_VMX_BIT (1 << 12)
+#define STATUS_TMX_BIT (1 << 13)
+#define STATUS_SMX_BIT (1 << 14)
+#define STATUS_BR_BIT (1 << 15)
+
+/* Interrupt mask bits */
+#define CONFIG_ALRT_BIT_ENBL (1 << 2)
+#define STATUS_INTR_SOCMIN_BIT (1 << 10)
+#define STATUS_INTR_SOCMAX_BIT (1 << 14)
+
+#define VFSOC0_LOCK 0x0000
+#define VFSOC0_UNLOCK 0x0080
+#define MODEL_UNLOCK1 0X0059
+#define MODEL_UNLOCK2 0X00C4
+#define MODEL_LOCK1 0X0000
+#define MODEL_LOCK2 0X0000
+
+#define dQ_ACC_DIV 0x4
+#define dP_ACC_100 0x1900
+#define dP_ACC_200 0x3200
+
+struct max17042_chip {
+ struct i2c_client *client;
+ struct power_supply battery;
+ struct max17042_platform_data *pdata;
+ struct work_struct work;
+ int init_complete;
+};
+
+static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value)
+{
+ 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 void max17042_set_reg(struct i2c_client *client,
+ struct max17042_reg_data *data, int size)
+{
+ int i;
+
+ for (i = 0; i < size; i++)
+ max17042_write_reg(client, data[i].addr, data[i].data);
+}
+
+static enum power_supply_property max17042_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+};
+
+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);
+ int ret;
+
+ if (!chip->init_complete)
+ return -EAGAIN;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = max17042_read_reg(chip->client, MAX17042_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & MAX17042_STATUS_BattAbsent)
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ ret = max17042_read_reg(chip->client, MAX17042_Cycles);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ ret = max17042_read_reg(chip->client, MAX17042_MinMaxVolt);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret >> 8;
+ val->intval *= 20000; /* Units of LSB = 20mV */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ ret = max17042_read_reg(chip->client, MAX17042_V_empty);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret >> 7;
+ val->intval *= 10000; /* Units of LSB = 10mV */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = max17042_read_reg(chip->client, MAX17042_VCELL);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret * 625 / 8;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret * 625 / 8;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = max17042_read_reg(chip->client, MAX17042_RepSOC);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret >> 8;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = max17042_read_reg(chip->client, MAX17042_FullCAP);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret * 1000 / 2;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = max17042_read_reg(chip->client, MAX17042_TEMP);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret;
+ /* The value is signed. */
+ if (val->intval & 0x8000) {
+ val->intval = (0x7fff & ~val->intval) + 1;
+ val->intval *= -1;
+ }
+ /* The value is converted into deci-centigrade scale */
+ /* Units of LSB = 1 / 256 degree Celsius */
+ val->intval = val->intval * 10 / 256;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (chip->pdata->enable_current_sense) {
+ ret = max17042_read_reg(chip->client, MAX17042_Current);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret;
+ if (val->intval & 0x8000) {
+ /* Negative */
+ val->intval = ~val->intval & 0x7fff;
+ val->intval++;
+ val->intval *= -1;
+ }
+ val->intval *= 1562500 / chip->pdata->r_sns;
+ } else {
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ if (chip->pdata->enable_current_sense) {
+ ret = max17042_read_reg(chip->client,
+ MAX17042_AvgCurrent);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret;
+ if (val->intval & 0x8000) {
+ /* Negative */
+ val->intval = ~val->intval & 0x7fff;
+ val->intval++;
+ val->intval *= -1;
+ }
+ val->intval *= 1562500 / chip->pdata->r_sns;
+ } else {
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max17042_write_verify_reg(struct i2c_client *client,
+ u8 reg, u16 value)
+{
+ int retries = 8;
+ int ret;
+ u16 read_value;
+
+ do {
+ ret = i2c_smbus_write_word_data(client, reg, value);
+ read_value = max17042_read_reg(client, reg);
+ if (read_value != value) {
+ ret = -EIO;
+ retries--;
+ }
+ } while (retries && read_value != value);
+
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ return ret;
+}
+
+static inline void max17042_override_por(
+ struct i2c_client *client, u8 reg, u16 value)
+{
+ if (value)
+ max17042_write_reg(client, reg, value);
+}
+
+static inline void max10742_unlock_model(struct max17042_chip *chip)
+{
+ struct i2c_client *client = chip->client;
+ max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_UNLOCK1);
+ max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_UNLOCK2);
+}
+
+static inline void max10742_lock_model(struct max17042_chip *chip)
+{
+ struct i2c_client *client = chip->client;
+ max17042_write_reg(client, MAX17042_MLOCKReg1, MODEL_LOCK1);
+ max17042_write_reg(client, MAX17042_MLOCKReg2, MODEL_LOCK2);
+}
+
+static inline void max17042_write_model_data(struct max17042_chip *chip,
+ u8 addr, int size)
+{
+ struct i2c_client *client = chip->client;
+ int i;
+ for (i = 0; i < size; i++)
+ max17042_write_reg(client, addr + i,
+ chip->pdata->config_data->cell_char_tbl[i]);
+}
+
+static inline void max17042_read_model_data(struct max17042_chip *chip,
+ u8 addr, u16 *data, int size)
+{
+ struct i2c_client *client = chip->client;
+ int i;
+
+ for (i = 0; i < size; i++)
+ data[i] = max17042_read_reg(client, addr + i);
+}
+
+static inline int max17042_model_data_compare(struct max17042_chip *chip,
+ u16 *data1, u16 *data2, int size)
+{
+ int i;
+
+ if (memcmp(data1, data2, size)) {
+ dev_err(&chip->client->dev, "%s compare failed\n", __func__);
+ for (i = 0; i < size; i++)
+ dev_info(&chip->client->dev, "0x%x, 0x%x",
+ data1[i], data2[i]);
+ dev_info(&chip->client->dev, "\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max17042_init_model(struct max17042_chip *chip)
+{
+ int ret;
+ int table_size =
+ sizeof(chip->pdata->config_data->cell_char_tbl)/sizeof(u16);
+ u16 *temp_data;
+
+ temp_data = kzalloc(table_size, GFP_KERNEL);
+ if (!temp_data)
+ return -ENOMEM;
+
+ max10742_unlock_model(chip);
+ max17042_write_model_data(chip, MAX17042_MODELChrTbl,
+ table_size);
+ max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data,
+ table_size);
+
+ ret = max17042_model_data_compare(
+ chip,
+ chip->pdata->config_data->cell_char_tbl,
+ temp_data,
+ table_size);
+
+ max10742_lock_model(chip);
+ kfree(temp_data);
+
+ return ret;
+}
+
+static int max17042_verify_model_lock(struct max17042_chip *chip)
+{
+ int i;
+ int table_size =
+ sizeof(chip->pdata->config_data->cell_char_tbl);
+ u16 *temp_data;
+ int ret = 0;
+
+ temp_data = kzalloc(table_size, GFP_KERNEL);
+ if (!temp_data)
+ return -ENOMEM;
+
+ max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data,
+ table_size);
+ for (i = 0; i < table_size; i++)
+ if (temp_data[i])
+ ret = -EINVAL;
+
+ kfree(temp_data);
+ return ret;
+}
+
+static void max17042_write_config_regs(struct max17042_chip *chip)
+{
+ struct max17042_config_data *config = chip->pdata->config_data;
+
+ max17042_write_reg(chip->client, MAX17042_CONFIG, config->config);
+ max17042_write_reg(chip->client, MAX17042_LearnCFG, config->learn_cfg);
+ max17042_write_reg(chip->client, MAX17042_FilterCFG,
+ config->filter_cfg);
+ max17042_write_reg(chip->client, MAX17042_RelaxCFG, config->relax_cfg);
+}
+
+static void max17042_write_custom_regs(struct max17042_chip *chip)
+{
+ struct max17042_config_data *config = chip->pdata->config_data;
+
+ max17042_write_verify_reg(chip->client, MAX17042_RCOMP0,
+ config->rcomp0);
+ max17042_write_verify_reg(chip->client, MAX17042_TempCo,
+ config->tcompc0);
+ max17042_write_reg(chip->client, MAX17042_EmptyTempCo,
+ config->empty_tempco);
+ max17042_write_verify_reg(chip->client, MAX17042_K_empty0,
+ config->kempty0);
+ max17042_write_verify_reg(chip->client, MAX17042_ICHGTerm,
+ config->ichgt_term);
+}
+
+static void max17042_update_capacity_regs(struct max17042_chip *chip)
+{
+ struct max17042_config_data *config = chip->pdata->config_data;
+
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+ config->fullcap);
+ max17042_write_reg(chip->client, MAX17042_DesignCap,
+ config->design_cap);
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+ config->fullcapnom);
+}
+
+static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip)
+{
+ u16 vfSoc;
+
+ vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC);
+ max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK);
+ max17042_write_verify_reg(chip->client, MAX17042_VFSOC0, vfSoc);
+ max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK);
+}
+
+static void max17042_load_new_capacity_params(struct max17042_chip *chip)
+{
+ u16 full_cap0, rep_cap, dq_acc, vfSoc;
+ u32 rem_cap;
+
+ struct max17042_config_data *config = chip->pdata->config_data;
+
+ full_cap0 = max17042_read_reg(chip->client, MAX17042_FullCAP0);
+ vfSoc = max17042_read_reg(chip->client, MAX17042_VFSOC);
+
+ /* fg_vfSoc needs to shifted by 8 bits to get the
+ * perc in 1% accuracy, to get the right rem_cap multiply
+ * full_cap0, fg_vfSoc and devide by 100
+ */
+ rem_cap = ((vfSoc >> 8) * full_cap0) / 100;
+ max17042_write_verify_reg(chip->client, MAX17042_RemCap, (u16)rem_cap);
+
+ rep_cap = (u16)rem_cap;
+ max17042_write_verify_reg(chip->client, MAX17042_RepCap, rep_cap);
+
+ /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */
+ dq_acc = config->fullcap / dQ_ACC_DIV;
+ max17042_write_verify_reg(chip->client, MAX17042_dQacc, dq_acc);
+ max17042_write_verify_reg(chip->client, MAX17042_dPacc, dP_ACC_200);
+
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAP,
+ config->fullcap);
+ max17042_write_reg(chip->client, MAX17042_DesignCap,
+ config->design_cap);
+ max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom,
+ config->fullcapnom);
+}
+
+/*
+ * Block write all the override values coming from platform data.
+ * This function MUST be called before the POR initialization proceedure
+ * specified by maxim.
+ */
+static inline void max17042_override_por_values(struct max17042_chip *chip)
+{
+ struct i2c_client *client = chip->client;
+ struct max17042_config_data *config = chip->pdata->config_data;
+
+ max17042_override_por(client, MAX17042_TGAIN, config->tgain);
+ max17042_override_por(client, MAx17042_TOFF, config->toff);
+ max17042_override_por(client, MAX17042_CGAIN, config->cgain);
+ max17042_override_por(client, MAX17042_COFF, config->coff);
+
+ max17042_override_por(client, MAX17042_VALRT_Th, config->valrt_thresh);
+ max17042_override_por(client, MAX17042_TALRT_Th, config->talrt_thresh);
+ max17042_override_por(client, MAX17042_SALRT_Th,
+ config->soc_alrt_thresh);
+ max17042_override_por(client, MAX17042_CONFIG, config->config);
+ max17042_override_por(client, MAX17042_SHDNTIMER, config->shdntimer);
+
+ max17042_override_por(client, MAX17042_DesignCap, config->design_cap);
+ max17042_override_por(client, MAX17042_ICHGTerm, config->ichgt_term);
+
+ max17042_override_por(client, MAX17042_AtRate, config->at_rate);
+ max17042_override_por(client, MAX17042_LearnCFG, config->learn_cfg);
+ max17042_override_por(client, MAX17042_FilterCFG, config->filter_cfg);
+ max17042_override_por(client, MAX17042_RelaxCFG, config->relax_cfg);
+ max17042_override_por(client, MAX17042_MiscCFG, config->misc_cfg);
+ max17042_override_por(client, MAX17042_MaskSOC, config->masksoc);
+
+ max17042_override_por(client, MAX17042_FullCAP, config->fullcap);
+ max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom);
+ max17042_override_por(client, MAX17042_SOC_empty, config->socempty);
+ max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty);
+ max17042_override_por(client, MAX17042_dQacc, config->dqacc);
+ max17042_override_por(client, MAX17042_dPacc, config->dpacc);
+
+ max17042_override_por(client, MAX17042_V_empty, config->vempty);
+ max17042_override_por(client, MAX17042_TempNom, config->temp_nom);
+ max17042_override_por(client, MAX17042_TempLim, config->temp_lim);
+ max17042_override_por(client, MAX17042_FCTC, config->fctc);
+ max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0);
+ max17042_override_por(client, MAX17042_TempCo, config->tcompc0);
+ max17042_override_por(client, MAX17042_EmptyTempCo,
+ config->empty_tempco);
+ max17042_override_por(client, MAX17042_K_empty0, config->kempty0);
+}
+
+static int max17042_init_chip(struct max17042_chip *chip)
+{
+ int ret;
+ int val;
+
+ max17042_override_por_values(chip);
+ /* After Power up, the MAX17042 requires 500mS in order
+ * to perform signal debouncing and initial SOC reporting
+ */
+ msleep(500);
+
+ /* Initialize configaration */
+ max17042_write_config_regs(chip);
+
+ /* write cell characterization data */
+ ret = max17042_init_model(chip);
+ if (ret) {
+ dev_err(&chip->client->dev, "%s init failed\n",
+ __func__);
+ return -EIO;
+ }
+ max17042_verify_model_lock(chip);
+ if (ret) {
+ dev_err(&chip->client->dev, "%s lock verify failed\n",
+ __func__);
+ return -EIO;
+ }
+ /* write custom parameters */
+ max17042_write_custom_regs(chip);
+
+ /* update capacity params */
+ max17042_update_capacity_regs(chip);
+
+ /* delay must be atleast 350mS to allow VFSOC
+ * to be calculated from the new configuration
+ */
+ msleep(350);
+
+ /* reset vfsoc0 reg */
+ max17042_reset_vfsoc0_reg(chip);
+
+ /* load new capacity params */
+ max17042_load_new_capacity_params(chip);
+
+ /* Init complete, Clear the POR bit */
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ max17042_write_reg(chip->client, MAX17042_STATUS,
+ val & (~STATUS_POR_BIT));
+ return 0;
+}
+
+static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
+{
+ u16 soc, soc_tr;
+
+ /* program interrupt thesholds such that we should
+ * get interrupt for every 'off' perc change in the soc
+ */
+ soc = max17042_read_reg(chip->client, MAX17042_RepSOC) >> 8;
+ soc_tr = (soc + off) << 8;
+ soc_tr |= (soc - off);
+ max17042_write_reg(chip->client, MAX17042_SALRT_Th, soc_tr);
+}
+
+static irqreturn_t max17042_thread_handler(int id, void *dev)
+{
+ struct max17042_chip *chip = dev;
+ u16 val;
+
+ val = max17042_read_reg(chip->client, MAX17042_STATUS);
+ if ((val & STATUS_INTR_SOCMIN_BIT) ||
+ (val & STATUS_INTR_SOCMAX_BIT)) {
+ dev_info(&chip->client->dev, "SOC threshold INTR\n");
+ max17042_set_soc_threshold(chip, 1);
+ }
+
+ power_supply_changed(&chip->battery);
+ return IRQ_HANDLED;
+}
+
+static void max17042_init_worker(struct work_struct *work)
+{
+ struct max17042_chip *chip = container_of(work,
+ struct max17042_chip, work);
+ int ret;
+
+ /* Initialize registers according to values from the platform data */
+ if (chip->pdata->enable_por_init && chip->pdata->config_data) {
+ ret = max17042_init_chip(chip);
+ if (ret)
+ return;
+ }
+
+ chip->init_complete = 1;
+}
+
+#ifdef CONFIG_OF
+static struct max17042_platform_data *
+max17042_get_pdata(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ u32 prop;
+ struct max17042_platform_data *pdata;
+
+ if (!np)
+ return dev->platform_data;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ /*
+ * Require current sense resistor value to be specified for
+ * current-sense functionality to be enabled at all.
+ */
+ if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) {
+ pdata->r_sns = prop;
+ pdata->enable_current_sense = true;
+ }
+
+ return pdata;
+}
+#else
+static struct max17042_platform_data *
+max17042_get_pdata(struct device *dev)
+{
+ return dev->platform_data;
+}
+#endif
+
+static int __devinit max17042_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct max17042_chip *chip;
+ int ret;
+ int reg;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return -EIO;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->pdata = max17042_get_pdata(&client->dev);
+ if (!chip->pdata) {
+ dev_err(&client->dev, "no platform data provided\n");
+ return -EINVAL;
+ }
+
+ i2c_set_clientdata(client, chip);
+
+ 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);
+
+ /* When current is not measured,
+ * CURRENT_NOW and CURRENT_AVG properties should be invisible. */
+ if (!chip->pdata->enable_current_sense)
+ chip->battery.num_properties -= 2;
+
+ if (chip->pdata->r_sns == 0)
+ chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
+
+ if (chip->pdata->init_data)
+ max17042_set_reg(client, chip->pdata->init_data,
+ chip->pdata->num_init_data);
+
+ 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);
+ }
+
+ if (client->irq) {
+ ret = request_threaded_irq(client->irq, NULL,
+ max17042_thread_handler,
+ IRQF_TRIGGER_FALLING,
+ chip->battery.name, chip);
+ if (!ret) {
+ reg = max17042_read_reg(client, MAX17042_CONFIG);
+ reg |= CONFIG_ALRT_BIT_ENBL;
+ max17042_write_reg(client, MAX17042_CONFIG, reg);
+ max17042_set_soc_threshold(chip, 1);
+ } else
+ dev_err(&client->dev, "%s(): cannot get IRQ\n",
+ __func__);
+ }
+
+ reg = max17042_read_reg(chip->client, MAX17042_STATUS);
+
+ if (reg & STATUS_POR_BIT) {
+ INIT_WORK(&chip->work, max17042_init_worker);
+ schedule_work(&chip->work);
+ } else {
+ chip->init_complete = 1;
+ }
+
+ ret = power_supply_register(&client->dev, &chip->battery);
+ if (ret)
+ dev_err(&client->dev, "failed: power supply register\n");
+ return ret;
+}
+
+static int __devexit max17042_remove(struct i2c_client *client)
+{
+ struct max17042_chip *chip = i2c_get_clientdata(client);
+
+ power_supply_unregister(&chip->battery);
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id max17042_dt_match[] = {
+ { .compatible = "maxim,max17042" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, max17042_dt_match);
+#endif
+
+static const struct i2c_device_id max17042_id[] = {
+ { "max17042", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max17042_id);
+
+static struct i2c_driver max17042_i2c_driver = {
+ .driver = {
+ .name = "max17042",
+ .of_match_table = of_match_ptr(max17042_dt_match),
+ },
+ .probe = max17042_probe,
+ .remove = __devexit_p(max17042_remove),
+ .id_table = max17042_id,
+};
+module_i2c_driver(max17042_i2c_driver);
+
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_DESCRIPTION("MAX17042 Fuel Gauge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c
new file mode 100644
index 00000000..3e23f43e
--- /dev/null
+++ b/drivers/power/max8903_charger.c
@@ -0,0 +1,382 @@
+/*
+ * 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/module.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/power/max8903_charger.h>
+
+struct max8903_data {
+ struct max8903_pdata pdata;
+ struct device *dev;
+ struct power_supply psy;
+ bool fault;
+ bool usb_in;
+ bool ta_in;
+};
+
+static enum power_supply_property max8903_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS, /* Charger status output */
+ POWER_SUPPLY_PROP_ONLINE, /* External power source */
+ POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */
+};
+
+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)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (data->usb_in || data->ta_in)
+ val->intval = 1;
+ 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;
+
+ /* 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");
+
+ 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);
+
+ 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;
+
+ /* 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");
+
+ 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);
+
+ 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 device *dev = &pdev->dev;
+ struct max8903_pdata *pdata = pdev->dev.platform_data;
+ int ret = 0;
+ int gpio;
+ int ta_in = 0;
+ int usb_in = 0;
+
+ data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL);
+ if (data == NULL) {
+ dev_err(dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+ memcpy(&data->pdata, pdata, sizeof(struct max8903_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 {
+ dev_err(dev, "When DC is wired, DOK and DCM should"
+ " be wired as well.\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;
+ 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->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;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ data->fault = false;
+ data->ta_in = ta_in;
+ data->usb_in = usb_in;
+
+ data->psy.name = "max8903_charger";
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ 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);
+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);
+ power_supply_unregister(&data->psy);
+ 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,
+ },
+};
+
+module_platform_driver(max8903_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MAX8903 Charger Driver");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_ALIAS("platform:max8903-charger");
diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c
new file mode 100644
index 00000000..daa333bd
--- /dev/null
+++ b/drivers/power/max8925_power.c
@@ -0,0 +1,530 @@
+/*
+ * 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;
+ unsigned no_temp_support:1;
+ unsigned no_insert_detect:1;
+
+ 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 removed\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_cmd;
+ int meas_reg = 0, ret;
+
+ switch (type) {
+ case MEASURE_VCHG:
+ meas_cmd = MAX8925_CMD_VCHG;
+ meas_reg = MAX8925_ADC_VCHG;
+ break;
+ case MEASURE_VBBATT:
+ meas_cmd = MAX8925_CMD_VBBATT;
+ meas_reg = MAX8925_ADC_VBBATT;
+ break;
+ case MEASURE_VMBATT:
+ meas_cmd = MAX8925_CMD_VMBATT;
+ meas_reg = MAX8925_ADC_VMBATT;
+ break;
+ case MEASURE_ISNS:
+ meas_cmd = MAX8925_CMD_ISNS;
+ meas_reg = MAX8925_ADC_ISNS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ max8925_reg_write(info->adc, meas_cmd, 0);
+ max8925_bulk_read(info->adc, meas_reg, 2, buf);
+ ret = ((buf[0]<<8) | 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 * 2000; /* unit is uV */
+ 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 * 2000; /* unit is uV */
+ 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);
+ 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 * 2000; /* unit is uV */
+ 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) {
+ /* assume r_sns is 0.02 */
+ ret = ((ret * 6250) - 3125) /* uA */;
+ 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");
+ if (!info->no_insert_detect) {
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert");
+ }
+ if (!info->no_temp_support) {
+ 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->usb_online = 0;
+ info->bat_online = 0;
+
+ /* check for power - can miss interrupt at boot time */
+ if (start_measure(info, MEASURE_VCHG) * 2000 > 500000)
+ info->ac_online = 1;
+ else
+ info->ac_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;
+ info->ac.supplied_to = pdata->supplied_to;
+ info->ac.num_supplicants = pdata->num_supplicants;
+ 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;
+ info->usb.supplied_to = pdata->supplied_to;
+ info->usb.num_supplicants = pdata->num_supplicants;
+
+ 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;
+ info->no_temp_support = pdata->no_temp_support;
+ info->no_insert_detect = pdata->no_insert_detect;
+
+ 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",
+ },
+};
+
+module_platform_driver(max8925_power_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Power supply driver for MAX8925");
+MODULE_ALIAS("platform:max8925-power");
diff --git a/drivers/power/max8997_charger.c b/drivers/power/max8997_charger.c
new file mode 100644
index 00000000..6e88c5d0
--- /dev/null
+++ b/drivers/power/max8997_charger.c
@@ -0,0 +1,208 @@
+/*
+ * max8997_charger.c - Power supply consumer driver for the Maxim 8997/8966
+ *
+ * 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/err.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+struct charger_data {
+ struct device *dev;
+ struct max8997_dev *iodev;
+ struct power_supply battery;
+};
+
+static enum power_supply_property max8997_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS, /* "FULL" or "NOT FULL" only. */
+ POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */
+ POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */
+};
+
+/* Note that the charger control is done by a current regulator "CHARGER" */
+static int max8997_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct charger_data *charger = container_of(psy,
+ struct charger_data, battery);
+ struct i2c_client *i2c = charger->iodev->i2c;
+ int ret;
+ u8 reg;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = 0;
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &reg);
+ if (ret)
+ return ret;
+ if ((reg & (1 << 0)) == 0x1)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 0;
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &reg);
+ if (ret)
+ return ret;
+ if ((reg & (1 << 2)) == 0x0)
+ val->intval = 1;
+
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &reg);
+ if (ret)
+ return ret;
+ /* DCINOK */
+ if (reg & (1 << 1))
+ val->intval = 1;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static __devinit int max8997_battery_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct charger_data *charger;
+ struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev);
+
+ if (!pdata)
+ return -EINVAL;
+
+ if (pdata->eoc_mA) {
+ int val = (pdata->eoc_mA - 50) / 10;
+ if (val < 0)
+ val = 0;
+ if (val > 0xf)
+ val = 0xf;
+
+ ret = max8997_update_reg(iodev->i2c,
+ MAX8997_REG_MBCCTRL5, val, 0xf);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot use i2c bus.\n");
+ return ret;
+ }
+ }
+
+ switch (pdata->timeout) {
+ case 5:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x2 << 4, 0x7 << 4);
+ break;
+ case 6:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x3 << 4, 0x7 << 4);
+ break;
+ case 7:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x4 << 4, 0x7 << 4);
+ break;
+ case 0:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x7 << 4, 0x7 << 4);
+ break;
+ default:
+ dev_err(&pdev->dev, "incorrect timeout value (%d)\n",
+ pdata->timeout);
+ return -EINVAL;
+ }
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot use i2c bus.\n");
+ return ret;
+ }
+
+ charger = kzalloc(sizeof(struct charger_data), GFP_KERNEL);
+ if (charger == NULL) {
+ dev_err(&pdev->dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, charger);
+
+ charger->battery.name = "max8997_pmic";
+ charger->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ charger->battery.get_property = max8997_battery_get_property;
+ charger->battery.properties = max8997_battery_props;
+ charger->battery.num_properties = ARRAY_SIZE(max8997_battery_props);
+
+ charger->dev = &pdev->dev;
+ charger->iodev = iodev;
+
+ ret = power_supply_register(&pdev->dev, &charger->battery);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ goto err;
+ }
+
+ return 0;
+err:
+ kfree(charger);
+ return ret;
+}
+
+static int __devexit max8997_battery_remove(struct platform_device *pdev)
+{
+ struct charger_data *charger = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&charger->battery);
+ kfree(charger);
+ return 0;
+}
+
+static const struct platform_device_id max8997_battery_id[] = {
+ { "max8997-battery", 0 },
+ { }
+};
+
+static struct platform_driver max8997_battery_driver = {
+ .driver = {
+ .name = "max8997-battery",
+ .owner = THIS_MODULE,
+ },
+ .probe = max8997_battery_probe,
+ .remove = __devexit_p(max8997_battery_remove),
+ .id_table = max8997_battery_id,
+};
+
+static int __init max8997_battery_init(void)
+{
+ return platform_driver_register(&max8997_battery_driver);
+}
+subsys_initcall(max8997_battery_init);
+
+static void __exit max8997_battery_cleanup(void)
+{
+ platform_driver_unregister(&max8997_battery_driver);
+}
+module_exit(max8997_battery_cleanup);
+
+MODULE_DESCRIPTION("MAXIM 8997/8966 battery control driver");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/max8998_charger.c b/drivers/power/max8998_charger.c
new file mode 100644
index 00000000..6dc01c25
--- /dev/null
+++ b/drivers/power/max8998_charger.c
@@ -0,0 +1,211 @@
+/*
+ * max8998_charger.c - Power supply consumer driver for the Maxim 8998/LP3974
+ *
+ * Copyright (C) 2009-2010 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/err.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max8998.h>
+#include <linux/mfd/max8998-private.h>
+
+struct max8998_battery_data {
+ struct device *dev;
+ struct max8998_dev *iodev;
+ struct power_supply battery;
+};
+
+static enum power_supply_property max8998_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */
+ POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */
+};
+
+/* Note that the charger control is done by a current regulator "CHARGER" */
+static int max8998_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8998_battery_data *max8998 = container_of(psy,
+ struct max8998_battery_data, battery);
+ struct i2c_client *i2c = max8998->iodev->i2c;
+ int ret;
+ u8 reg;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, &reg);
+ if (ret)
+ return ret;
+ if (reg & (1 << 4))
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, &reg);
+ if (ret)
+ return ret;
+ if (reg & (1 << 3))
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static __devinit int max8998_battery_probe(struct platform_device *pdev)
+{
+ struct max8998_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max8998_platform_data *pdata = dev_get_platdata(iodev->dev);
+ struct max8998_battery_data *max8998;
+ struct i2c_client *i2c;
+ int ret = 0;
+
+ if (!pdata) {
+ dev_err(pdev->dev.parent, "No platform init data supplied\n");
+ return -ENODEV;
+ }
+
+ max8998 = kzalloc(sizeof(struct max8998_battery_data), GFP_KERNEL);
+ if (!max8998)
+ return -ENOMEM;
+
+ max8998->dev = &pdev->dev;
+ max8998->iodev = iodev;
+ platform_set_drvdata(pdev, max8998);
+ i2c = max8998->iodev->i2c;
+
+ /* Setup "End of Charge" */
+ /* If EOC value equals 0,
+ * remain value set from bootloader or default value */
+ if (pdata->eoc >= 10 && pdata->eoc <= 45) {
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1,
+ (pdata->eoc / 5 - 2) << 5, 0x7 << 5);
+ } else if (pdata->eoc == 0) {
+ dev_dbg(max8998->dev,
+ "EOC value not set: leave it unchanged.\n");
+ } else {
+ dev_err(max8998->dev, "Invalid EOC value\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* Setup Charge Restart Level */
+ switch (pdata->restart) {
+ case 100:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x1 << 3, 0x3 << 3);
+ break;
+ case 150:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x0 << 3, 0x3 << 3);
+ break;
+ case 200:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x2 << 3, 0x3 << 3);
+ break;
+ case -1:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x3 << 3, 0x3 << 3);
+ break;
+ case 0:
+ dev_dbg(max8998->dev,
+ "Restart Level not set: leave it unchanged.\n");
+ break;
+ default:
+ dev_err(max8998->dev, "Invalid Restart Level\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ /* Setup Charge Full Timeout */
+ switch (pdata->timeout) {
+ case 5:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x0 << 4, 0x3 << 4);
+ break;
+ case 6:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x1 << 4, 0x3 << 4);
+ break;
+ case 7:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x2 << 4, 0x3 << 4);
+ break;
+ case -1:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x3 << 4, 0x3 << 4);
+ break;
+ case 0:
+ dev_dbg(max8998->dev,
+ "Full Timeout not set: leave it unchanged.\n");
+ break;
+ default:
+ dev_err(max8998->dev, "Invalid Full Timeout value\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ max8998->battery.name = "max8998_pmic";
+ max8998->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ max8998->battery.get_property = max8998_battery_get_property;
+ max8998->battery.properties = max8998_battery_props;
+ max8998->battery.num_properties = ARRAY_SIZE(max8998_battery_props);
+
+ ret = power_supply_register(max8998->dev, &max8998->battery);
+ if (ret) {
+ dev_err(max8998->dev, "failed: power supply register\n");
+ goto err;
+ }
+
+ return 0;
+err:
+ kfree(max8998);
+ return ret;
+}
+
+static int __devexit max8998_battery_remove(struct platform_device *pdev)
+{
+ struct max8998_battery_data *max8998 = platform_get_drvdata(pdev);
+
+ power_supply_unregister(&max8998->battery);
+ kfree(max8998);
+
+ return 0;
+}
+
+static const struct platform_device_id max8998_battery_id[] = {
+ { "max8998-battery", TYPE_MAX8998 },
+ { }
+};
+
+static struct platform_driver max8998_battery_driver = {
+ .driver = {
+ .name = "max8998-battery",
+ .owner = THIS_MODULE,
+ },
+ .probe = max8998_battery_probe,
+ .remove = __devexit_p(max8998_battery_remove),
+ .id_table = max8998_battery_id,
+};
+
+module_platform_driver(max8998_battery_driver);
+
+MODULE_DESCRIPTION("MAXIM 8998 battery control driver");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:max8998-battery");
diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c
new file mode 100644
index 00000000..7385092f
--- /dev/null
+++ b/drivers/power/olpc_battery.c
@@ -0,0 +1,637 @@
+/*
+ * 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 power_supply olpc_bat = {
+ .name = "olpc-battery",
+ .get_property = olpc_bat_get_property,
+ .use_for_apm = 1,
+};
+
+static int olpc_battery_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ if (device_may_wakeup(olpc_ac.dev))
+ olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR);
+ else
+ olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR);
+
+ if (device_may_wakeup(olpc_bat.dev))
+ olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
+ | EC_SCI_SRC_BATERR);
+ else
+ olpc_ec_wakeup_clear(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
+ | EC_SCI_SRC_BATERR);
+
+ return 0;
+}
+
+static int __devinit olpc_battery_probe(struct platform_device *pdev)
+{
+ int ret;
+ uint8_t status;
+
+ /*
+ * 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 */
+
+ ret = power_supply_register(&pdev->dev, &olpc_ac);
+ if (ret)
+ return ret;
+
+ 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(&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;
+
+ if (olpc_ec_wakeup_available()) {
+ device_set_wakeup_capable(olpc_ac.dev, true);
+ device_set_wakeup_capable(olpc_bat.dev, true);
+ }
+
+ return 0;
+
+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);
+ return ret;
+}
+
+static int __devexit olpc_battery_remove(struct platform_device *pdev)
+{
+ 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);
+ return 0;
+}
+
+static const struct of_device_id olpc_battery_ids[] __devinitconst = {
+ { .compatible = "olpc,xo1-battery" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, olpc_battery_ids);
+
+static struct platform_driver olpc_battery_driver = {
+ .driver = {
+ .name = "olpc-battery",
+ .owner = THIS_MODULE,
+ .of_match_table = olpc_battery_ids,
+ },
+ .probe = olpc_battery_probe,
+ .remove = __devexit_p(olpc_battery_remove),
+ .suspend = olpc_battery_suspend,
+};
+
+module_platform_driver(olpc_battery_driver);
+
+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..3d1e9efb
--- /dev/null
+++ b/drivers/power/pcf50633-charger.c
@@ -0,0 +1,482 @@
+/* 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),
+};
+
+module_platform_driver(pcf50633_mbc_driver);
+
+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..214468f4
--- /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;
+
+#ifdef CONFIG_USB_OTG_UTILS
+static struct usb_phy *transceiver;
+static struct notifier_block otg_nb;
+#endif
+
+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);
+ }
+
+#ifdef CONFIG_USB_OTG_UTILS
+ transceiver = usb_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;
+ }
+#endif
+
+ 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;
+ }
+ }
+
+#ifdef CONFIG_USB_OTG_UTILS
+ if (transceiver && pdata->use_otg_notifier) {
+ otg_nb.notifier_call = otg_handle_notification;
+ ret = usb_register_notifier(transceiver, &otg_nb);
+ if (ret) {
+ dev_err(dev, "failure to register otg notifier\n");
+ goto otg_reg_notifier_failed;
+ }
+ polling = 0;
+ }
+#endif
+
+ 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;
+
+#ifdef CONFIG_USB_OTG_UTILS
+otg_reg_notifier_failed:
+ if (pdata->is_usb_online && usb_irq)
+ free_irq(usb_irq->start, &pda_psy_usb);
+#endif
+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);
+#ifdef CONFIG_USB_OTG_UTILS
+ if (transceiver)
+ usb_put_transceiver(transceiver);
+#endif
+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)
+ usb_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 */
+
+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,
+};
+
+module_platform_driver(pda_power_pdrv);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>");
+MODULE_ALIAS("platform:pda-power");
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..cc439fd8
--- /dev/null
+++ b/drivers/power/power_supply.h
@@ -0,0 +1,42 @@
+/*
+ * 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
+ */
+
+struct device;
+struct device_type;
+struct power_supply;
+
+#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..773fca15
--- /dev/null
+++ b/drivers/power/power_supply_core.c
@@ -0,0 +1,275 @@
+/*
+ * 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)
+ pm_relax(psy->dev);
+ 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;
+ pm_stay_awake(psy->dev);
+ 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_is_system_supplied(struct device *dev, void *data)
+{
+ union power_supply_propval ret = {0,};
+ struct power_supply *psy = dev_get_drvdata(dev);
+ unsigned int *count = data;
+
+ (*count)++;
+ 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;
+ unsigned int count = 0;
+
+ error = class_for_each_device(power_supply_class, NULL, &count,
+ __power_supply_is_system_supplied);
+
+ /*
+ * If no power class device was found at all, most probably we are
+ * running on a desktop system, so assume we are on mains power.
+ */
+ if (count == 0)
+ return 1;
+
+ 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);
+
+int power_supply_powers(struct power_supply *psy, struct device *dev)
+{
+ return sysfs_create_link(&psy->dev->kobj, &dev->kobj, "powers");
+}
+EXPORT_SYMBOL_GPL(power_supply_powers);
+
+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;
+
+ rc = device_add(dev);
+ if (rc)
+ goto device_add_failed;
+
+ spin_lock_init(&psy->changed_lock);
+ rc = device_init_wakeup(dev, true);
+ if (rc)
+ goto wakeup_init_failed;
+
+ rc = power_supply_create_triggers(psy);
+ if (rc)
+ goto create_triggers_failed;
+
+ power_supply_changed(psy);
+
+ goto success;
+
+create_triggers_failed:
+wakeup_init_failed:
+ 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);
+ sysfs_remove_link(&psy->dev->kobj, "powers");
+ power_supply_remove_triggers(psy);
+ 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..995f966e
--- /dev/null
+++ b/drivers/power/power_supply_leds.c
@@ -0,0 +1,180 @@
+/*
+ * 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/device.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..b20acfa9
--- /dev/null
+++ b/drivers/power/power_supply_sysfs.c
@@ -0,0 +1,317 @@
+/*
+ * 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/device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/stat.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[] = {
+ "Unknown", "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"
+ };
+ static char *scope_text[] = {
+ "Unknown", "System", "Device"
+ };
+ 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: %zd\n",
+ attr->attr.name, ret);
+ 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_SCOPE)
+ return sprintf(buf, "%s\n", scope_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),
+ POWER_SUPPLY_ATTR(scope),
+ /* Local extensions */
+ POWER_SUPPLY_ATTR(usb_hc),
+ POWER_SUPPLY_ATTR(usb_otg),
+ POWER_SUPPLY_ATTR(charge_enabled),
+ /* 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 umode_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);
+ umode_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/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c
new file mode 100644
index 00000000..8b804a56
--- /dev/null
+++ b/drivers/power/s3c_adc_battery.c
@@ -0,0 +1,447 @@
+/*
+ * 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 <linux/module.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 int gather_samples(struct s3c_adc_client *client, int num, int channel)
+{
+ int value, i;
+
+ /* default to 1 if nothing is set */
+ if (num < 1)
+ num = 1;
+
+ value = 0;
+ for (i = 0; i < num; i++)
+ value += s3c_adc_read(client, channel);
+ value /= num;
+
+ return value;
+}
+
+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 = gather_samples(bat->client,
+ bat->pdata->backup_volt_samples,
+ 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 = gather_samples(bat->client,
+ bat->pdata->volt_samples,
+ bat->pdata->volt_channel) * bat->pdata->volt_mult;
+ bat->cur_value = gather_samples(bat->client,
+ bat->pdata->current_samples,
+ 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 __devinit 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,
+};
+
+module_platform_driver(s3c_adc_bat_driver);
+
+MODULE_AUTHOR("Vasily Khoruzhick <anarsoul@gmail.com>");
+MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c
new file mode 100644
index 00000000..06b659d9
--- /dev/null
+++ b/drivers/power/sbs-battery.c
@@ -0,0 +1,858 @@
+/*
+ * Gas Gauge driver for SBS Compliant Batteries
+ *
+ * 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/sbs-battery.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 sbs_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 SBS_DATA(_psp, _addr, _min_value, _max_value) { \
+ .psp = _psp, \
+ .addr = _addr, \
+ .min_value = _min_value, \
+ .max_value = _max_value, \
+}
+
+static const struct chip_data {
+ enum power_supply_property psp;
+ u8 addr;
+ int min_value;
+ int max_value;
+} sbs_data[] = {
+ [REG_MANUFACTURER_DATA] =
+ SBS_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535),
+ [REG_TEMPERATURE] =
+ SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535),
+ [REG_VOLTAGE] =
+ SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000),
+ [REG_CURRENT] =
+ SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767),
+ [REG_CAPACITY] =
+ SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100),
+ [REG_REMAINING_CAPACITY] =
+ SBS_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535),
+ [REG_REMAINING_CAPACITY_CHARGE] =
+ SBS_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535),
+ [REG_FULL_CHARGE_CAPACITY] =
+ SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535),
+ [REG_FULL_CHARGE_CAPACITY_CHARGE] =
+ SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535),
+ [REG_TIME_TO_EMPTY] =
+ SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535),
+ [REG_TIME_TO_FULL] =
+ SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535),
+ [REG_STATUS] =
+ SBS_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535),
+ [REG_CYCLE_COUNT] =
+ SBS_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535),
+ [REG_DESIGN_CAPACITY] =
+ SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, 65535),
+ [REG_DESIGN_CAPACITY_CHARGE] =
+ SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, 65535),
+ [REG_DESIGN_VOLTAGE] =
+ SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, 65535),
+ [REG_SERIAL_NUMBER] =
+ SBS_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535),
+};
+
+static enum power_supply_property sbs_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 sbs_info {
+ struct i2c_client *client;
+ struct power_supply power_supply;
+ struct sbs_platform_data *pdata;
+ bool is_present;
+ bool gpio_detect;
+ bool enable_detection;
+ int irq;
+ int last_state;
+ int poll_time;
+ struct delayed_work work;
+ int ignore_changes;
+};
+
+static int sbs_read_word_data(struct i2c_client *client, u8 address)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+ s32 ret = 0;
+ int retries = 1;
+
+ if (chip->pdata)
+ retries = max(chip->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 sbs_write_word_data(struct i2c_client *client, u8 address,
+ u16 value)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+ s32 ret = 0;
+ int retries = 1;
+
+ if (chip->pdata)
+ retries = max(chip->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 sbs_get_battery_presence_and_health(
+ struct i2c_client *client, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ s32 ret;
+ struct sbs_info *chip = i2c_get_clientdata(client);
+
+ if (psp == POWER_SUPPLY_PROP_PRESENT &&
+ chip->gpio_detect) {
+ ret = gpio_get_value(chip->pdata->battery_detect);
+ if (ret == chip->pdata->battery_detect_present)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ chip->is_present = val->intval;
+ return ret;
+ }
+
+ /* Write to ManufacturerAccess with
+ * ManufacturerAccess command and then
+ * read the status */
+ ret = sbs_write_word_data(client, sbs_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 = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr);
+ if (ret < 0)
+ return ret;
+
+ if (ret < sbs_data[REG_MANUFACTURER_DATA].min_value ||
+ ret > sbs_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 sbs_get_battery_property(struct i2c_client *client,
+ int reg_offset, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+ s32 ret;
+
+ ret = sbs_read_word_data(client, sbs_data[reg_offset].addr);
+ if (ret < 0)
+ return ret;
+
+ /* returned values are 16 bit */
+ if (sbs_data[reg_offset].min_value < 0)
+ ret = (s16)ret;
+
+ if (ret >= sbs_data[reg_offset].min_value &&
+ ret <= sbs_data[reg_offset].max_value) {
+ val->intval = ret;
+ if (psp != POWER_SUPPLY_PROP_STATUS)
+ return 0;
+
+ 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;
+
+ if (chip->poll_time == 0)
+ chip->last_state = val->intval;
+ else if (chip->last_state != val->intval) {
+ cancel_delayed_work_sync(&chip->work);
+ power_supply_changed(&chip->power_supply);
+ chip->poll_time = 0;
+ }
+ } else {
+ if (psp == POWER_SUPPLY_PROP_STATUS)
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ else
+ val->intval = 0;
+ }
+
+ return 0;
+}
+
+static void sbs_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:
+ /* sbs 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:
+ /* sbs 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:
+ /* sbs 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 sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client,
+ enum sbs_battery_mode mode)
+{
+ int ret, original_val;
+
+ original_val = sbs_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 = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret);
+ if (ret < 0)
+ return ret;
+
+ return original_val & BATTERY_MODE_MASK;
+}
+
+static int sbs_get_battery_capacity(struct i2c_client *client,
+ int reg_offset, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ s32 ret;
+ enum sbs_battery_mode mode = BATTERY_MODE_WATTS;
+
+ if (power_supply_is_amp_property(psp))
+ mode = BATTERY_MODE_AMPS;
+
+ mode = sbs_set_battery_mode(client, mode);
+ if (mode < 0)
+ return mode;
+
+ ret = sbs_read_word_data(client, sbs_data[reg_offset].addr);
+ if (ret < 0)
+ return ret;
+
+ if (psp == POWER_SUPPLY_PROP_CAPACITY) {
+ /* sbs spec says that this can be >100 %
+ * even if max value is 100 % */
+ val->intval = min(ret, 100);
+ } else
+ val->intval = ret;
+
+ ret = sbs_set_battery_mode(client, mode);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static char sbs_serial[5];
+static int sbs_get_battery_serial_number(struct i2c_client *client,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(sbs_serial, "%04x", ret);
+ val->strval = sbs_serial;
+
+ return 0;
+}
+
+static int sbs_get_property_index(struct i2c_client *client,
+ enum power_supply_property psp)
+{
+ int count;
+ for (count = 0; count < ARRAY_SIZE(sbs_data); count++)
+ if (psp == sbs_data[count].psp)
+ return count;
+
+ dev_warn(&client->dev,
+ "%s: Invalid Property - %d\n", __func__, psp);
+
+ return -EINVAL;
+}
+
+static int sbs_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct sbs_info *chip = container_of(psy,
+ struct sbs_info, power_supply);
+ struct i2c_client *client = chip->client;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = sbs_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 = sbs_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = sbs_get_battery_capacity(client, ret, psp, val);
+ break;
+
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ ret = sbs_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 = sbs_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = sbs_get_battery_property(client, ret, psp, val);
+ break;
+
+ default:
+ dev_err(&client->dev,
+ "%s: INVALID property\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!chip->enable_detection)
+ goto done;
+
+ if (!chip->gpio_detect &&
+ chip->is_present != (ret >= 0)) {
+ chip->is_present = (ret >= 0);
+ power_supply_changed(&chip->power_supply);
+ }
+
+done:
+ if (!ret) {
+ /* Convert units to match requirements for power supply class */
+ sbs_unit_adjustment(client, psp, val);
+ }
+
+ dev_dbg(&client->dev,
+ "%s: property = %d, value = %x\n", __func__, psp, val->intval);
+
+ if (ret && chip->is_present)
+ return ret;
+
+ /* battery not present, so return NODATA for properties */
+ if (ret)
+ return -ENODATA;
+
+ return 0;
+}
+
+static irqreturn_t sbs_irq(int irq, void *devid)
+{
+ struct power_supply *battery = devid;
+
+ power_supply_changed(battery);
+
+ return IRQ_HANDLED;
+}
+
+static void sbs_external_power_changed(struct power_supply *psy)
+{
+ struct sbs_info *chip;
+
+ chip = container_of(psy, struct sbs_info, power_supply);
+
+ if (chip->ignore_changes > 0) {
+ chip->ignore_changes--;
+ return;
+ }
+
+ /* cancel outstanding work */
+ cancel_delayed_work_sync(&chip->work);
+
+ schedule_delayed_work(&chip->work, HZ);
+ chip->poll_time = chip->pdata->poll_retry_count;
+}
+
+static void sbs_delayed_work(struct work_struct *work)
+{
+ struct sbs_info *chip;
+ s32 ret;
+
+ chip = container_of(work, struct sbs_info, work.work);
+
+ ret = sbs_read_word_data(chip->client, sbs_data[REG_STATUS].addr);
+ /* if the read failed, give up on this work */
+ if (ret < 0) {
+ chip->poll_time = 0;
+ return;
+ }
+
+ if (ret & BATTERY_FULL_CHARGED)
+ ret = POWER_SUPPLY_STATUS_FULL;
+ else if (ret & BATTERY_FULL_DISCHARGED)
+ ret = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (ret & BATTERY_DISCHARGING)
+ ret = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ ret = POWER_SUPPLY_STATUS_CHARGING;
+
+ if (chip->last_state != ret) {
+ chip->poll_time = 0;
+ power_supply_changed(&chip->power_supply);
+ return;
+ }
+ if (chip->poll_time > 0) {
+ schedule_delayed_work(&chip->work, HZ);
+ chip->poll_time--;
+ return;
+ }
+}
+
+#if defined(CONFIG_OF)
+
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+
+static const struct of_device_id sbs_dt_ids[] = {
+ { .compatible = "sbs,sbs-battery" },
+ { .compatible = "ti,bq20z75" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sbs_dt_ids);
+
+static struct sbs_platform_data *sbs_of_populate_pdata(
+ struct i2c_client *client)
+{
+ struct device_node *of_node = client->dev.of_node;
+ struct sbs_platform_data *pdata = client->dev.platform_data;
+ enum of_gpio_flags gpio_flags;
+ int rc;
+ u32 prop;
+
+ /* verify this driver matches this device */
+ if (!of_node)
+ return NULL;
+
+ /* if platform data is set, honor it */
+ if (pdata)
+ return pdata;
+
+ /* first make sure at least one property is set, otherwise
+ * it won't change behavior from running without pdata.
+ */
+ if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) &&
+ !of_get_property(of_node, "sbs,poll-retry-count", NULL) &&
+ !of_get_property(of_node, "sbs,battery-detect-gpios", NULL))
+ goto of_out;
+
+ pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data),
+ GFP_KERNEL);
+ if (!pdata)
+ goto of_out;
+
+ rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop);
+ if (!rc)
+ pdata->i2c_retry_count = prop;
+
+ rc = of_property_read_u32(of_node, "sbs,poll-retry-count", &prop);
+ if (!rc)
+ pdata->poll_retry_count = prop;
+
+ if (!of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) {
+ pdata->battery_detect = -1;
+ goto of_out;
+ }
+
+ pdata->battery_detect = of_get_named_gpio_flags(of_node,
+ "sbs,battery-detect-gpios", 0, &gpio_flags);
+
+ if (gpio_flags & OF_GPIO_ACTIVE_LOW)
+ pdata->battery_detect_present = 0;
+ else
+ pdata->battery_detect_present = 1;
+
+of_out:
+ return pdata;
+}
+#else
+#define sbs_dt_ids NULL
+static struct sbs_platform_data *sbs_of_populate_pdata(
+ struct i2c_client *client)
+{
+ return client->dev.platform_data;
+}
+#endif
+
+static int __devinit sbs_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sbs_info *chip;
+ struct sbs_platform_data *pdata = client->dev.platform_data;
+ int rc;
+ int irq;
+ char *name;
+
+ name = kasprintf(GFP_KERNEL, "sbs-%s", dev_name(&client->dev));
+ if (!name) {
+ dev_err(&client->dev, "Failed to allocate device name\n");
+ return -ENOMEM;
+ }
+
+ chip = kzalloc(sizeof(struct sbs_info), GFP_KERNEL);
+ if (!chip) {
+ rc = -ENOMEM;
+ goto exit_free_name;
+ }
+
+ chip->client = client;
+ chip->enable_detection = false;
+ chip->gpio_detect = false;
+ chip->power_supply.name = name;
+ chip->power_supply.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->power_supply.properties = sbs_properties;
+ chip->power_supply.num_properties = ARRAY_SIZE(sbs_properties);
+ chip->power_supply.get_property = sbs_get_property;
+ /* ignore first notification of external change, it is generated
+ * from the power_supply_register call back
+ */
+ chip->ignore_changes = 1;
+ chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
+ chip->power_supply.external_power_changed = sbs_external_power_changed;
+
+ pdata = sbs_of_populate_pdata(client);
+
+ if (pdata) {
+ chip->gpio_detect = gpio_is_valid(pdata->battery_detect);
+ chip->pdata = pdata;
+ }
+
+ i2c_set_clientdata(client, chip);
+
+ if (!chip->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);
+ chip->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);
+ chip->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);
+ chip->gpio_detect = false;
+ goto skip_gpio;
+ }
+
+ rc = request_irq(irq, sbs_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(&client->dev), &chip->power_supply);
+ if (rc) {
+ dev_warn(&client->dev, "Failed to request irq: %d\n", rc);
+ gpio_free(pdata->battery_detect);
+ chip->gpio_detect = false;
+ goto skip_gpio;
+ }
+
+ chip->irq = irq;
+
+skip_gpio:
+
+ rc = power_supply_register(&client->dev, &chip->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);
+
+ INIT_DELAYED_WORK(&chip->work, sbs_delayed_work);
+
+ chip->enable_detection = true;
+
+ return 0;
+
+exit_psupply:
+ if (chip->irq)
+ free_irq(chip->irq, &chip->power_supply);
+ if (chip->gpio_detect)
+ gpio_free(pdata->battery_detect);
+
+ kfree(chip);
+
+exit_free_name:
+ kfree(name);
+
+ return rc;
+}
+
+static int __devexit sbs_remove(struct i2c_client *client)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+
+ if (chip->irq)
+ free_irq(chip->irq, &chip->power_supply);
+ if (chip->gpio_detect)
+ gpio_free(chip->pdata->battery_detect);
+
+ power_supply_unregister(&chip->power_supply);
+
+ cancel_delayed_work_sync(&chip->work);
+
+ kfree(chip->power_supply.name);
+ kfree(chip);
+ chip = NULL;
+
+ return 0;
+}
+
+#if defined CONFIG_PM
+static int sbs_suspend(struct i2c_client *client,
+ pm_message_t state)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+ s32 ret;
+
+ if (chip->poll_time > 0)
+ cancel_delayed_work_sync(&chip->work);
+
+ /* write to manufacturer access with sleep command */
+ ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr,
+ MANUFACTURER_ACCESS_SLEEP);
+ if (chip->is_present && ret < 0)
+ return ret;
+
+ return 0;
+}
+#else
+#define sbs_suspend NULL
+#endif
+/* any smbus transaction will wake up sbs */
+#define sbs_resume NULL
+
+static const struct i2c_device_id sbs_id[] = {
+ { "bq20z75", 0 },
+ { "sbs-battery", 1 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, sbs_id);
+
+static struct i2c_driver sbs_battery_driver = {
+ .probe = sbs_probe,
+ .remove = __devexit_p(sbs_remove),
+ .suspend = sbs_suspend,
+ .resume = sbs_resume,
+ .id_table = sbs_id,
+ .driver = {
+ .name = "sbs-battery",
+ .of_match_table = sbs_dt_ids,
+ },
+};
+module_i2c_driver(sbs_battery_driver);
+
+MODULE_DESCRIPTION("SBS battery monitor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c
new file mode 100644
index 00000000..fb56ec75
--- /dev/null
+++ b/drivers/power/smb347-charger.c
@@ -0,0 +1,1600 @@
+/*
+ * Summit Microelectronics SMB347 Battery Charger Driver
+ *
+ * Copyright (C) 2011, Intel Corporation
+ *
+ * Authors: Bruce E. Robertson <bruce.e.robertson@intel.com>
+ * Mika Westerberg <mika.westerberg@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/power/smb347-charger.h>
+#include <linux/seq_file.h>
+#include <linux/delay.h>
+
+/*
+ * Configuration registers. These are mirrored to volatile RAM and can be
+ * written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be
+ * reloaded from non-volatile registers after POR.
+ */
+#define CFG_CHARGE_CURRENT 0x00
+#define CFG_CHARGE_CURRENT_FCC_MASK 0xe0
+#define CFG_CHARGE_CURRENT_FCC_SHIFT 5
+#define CFG_CHARGE_CURRENT_PCC_MASK 0x18
+#define CFG_CHARGE_CURRENT_PCC_SHIFT 3
+#define CFG_CHARGE_CURRENT_TC_MASK 0x07
+#define CFG_CURRENT_LIMIT 0x01
+#define CFG_CURRENT_LIMIT_DC_MASK 0xf0
+#define CFG_CURRENT_LIMIT_DC_SHIFT 4
+#define CFG_CURRENT_LIMIT_USB_MASK 0x0f
+#define CFG_VARIOUS_FUNCTION 0x02
+#define CFG_INPUT_SOURCE_PRIORITY BIT(2)
+#define CFG_FLOAT_VOLTAGE 0x03
+#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0
+#define CFG_FLOAT_VOLTAGE_MASK 0x3F
+#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6
+#define CFG_CHARGE_CONTROL 0x04
+#define CFG_AUTOMATIC_RECHARGE_DISABLE BIT(7)
+#define CFG_STAT 0x05
+#define CFG_STAT_DISABLED BIT(5)
+#define CFG_STAT_ACTIVE_HIGH BIT(7)
+#define CFG_PIN 0x06
+#define CFG_PIN_EN_CTRL_MASK 0x60
+#define CFG_PIN_USB_MODE_CTRL BIT(4)
+#define CFG_PIN_EN_CTRL_ACTIVE_HIGH 0x40
+#define CFG_PIN_EN_CTRL_ACTIVE_LOW 0x60
+#define CFG_PIN_EN_APSD_IRQ BIT(1)
+#define CFG_PIN_EN_CHARGER_ERROR BIT(2)
+#define CFG_THERM 0x07
+#define CFG_THERM_SOFT_HOT_COMPENSATION_MASK 0x03
+#define CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT 0
+#define CFG_THERM_SOFT_COLD_COMPENSATION_MASK 0x0c
+#define CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT 2
+#define CFG_THERM_MONITOR_DISABLED BIT(4)
+#define CFG_SYSOK 0x08
+#define CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED BIT(2)
+#define CFG_OTHER 0x09
+#define CFG_OTHER_RID_MASK 0xc0
+#define CFG_OTHER_RID_ENABLED_AUTO_OTG 0xc0
+#define CFG_OTG 0x0a
+#define CFG_OTG_TEMP_THRESHOLD_MASK 0x30
+#define CFG_OTG_TEMP_THRESHOLD_SHIFT 4
+#define CFG_OTG_CC_COMPENSATION_MASK 0xc0
+#define CFG_OTG_CC_COMPENSATION_SHIFT 6
+#define CFG_TEMP_LIMIT 0x0b
+#define CFG_TEMP_LIMIT_SOFT_HOT_MASK 0x03
+#define CFG_TEMP_LIMIT_SOFT_HOT_SHIFT 0
+#define CFG_TEMP_LIMIT_SOFT_COLD_MASK 0x0c
+#define CFG_TEMP_LIMIT_SOFT_COLD_SHIFT 2
+#define CFG_TEMP_LIMIT_HARD_HOT_MASK 0x30
+#define CFG_TEMP_LIMIT_HARD_HOT_SHIFT 4
+#define CFG_TEMP_LIMIT_HARD_COLD_MASK 0xc0
+#define CFG_TEMP_LIMIT_HARD_COLD_SHIFT 6
+#define CFG_FAULT_IRQ 0x0c
+#define CFG_FAULT_IRQ_DCIN_UV BIT(2)
+#define CFG_STATUS_IRQ 0x0d
+#define CFG_STATUS_IRQ_TERMINATION_OR_TAPER BIT(4)
+#define CFG_ADDRESS 0x0e
+
+/* Command registers */
+#define CMD_A 0x30
+#define CMD_A_CHG_ENABLED BIT(1)
+#define CMD_A_SUSPEND_ENABLED BIT(2)
+#define CMD_A_OTG_ENABLE BIT(4)
+#define CMD_A_ALLOW_WRITE BIT(7)
+#define CMD_B 0x31
+#define CMD_B_POR BIT(7)
+#define CMD_B_USB59_MODE BIT(1)
+#define CMD_B_HC_MODE BIT(0)
+#define CMD_C 0x33
+
+/* Interrupt Status registers */
+#define IRQSTAT_A 0x35
+#define IRQSTAT_C 0x37
+#define IRQSTAT_C_TERMINATION_STAT BIT(0)
+#define IRQSTAT_C_TERMINATION_IRQ BIT(1)
+#define IRQSTAT_C_TAPER_IRQ BIT(3)
+#define IRQSTAT_E 0x39
+#define IRQSTAT_E_USBIN_UV_STAT BIT(0)
+#define IRQSTAT_E_USBIN_UV_IRQ BIT(1)
+#define IRQSTAT_E_DCIN_UV_STAT BIT(4)
+#define IRQSTAT_E_DCIN_UV_IRQ BIT(5)
+#define IRQSTAT_F 0x3a
+
+/* Status registers */
+#define STAT_A 0x3b
+#define STAT_A_FLOAT_VOLTAGE_MASK 0x3f
+#define STAT_B 0x3c
+#define STAT_C 0x3d
+#define STAT_C_CHG_ENABLED BIT(0)
+#define STAT_C_CHG_STATUS BIT(5)
+#define STAT_C_CHG_MASK 0x06
+#define STAT_C_CHG_SHIFT 1
+#define STAT_C_CHARGER_ERROR BIT(6)
+#define STAT_E 0x3f
+
+/**
+ * struct smb347_charger - smb347 charger instance
+ * @lock: protects concurrent access to online variables
+ * @client: pointer to i2c client
+ * @mains: power_supply instance for AC/DC power
+ * @usb: power_supply instance for USB power
+ * @battery: power_supply instance for battery
+ * @mains_online: is AC/DC input connected
+ * @usb_online: is USB input connected
+ * @charging_enabled: is charging enabled
+ * @dentry: for debugfs
+ * @pdata: pointer to platform data
+ */
+struct smb347_charger {
+ struct mutex lock;
+ struct i2c_client *client;
+ struct power_supply mains;
+ struct power_supply usb;
+ struct power_supply battery;
+ bool mains_online;
+ bool usb_online;
+ bool charging_enabled;
+ unsigned int mains_current_limit;
+ bool usb_hc_mode;
+ bool usb_otg_enabled;
+ bool is_fully_charged;
+ int en_gpio;
+ struct dentry *dentry;
+ const struct smb347_charger_platform_data *pdata;
+};
+
+/* Fast charge current in uA */
+static const unsigned int fcc_tbl[] = {
+ 700000,
+ 900000,
+ 1200000,
+ 1500000,
+ 1800000,
+ 2000000,
+ 2200000,
+ 2500000,
+};
+
+/* Pre-charge current in uA */
+static const unsigned int pcc_tbl[] = {
+ 100000,
+ 150000,
+ 200000,
+ 250000,
+};
+
+/* Termination current in uA */
+static const unsigned int tc_tbl[] = {
+ 37500,
+ 50000,
+ 100000,
+ 150000,
+ 200000,
+ 250000,
+ 500000,
+ 600000,
+};
+
+/* Input current limit in uA */
+static const unsigned int icl_tbl[] = {
+ 300000,
+ 500000,
+ 700000,
+ 900000,
+ 1200000,
+ 1500000,
+ 1800000,
+ 2000000,
+ 2200000,
+ 2500000,
+};
+
+/* Charge current compensation in uA */
+static const unsigned int ccc_tbl[] = {
+ 250000,
+ 700000,
+ 900000,
+ 1200000,
+};
+
+/* Convert register value to current using lookup table */
+static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val)
+{
+ if (val >= size)
+ return -EINVAL;
+ return tbl[val];
+}
+
+/* Convert current to register value using lookup table */
+static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ if (val < tbl[i])
+ break;
+ return i > 0 ? i - 1 : -EINVAL;
+}
+
+static int smb347_read(struct smb347_charger *smb, u8 reg)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(smb->client, reg);
+ if (ret < 0)
+ dev_warn(&smb->client->dev, "failed to read reg 0x%x: %d\n",
+ reg, ret);
+ return ret;
+}
+
+static int smb347_write(struct smb347_charger *smb, u8 reg, u8 val)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(smb->client, reg, val);
+ if (ret < 0)
+ dev_warn(&smb->client->dev, "failed to write reg 0x%x: %d\n",
+ reg, ret);
+ return ret;
+}
+
+/**
+ * smb347_update_status - updates the charging status
+ * @smb: pointer to smb347 charger instance
+ *
+ * Function checks status of the charging and updates internal state
+ * accordingly. Returns %0 if there is no change in status, %1 if the
+ * status has changed and negative errno in case of failure.
+ */
+static int smb347_update_status(struct smb347_charger *smb)
+{
+ bool usb = false;
+ bool dc = false;
+ int ret;
+
+ ret = smb347_read(smb, IRQSTAT_E);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Dc and usb are set depending on whether they are enabled in
+ * platform data _and_ whether corresponding undervoltage is set.
+ */
+ if (smb->pdata->use_mains)
+ dc = !(ret & IRQSTAT_E_DCIN_UV_STAT);
+ if (smb->pdata->use_usb)
+ usb = !(ret & IRQSTAT_E_USBIN_UV_STAT);
+
+ mutex_lock(&smb->lock);
+ ret = smb->mains_online != dc || smb->usb_online != usb;
+ smb->mains_online = dc;
+ smb->usb_online = usb;
+ mutex_unlock(&smb->lock);
+
+ return ret;
+}
+
+/*
+ * smb347_is_online - returns whether input power source is connected
+ * @smb: pointer to smb347 charger instance
+ *
+ * Returns %true if input power source is connected. Note that this is
+ * dependent on what platform has configured for usable power sources. For
+ * example if USB is disabled, this will return %false even if the USB
+ * cable is connected.
+ */
+static bool smb347_is_online(struct smb347_charger *smb)
+{
+ bool ret;
+
+ mutex_lock(&smb->lock);
+ ret = smb->usb_online || smb->mains_online;
+ mutex_unlock(&smb->lock);
+
+ return ret;
+}
+
+/**
+ * smb347_charging_status - returns status of charging
+ * @smb: pointer to smb347 charger instance
+ *
+ * Function returns charging status. %0 means no charging is in progress,
+ * %1 means pre-charging, %2 fast-charging and %3 taper-charging.
+ */
+static int smb347_charging_status(struct smb347_charger *smb)
+{
+ int ret;
+
+ if (!smb347_is_online(smb))
+ return 0;
+
+ ret = smb347_read(smb, STAT_C);
+ if (ret < 0)
+ return 0;
+
+ return (ret & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT;
+}
+
+static int smb347_charging_set(struct smb347_charger *smb, bool enable)
+{
+ int ret = 0;
+
+ if (enable && !smb->charging_enabled)
+ smb->is_fully_charged = false;
+
+ if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) {
+ smb->charging_enabled = enable;
+
+ if (smb->en_gpio)
+ gpio_set_value(
+ smb->en_gpio,
+ (smb->pdata->enable_control ==
+ SMB347_CHG_ENABLE_PIN_ACTIVE_LOW) ^ enable);
+ return 0;
+ }
+
+ mutex_lock(&smb->lock);
+ if (smb->charging_enabled != enable) {
+ ret = smb347_read(smb, CMD_A);
+ if (ret < 0)
+ goto out;
+
+ smb->charging_enabled = enable;
+
+ if (enable)
+ ret |= CMD_A_CHG_ENABLED;
+ else
+ ret &= ~CMD_A_CHG_ENABLED;
+
+ ret = smb347_write(smb, CMD_A, ret);
+ }
+out:
+ mutex_unlock(&smb->lock);
+ return ret;
+}
+
+static inline int smb347_charging_enable(struct smb347_charger *smb)
+{
+ return smb347_charging_set(smb, true);
+}
+
+static inline int smb347_charging_disable(struct smb347_charger *smb)
+{
+ return smb347_charging_set(smb, false);
+}
+
+static int smb347_update_online(struct smb347_charger *smb)
+{
+ int ret;
+
+ /*
+ * Depending on whether valid power source is connected or not, we
+ * disable or enable the charging. We do it manually because it
+ * depends on how the platform has configured the valid inputs.
+ */
+ if (smb347_is_online(smb)) {
+ ret = smb347_charging_enable(smb);
+ if (ret < 0)
+ dev_err(&smb->client->dev,
+ "failed to enable charging\n");
+ } else {
+ ret = smb347_charging_disable(smb);
+ if (ret < 0)
+ dev_err(&smb->client->dev,
+ "failed to disable charging\n");
+ }
+
+ return ret;
+}
+
+static int smb347_set_charge_current(struct smb347_charger *smb)
+{
+ int ret, val;
+
+ ret = smb347_read(smb, CFG_CHARGE_CURRENT);
+ if (ret < 0)
+ return ret;
+
+ if (smb->pdata->max_charge_current) {
+ val = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl),
+ smb->pdata->max_charge_current);
+ if (val < 0)
+ return val;
+
+ ret &= ~CFG_CHARGE_CURRENT_FCC_MASK;
+ ret |= val << CFG_CHARGE_CURRENT_FCC_SHIFT;
+ }
+
+ if (smb->pdata->pre_charge_current) {
+ val = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl),
+ smb->pdata->pre_charge_current);
+ if (val < 0)
+ return val;
+
+ ret &= ~CFG_CHARGE_CURRENT_PCC_MASK;
+ ret |= val << CFG_CHARGE_CURRENT_PCC_SHIFT;
+ }
+
+ if (smb->pdata->termination_current) {
+ val = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl),
+ smb->pdata->termination_current);
+ if (val < 0)
+ return val;
+
+ ret &= ~CFG_CHARGE_CURRENT_TC_MASK;
+ ret |= val;
+ }
+
+ return smb347_write(smb, CFG_CHARGE_CURRENT, ret);
+}
+
+static int smb347_set_current_limits(struct smb347_charger *smb)
+{
+ int ret, val;
+
+ ret = smb347_read(smb, CFG_CURRENT_LIMIT);
+ if (ret < 0)
+ return ret;
+
+ if (smb->mains_current_limit) {
+ val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
+ smb->mains_current_limit);
+ if (val < 0)
+ return val;
+
+ ret &= ~CFG_CURRENT_LIMIT_DC_MASK;
+ ret |= val << CFG_CURRENT_LIMIT_DC_SHIFT;
+ }
+
+ if (smb->pdata->usb_hc_current_limit) {
+ val = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
+ smb->pdata->usb_hc_current_limit);
+ if (val < 0)
+ return val;
+
+ ret &= ~CFG_CURRENT_LIMIT_USB_MASK;
+ ret |= val;
+ }
+
+ return smb347_write(smb, CFG_CURRENT_LIMIT, ret);
+}
+
+static int smb347_set_voltage_limits(struct smb347_charger *smb)
+{
+ int ret, val;
+
+ ret = smb347_read(smb, CFG_FLOAT_VOLTAGE);
+ if (ret < 0)
+ return ret;
+
+ if (smb->pdata->pre_to_fast_voltage) {
+ val = smb->pdata->pre_to_fast_voltage;
+
+ /* uV */
+ val = clamp_val(val, 2400000, 3000000) - 2400000;
+ val /= 200000;
+
+ ret &= ~CFG_FLOAT_VOLTAGE_THRESHOLD_MASK;
+ ret |= val << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT;
+ }
+
+ if (smb->pdata->max_charge_voltage) {
+ val = smb->pdata->max_charge_voltage;
+
+ /* uV */
+ val = clamp_val(val, 3500000, 4500000) - 3500000;
+ val /= 20000;
+
+ ret &= ~CFG_FLOAT_VOLTAGE_MASK;
+ ret |= val;
+ }
+
+ return smb347_write(smb, CFG_FLOAT_VOLTAGE, ret);
+}
+
+static int smb347_set_temp_limits(struct smb347_charger *smb)
+{
+ bool enable_therm_monitor = false;
+ int ret, val;
+
+ if (smb->pdata->chip_temp_threshold) {
+ val = smb->pdata->chip_temp_threshold;
+
+ /* degree C */
+ val = clamp_val(val, 100, 130) - 100;
+ val /= 10;
+
+ ret = smb347_read(smb, CFG_OTG);
+ if (ret < 0)
+ return ret;
+
+ ret &= ~CFG_OTG_TEMP_THRESHOLD_MASK;
+ ret |= val << CFG_OTG_TEMP_THRESHOLD_SHIFT;
+
+ ret = smb347_write(smb, CFG_OTG, ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = smb347_read(smb, CFG_TEMP_LIMIT);
+ if (ret < 0)
+ return ret;
+
+ if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+ val = smb->pdata->soft_cold_temp_limit;
+
+ val = clamp_val(val, 0, 15);
+ val /= 5;
+ /* this goes from higher to lower so invert the value */
+ val = ~val & 0x3;
+
+ ret &= ~CFG_TEMP_LIMIT_SOFT_COLD_MASK;
+ ret |= val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT;
+
+ enable_therm_monitor = true;
+ }
+
+ if (smb->pdata->soft_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+ val = smb->pdata->soft_hot_temp_limit;
+
+ val = clamp_val(val, 40, 55) - 40;
+ val /= 5;
+
+ ret &= ~CFG_TEMP_LIMIT_SOFT_HOT_MASK;
+ ret |= val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT;
+
+ enable_therm_monitor = true;
+ }
+
+ if (smb->pdata->hard_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+ val = smb->pdata->hard_cold_temp_limit;
+
+ val = clamp_val(val, -5, 10) + 5;
+ val /= 5;
+ /* this goes from higher to lower so invert the value */
+ val = ~val & 0x3;
+
+ ret &= ~CFG_TEMP_LIMIT_HARD_COLD_MASK;
+ ret |= val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT;
+
+ enable_therm_monitor = true;
+ }
+
+ if (smb->pdata->hard_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+ val = smb->pdata->hard_hot_temp_limit;
+
+ val = clamp_val(val, 50, 65) - 50;
+ val /= 5;
+
+ ret &= ~CFG_TEMP_LIMIT_HARD_HOT_MASK;
+ ret |= val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT;
+
+ enable_therm_monitor = true;
+ }
+
+ ret = smb347_write(smb, CFG_TEMP_LIMIT, ret);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * If any of the temperature limits are set, we also enable the
+ * thermistor monitoring.
+ *
+ * When soft limits are hit, the device will start to compensate
+ * current and/or voltage depending on the configuration.
+ *
+ * When hard limit is hit, the device will suspend charging
+ * depending on the configuration.
+ */
+ if (enable_therm_monitor) {
+ ret = smb347_read(smb, CFG_THERM);
+ if (ret < 0)
+ return ret;
+
+ ret &= ~CFG_THERM_MONITOR_DISABLED;
+
+ ret = smb347_write(smb, CFG_THERM, ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->suspend_on_hard_temp_limit) {
+ ret = smb347_read(smb, CFG_SYSOK);
+ if (ret < 0)
+ return ret;
+
+ ret &= ~CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED;
+
+ ret = smb347_write(smb, CFG_SYSOK, ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->soft_temp_limit_compensation !=
+ SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) {
+ val = smb->pdata->soft_temp_limit_compensation & 0x3;
+
+ ret = smb347_read(smb, CFG_THERM);
+ if (ret < 0)
+ return ret;
+
+ ret &= ~CFG_THERM_SOFT_HOT_COMPENSATION_MASK;
+ ret |= val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT;
+
+ ret &= ~CFG_THERM_SOFT_COLD_COMPENSATION_MASK;
+ ret |= val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT;
+
+ ret = smb347_write(smb, CFG_THERM, ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->charge_current_compensation) {
+ val = current_to_hw(ccc_tbl, ARRAY_SIZE(ccc_tbl),
+ smb->pdata->charge_current_compensation);
+ if (val < 0)
+ return val;
+
+ ret = smb347_read(smb, CFG_OTG);
+ if (ret < 0)
+ return ret;
+
+ ret &= ~CFG_OTG_CC_COMPENSATION_MASK;
+ ret |= (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT;
+
+ ret = smb347_write(smb, CFG_OTG, ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * smb347_set_writable - enables/disables writing to non-volatile registers
+ * @smb: pointer to smb347 charger instance
+ *
+ * You can enable/disable writing to the non-volatile configuration
+ * registers by calling this function.
+ *
+ * Returns %0 on success and negative errno in case of failure.
+ */
+static int smb347_set_writable(struct smb347_charger *smb, bool writable)
+{
+ int ret;
+
+ ret = smb347_read(smb, CMD_A);
+ if (ret < 0)
+ return ret;
+
+ if (writable)
+ ret |= CMD_A_ALLOW_WRITE;
+ else
+ ret &= ~CMD_A_ALLOW_WRITE;
+
+ return smb347_write(smb, CMD_A, ret);
+}
+
+static int smb347_irq_set(struct smb347_charger *smb, bool enable)
+{
+ int ret;
+
+ ret = smb347_set_writable(smb, true);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Enable/disable interrupts for:
+ * - under voltage
+ * - termination current reached
+ * - charger error
+ */
+ if (enable) {
+ ret = smb347_write(smb, CFG_FAULT_IRQ, CFG_FAULT_IRQ_DCIN_UV);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_write(smb, CFG_STATUS_IRQ,
+ CFG_STATUS_IRQ_TERMINATION_OR_TAPER);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_read(smb, CFG_PIN);
+ if (ret < 0)
+ goto fail;
+
+ ret |= CFG_PIN_EN_CHARGER_ERROR;
+
+ ret = smb347_write(smb, CFG_PIN, ret);
+ } else {
+ ret = smb347_write(smb, CFG_FAULT_IRQ, 0);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_write(smb, CFG_STATUS_IRQ, 0);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_read(smb, CFG_PIN);
+ if (ret < 0)
+ goto fail;
+
+ ret &= ~CFG_PIN_EN_CHARGER_ERROR;
+
+ ret = smb347_write(smb, CFG_PIN, ret);
+ }
+
+fail:
+ smb347_set_writable(smb, false);
+ return ret;
+}
+
+static inline int smb347_irq_enable(struct smb347_charger *smb)
+{
+ return smb347_irq_set(smb, true);
+}
+
+static inline int smb347_irq_disable(struct smb347_charger *smb)
+{
+ return smb347_irq_set(smb, false);
+}
+
+static irqreturn_t smb347_interrupt(int irq, void *data)
+{
+ struct smb347_charger *smb = data;
+ int stat_c, t;
+ u8 irqstat[6];
+ irqreturn_t ret = IRQ_NONE;
+
+ t = i2c_smbus_read_i2c_block_data(smb->client, IRQSTAT_A, 6, irqstat);
+ if (t < 0) {
+ dev_warn(&smb->client->dev,
+ "reading IRQSTAT registers failed\n");
+ return IRQ_NONE;
+ }
+
+ stat_c = smb347_read(smb, STAT_C);
+ if (stat_c < 0) {
+ dev_warn(&smb->client->dev, "reading STAT_C failed\n");
+ return IRQ_NONE;
+ }
+
+ pr_debug("%s: stat c=%x irq a=%x b=%x c=%x d=%x e=%x f=%x\n",
+ __func__, stat_c, irqstat[0], irqstat[1], irqstat[2],
+ irqstat[3], irqstat[4], irqstat[5]);
+
+ /*
+ * If we get charger error we report the error back to user and
+ * disable charging.
+ */
+ if (stat_c & STAT_C_CHARGER_ERROR) {
+ dev_err(&smb->client->dev,
+ "error in charger, disabling charging\n");
+
+ smb347_charging_disable(smb);
+ power_supply_changed(&smb->battery);
+
+ ret = IRQ_HANDLED;
+ } else if (((stat_c & STAT_C_CHG_STATUS) ||
+ (irqstat[2] & (IRQSTAT_C_TERMINATION_IRQ |
+ IRQSTAT_C_TERMINATION_STAT))) &&
+ !smb->is_fully_charged) {
+ dev_info(&smb->client->dev, "charge terminated\n");
+ smb->is_fully_charged = true;
+ smb347_charging_disable(smb);
+ power_supply_changed(&smb->battery);
+ ret = IRQ_HANDLED;
+ }
+
+ if (irqstat[2] & IRQSTAT_C_TAPER_IRQ)
+ ret = IRQ_HANDLED;
+
+ /*
+ * If we got an under voltage interrupt it means that AC/USB input
+ * was disconnected.
+ */
+ if (irqstat[4] & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ))
+ ret = IRQ_HANDLED;
+
+ if (smb347_update_status(smb) > 0) {
+ smb347_update_online(smb);
+ power_supply_changed(&smb->mains);
+ power_supply_changed(&smb->usb);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static int smb347_irq_init(struct smb347_charger *smb)
+{
+ const struct smb347_charger_platform_data *pdata = smb->pdata;
+ int ret, irq = gpio_to_irq(pdata->irq_gpio);
+
+ ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, smb->client->name);
+ if (ret < 0)
+ goto fail;
+
+ ret = request_threaded_irq(irq, NULL, smb347_interrupt,
+ pdata->disable_stat_interrupts ?
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT :
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ smb->client->name, smb);
+ if (ret < 0)
+ goto fail_gpio;
+
+ ret = enable_irq_wake(irq);
+ if (ret)
+ pr_err("%s: failed to enable wake on irq %d\n", __func__, irq);
+
+ smb->client->irq = irq;
+ return 0;
+
+fail_gpio:
+ gpio_free(pdata->irq_gpio);
+fail:
+ smb->client->irq = 0;
+ return ret;
+}
+
+static int smb347_hw_init(struct smb347_charger *smb)
+{
+ int ret;
+
+ ret = smb347_set_writable(smb, true);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Program the platform specific configuration values to the device
+ * first.
+ */
+ ret = smb347_set_charge_current(smb);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_set_current_limits(smb);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_set_voltage_limits(smb);
+ if (ret < 0)
+ goto fail;
+
+// HACK for Manta pre-alpha 0.2, TH_BATTERY not connected properly
+#if 0 // HACK
+ ret = smb347_set_temp_limits(smb);
+ if (ret < 0)
+ goto fail;
+#endif // HACK
+
+ /* If USB charging is disabled we put the USB in suspend mode */
+ if (!smb->pdata->use_usb) {
+ ret = smb347_read(smb, CMD_A);
+ if (ret < 0)
+ goto fail;
+
+ ret |= CMD_A_SUSPEND_ENABLED;
+
+ ret = smb347_write(smb, CMD_A, ret);
+ if (ret < 0)
+ goto fail;
+ }
+
+ ret = smb347_read(smb, CFG_OTHER);
+ if (ret < 0)
+ goto fail;
+
+ /*
+ * If configured by platform data, we enable hardware Auto-OTG
+ * support for driving VBUS. Otherwise we disable it.
+ */
+ ret &= ~CFG_OTHER_RID_MASK;
+ if (smb->pdata->use_usb_otg)
+ ret |= CFG_OTHER_RID_ENABLED_AUTO_OTG;
+
+ ret = smb347_write(smb, CFG_OTHER, ret);
+ if (ret < 0)
+ goto fail;
+
+ /* If configured by platform data, disable AUTOMATIC RECHARGE */
+ if (smb->pdata->disable_automatic_recharge) {
+ ret = smb347_read(smb, CFG_CHARGE_CONTROL);
+ if (ret < 0)
+ goto fail;
+
+ ret |= CFG_AUTOMATIC_RECHARGE_DISABLE;
+
+ ret = smb347_write(smb, CFG_CHARGE_CONTROL, ret);
+ if (ret < 0)
+ goto fail;
+ }
+
+ ret = smb347_read(smb, CFG_PIN);
+ if (ret < 0)
+ goto fail;
+
+ /*
+ * Make the charging functionality controllable by a write to the
+ * command register unless pin control is specified in the platform
+ * data.
+ */
+ ret &= ~(CFG_PIN_EN_CTRL_MASK | CFG_PIN_USB_MODE_CTRL);
+
+ switch (smb->pdata->enable_control) {
+ case SMB347_CHG_ENABLE_SW:
+ /* Do nothing, 0 means i2c control */
+ break;
+ case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW:
+ ret |= CFG_PIN_EN_CTRL_ACTIVE_LOW;
+ break;
+ case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH:
+ ret |= CFG_PIN_EN_CTRL_ACTIVE_HIGH;
+ break;
+ }
+
+ if (smb->pdata->usb_mode_pin_ctrl)
+ ret |= CFG_PIN_USB_MODE_CTRL;
+
+ /* Disable Automatic Power Source Detection (APSD) interrupt. */
+ ret &= ~CFG_PIN_EN_APSD_IRQ;
+
+ ret = smb347_write(smb, CFG_PIN, ret);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_update_status(smb);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_update_online(smb);
+
+ if ((smb->pdata->irq_gpio >= 0) &&
+ !smb->pdata->disable_stat_interrupts) {
+ /*
+ * Configure the STAT output to be suitable for interrupts:
+ * disable all other output (except interrupts) and make it
+ * active low.
+ */
+ ret = smb347_read(smb, CFG_STAT);
+ if (ret < 0)
+ goto fail;
+
+ ret &= ~CFG_STAT_ACTIVE_HIGH;
+ ret |= CFG_STAT_DISABLED;
+
+ ret = smb347_write(smb, CFG_STAT, ret);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_irq_enable(smb);
+ if (ret < 0)
+ goto fail;
+ }
+
+fail:
+ smb347_set_writable(smb, false);
+ return ret;
+}
+
+static int smb347_mains_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb347_charger *smb =
+ container_of(psy, struct smb347_charger, mains);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = smb->mains_online;
+ return 0;
+
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = smb->mains_current_limit;
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+ return -EINVAL;
+}
+
+static int smb347_mains_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct smb347_charger *smb =
+ container_of(psy, struct smb347_charger, mains);
+ int ret;
+ bool oldval;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ oldval = smb->mains_online;
+
+ smb->mains_online = val->intval;
+
+ smb347_set_writable(smb, true);
+
+ ret = smb347_read(smb, CMD_A);
+ if (ret < 0)
+ return -EINVAL;
+
+ ret &= ~CMD_A_SUSPEND_ENABLED;
+ if (val->intval)
+ ret |= CMD_A_SUSPEND_ENABLED;
+
+ ret = smb347_write(smb, CMD_A, ret);
+
+ smb347_hw_init(smb);
+
+ smb347_set_writable(smb, false);
+
+ if (smb->mains_online != oldval)
+ power_supply_changed(psy);
+ return 0;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ smb->mains_current_limit = val->intval;
+ smb347_hw_init(smb);
+ return 0;
+
+ default:
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
+
+static int smb347_mains_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property smb347_mains_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static int smb347_usb_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb347_charger *smb =
+ container_of(psy, struct smb347_charger, usb);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = smb->usb_online;
+ return 0;
+
+ case POWER_SUPPLY_PROP_USB_HC:
+ val->intval = smb->usb_hc_mode;
+ return 0;
+
+ case POWER_SUPPLY_PROP_USB_OTG:
+ val->intval = smb->usb_otg_enabled;
+ return 0;
+
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+static int smb347_usb_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int ret = -EINVAL;
+ struct smb347_charger *smb =
+ container_of(psy, struct smb347_charger, usb);
+ bool oldval;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ oldval = smb->usb_online;
+ smb->usb_online = val->intval;
+
+ if (smb->usb_online != oldval)
+ power_supply_changed(psy);
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_USB_HC:
+ smb347_set_writable(smb, true);
+ ret = smb347_write(smb, CMD_B, val->intval ?
+ CMD_B_HC_MODE : CMD_B_USB59_MODE);
+ smb347_set_writable(smb, false);
+ smb->usb_hc_mode = val->intval;
+ break;
+
+ case POWER_SUPPLY_PROP_USB_OTG:
+ ret = smb347_read(smb, CMD_A);
+
+ if (ret < 0)
+ return ret;
+
+ if (val->intval)
+ ret |= CMD_A_OTG_ENABLE;
+ else
+ ret &= ~CMD_A_OTG_ENABLE;
+
+ ret = smb347_write(smb, CMD_A, ret);
+
+ if (ret >= 0)
+ smb->usb_otg_enabled = val->intval;
+
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int smb347_usb_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_USB_HC:
+ case POWER_SUPPLY_PROP_USB_OTG:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property smb347_usb_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_USB_HC,
+ POWER_SUPPLY_PROP_USB_OTG,
+};
+
+static int smb347_battery_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb347_charger *smb =
+ container_of(psy, struct smb347_charger, battery);
+ const struct smb347_charger_platform_data *pdata = smb->pdata;
+ int ret;
+
+ ret = smb347_update_status(smb);
+ if (ret < 0)
+ return ret;
+
+ if (ret > 0) {
+ smb347_update_online(smb);
+ power_supply_changed(&smb->mains);
+ power_supply_changed(&smb->usb);
+ }
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!smb347_is_online(smb)) {
+ smb->is_fully_charged = false;
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ }
+ if (smb347_charging_status(smb))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = smb->is_fully_charged ?
+ POWER_SUPPLY_STATUS_FULL :
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (!smb347_is_online(smb))
+ return -ENODATA;
+
+ /*
+ * We handle trickle and pre-charging the same, and taper
+ * and none the same.
+ */
+ switch (smb347_charging_status(smb)) {
+ case 1:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case 2:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = pdata->battery_info.technology;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = pdata->battery_info.voltage_min_design;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = pdata->battery_info.voltage_max_design;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (!smb347_is_online(smb))
+ return -ENODATA;
+ ret = smb347_read(smb, STAT_A);
+ if (ret < 0)
+ return ret;
+
+ ret &= STAT_A_FLOAT_VOLTAGE_MASK;
+ if (ret > 0x3d)
+ ret = 0x3d;
+
+ val->intval = 3500000 + ret * 20000;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (!smb347_is_online(smb))
+ return -ENODATA;
+
+ ret = smb347_read(smb, STAT_B);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The current value is composition of FCC and PCC values
+ * and we can detect which table to use from bit 5.
+ */
+ if (ret & 0x20) {
+ val->intval = hw_to_current(fcc_tbl,
+ ARRAY_SIZE(fcc_tbl),
+ ret & 7);
+ } else {
+ ret >>= 3;
+ val->intval = hw_to_current(pcc_tbl,
+ ARRAY_SIZE(pcc_tbl),
+ ret & 7);
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = pdata->battery_info.charge_full_design;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ val->intval = smb->charging_enabled;
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = pdata->battery_info.name;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int smb347_battery_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int ret = -EINVAL;
+ struct smb347_charger *smb =
+ container_of(psy, struct smb347_charger, battery);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ ret = smb347_charging_set(smb, val->intval);
+ break;
+
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static int smb347_battery_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGE_ENABLED:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property smb347_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_ENABLED,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int smb347_debugfs_show(struct seq_file *s, void *data)
+{
+ struct smb347_charger *smb = s->private;
+ int ret;
+ u8 reg;
+
+ seq_printf(s, "Control registers:\n");
+ seq_printf(s, "==================\n");
+ for (reg = CFG_CHARGE_CURRENT; reg <= CFG_ADDRESS; reg++) {
+ ret = smb347_read(smb, reg);
+ seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
+ }
+ seq_printf(s, "\n");
+
+ seq_printf(s, "Command registers:\n");
+ seq_printf(s, "==================\n");
+ ret = smb347_read(smb, CMD_A);
+ seq_printf(s, "0x%02x:\t0x%02x\n", CMD_A, ret);
+ ret = smb347_read(smb, CMD_B);
+ seq_printf(s, "0x%02x:\t0x%02x\n", CMD_B, ret);
+ ret = smb347_read(smb, CMD_C);
+ seq_printf(s, "0x%02x:\t0x%02x\n", CMD_C, ret);
+ seq_printf(s, "\n");
+
+ seq_printf(s, "Interrupt status registers:\n");
+ seq_printf(s, "===========================\n");
+ for (reg = IRQSTAT_A; reg <= IRQSTAT_F; reg++) {
+ ret = smb347_read(smb, reg);
+ seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
+ }
+ seq_printf(s, "\n");
+
+ seq_printf(s, "Status registers:\n");
+ seq_printf(s, "=================\n");
+ for (reg = STAT_A; reg <= STAT_E; reg++) {
+ ret = smb347_read(smb, reg);
+ seq_printf(s, "0x%02x:\t0x%02x\n", reg, ret);
+ }
+
+ return 0;
+}
+
+static int smb347_debugfs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, smb347_debugfs_show, inode->i_private);
+}
+
+static const struct file_operations smb347_debugfs_fops = {
+ .open = smb347_debugfs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int smb347_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ static char *battery[] = { "smb347-battery" };
+ const struct smb347_charger_platform_data *pdata;
+ struct device *dev = &client->dev;
+ struct smb347_charger *smb;
+ int ret;
+
+ pdata = dev->platform_data;
+ if (!pdata)
+ return -EINVAL;
+
+ if (!pdata->use_mains && !pdata->use_usb)
+ return -EINVAL;
+
+ smb = devm_kzalloc(dev, sizeof(*smb), GFP_KERNEL);
+ if (!smb)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, smb);
+
+ mutex_init(&smb->lock);
+ smb->client = client;
+ smb->pdata = pdata;
+
+ smb->mains_current_limit = smb->pdata->mains_current_limit;
+
+ if (pdata->en_gpio) {
+ ret = gpio_request_one(
+ pdata->en_gpio,
+ smb->pdata->enable_control ==
+ SMB347_CHG_ENABLE_PIN_ACTIVE_LOW ?
+ GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
+ smb->client->name);
+ if (ret < 0)
+ dev_warn(dev, "failed to claim EN GPIO: %d\n", ret);
+ else
+ smb->en_gpio = pdata->en_gpio;
+ }
+
+ ret = smb347_write(smb, CMD_B, CMD_B_POR);
+ if (ret < 0)
+ return ret;
+
+ msleep(20);
+
+ ret = smb347_read(smb, CMD_B);
+ if (ret < 0) {
+ dev_err(dev, "failed read after reset\n");
+ return ret;
+ }
+
+ ret = smb347_hw_init(smb);
+ if (ret < 0)
+ return ret;
+
+ smb->mains.name = "smb347-mains";
+ smb->mains.type = POWER_SUPPLY_TYPE_MAINS;
+ smb->mains.get_property = smb347_mains_get_property;
+ smb->mains.set_property = smb347_mains_set_property;
+ smb->mains.property_is_writeable = smb347_mains_property_is_writeable;
+ smb->mains.properties = smb347_mains_properties;
+ smb->mains.num_properties = ARRAY_SIZE(smb347_mains_properties);
+ smb->mains.supplied_to = battery;
+ smb->mains.num_supplicants = ARRAY_SIZE(battery);
+
+ smb->usb.name = "smb347-usb";
+ smb->usb.type = POWER_SUPPLY_TYPE_USB;
+ smb->usb.get_property = smb347_usb_get_property;
+ smb->usb.set_property = smb347_usb_set_property;
+ smb->usb.property_is_writeable = smb347_usb_property_is_writeable;
+ smb->usb.properties = smb347_usb_properties;
+ smb->usb.num_properties = ARRAY_SIZE(smb347_usb_properties);
+ smb->usb.supplied_to = battery;
+ smb->usb.num_supplicants = ARRAY_SIZE(battery);
+
+ smb->battery.name = "smb347-battery";
+ smb->battery.type = POWER_SUPPLY_TYPE_BATTERY;
+ smb->battery.get_property = smb347_battery_get_property;
+ smb->battery.set_property = smb347_battery_set_property;
+ smb->battery.property_is_writeable = smb347_battery_property_is_writeable;
+ smb->battery.properties = smb347_battery_properties;
+ smb->battery.num_properties = ARRAY_SIZE(smb347_battery_properties);
+
+ if (smb->pdata->supplied_to) {
+ smb->battery.supplied_to = smb->pdata->supplied_to;
+ smb->battery.num_supplicants = smb->pdata->num_supplicants;
+ smb->battery.external_power_changed = power_supply_changed;
+ }
+
+ ret = power_supply_register(dev, &smb->mains);
+ if (ret < 0)
+ return ret;
+
+ ret = power_supply_register(dev, &smb->usb);
+ if (ret < 0) {
+ power_supply_unregister(&smb->mains);
+ return ret;
+ }
+
+ ret = power_supply_register(dev, &smb->battery);
+ if (ret < 0) {
+ power_supply_unregister(&smb->usb);
+ power_supply_unregister(&smb->mains);
+ return ret;
+ }
+
+ /*
+ * Interrupt pin is optional. If it is connected, we setup the
+ * interrupt support here.
+ */
+ if (pdata->irq_gpio >= 0) {
+ ret = smb347_irq_init(smb);
+ if (ret < 0) {
+ dev_warn(dev, "failed to initialize IRQ: %d\n", ret);
+ dev_warn(dev, "disabling IRQ support\n");
+ }
+ }
+
+ smb->dentry = debugfs_create_file("smb347-regs", S_IRUSR, NULL, smb,
+ &smb347_debugfs_fops);
+ return 0;
+}
+
+static int smb347_remove(struct i2c_client *client)
+{
+ struct smb347_charger *smb = i2c_get_clientdata(client);
+
+ if (!IS_ERR_OR_NULL(smb->dentry))
+ debugfs_remove(smb->dentry);
+
+ if (client->irq) {
+ smb347_irq_disable(smb);
+ disable_irq_wake(client->irq);
+ free_irq(client->irq, smb);
+ gpio_free(smb->pdata->irq_gpio);
+ }
+
+ power_supply_unregister(&smb->battery);
+ power_supply_unregister(&smb->usb);
+ power_supply_unregister(&smb->mains);
+ return 0;
+}
+
+static int smb347_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ if (client->irq)
+ disable_irq(client->irq);
+ return 0;
+}
+
+static int smb347_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ if (client->irq)
+ enable_irq(client->irq);
+ return 0;
+}
+
+static const struct dev_pm_ops smb347_pm_ops = {
+ .suspend = smb347_suspend,
+ .resume = smb347_resume,
+};
+
+static const struct i2c_device_id smb347_id[] = {
+ { "smb347", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, smb347_id);
+
+static struct i2c_driver smb347_driver = {
+ .driver = {
+ .name = "smb347",
+ .pm = &smb347_pm_ops,
+ },
+ .probe = smb347_probe,
+ .remove = __devexit_p(smb347_remove),
+ .id_table = smb347_id,
+};
+
+static int __init smb347_init(void)
+{
+ return i2c_add_driver(&smb347_driver);
+}
+module_init(smb347_init);
+
+static void __exit smb347_exit(void)
+{
+ i2c_del_driver(&smb347_driver);
+}
+module_exit(smb347_exit);
+
+MODULE_AUTHOR("Bruce E. Robertson <bruce.e.robertson@intel.com>");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
+MODULE_DESCRIPTION("SMB347 battery charger driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("i2c:smb347");
diff --git a/drivers/power/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..28bbe7e0
--- /dev/null
+++ b/drivers/power/tosa_battery.c
@@ -0,0 +1,446 @@
+/*
+ * 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 gpio tosa_bat_gpios[] = {
+ { TOSA_GPIO_CHARGE_OFF, GPIOF_OUT_INIT_HIGH, "main charge off" },
+ { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" },
+ { TOSA_GPIO_BAT_SW_ON, GPIOF_OUT_INIT_LOW, "battery switch" },
+ { TOSA_GPIO_BAT0_V_ON, GPIOF_OUT_INIT_LOW, "main battery" },
+ { TOSA_GPIO_BAT1_V_ON, GPIOF_OUT_INIT_LOW, "jacket battery" },
+ { TOSA_GPIO_BAT1_TH_ON, GPIOF_OUT_INIT_LOW, "main battery temp" },
+ { TOSA_GPIO_BAT0_TH_ON, GPIOF_OUT_INIT_LOW, "jacket battery temp" },
+ { TOSA_GPIO_BU_CHRG_ON, GPIOF_OUT_INIT_LOW, "backup battery" },
+ { TOSA_GPIO_BAT0_CRG, GPIOF_IN, "main battery full" },
+ { TOSA_GPIO_BAT1_CRG, GPIOF_IN, "jacket battery full" },
+ { TOSA_GPIO_BAT0_LOW, GPIOF_IN, "main battery low" },
+ { TOSA_GPIO_BAT1_LOW, GPIOF_IN, "jacket battery low" },
+ { TOSA_GPIO_JACKET_DETECT, GPIOF_IN, "jacket detect" },
+};
+
+#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;
+
+ if (!machine_is_tosa())
+ return -ENODEV;
+
+ ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
+ if (ret)
+ return ret;
+
+ 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);
+
+ gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
+ return ret;
+}
+
+static int __devexit tosa_bat_remove(struct platform_device *dev)
+{
+ 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);
+ gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
+ 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,
+};
+
+module_platform_driver(tosa_bat_driver);
+
+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..fdad850c
--- /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, 0644);
+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 usb_phy *transceiver;
+ struct notifier_block usb_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, usb_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;
+ u32 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 = usb_get_transceiver();
+ if (bci->transceiver != NULL) {
+ bci->usb_nb.notifier_call = twl4030_bci_usb_ncb;
+ usb_register_notifier(bci->transceiver, &bci->usb_nb);
+ }
+
+ /* Enable interrupts now. */
+ reg = ~(u32)(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 = ~(u32)(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) {
+ usb_unregister_notifier(bci->transceiver, &bci->usb_nb);
+ usb_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) {
+ usb_unregister_notifier(bci->transceiver, &bci->usb_nb);
+ usb_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žvydas 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..6243e697
--- /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;
+ char name[20];
+};
+
+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_pdata *wm831x_pdata = wm831x->dev->platform_data;
+ 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);
+
+ if (wm831x_pdata && wm831x_pdata->wm831x_num)
+ snprintf(devdata->name, sizeof(devdata->name),
+ "wm831x-backup.%d", wm831x_pdata->wm831x_num);
+ else
+ snprintf(devdata->name, sizeof(devdata->name),
+ "wm831x-backup");
+
+ backup->name = devdata->name;
+ 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->backup.name);
+ 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",
+ },
+};
+
+module_platform_driver(wm831x_backup_driver);
+
+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..987332b7
--- /dev/null
+++ b/drivers/power/wm831x_power.c
@@ -0,0 +1,663 @@
+/*
+ * 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;
+ char wall_name[20];
+ char usb_name[20];
+ char battery_name[20];
+ bool have_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 */
+ if (wm831x_power->have_battery)
+ 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. */
+ if (wm831x_power->have_battery)
+ 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_pdata *wm831x_pdata = wm831x->dev->platform_data;
+ 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;
+
+ if (wm831x_pdata && wm831x_pdata->wm831x_num) {
+ snprintf(power->wall_name, sizeof(power->wall_name),
+ "wm831x-wall.%d", wm831x_pdata->wm831x_num);
+ snprintf(power->battery_name, sizeof(power->wall_name),
+ "wm831x-battery.%d", wm831x_pdata->wm831x_num);
+ snprintf(power->usb_name, sizeof(power->wall_name),
+ "wm831x-usb.%d", wm831x_pdata->wm831x_num);
+ } else {
+ snprintf(power->wall_name, sizeof(power->wall_name),
+ "wm831x-wall");
+ snprintf(power->battery_name, sizeof(power->wall_name),
+ "wm831x-battery");
+ snprintf(power->usb_name, sizeof(power->wall_name),
+ "wm831x-usb");
+ }
+
+ /* We ignore configuration failures since we can still read back
+ * the status without enabling the charger.
+ */
+ wm831x_config_battery(wm831x);
+
+ wall->name = power->wall_name;
+ 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;
+
+ usb->name = power->usb_name,
+ 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_wall;
+
+ ret = wm831x_reg_read(wm831x, WM831X_CHARGER_CONTROL_1);
+ if (ret < 0)
+ goto err_wall;
+ power->have_battery = ret & WM831X_CHG_ENA;
+
+ if (power->have_battery) {
+ battery->name = power->battery_name;
+ 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_usb;
+ }
+
+ 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_battery;
+ }
+
+ 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_battery:
+ if (power->have_battery)
+ power_supply_unregister(battery);
+err_usb:
+ power_supply_unregister(usb);
+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);
+
+ if (wm831x_power->have_battery)
+ 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",
+ },
+};
+
+module_platform_driver(wm831x_power_driver);
+
+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..fae04d38
--- /dev/null
+++ b/drivers/power/wm8350_power.c
@@ -0,0 +1,529 @@
+/*
+ * 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",
+ },
+};
+
+module_platform_driver(wm8350_power_driver);
+
+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..d2d4c08c
--- /dev/null
+++ b/drivers/power/wm97xx_battery.c
@@ -0,0 +1,295 @@
+/*
+ * 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 struct work_struct bat_work;
+static DEFINE_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;
+
+ 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, 0,
+ "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),
+};
+
+module_platform_driver(wm97xx_bat_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("WM97xx battery driver");
diff --git a/drivers/power/wmt_battery/Makefile b/drivers/power/wmt_battery/Makefile
new file mode 100755
index 00000000..e4287ba0
--- /dev/null
+++ b/drivers/power/wmt_battery/Makefile
@@ -0,0 +1,15 @@
+
+# charger
+obj-y += charger/g2214/
+obj-y += charger/mp2625/
+
+# gauge
+obj-y += gauge/vt1603/
+obj-y += gauge/ug31xx/
+obj-y += gauge/sp2541/
+obj-y += gauge/bq27xx/
+obj-y += gauge/saradc/
+
+# core
+obj-y += wmt_battery.o
+
diff --git a/drivers/power/wmt_battery/charger/g2214/Kconfig b/drivers/power/wmt_battery/charger/g2214/Kconfig
new file mode 100755
index 00000000..1ca457f8
--- /dev/null
+++ b/drivers/power/wmt_battery/charger/g2214/Kconfig
@@ -0,0 +1,5 @@
+config G2214_CHARGER
+ tristate "GMT G2214 Charger driver (GMT & others)"
+ depends on I2C
+ help
+ Say Y here to enable support for charger with g2214 chip. \ No newline at end of file
diff --git a/drivers/power/wmt_battery/charger/g2214/Makefile b/drivers/power/wmt_battery/charger/g2214/Makefile
new file mode 100755
index 00000000..188872a7
--- /dev/null
+++ b/drivers/power/wmt_battery/charger/g2214/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for GMT charger core.
+#
+
+obj-y += g2214_charger.o
+
+
diff --git a/drivers/power/wmt_battery/charger/g2214/g2214_charger.c b/drivers/power/wmt_battery/charger/g2214/g2214_charger.c
new file mode 100755
index 00000000..944790e8
--- /dev/null
+++ b/drivers/power/wmt_battery/charger/g2214/g2214_charger.c
@@ -0,0 +1,762 @@
+/*
+ * g2214_charger.c - WonderMedia Charger Driver.
+ *
+ * Copyright (C) 2013 WonderMedia Technologies, 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.
+ *
+ * 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/module.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <asm/uaccess.h>
+#include <mach/hardware.h>
+#include <mach/wmt_env.h>
+#include <linux/power/wmt_battery.h>
+#include <mach/gmt-core.h>
+#include <linux/reboot.h>
+
+#define DRVNAME "gmt-charger"
+
+#undef pr_err
+#define pr_err(fmt, args...) printk("[" DRVNAME "] " fmt, ##args)
+
+#define REG_A0 0x00
+#define REG_A1 0x01
+#define REG_A2 0x02
+#define REG_A3 0x03
+#define REG_A4 0x04
+#define REG_A5 0x05
+#define REG_A6 0x06
+#define REG_A7 0x07
+#define REG_A8 0x08
+#define REG_A9 0x09
+#define REG_A10 0x0A
+#define REG_A11 0x0B
+#define REG_A12 0x0C
+#define REG_A13 0x0D
+
+struct g2214_charger {
+ struct gmt2214_dev *gmt_dev;
+ struct device *dev;
+ struct mutex lock;
+
+ struct power_supply psy_ac;
+ struct power_supply psy_usb;
+ struct delayed_work monitor_work;
+
+ int ac_online;
+ int usb_online;
+ int charger_status;
+ int sleeping;
+
+ union {
+ struct {
+ unsigned int cable_type:4;
+ unsigned int current_sw_mode:1;
+ unsigned int pc_charging:1;
+ };
+ uint32_t flag;
+ };
+
+ int iset_dcin;
+ int iset_vbus;
+ int vseta;
+ int iseta_small;
+ int iseta_large;
+ int safety_time;
+ int otg_power;
+ int pc_power_mode;
+};
+
+static struct g2214_charger *g_charger = NULL;
+
+static int parse_charger_param(struct g2214_charger *ch)
+{
+ static const char uboot_env[] = "wmt.charger.param";
+ char buf[64];
+ size_t l = sizeof(buf);
+ int n;
+
+ if (wmt_getsyspara((char *)uboot_env, buf, &l))
+ return -ENODEV;
+ if (prefixcmp(buf, "g2214:"))
+ return -ENODEV;
+ if (!ch)
+ return 0;
+
+ n = sscanf(buf + 6, "%x:%d:%d:%d:%d:%d:%d:%d:%d",
+ &ch->flag,
+ &ch->iset_dcin, &ch->iset_vbus, &ch->vseta,
+ &ch->iseta_small, &ch->iseta_large,
+ &ch->safety_time, &ch->otg_power, &ch->pc_power_mode);
+ if (n < 8) {
+ pr_err("%s invalid\n", uboot_env);
+ return -EINVAL;
+ }
+
+ pr_info("charger match g2214, %s cable, %s current switch\n"
+ "PC connected is %scharging\n"
+ "dcin %d mA, vbus %d mA, %d mV\n"
+ "charging current %d~%d mA %d hour, %s otg power\n"
+ "pc power in %s mode\n",
+ (ch->cable_type == CABLE_TYPE_DC) ? "DC" : "USB",
+ (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) ? "dynamic" : "sleep",
+ (ch->pc_charging == PC_CONNECTED_NOT_CHARGING) ? "not " : "",
+ ch->iset_dcin, ch->iset_vbus, ch->vseta,
+ ch->iseta_small, ch->iseta_large,
+ ch->safety_time, ch->otg_power ? "switch" : "no",
+ ch->pc_power_mode ? "lowpower" : "normal");
+ return 0;
+}
+
+static int g2214_read(struct g2214_charger *ch, uint8_t reg)
+{
+ unsigned int rt_value = 0;
+ gmt2214_reg_read(ch->gmt_dev, reg, &rt_value);
+ return rt_value;
+}
+
+static int g2214_write(struct g2214_charger *ch, uint8_t reg, uint8_t val)
+{
+ return gmt2214_reg_write(ch->gmt_dev, reg, val);
+}
+
+static inline void g2214_enotg_config(struct g2214_charger *ch, int enable)
+{
+ int val = g2214_read(ch, REG_A8);
+ if (enable)
+ val |= BIT3;
+ else
+ val &= ~BIT3;
+ g2214_write(ch, REG_A8, val);
+}
+
+static inline void g2214_vseta_config(struct g2214_charger *ch)
+{
+ int val, vseta;
+
+ if (ch->vseta < 4150)
+ vseta = 0;
+ else if (ch->vseta < 4200)
+ vseta = 1;
+ else if (ch->vseta < 4350)
+ vseta = 2;
+ else
+ vseta = 3;
+
+ val = g2214_read(ch, REG_A8);
+ val &= ~(3 << 6);
+ val |= vseta << 6;
+ g2214_write(ch, REG_A8, val);
+}
+
+static inline void g2214_current_config(struct g2214_charger *ch,
+ int dcin_mA, int vbus_mA, int charge_mA)
+{
+ int iset_dcin, iset_vbus, iseta;
+
+ if (dcin_mA <= 1000)
+ iset_dcin = 0;
+ else if (dcin_mA <= 1500)
+ iset_dcin = 1;
+ else if (dcin_mA <= 2000)
+ iset_dcin = 2;
+ else
+ iset_dcin = 3;
+
+ if (vbus_mA <= 95)
+ iset_vbus = 0;
+ else if (vbus_mA <= 475)
+ iset_vbus = 1;
+ else if (vbus_mA <= 950)
+ iset_vbus = 2;
+ else
+ iset_vbus = 3;
+
+ if (charge_mA < 300 || charge_mA > 1800)
+ iseta = 2;
+ else
+ iseta = ((charge_mA - 300) / 100);
+
+ g2214_write(ch, REG_A5, iset_dcin << 6 | iset_vbus << 4 | iseta);
+}
+
+static void current_refresh(struct g2214_charger *ch)
+{
+ int dcin_mA, vbus_mA, charge_mA;
+
+ dcin_mA = ch->iset_dcin;
+
+ switch (ch->charger_status) {
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ vbus_mA = ch->pc_power_mode ? 95 : 475;
+ charge_mA = ch->iseta_small;
+ break;
+ case POWER_SUPPLY_STATUS_FULL:
+ case POWER_SUPPLY_STATUS_CHARGING:
+ vbus_mA = ch->iset_vbus;
+ if (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) {
+ charge_mA = ch->iseta_large;
+ } else {
+ charge_mA = ch->sleeping ? ch->iseta_large
+ : ch->iseta_small;
+ }
+ if (ch->cable_type == CABLE_TYPE_USB && wmt_is_pc_connected()) {
+ vbus_mA = 475;
+ charge_mA = ch->iseta_small;
+ }
+ break;
+ default:
+ return;
+ }
+
+ printk(KERN_DEBUG " ## %s: dcin_mA %d, vbus_mA %d, charge_mA %d\n",
+ __func__, dcin_mA, vbus_mA, charge_mA);
+ g2214_current_config(ch, dcin_mA, vbus_mA, charge_mA);
+ g2214_vseta_config(ch);
+}
+
+static void g2214_endpm_config(struct g2214_charger *ch, int en)
+{
+ int val = g2214_read(ch, REG_A0);
+ if (en)
+ val |= BIT3;
+ else
+ val &= ~BIT3;
+ g2214_write(ch, REG_A0, val);
+}
+
+static void g2214_safety_time_init(struct g2214_charger *ch)
+{
+ int val;
+ int safety_time = ch->safety_time - 1;
+
+ if (safety_time < 0)
+ safety_time = 0;
+ else if (safety_time > 16)
+ safety_time = 15;
+
+ val = g2214_read(ch, REG_A6);
+ val &= (~(BIT4 | BIT5 | BIT6 | BIT7));
+ val |= (safety_time << 4);
+ g2214_write(ch, REG_A6, val);
+}
+
+static void g2214_ntc_init(struct g2214_charger *ch)
+{
+ int val;
+ val = g2214_read(ch, REG_A0);
+ val &= ~BIT1; //Enable Auto NTC-R Type Detection
+ g2214_write(ch, REG_A0, val);
+ g2214_write(ch, REG_A7, 0); //Set HOT boundary to 60
+}
+
+static int g2214_reg_init(struct g2214_charger *ch)
+{
+ g2214_safety_time_init(ch);
+ g2214_enotg_config(ch, 0);
+ g2214_current_config(ch, ch->iset_dcin, ch->iset_vbus, ch->iseta_small);
+ g2214_endpm_config(ch, ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC);
+ g2214_ntc_init(ch);
+ return 0;
+}
+
+static void g2214_regs_dump(struct g2214_charger *ch)
+{
+ int reg;
+ for (reg = REG_A0; reg <= REG_A13; reg++)
+ printk(KERN_DEBUG " ## reg A%d: 0x%x\n ",
+ reg, g2214_read(ch, reg));
+}
+
+static void g2214_charge_enable(struct g2214_charger *ch, int en)
+{
+ int val = g2214_read(ch, REG_A6);
+ if (en)
+ val &= ~BIT3;
+ else
+ val |= BIT3;
+ g2214_write(ch, REG_A6, val);
+}
+
+static inline int g2214_is_full(struct g2214_charger *ch)
+{
+ return !!(g2214_read(ch, REG_A12) & 0x10);
+}
+
+static int g2214_read_status(struct g2214_charger *ch, int *pst)
+{
+ if (!wmt_is_dc_plugin()) {
+ *pst = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ switch (ch->cable_type) {
+ case CABLE_TYPE_DC:
+ *pst = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case CABLE_TYPE_USB:
+ if (wmt_is_otg_plugin()) {
+ *pst = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else if (wmt_is_pc_connected()) {
+ if (ch->pc_charging == PC_CONNECTED_CHARGING)
+ *pst = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ *pst = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else {
+ *pst = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (*pst == POWER_SUPPLY_STATUS_CHARGING) {
+ if (g2214_is_full(ch))
+ *pst = POWER_SUPPLY_STATUS_FULL;
+ }
+ return 0;
+}
+
+static void charger_monitor_work(struct work_struct *work)
+{
+ struct g2214_charger *ch=
+ container_of(work, struct g2214_charger, monitor_work.work);
+ int ac_online = 0;
+ int usb_online = 0;
+ int charger_status;
+ int ts_meter = 0;
+
+ g2214_regs_dump(ch);
+
+ if (wmt_is_otg_plugin() && ch->otg_power)
+ g2214_enotg_config(ch, 1);
+ else {
+ g2214_enotg_config(ch, 0);
+ msleep(30);
+ }
+
+ g2214_write(ch, REG_A9, 0xFF);
+
+ g2214_read_status(ch, &charger_status);
+
+ ts_meter = (g2214_read(ch, REG_A10) & 0xE0) >> 5;
+ if (ts_meter == 0x11) {
+ charger_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ g2214_charge_enable(ch, 0);
+ printk("Battery Overheat, Charge Disable\n");
+ } else if (ts_meter <= 0)
+ g2214_charge_enable(ch, 1);
+
+ if (charger_status == POWER_SUPPLY_STATUS_CHARGING ||
+ charger_status == POWER_SUPPLY_STATUS_FULL) {
+ if (ch->cable_type == CABLE_TYPE_USB &&
+ ch->pc_charging == PC_CONNECTED_CHARGING &&
+ wmt_is_pc_connected())
+ usb_online = 1;
+ else
+ ac_online = 1;
+ }
+
+ if (ch->charger_status != charger_status ||
+ ch->ac_online != ac_online) {
+ ch->charger_status = charger_status;
+ ch->ac_online = ac_online;
+ power_supply_changed(&ch->psy_ac);
+ }
+
+ if (ch->pc_charging == PC_CONNECTED_CHARGING &&
+ ch->usb_online != usb_online) {
+ ch->usb_online = usb_online;
+ power_supply_changed(&ch->psy_usb);
+ }
+
+ current_refresh(ch);
+ led_power_enable(charger_status == POWER_SUPPLY_STATUS_CHARGING ||
+ charger_status == POWER_SUPPLY_STATUS_FULL);
+
+ g2214_write(ch, REG_A9, 0xF0);
+ g2214_write(ch, REG_A12, g2214_read(ch, REG_A12) & (~BIT0));
+}
+
+void g2214_pc_connected(void)
+{
+ if (g_charger)
+ schedule_delayed_work(&g_charger->monitor_work, 1.5*HZ);
+}
+
+static irqreturn_t otg_irq(int irq, void *data)
+{
+ struct g2214_charger *ch = data;
+
+ if (REG8_VAL(USB_BASE_ADD + 0x7F1) & BIT7) {
+ REG8_VAL(USB_BASE_ADD + 0x7F1) = BIT7;
+ schedule_delayed_work(&ch->monitor_work, 0);
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+// g2214 + dcdet use wakeup0 interrupt
+static irqreturn_t g2214_dcdet_irq(int irq, void *data)
+{
+ struct g2214_charger *ch = data;
+
+ // turn off the led immediately
+ if (!wmt_is_dc_plugin())
+ led_power_enable(0);
+
+ if (PMCIS_VAL & BIT0) {
+ pmc_clear_intr_status(WKS_WK0);
+ schedule_delayed_work(&ch->monitor_work, 0);
+ return IRQ_HANDLED;
+ }
+
+ if (PMCIS_VAL & BIT27) {
+ pmc_clear_intr_status(WKS_DCDET);
+ schedule_delayed_work(&ch->monitor_work, 1.5*HZ);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static inline void pid_check_irq_enable(void)
+{
+ REG8_VAL(USB_BASE_ADD+0x7F2) |= BIT1;
+}
+
+static inline void pid_check_irq_disable(void)
+{
+ REG8_VAL(USB_BASE_ADD+0x7F2) &= ~BIT1;
+}
+
+static int irqs_init(struct g2214_charger *ch)
+{
+ unsigned long iflag = IRQF_SHARED;
+ int ret;
+
+ ret = devm_request_irq(ch->dev, IRQ_UHDC, otg_irq, iflag, "USBOTG", ch);
+ if (ret < 0) {
+ dev_err(ch->dev, "IRQ_UHDC irq request failed %d\n", ret);
+ return ret;
+ }
+
+ if (REG32_VAL(0xfe120000) == 0x35100101)
+ iflag |= IRQF_NO_SUSPEND;
+
+ ret = devm_request_irq(ch->dev, IRQ_PMC_WAKEUP, g2214_dcdet_irq, iflag,
+ "G2214-DCDET", ch);
+ if (ret < 0) {
+ pr_err("register DCDET irq failed\n");
+ return ret;
+ }
+
+ pid_check_irq_enable();
+ pmc_enable_wakeup_isr(WKS_WK0, 2);
+ wmt_dcdet_irq_enable();
+ return 0;
+}
+
+static void irqs_release(struct g2214_charger *ch)
+{
+ pid_check_irq_disable();
+ wmt_dcdet_irq_disable();
+ pmc_disable_wakeup_isr(WKS_WK0);
+}
+
+static enum power_supply_property ac_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
+static int ac_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct g2214_charger *ch =
+ container_of(psy, struct g2214_charger, psy_ac);
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = ch->ac_online;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ return g2214_read_status(ch, &val->intval);
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct g2214_charger *ch =
+ container_of(psy, struct g2214_charger, psy_usb);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = ch->usb_online;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property usb_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *power_supplied_to[] = {
+ "battery",
+};
+
+static int power_supply_init(struct g2214_charger *ch)
+{
+ int ret;
+
+ ch->psy_ac.name = "ac",
+ ch->psy_ac.type = POWER_SUPPLY_TYPE_MAINS,
+ ch->psy_ac.supplied_to = power_supplied_to,
+ ch->psy_ac.num_supplicants = ARRAY_SIZE(power_supplied_to),
+ ch->psy_ac.properties = ac_properties,
+ ch->psy_ac.num_properties = ARRAY_SIZE(ac_properties),
+ ch->psy_ac.get_property = ac_get_property,
+ ret = power_supply_register(ch->dev, &ch->psy_ac);
+ if (ret) {
+ dev_err(ch->dev, "register ac power supply failed.\n");
+ }
+
+ if (ch->pc_charging == PC_CONNECTED_CHARGING) {
+ ch->psy_usb.name = "usb",
+ ch->psy_usb.type = POWER_SUPPLY_TYPE_USB,
+ ch->psy_usb.supplied_to = power_supplied_to,
+ ch->psy_usb.num_supplicants = ARRAY_SIZE(power_supplied_to),
+ ch->psy_usb.properties = usb_properties,
+ ch->psy_usb.num_properties = ARRAY_SIZE(usb_properties),
+ ch->psy_usb.get_property = usb_get_property,
+ ret = power_supply_register(ch->dev, &ch->psy_usb);
+ if (ret) {
+ dev_err(ch->dev, "register ac power supply failed.\n");
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static void power_supply_release(struct g2214_charger *ch)
+{
+ power_supply_unregister(&ch->psy_ac);
+}
+
+#define G2214_PROC_NAME "driver/g2214_regs"
+
+static int g2214_proc_show(struct seq_file *seq, void *offset)
+{
+ int reg;
+ for (reg = REG_A0; reg <= REG_A13; reg++)
+ seq_printf(seq, "reg A%d: 0x%x\n ",
+ reg, g2214_read(g_charger, reg));
+ return 0;
+}
+
+static int g2214_proc_open(struct inode *inode, struct file *file)
+{
+ int ret;
+ if (!try_module_get(THIS_MODULE))
+ return -ENODEV;
+
+ ret = single_open(file, g2214_proc_show, NULL);
+ if (ret)
+ module_put(THIS_MODULE);
+ return ret;
+}
+
+static ssize_t g2214_proc_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *pos)
+{
+ char cmd[32];
+ unsigned long len = count;
+ int reg, val;
+
+ if (len > sizeof(cmd))
+ len = sizeof(cmd);
+
+ if (copy_from_user(cmd, buffer, len))
+ return -EFAULT;
+ if (sscanf(cmd, "r%d=0x%02x", &reg, &val) != 2)
+ return -EINVAL;
+ if (reg > REG_A13)
+ return -EINVAL;
+
+ if (g_charger) {
+ g2214_write(g_charger, reg, val);
+ pr_info("## %s: reg %d -> 0x%02x\n", __func__, reg, val);
+ }
+
+ return count;
+}
+
+static int g2214_proc_release(struct inode *inode, struct file *file)
+{
+ int res = single_release(inode, file);
+ module_put(THIS_MODULE);
+ return res;
+}
+
+static const struct file_operations g2214_proc_fops = {
+ .open = g2214_proc_open,
+ .read = seq_read,
+ .write = g2214_proc_write,
+ .llseek = seq_lseek,
+ .release = g2214_proc_release,
+};
+
+static inline void g2214_proc_setup(void)
+{
+ proc_create(G2214_PROC_NAME, 0, NULL, &g2214_proc_fops);
+}
+
+static inline void g2214_proc_cleanup(void)
+{
+ remove_proc_entry(G2214_PROC_NAME, NULL);
+}
+
+static int g2214_reboot_notifie(struct notifier_block *nb, unsigned long event, void *unused)
+{
+ struct g2214_charger *ch = g_charger;
+ cancel_delayed_work_sync(&ch->monitor_work);
+ g2214_enotg_config(ch, 0);
+ g2214_write(ch, REG_A9, 0xFF);
+ g2214_write(ch, REG_A11, 0xFF);
+ return NOTIFY_OK;
+}
+
+static struct notifier_block g2214_reboot_notifier = {
+ .notifier_call = g2214_reboot_notifie,
+};
+
+
+static int __devinit g2214_probe(struct platform_device *pdev)
+{
+ struct g2214_charger *ch;
+ int ret;
+
+ ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL);
+ if (!ch)
+ return -ENOMEM;
+
+ if ((ret = parse_charger_param(ch)))
+ return ret;
+ parse_charger_led();
+
+ ch->dev = &pdev->dev;
+ ch->gmt_dev = dev_get_drvdata(pdev->dev.parent);
+ platform_set_drvdata(pdev, ch);
+
+ if ((ret = power_supply_init(ch)))
+ return ret;
+
+ INIT_DELAYED_WORK(&ch->monitor_work, charger_monitor_work);
+
+ if ((ret = irqs_init(ch))) {
+ power_supply_release(ch);
+ return ret;
+ }
+
+ g_charger = ch;
+ g2214_reg_init(ch);
+ g2214_proc_setup();
+
+ register_reboot_notifier(&g2214_reboot_notifier);
+ schedule_delayed_work(&ch->monitor_work, 0);
+ pr_info(DRVNAME " install success.\n");
+ return 0;
+}
+
+static int __devexit g2214_remove(struct platform_device *pdev)
+{
+ struct g2214_charger *ch = platform_get_drvdata(pdev);
+ irqs_release(ch);
+ cancel_delayed_work_sync(&ch->monitor_work);
+ power_supply_release(ch);
+ g2214_proc_cleanup();
+ g_charger = NULL;
+ return 0;
+}
+
+static int g2214_suspend(struct device *dev)
+{
+ struct g2214_charger *ch = dev_get_drvdata(dev);
+ cancel_delayed_work_sync(&ch->monitor_work);
+ ch->sleeping = 1;
+ current_refresh(ch);
+ g2214_write(ch, REG_A11, 0xCF);
+ return 0;
+}
+
+static int g2214_resume(struct device *dev)
+{
+ struct g2214_charger *ch = dev_get_drvdata(dev);
+
+ // turn off the led immediately
+ if (!wmt_is_dc_plugin())
+ led_power_enable(0);
+
+ pid_check_irq_enable();
+ ch->sleeping = 0;
+ current_refresh(ch);
+ schedule_delayed_work(&ch->monitor_work, HZ);
+ g2214_write(ch, REG_A11, 0xFF);
+ return 0;
+}
+
+static const struct dev_pm_ops g2214_pm_ops = {
+ .suspend = g2214_suspend,
+ .resume = g2214_resume,
+};
+
+static struct platform_driver g2214_driver = {
+ .driver = {
+ .name = DRVNAME,
+ .owner = THIS_MODULE,
+ .pm = &g2214_pm_ops,
+ },
+ .probe = g2214_probe,
+ .remove = __devexit_p(g2214_remove),
+};
+
+static int __init g2214_init(void)
+{
+ if (parse_charger_param(NULL))
+ return -ENODEV;
+ return platform_driver_register(&g2214_driver);
+}
+
+static void __exit g2214_exit(void)
+{
+ return platform_driver_unregister(&g2214_driver);
+}
+
+module_init(g2214_init);
+module_exit(g2214_exit);
+
+MODULE_AUTHOR("WonderMedia Technologies, Inc.");
+MODULE_DESCRIPTION("GMT2144 battery charger driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("i2c:g2214");
+
diff --git a/drivers/power/wmt_battery/charger/mp2625/Kconfig b/drivers/power/wmt_battery/charger/mp2625/Kconfig
new file mode 100755
index 00000000..38282c65
--- /dev/null
+++ b/drivers/power/wmt_battery/charger/mp2625/Kconfig
@@ -0,0 +1,5 @@
+config MP2625_CHARGER
+ tristate "MP2625 Charger driver"
+ depends on I2C
+ help
+ Say Y here to enable support for charger with mp2625 chip.
diff --git a/drivers/power/wmt_battery/charger/mp2625/Makefile b/drivers/power/wmt_battery/charger/mp2625/Makefile
new file mode 100755
index 00000000..9bcfa83d
--- /dev/null
+++ b/drivers/power/wmt_battery/charger/mp2625/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for MP2625 charger core.
+#
+
+obj-y += mp2625_charger.o
+
diff --git a/drivers/power/wmt_battery/charger/mp2625/mp2625_charger.c b/drivers/power/wmt_battery/charger/mp2625/mp2625_charger.c
new file mode 100755
index 00000000..581e89d8
--- /dev/null
+++ b/drivers/power/wmt_battery/charger/mp2625/mp2625_charger.c
@@ -0,0 +1,428 @@
+/*
+ * mp2625_charger.c - WonderMedia Charger Driver.
+ *
+ * Copyright (C) 2013 WonderMedia Technologies, 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.
+ *
+ * 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/module.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <mach/wmt_env.h>
+#include <mach/hardware.h>
+#include <mach/wmt_iomux.h>
+#include <linux/gpio.h>
+#include <linux/power/wmt_battery.h>
+
+#define DRVNAME "mp2625-charger"
+
+#undef pr_err
+#undef pr_info
+#define pr_err(fmt, args...) printk("[" DRVNAME "] " fmt, ##args)
+#define pr_info(fmt, args...) printk("[" DRVNAME "] " fmt, ##args)
+
+struct mp2625_charger {
+ struct device *dev;
+ int ac_online;
+ int usb_online;
+ int charger_status;
+ int sleeping;
+
+ union {
+ struct {
+ unsigned int cable_type:4;
+ unsigned int current_sw_mode:1;
+ unsigned int pc_charging:1;
+ };
+ uint32_t flag;
+ };
+
+ int full_pin;
+ int full_level;
+ int current_pin;
+ int current_large_level;
+
+ struct power_supply psy_ac;
+ struct power_supply psy_usb;
+ struct delayed_work dwork;
+};
+
+static struct mp2625_charger *g_charger;
+
+static int parse_charger_param(struct mp2625_charger *ch)
+{
+ static char uboot_env[] = "wmt.charger.param";
+ char buf[64];
+ size_t l = sizeof(buf);
+ int ret;
+
+ if (wmt_getsyspara(uboot_env, buf, &l))
+ return -ENODEV;
+ if (prefixcmp(buf, "mp2625:"))
+ return -ENODEV;
+ if (!ch)
+ return 0;
+
+ ret = sscanf(buf + 7, "%x:%d:%d:%d:%d",
+ &ch->flag,
+ &ch->full_pin, &ch->full_level,
+ &ch->current_pin, &ch->current_large_level);
+ if (ret < 5) {
+ pr_err("Invalid uboot env: %s\n", uboot_env);
+ return -EINVAL;
+ }
+
+ if (ch->cable_type != CABLE_TYPE_DC &&
+ ch->cable_type != CABLE_TYPE_USB) {
+ pr_err("Invalid type %d\n", ch->cable_type);
+ return -EINVAL;
+ }
+
+ if (gpio_is_valid(ch->full_pin)) {
+ ret = devm_gpio_request(ch->dev, ch->full_pin, "charger full");
+ if (ret) {
+ pr_err("gpio%d request fail %d\n", ch->full_pin, ret);
+ return ret;
+ }
+ wmt_gpio_setpull(ch->full_pin, (ch->full_level) ?
+ WMT_GPIO_PULL_DOWN : WMT_GPIO_PULL_UP);
+ gpio_direction_input(ch->full_pin);
+ }
+
+ if (gpio_is_valid(ch->current_pin)) {
+ ret = devm_gpio_request(ch->dev, ch->current_pin, "charger current");
+ if (ret) {
+ pr_err("gpio%d request fail %d\n", ch->current_pin, ret);
+ return ret;
+ }
+ gpio_direction_output(ch->current_pin, !ch->current_large_level);
+ }
+
+ pr_info("charger match " DRVNAME ", %s cable, full %d, current %d\n"
+ "%s current switch, PC connected is %scharging\n",
+ (ch->cable_type == CABLE_TYPE_DC) ? "DC" : "USB",
+ ch->full_pin, ch->current_pin,
+ (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC) ? "dynamic" : "sleep",
+ (ch->pc_charging == PC_CONNECTED_NOT_CHARGING) ? "not " : "");
+ return 0;
+}
+
+static inline void set_current(struct mp2625_charger *ch)
+{
+ int large;
+ int charging = (ch->charger_status == POWER_SUPPLY_STATUS_CHARGING ||
+ ch->charger_status == POWER_SUPPLY_STATUS_FULL);
+
+ if (ch->current_sw_mode == CURRENT_SWITCH_DYNAMIC)
+ large = charging;
+ else
+ large = ch->sleeping ? 1 : 0;
+
+ if (ch->cable_type == CABLE_TYPE_USB && wmt_is_pc_connected())
+ large = 0;
+
+ if (gpio_is_valid(ch->current_pin)) {
+ gpio_direction_output(ch->current_pin,
+ large ? ch->current_large_level :
+ !ch->current_large_level);
+ printk(KERN_DEBUG "set %s current\n", large ? "large" : "small");
+ }
+}
+
+static int ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct mp2625_charger *ch =
+ container_of(psy, struct mp2625_charger, psy_ac);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = ch->ac_online;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = ch->charger_status;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property ac_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
+static int usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct mp2625_charger *ch =
+ container_of(psy, struct mp2625_charger, psy_usb);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = ch->usb_online;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property usb_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *power_supplied_to[] = {
+ "battery",
+};
+
+static int power_supply_init(struct mp2625_charger *ch)
+{
+ int ret;
+
+ ch->psy_ac.name = "ac",
+ ch->psy_ac.type = POWER_SUPPLY_TYPE_MAINS,
+ ch->psy_ac.supplied_to = power_supplied_to,
+ ch->psy_ac.num_supplicants = ARRAY_SIZE(power_supplied_to),
+ ch->psy_ac.properties = ac_properties,
+ ch->psy_ac.num_properties = ARRAY_SIZE(ac_properties),
+ ch->psy_ac.get_property = ac_get_property,
+ ret = power_supply_register(ch->dev, &ch->psy_ac);
+ if (ret) {
+ dev_err(ch->dev, "register ac power supply failed.\n");
+ return ret;
+ }
+
+ if (ch->pc_charging == PC_CONNECTED_CHARGING) {
+ ch->psy_usb.name = "usb",
+ ch->psy_usb.type = POWER_SUPPLY_TYPE_USB,
+ ch->psy_usb.supplied_to = power_supplied_to,
+ ch->psy_usb.num_supplicants = ARRAY_SIZE(power_supplied_to),
+ ch->psy_usb.properties = usb_properties,
+ ch->psy_usb.num_properties = ARRAY_SIZE(usb_properties),
+ ch->psy_usb.get_property = usb_get_property,
+ ret = power_supply_register(ch->dev, &ch->psy_usb);
+ if (ret) {
+ dev_err(ch->dev, "register ac power supply failed.\n");
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void power_supply_release(struct mp2625_charger *ch)
+{
+ power_supply_unregister(&ch->psy_ac);
+ if (ch->pc_charging == PC_CONNECTED_CHARGING)
+ power_supply_unregister(&ch->psy_usb);
+}
+
+static void mp2625_charger_work(struct work_struct *work)
+{
+ struct mp2625_charger *ch =
+ container_of(work, struct mp2625_charger, dwork.work);
+ int ac_online = 0;
+ int usb_online = 0;
+ int charger_status = 0;
+
+ if (wmt_is_dc_plugin()) {
+ if (ch->cable_type == CABLE_TYPE_USB && wmt_is_pc_connected()) {
+ if (ch->pc_charging == PC_CONNECTED_CHARGING) {
+ charger_status = POWER_SUPPLY_STATUS_CHARGING;
+ usb_online = 1;
+ } else
+ charger_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ charger_status = POWER_SUPPLY_STATUS_CHARGING;
+ ac_online = 1;
+ }
+
+ if (charger_status == POWER_SUPPLY_STATUS_CHARGING &&
+ gpio_get_value(ch->full_pin) == ch->full_level)
+ charger_status = POWER_SUPPLY_STATUS_FULL;
+ } else
+ charger_status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ if (ch->ac_online != ac_online ||
+ ch->charger_status != charger_status) {
+ ch->ac_online = ac_online;
+ ch->charger_status = charger_status;
+ power_supply_changed(&ch->psy_ac);
+ }
+
+ if (ch->pc_charging == PC_CONNECTED_CHARGING &&
+ ch->usb_online != usb_online) {
+ ch->usb_online = usb_online;
+ power_supply_changed(&ch->psy_usb);
+ }
+
+ set_current(ch);
+ led_power_enable(charger_status == POWER_SUPPLY_STATUS_CHARGING ||
+ charger_status == POWER_SUPPLY_STATUS_FULL);
+}
+
+void mp2625_pc_connected(void)
+{
+ if (g_charger)
+ schedule_delayed_work(&g_charger->dwork, 0);
+}
+
+static irqreturn_t dcdet_irq(int irq, void *data)
+{
+ struct mp2625_charger *ch = data;
+
+ if (PMCIS_VAL & BIT27) {
+ pmc_clear_intr_status(WKS_DCDET);
+ schedule_delayed_work(&ch->dwork, HZ/2);
+ return IRQ_HANDLED;
+ }
+ return IRQ_NONE;
+}
+
+static int irqs_init(struct mp2625_charger *ch)
+{
+ unsigned long iflag = IRQF_SHARED;
+ int ret;
+
+ if (REG32_VAL(0xfe120000) == 0x35100101)
+ iflag |= IRQF_NO_SUSPEND;
+
+ ret = devm_request_irq(ch->dev, IRQ_PMC_WAKEUP, dcdet_irq,
+ iflag, "WMT-DCDET", ch);
+ if (ret < 0) {
+ pr_err("register DCDET irq failed\n");
+ return ret;
+ }
+
+ wmt_dcdet_irq_enable();
+ return 0;
+}
+
+static void irqs_release(struct mp2625_charger *ch)
+{
+ wmt_dcdet_irq_disable();
+}
+
+static int mp2625_probe(struct platform_device *pdev)
+{
+ struct mp2625_charger *ch;
+ int ret;
+
+ ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL);
+ if (!ch)
+ return -ENOMEM;
+
+ ch->dev = &pdev->dev;
+ platform_set_drvdata(pdev, ch);
+
+ ret = parse_charger_param(ch);
+ if (ret)
+ return ret;;
+ parse_charger_led();
+
+ if ((ret = power_supply_init(ch)))
+ return ret;
+
+ INIT_DELAYED_WORK(&ch->dwork, mp2625_charger_work);
+
+ if ((ret = irqs_init(ch))) {
+ power_supply_release(ch);
+ return ret;
+ }
+
+ g_charger = ch;
+ schedule_delayed_work(&ch->dwork, 0);
+
+ pr_info(DRVNAME " install success.\n");
+ return 0;
+}
+
+static int __devexit mp2625_remove(struct platform_device *pdev)
+{
+ struct mp2625_charger *ch = platform_get_drvdata(pdev);
+ irqs_release(ch);
+ cancel_delayed_work_sync(&ch->dwork);
+ power_supply_release(ch);
+ g_charger = NULL;
+ return 0;
+}
+
+static int mp2625_suspend(struct device *dev)
+{
+ struct mp2625_charger *ch = dev_get_drvdata(dev);
+ cancel_delayed_work_sync(&ch->dwork);
+ ch->sleeping = 1;
+ set_current(ch);
+ return 0;
+}
+
+static int mp2625_resume(struct device *dev)
+{
+ struct mp2625_charger *ch = dev_get_drvdata(dev);
+ schedule_delayed_work(&ch->dwork, HZ/2);
+ ch->sleeping = 0;
+ set_current(ch);
+ return 0;
+}
+
+static const struct dev_pm_ops mp2625_pm_ops = {
+ .suspend = mp2625_suspend,
+ .resume = mp2625_resume,
+};
+
+static struct platform_driver mp2625_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = DRVNAME,
+ .pm = &mp2625_pm_ops,
+ },
+ .probe = mp2625_probe,
+ .remove = mp2625_remove,
+};
+
+static struct platform_device *pdev;
+
+static int __init mp2625_init(void)
+{
+ int ret;
+
+ ret = parse_charger_param(NULL);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&mp2625_driver);
+ if (ret)
+ return ret;
+
+ pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
+ if (IS_ERR(pdev)) {
+ ret = PTR_ERR(pdev);
+ platform_driver_unregister(&mp2625_driver);
+ }
+ return ret;
+}
+
+static void __exit mp2625_exit(void)
+{
+ platform_device_unregister(pdev);
+ platform_driver_unregister(&mp2625_driver);
+}
+
+module_init(mp2625_init);
+module_exit(mp2625_exit);
+
+MODULE_AUTHOR("WonderMedia");
+MODULE_DESCRIPTION("MP2625 Charger Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/power/wmt_battery/gauge/bq27xx/Makefile b/drivers/power/wmt_battery/gauge/bq27xx/Makefile
new file mode 100755
index 00000000..446fbf59
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/bq27xx/Makefile
@@ -0,0 +1,6 @@
+
+s_wmt_batt_bq27xx-objs += \
+ bq27x00_battery.o
+
+obj-m += s_wmt_batt_bq27xx.o
+
diff --git a/drivers/power/wmt_battery/gauge/bq27xx/bq27x00_battery.c b/drivers/power/wmt_battery/gauge/bq27xx/bq27x00_battery.c
new file mode 100755
index 00000000..7a123615
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/bq27xx/bq27x00_battery.c
@@ -0,0 +1,1011 @@
+/*
+ * 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 <mach/wmt_env.h>
+#include <linux/power/wmt_battery.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 capacity */
+#define BQ27x00_REG_LMD 0x12 /* Last measured discharge */
+#define BQ27x00_REG_CYCT 0x2A /* Cycle count total */
+#define BQ27x00_REG_AE 0x22 /* Available energy */
+
+#define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */
+#define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */
+#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */
+#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */
+#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */
+#define BQ27000_FLAG_FC BIT(5)
+#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */
+
+#define BQ27500_REG_SOC 0x2C
+#define BQ27500_REG_DCAP 0x3C /* Design capacity */
+#define BQ27500_FLAG_DSC BIT(0)
+#define BQ27500_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */
+#define BQ27500_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27500_FLAG_FC BIT(9)
+
+#define BQ27000_RS 20 /* Resistor sense */
+
+#define CONFIG_BATTERY_BQ27X00_I2C
+
+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 energy;
+ int flags;
+};
+
+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_CAPACITY_LEVEL,
+ 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 int i2c_bus = 0;
+static unsigned int poll_interval = 5;
+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_dbg(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_dbg(di->dev, "error reading charge register %02x: %d\n",
+ reg, charge);
+ 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_dbg(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 Available energy in µWh
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_energy(struct bq27x00_device_info *di)
+{
+ int ae;
+
+ ae = bq27x00_read(di, BQ27x00_REG_AE, false);
+ if (ae < 0) {
+ dev_dbg(di->dev, "error reading available energy\n");
+ return ae;
+ }
+
+ if (di->chip == BQ27500)
+ ae *= 1000;
+ else
+ ae = ae * 29200 / BQ27000_RS;
+
+ return ae;
+}
+
+/*
+ * Return the battery temperature in tenths of degree Celsius
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di)
+{
+ int temp;
+
+ temp = bq27x00_read(di, BQ27x00_REG_TEMP, false);
+ if (temp < 0) {
+ dev_err(di->dev, "error reading temperature\n");
+ return temp;
+ }
+
+ if (di->chip == BQ27500)
+ temp -= 2731;
+ else
+ temp = ((temp * 5) - 5463) / 2;
+
+ return temp;
+}
+
+/*
+ * 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_dbg(di->dev, "error reading time 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;
+ short voltage_now = 0,current_now = 0,capacity_now = 0;
+
+ cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500);
+ if (cache.flags >= 0) {
+ if (!is_bq27500 && (cache.flags & BQ27000_FLAG_CI)) {
+ dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n");
+ cache.capacity = -ENODATA;
+ cache.energy = -ENODATA;
+ cache.time_to_empty = -ENODATA;
+ cache.time_to_empty_avg = -ENODATA;
+ cache.time_to_full = -ENODATA;
+ cache.charge_full = -ENODATA;
+ } else {
+ cache.capacity = bq27x00_battery_read_rsoc(di);
+ cache.energy = bq27x00_battery_read_energy(di);
+ 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.temperature = bq27x00_battery_read_temperature(di);
+ cache.cycle_count = bq27x00_battery_read_cyct(di);
+
+ /* We only have to read charge design full once */
+ if (di->charge_design_full <= 0)
+ di->charge_design_full = bq27x00_battery_read_ilmd(di);
+ voltage_now = bq27x00_read(di, BQ27x00_REG_VOLT, false)*2;
+ current_now = bq27x00_read(di, BQ27x00_REG_AI, false);
+ capacity_now = bq27x00_read(di, BQ27x00_REG_NAC, false);
+ }
+
+ if (memcmp(&di->cache, &cache, sizeof(cache)) != 0) {
+ di->cache = cache;
+ power_supply_changed(&di->bat);
+ }
+
+ //printk("%s percentage=%d,voltage=%d,current=%d,temperature=%d,%d,flag=%d\n",__FUNCTION__,cache.capacity,voltage_now,current_now,cache.temperature,capacity_now,cache.flags);
+
+ 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 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;
+ int flags;
+
+ curr = bq27x00_read(di, BQ27x00_REG_AI, false);
+ if (curr < 0) {
+ dev_err(di->dev, "error reading current\n");
+ return curr;
+ }
+
+ if (di->chip == BQ27500) {
+ /* bq27500 returns signed value */
+ val->intval = (int)((s16)curr) * 1000;
+ } else {
+ flags = bq27x00_read(di, BQ27x00_REG_FLAGS, false);
+ if (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;
+
+ status = charger_get_status();
+ if (status < 0)
+ return status;
+
+ if (status == POWER_SUPPLY_STATUS_CHARGING) {
+ 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;
+}
+
+static int bq27x00_battery_capacity_level(struct bq27x00_device_info *di,
+ union power_supply_propval *val)
+{
+ int level;
+
+ if (di->chip == BQ27500) {
+ if (di->cache.flags & BQ27500_FLAG_FC)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (di->cache.flags & BQ27500_FLAG_SOC1)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else if (di->cache.flags & BQ27500_FLAG_SOCF)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ else
+ level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ } else {
+ if (di->cache.flags & BQ27000_FLAG_FC)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (di->cache.flags & BQ27000_FLAG_EDV1)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else if (di->cache.flags & BQ27000_FLAG_EDVF)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ else
+ level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ }
+
+ val->intval = level;
+
+ 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) {
+ dev_err(di->dev, "error reading voltage\n");
+ return volt;
+ }
+
+ val->intval = volt * 1000 * 2;
+
+ 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_CAPACITY_LEVEL:
+ ret = bq27x00_battery_capacity_level(di, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = bq27x00_simple_value(di->cache.temperature, 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_simple_value(di->cache.energy, 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)
+{
+ /*
+ * power_supply_unregister call bq27x00_battery_get_property which
+ * call bq27x00_battery_poll.
+ * Make sure that bq27x00_battery_poll will not call
+ * schedule_delayed_work again after unregister (which cause OOPS).
+ */
+ poll_interval = 0;
+
+ 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 int bq27x00_battery_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ struct bq27x00_device_info *di = i2c_get_clientdata(client);
+ cancel_delayed_work_sync(&di->work);
+ return 0;
+}
+
+static int bq27x00_battery_resume(struct i2c_client *client)
+{
+ struct bq27x00_device_info *di = i2c_get_clientdata(client);
+ schedule_delayed_work(&di->work, 1 * HZ);
+ 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,
+ .suspend = bq27x00_battery_suspend,
+ .resume = bq27x00_battery_resume,
+ .id_table = bq27x00_id,
+};
+
+static struct i2c_board_info bq27x00_board_info = {
+ I2C_BOARD_INFO("bq27500", 0x55),
+};
+
+static inline int bq27x00_battery_i2c_init(void)
+{
+ struct i2c_client *client;
+ struct i2c_adapter *adap;
+
+ int ret = i2c_add_driver(&bq27x00_battery_driver);
+ if (ret)
+ printk(KERN_ERR "Unable to register BQ27x00 i2c driver\n");
+ adap = i2c_get_adapter(i2c_bus);
+ if (!adap)
+ return -ENODEV;
+ client = i2c_new_device(adap, &bq27x00_board_info);
+ i2c_put_adapter(adap);
+ if (!client) {
+ printk("bq27x00 i2c_new_device failed\n");
+ return -ENODEV;
+ }
+
+ 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 int bq27000_battery_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct bq27x00_device_info *di = platform_get_drvdata(pdev);
+ cancel_delayed_work_sync(&di->work);
+ return 0;
+}
+
+static int bq27000_battery_resume(struct platform_device *pdev)
+{
+ struct bq27x00_device_info *di = platform_get_drvdata(pdev);
+ schedule_delayed_work(&di->work, 1 * HZ);
+ return 0;
+}
+
+static struct platform_driver bq27000_battery_driver = {
+ .probe = bq27000_battery_probe,
+ .remove = __devexit_p(bq27000_battery_remove),
+ .suspend = bq27000_battery_suspend;
+ .resume = bq27000_battery_resume;
+ .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 parse_battery_param(void)
+{
+ char env[] = "wmt.battery.param";
+ char buf[64];
+ char *p;
+ size_t l = sizeof(buf);
+ int i;
+
+ if (wmt_getsyspara(env, buf, &l))
+ return -EINVAL;
+
+ if (prefixcmp(buf, "bq27xx:"))
+ return -ENODEV;
+
+ p = strchr(buf, ':');
+
+ i = sscanf(p + 1, "%d", &i2c_bus);
+ if (i < 1) {
+ printk("Parse_param err: Can't get i2c bus num\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+
+static int __init bq27x00_battery_init(void)
+{
+ int ret;
+
+ if (parse_battery_param())
+ return -ENODEV;
+
+ 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/wmt_battery/gauge/saradc/Makefile b/drivers/power/wmt_battery/gauge/saradc/Makefile
new file mode 100644
index 00000000..8b63f3f3
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/saradc/Makefile
@@ -0,0 +1,4 @@
+
+s_wmt_batt_saradc-objs += saradc_battery.o
+obj-m += s_wmt_batt_saradc.o
+
diff --git a/drivers/power/wmt_battery/gauge/saradc/saradc_battery.c b/drivers/power/wmt_battery/gauge/saradc/saradc_battery.c
new file mode 100644
index 00000000..9bdf50c8
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/saradc/saradc_battery.c
@@ -0,0 +1,341 @@
+/*
+ * adc_battery.c - WonderMedia Adc Battery Driver.
+ *
+ * Copyright (C) 2013 WonderMedia Technologies, 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.
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/power/wmt_battery.h>
+
+#define DRVNAME "adc-batt"
+
+#undef pr_err
+#define pr_err(fmt, args...) printk("[" DRVNAME "] " fmt, ##args)
+#undef pr_info
+#define pr_info(fmt, args...) printk("[" DRVNAME "] " fmt, ##args)
+
+enum {
+ COMPENSATION_VOLUME = 0,
+ COMPENSATION_BRIGHTNESS,
+ COMPENSATION_WIFI,
+ COMPENSATION_VIDEO,
+ COMPENSATION_USB,
+ COMPENSATION_HDMI,
+ COMPENSATION_COUNT
+};
+
+static const char *compensation_strings[] = {
+ "volume",
+ "brightness",
+ "wifi",
+ "video",
+ "usb",
+ "hdmi"
+};
+
+struct adc_device_info {
+ struct device *dev;
+ struct power_supply ps_bat;
+ struct mutex mutex;
+
+ int compensation[COMPENSATION_COUNT];
+ int capacity;
+ int sleeping;
+ int debug;
+};
+
+static struct adc_device_info *adc_dev_info = NULL;
+
+static inline int adc_manual_read_volt(void)
+{
+ extern unsigned int ReadBattery(void);
+ return ReadBattery();
+}
+
+static inline int volt_reg_to_mV(int value)
+{
+ // voltage = adc * (3300/128) * (1430/1000) = adc * 4719 / 128
+
+ return ((value * 4719) / 128);
+}
+
+static int adc_bat_read_voltage(struct adc_device_info *di, int *intval)
+{
+ int ret;
+
+ ret = adc_manual_read_volt();
+ if (ret < 0)
+ return ret;
+
+ *intval = volt_reg_to_mV(ret);
+ return 0;
+}
+
+static int adc_bat_read_status(struct adc_device_info *di, int *intval)
+{
+ int status;
+
+ status = charger_get_status();
+ if (status < 0)
+ return status;
+
+ if (status == POWER_SUPPLY_STATUS_CHARGING && di->capacity == 100)
+ status = POWER_SUPPLY_STATUS_FULL;
+
+ *intval = status;
+ return 0;
+}
+
+static int adc_proc_read(char *buf, char **start, off_t offset, int len,
+ int *eof, void *data)
+{
+ int l = 0, i;
+ int ret, status, dcin, voltage, full;
+ struct adc_device_info *di = adc_dev_info;
+
+ mutex_lock(&di->mutex);
+
+ ret = adc_bat_read_status(di, &status);
+ if (ret) {
+ pr_err("adc_bat_read_status failed\n");
+ return 0;
+ }
+ ret = adc_bat_read_voltage(di, &voltage);
+ if (ret) {
+ pr_err("adc_bat_read_voltage failed\n");
+ return 0;
+ }
+ dcin = power_supply_is_system_supplied();
+ full = charger_is_full();
+
+ l += sprintf(buf + l, "status : %d\n", status);
+ l += sprintf(buf + l, "dcin : %d\n", dcin);
+ l += sprintf(buf + l, "voltage : %d\n", voltage);
+ l += sprintf(buf + l, "full : %d\n", full);
+ l += sprintf(buf + l, "sleeping : %d\n", di->sleeping);
+ l += sprintf(buf + l, "debug : %d\n", di->debug);
+
+ for (i = 0; i < COMPENSATION_COUNT; i++) {
+ l += sprintf(buf +l, "compensation %10s : %d\n",
+ compensation_strings[i], di->compensation[i]);
+ }
+
+ /* clear after read */
+ di->sleeping = 0;
+
+ mutex_unlock(&di->mutex);
+ return l;
+}
+
+static int adc_proc_write(struct file *file, const char *buffer,
+ unsigned long count, void *data)
+{
+ int bm, usage;
+ struct adc_device_info *di = adc_dev_info;
+
+ if (sscanf(buffer, "capacity=%d", &di->capacity)) {
+ power_supply_changed(&di->ps_bat);
+ goto out;
+ }
+
+ if (sscanf(buffer, "debug=%d", &di->debug))
+ goto out;
+
+ if (sscanf(buffer, "MODULE_CHANGE:%d-%d", &bm, &usage) < 2) {
+ return 0;
+ }
+
+ if (bm < 0 || bm >= COMPENSATION_COUNT) {
+ printk("bm %d error, [0, %d)\n", bm, COMPENSATION_COUNT);
+ return 0;
+ }
+
+ if (usage > 100 || usage < 0) {
+ printk("usage %d error\n", usage);
+ return 0;
+ }
+
+ mutex_lock(&di->mutex);
+ di->compensation[bm] = usage;
+ mutex_unlock(&di->mutex);
+out:
+ return count;
+}
+
+#define BATTERY_PROC_NAME "battery_calibration"
+
+static void adc_proc_init(void)
+{
+ struct proc_dir_entry *entry;
+
+ entry = create_proc_entry(BATTERY_PROC_NAME, 0666, NULL);
+ if (entry) {
+ entry->read_proc = adc_proc_read;
+ entry->write_proc = adc_proc_write;
+ }
+}
+
+static void adc_proc_cleanup(void)
+{
+ remove_proc_entry(BATTERY_PROC_NAME, NULL);
+}
+
+#define to_adc_device_info(x) container_of((x), \
+ struct adc_device_info, ps_bat);
+
+static int adc_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct adc_device_info *di = to_adc_device_info(psy);
+
+ mutex_lock(&di->mutex);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = adc_bat_read_status(di, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = di->capacity;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = adc_bat_read_voltage(di, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&di->mutex);
+ return ret;
+}
+
+static void adc_external_power_changed(struct power_supply *psy)
+{
+ power_supply_changed(psy);
+}
+
+static enum power_supply_property adc_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+};
+
+static int __devinit adc_batt_probe(struct platform_device *pdev)
+{
+ struct adc_device_info *di;
+ int ret;
+
+ di = kzalloc(sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ dev_err(&pdev->dev, "no memery\n");
+ return -ENOMEM;
+ }
+
+ di->dev = &pdev->dev;
+ di->capacity = 50;
+
+ mutex_init(&di->mutex);
+
+ di->ps_bat.name = "battery";
+ di->ps_bat.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->ps_bat.properties = adc_battery_props;
+ di->ps_bat.num_properties = ARRAY_SIZE(adc_battery_props);
+ di->ps_bat.get_property = adc_battery_get_property;
+ di->ps_bat.external_power_changed = adc_external_power_changed;
+
+ ret = power_supply_register(di->dev, &di->ps_bat);
+ if (ret) {
+ dev_err(di->dev, "failed to register battery: %d\n", ret);
+ kfree(di);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, di);
+ adc_dev_info = di;
+
+ adc_proc_init();
+
+ pr_info("ADC Battery Driver Installed!\n");
+ return 0;
+}
+
+static int __devexit adc_batt_remove(struct platform_device *pdev)
+{
+ struct adc_device_info *di = platform_get_drvdata(pdev);
+ adc_proc_cleanup();
+ power_supply_unregister(&di->ps_bat);
+ kfree(di);
+ adc_dev_info = NULL;
+ pr_info("ADC Battery Driver Removed!\n");
+ return 0;
+}
+
+static int adc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ return 0;
+}
+
+static int adc_resume(struct platform_device *pdev)
+{
+ struct adc_device_info *di = platform_get_drvdata(pdev);
+ di->sleeping = 1;
+ return 0;
+}
+
+static struct platform_driver adc_batt_driver = {
+ .driver = {
+ .name = DRVNAME,
+ },
+ .probe = adc_batt_probe,
+ .remove = __devexit_p(adc_batt_remove),
+ .suspend = adc_suspend,
+ .resume = adc_resume,
+};
+
+static struct platform_device *pdev;
+
+static int __init adc_batt_init(void)
+{
+ pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
+ if (IS_ERR(pdev)) {
+ return PTR_ERR(pdev);
+ }
+ return platform_driver_register(&adc_batt_driver);
+}
+
+static void __exit adc_batt_exit(void)
+{
+ platform_driver_unregister(&adc_batt_driver);
+ platform_device_unregister(pdev);
+}
+
+module_init(adc_batt_init);
+module_exit(adc_batt_exit);
+
+MODULE_AUTHOR("WonderMedia");
+MODULE_DESCRIPTION("WonderMedia Adc Battery Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/power/wmt_battery/gauge/sp2541/Makefile b/drivers/power/wmt_battery/gauge/sp2541/Makefile
new file mode 100755
index 00000000..43637d92
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/sp2541/Makefile
@@ -0,0 +1,5 @@
+
+s_wmt_batt_sp2541-objs += sp2541_battery.o
+
+obj-m += s_wmt_batt_sp2541.o
+
diff --git a/drivers/power/wmt_battery/gauge/sp2541/sp2541_battery.c b/drivers/power/wmt_battery/gauge/sp2541/sp2541_battery.c
new file mode 100755
index 00000000..2c619925
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/sp2541/sp2541_battery.c
@@ -0,0 +1,765 @@
+/*
+ * SP2541 battery driver
+ *
+ * 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.
+ */
+
+#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/proc_fs.h>
+#include <asm/uaccess.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/power/wmt_battery.h>
+#include <mach/wmt_env.h>
+#include <linux/firmware.h>
+
+#define RAM_READ_CMD 0x55
+#define EEPROM_READ_CMD 0xF5
+#define EEPROM_WRITE_CMD 0xFA
+
+#define DRIVER_VERSION "1.1.0"
+#define SP2541_REG_TEMP 0x06
+#define SP2541_REG_VOLT 0x08
+#define SP2541_REG_AI 0x14
+#define SP2541_REG_FLAGS 0x0A
+#define SP2541_REG_TTE 0x16
+#define SP2541_REG_TTF 0x18
+#define SP2541_REG_TTECP 0x26
+#define SP2541_REG_RSOC 0x0B /* Relative State-of-Charge */
+#define SP2541_REG_SOC 0x2c
+
+#define SP2541_FLAG_DSC BIT(0)
+#define SP2541_FLAG_CHGS BIT(8)
+#define SP2541_FLAG_FC BIT(9)
+#define SP2541_FLAG_OTD BIT(14)
+#define SP2541_FLAG_OTC BIT(15)
+
+#define SP2541_SPEED 100 * 1000
+
+struct battery_param {
+ char rom_name[32];
+ int i2c_bus;
+ int interval;
+};
+
+static struct battery_param battery_param;
+
+/* 0x07a0 ~ 0x07af
+ * 0x07b0 ~ 0x07bf
+ */
+union rom_version {
+ struct {
+ uint32_t magic;
+ uint32_t version;
+ } v;
+ uint8_t bytes[8];
+};
+
+#define __ROM_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
+#define SP2514_MAGIC __ROM_VERSION('W', 'M', 'T')
+
+#define ROM_VERSION(ver) \
+ { \
+ .v.magic = SP2514_MAGIC, \
+ .v.version = ver, \
+ }
+
+struct rom_entry {
+ uint16_t address;
+ uint16_t value;
+};
+#define ROM_ENTRY(a, l, v) { .address = a, .length = l, .value = v }
+
+struct rom_struct {
+ union rom_version rv;
+ struct rom_entry *table;
+ size_t table_size;
+};
+
+#define ROM_STRUCT(ver) \
+ { \
+ .rv = ROM_VERSION(ver), \
+ }
+
+static struct rom_entry *rom_entry;
+static struct rom_struct rom_table = ROM_STRUCT(0);
+
+struct sp2541_struct {
+ struct device *dev;
+ struct i2c_client *client;
+ int capacity;
+ struct rom_struct *rom;
+
+ struct power_supply psy_bat;
+ struct delayed_work dwork;
+ unsigned int interval;
+};
+
+static int parse_battery_param(void)
+{
+ char env[] = "wmt.battery.param";
+ char buf[64];
+ char *p;
+ size_t l = sizeof(buf);
+ int i;
+
+ if (wmt_getsyspara(env, buf, &l))
+ return -EINVAL;
+
+ if (prefixcmp(buf, "sp2541_"))
+ return -ENODEV;
+
+ p = strchr(buf, ':');
+ strncpy(battery_param.rom_name, buf + 7, p - buf - 7);
+ pr_info("rom name -- %s\n", battery_param.rom_name);
+
+ i = sscanf(p + 1, "%d:%d",
+ &battery_param.i2c_bus, &battery_param.interval);
+ if (i < 2)
+ return -EINVAL;
+
+ return 0;
+}
+
+//#define SP2541_BIG_ENDIAN
+#ifdef SP2541_BIG_ENDIAN
+static u16 get_bigend_le16(const void *_ptr)
+{
+ const uint8_t *ptr = _ptr;
+ return (ptr[0]<<8) | (ptr[1]);
+}
+
+static inline uint16_t __get_le16(const void *buf)
+{
+ return get_bigend_le16(buf);
+}
+#else
+static inline uint16_t __get_le16(const void *buf)
+{
+ return get_unaligned_le16(buf);
+}
+#endif
+
+static int sp2541_read(struct sp2541_struct *sp, uint8_t cmd, uint8_t reg,
+ uint8_t buf[], unsigned len)
+{
+ struct i2c_client *client = sp->client;
+ struct i2c_msg xfer[2];
+ char data[2] = { cmd, reg };
+ int ret;
+
+ xfer[0].addr = client->addr;
+ xfer[0].flags = 0 | I2C_M_NOSTART;
+ xfer[0].len = 2;
+ xfer[0].buf = data;
+ xfer[1].addr = client->addr;
+ xfer[1].flags = I2C_M_RD;
+ xfer[1].len = len;
+ xfer[1].buf = buf;
+
+ ret = i2c_transfer(client->adapter, xfer, ARRAY_SIZE(xfer));
+ if (ret != ARRAY_SIZE(xfer)) {
+ pr_err("read[r:%d] errcode[%d]\n", reg, ret);
+ if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int sp2541_write(struct sp2541_struct *sp, uint8_t cmd, uint8_t reg,
+ const uint8_t buf)
+{
+ struct i2c_client *client = sp->client;
+ struct i2c_msg xfer;
+ char data[3] = { cmd, reg, buf };
+ int ret;
+
+ xfer.addr = client->addr;
+ xfer.flags = 0;
+ xfer.len = 3;
+ xfer.buf = data;
+
+ ret = i2c_transfer(client->adapter, &xfer, 1);
+ if (ret != 1) {
+ pr_err("read[r:%d] errcode[%d]\n", reg, ret);
+ if (ret < 0)
+ return ret;
+ else
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int eeprom_read_byte(struct sp2541_struct *sp, uint16_t addr, uint8_t *value)
+{
+ uint8_t tmp;
+ int i;
+
+ if (sp2541_write(sp, EEPROM_WRITE_CMD, 0x00, (addr >> 0) & 0xff) ||
+ sp2541_write(sp, EEPROM_WRITE_CMD, 0x01, (addr >> 8) & 0xff) ||
+ sp2541_write(sp, EEPROM_WRITE_CMD, 0x03, 0x06))
+ return -EIO;
+
+ for (i = 0; i < 10; i++) {
+ if (sp2541_read(sp, EEPROM_READ_CMD, 0x03, &tmp, 1))
+ return -EIO;
+ if (tmp == 0)
+ break;
+ }
+ if (i == 10)
+ return -EBUSY;
+
+ if (sp2541_read(sp, EEPROM_READ_CMD, 0x02, value, 1))
+ return -EIO;
+
+ return 0;
+}
+
+static int eeprom_write_byte(struct sp2541_struct *sp, uint16_t addr, uint8_t value)
+{
+ uint8_t tmp;
+ int i;
+
+ if (sp2541_write(sp, EEPROM_WRITE_CMD, 0x00, (addr >> 0) & 0xff) ||
+ sp2541_write(sp, EEPROM_WRITE_CMD, 0x01, (addr >> 8) & 0xff) ||
+ sp2541_write(sp, EEPROM_WRITE_CMD, 0x02, value) ||
+ sp2541_write(sp, EEPROM_WRITE_CMD, 0x03, 0x05))
+ return -EIO;
+
+ for (i = 0; i < 10; i++) {
+ if (sp2541_read(sp, EEPROM_READ_CMD, 0x03, &tmp, 1))
+ return -EIO;
+ if (tmp == 0)
+ break;
+ }
+ if (i == 10)
+ return -EBUSY;
+
+ return 0;
+}
+
+static int eeprom_read(struct sp2541_struct *sp, uint16_t start,
+ uint8_t *buf, size_t len)
+{
+ int ret, i;
+ for (i = 0; i < len; i++) {
+ ret = eeprom_read_byte(sp, start + i, buf + len - i - 1);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int eeprom_write(struct sp2541_struct *sp, uint16_t start,
+ uint8_t *buf, size_t len)
+{
+ int ret, i, j;
+ for (i = 0; i < len; i++) {
+ //pr_info("wirte addr=0x%x,val=0x%x\n", start + i, buf[len - i - 1]);
+ for (j = 0; j < 3; j++) {
+ ret = eeprom_write_byte(sp, start + i, buf[len - i - 1]);
+ if (ret)
+ pr_err("eeprom write byte err #%d\n", j);
+ else
+ break;
+ }
+ if (j == 3)
+ return ret;
+ }
+ return 0;
+}
+
+static int eeprom_update(struct sp2541_struct *sp,
+ struct rom_entry *table, size_t count)
+{
+ int ret, i;
+ for (i = 0; i < count; i++) {
+ ret = eeprom_write(sp, table[i].address,
+ (uint8_t *)&table[i].value,
+ 1);//table[i].length);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int sp2541_load_romtable(struct sp2541_struct *sp)
+{
+ char table_name[32];
+ const struct firmware *fw_entry;
+ int count, i;
+ unsigned int val1, val2, version;
+
+ sprintf(table_name, "%s.EEP", battery_param.rom_name);
+ for (i = 0; i < 3; i++) {
+ if(request_firmware(&fw_entry, table_name, sp->dev)!=0)
+ pr_err("cat't request firmware #%d\n", i);
+ else
+ break;
+ }
+ if (i == 3)
+ return -EINVAL;
+
+ if (fw_entry->size <= 0) {
+ pr_err("load firmware error\n");
+ release_firmware(fw_entry);
+ return -EINVAL;
+ }
+ count = (fw_entry->size - 13) / 13; //13 bytes per line, last line is version
+ rom_entry = kzalloc(count * sizeof(*rom_entry), GFP_KERNEL);
+
+ for (i = 0; i < count; i++) {
+ sscanf(fw_entry->data + i * 13, "%x=%x", &val1, &val2);
+ rom_entry[i].address = val1 & 0xFFFF;
+ rom_entry[i].value = val2 & 0xFF;
+ //pr_info("%d: %x = %x\n", i, rom_entry[i].address, rom_entry[i].value);
+ }
+ sscanf(fw_entry->data + i * 13, "version=%d", &version);
+ //pr_info("version is %d\n", version);
+
+ rom_table.rv.v.version = version;
+ rom_table.table = rom_entry;
+ rom_table.table_size = count;
+ release_firmware(fw_entry);
+ return 0;
+}
+
+static int sp2541_eeprom_check(struct sp2541_struct *sp)
+{
+ union rom_version version;
+ int ret;
+
+ if (sp2541_load_romtable(sp))
+ return 0;
+ sp->rom = &rom_table;
+
+ ret = eeprom_read(sp, 0x7a0, (uint8_t *)&version, sizeof(version));
+ if (ret)
+ return ret;
+
+ if (version.v.magic != SP2514_MAGIC ||
+ version.v.version != sp->rom->rv.v.version) {
+ pr_info("old version %d\nnew version %d\n", version.v.version,
+ sp->rom->rv.v.version);
+ ret = eeprom_update(sp, sp->rom->table, sp->rom->table_size);
+ if (ret) {
+ pr_err("eeprom_update failed\n");
+ return ret;
+ }
+
+ // update version
+ ret = eeprom_write(sp, 0x07a0,
+ (uint8_t *)&sp->rom->rv,
+ sizeof(sp->rom->rv));
+ if (ret) {
+ pr_err("sp2541 version update failed\n");
+ return ret;
+ }
+ }
+
+ if (rom_entry != NULL)
+ kfree(rom_entry);
+ rom_table.table = NULL;
+ rom_table.table_size = 0;
+
+ return 0;
+}
+
+static int sp2541_battery_temperature(struct sp2541_struct *sp)
+{
+ int ret;
+ int temp = 0;
+ uint8_t buf[2] ={0};
+
+ ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_TEMP,buf,2);
+ if (ret<0) {
+ dev_err(sp->dev, "error reading temperature\n");
+ return ret;
+ }
+
+ temp = __get_le16(buf);
+
+ temp = (temp/10) - 273;
+ return temp;
+}
+
+static int sp2541_battery_voltage(struct sp2541_struct *sp)
+{
+ uint8_t buf[2] = {0};
+ int volt = 0;
+ int ret;
+
+ ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_VOLT,buf,2);
+ if (ret<0) {
+ dev_err(sp->dev, "error reading voltage\n");
+ return ret;
+ }
+
+ volt = __get_le16(buf);
+
+ return volt;
+}
+
+/*
+ * Return the battery average current
+ * Note that current can be negative signed as well
+ * Or 0 if something fails.
+ */
+static int sp2541_battery_current(struct sp2541_struct *sp)
+{
+ int ret;
+ int curr = 0;
+ uint8_t buf[2] = {0};
+
+ ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_AI,buf,2);
+ if (ret<0) {
+ dev_err(sp->dev, "error reading current\n");
+ return 0;
+ }
+
+ curr = __get_le16(buf);
+
+ if (curr > 0x8000) {
+ //curr = 0xFFFF^(curr-1);
+ curr = curr-0x10000;
+ }
+
+ return curr;
+}
+
+/*
+ * Return the battery Relative State-of-Charge
+ * Or < 0 if something fails.
+ */
+static int sp2541_battery_rsoc(struct sp2541_struct *sp)
+{
+ int ret;
+ int rsoc = 0;
+ uint8_t buf[2];
+
+ ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_SOC,buf,2);
+ if (ret<0) {
+ dev_err(sp->dev, "error reading relative State-of-Charge\n");
+ return ret;
+ }
+
+ rsoc = __get_le16(buf);
+
+ return rsoc;
+}
+
+static int sp2541_battery_status(struct sp2541_struct *sp,
+ union power_supply_propval *val)
+{
+ int status = 0;
+ int capacity;
+#if 0
+ uint8_t buf[2] = {0};
+ int flags = 0;
+ int ret = 0;
+
+ ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_FLAGS, buf, 2);
+ if (ret < 0) {
+ dev_err(sp->dev, "error reading flags\n");
+ return ret;
+ }
+
+ flags = __get_le16(buf);
+#endif
+
+ status = charger_get_status();
+ if (status < 0)
+ return status;
+ capacity = sp2541_battery_rsoc(sp);
+
+ if (status == POWER_SUPPLY_STATUS_CHARGING && capacity == 100)//(flags & SP2541_FLAG_FC))
+ status = POWER_SUPPLY_STATUS_FULL;
+
+ val->intval = status;
+ return 0;
+}
+
+static int sp2541_health_status(struct sp2541_struct *sp,
+ union power_supply_propval *val)
+{
+ uint8_t buf[2] = {0};
+ int flags = 0;
+ int status;
+ int ret;
+
+ ret = sp2541_read(sp, RAM_READ_CMD,SP2541_REG_FLAGS, buf, 2);
+ if (ret < 0) {
+ dev_err(sp->dev, "error reading flags\n");
+ return ret;
+ }
+
+ flags = __get_le16(buf);
+
+ if ((flags & SP2541_FLAG_OTD)||(flags & SP2541_FLAG_OTC))
+ status = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else
+ status = POWER_SUPPLY_HEALTH_GOOD;
+
+ val->intval = status;
+ return 0;
+}
+
+static int sp2541_battery_time(struct sp2541_struct *sp, int reg,
+ union power_supply_propval *val)
+{
+ uint8_t buf[2] = {0};
+ int tval = 0;
+ int ret;
+
+ ret = sp2541_read(sp, RAM_READ_CMD,reg,buf,2);
+ if (ret < 0) {
+ dev_err(sp->dev, "error reading register %02x\n", reg);
+ return ret;
+ }
+
+ tval = __get_le16(buf);
+
+ if (tval == 65535)
+ return -ENODATA;
+
+ val->intval = tval * 60;
+ return 0;
+}
+
+#define to_sp2541_struct(x) container_of((x), struct sp2541_struct, psy_bat)
+
+static int sp2541_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sp2541_struct *sp = to_sp2541_struct(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = sp2541_battery_status(sp, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = sp2541_battery_voltage(sp);
+ if (psp == POWER_SUPPLY_PROP_PRESENT) {
+ val->intval = val->intval <= 0 ? 0 : 1;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = sp2541_battery_current(sp);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = sp2541_battery_rsoc(sp);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = sp2541_battery_temperature(sp);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = sp2541_health_status(sp, val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ ret = sp2541_battery_time(sp, SP2541_REG_TTE, val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ ret = sp2541_battery_time(sp, SP2541_REG_TTECP, val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ ret = sp2541_battery_time(sp, SP2541_REG_TTF, val);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property sp2541_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_TECHNOLOGY,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+static void sp2541_external_power_changed(struct power_supply *psy)
+{
+ power_supply_changed(psy);
+}
+
+static void sp2541_battery_work(struct work_struct *work)
+{
+ struct sp2541_struct *sp =
+ container_of(work, struct sp2541_struct, dwork.work);
+ static int last_cap = -1;
+
+ int curr_cap = sp2541_battery_rsoc(sp);
+ if (curr_cap != last_cap) {
+ power_supply_changed(&sp->psy_bat);
+ last_cap = curr_cap;
+ }
+
+ schedule_delayed_work(&sp->dwork, sp->interval);
+}
+
+static int sp2541_battery_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sp2541_struct *sp;
+ int ret;
+
+ sp = devm_kzalloc(&client->dev, sizeof(*sp), GFP_KERNEL);
+ if (!sp) {
+ dev_err(&client->dev, "failed to allocate device info data\n");
+ return -ENOMEM;
+ }
+
+ sp->client = client;
+ sp->dev = &client->dev;
+ i2c_set_clientdata(client, sp);
+
+ ret = sp2541_eeprom_check(sp);
+ if (ret) {
+ dev_err(sp->dev, "eeprom check failed\n");
+ return ret;
+ }
+
+ sp->interval = msecs_to_jiffies(battery_param.interval);
+
+ sp->psy_bat.name = "battery";
+ sp->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY;
+ sp->psy_bat.properties = sp2541_battery_props;
+ sp->psy_bat.num_properties = ARRAY_SIZE(sp2541_battery_props);
+ sp->psy_bat.get_property = sp2541_battery_get_property;
+ sp->psy_bat.external_power_changed = sp2541_external_power_changed;
+
+ ret = power_supply_register(&client->dev, &sp->psy_bat);
+ if (ret) {
+ dev_err(&client->dev, "failed to register battery\n");
+ return ret;
+ }
+
+ INIT_DELAYED_WORK(&sp->dwork, sp2541_battery_work);
+
+ schedule_delayed_work(&sp->dwork, sp->interval);
+ dev_info(&client->dev, "support ver. %s enabled\n", DRIVER_VERSION);
+ return 0;
+}
+
+static int sp2541_battery_remove(struct i2c_client *client)
+{
+ struct sp2541_struct *sp = i2c_get_clientdata(client);
+ cancel_delayed_work_sync(&sp->dwork);
+ power_supply_unregister(&sp->psy_bat);
+ return 0;
+}
+
+static int sp2541_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct sp2541_struct *sp = i2c_get_clientdata(client);
+ cancel_delayed_work_sync(&sp->dwork);
+ return 0;
+}
+
+static int sp2541_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct sp2541_struct *sp = i2c_get_clientdata(client);
+ schedule_delayed_work(&sp->dwork, 1*HZ);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(sp2541_dev_pm_ops,
+ sp2541_i2c_suspend, sp2541_i2c_resume);
+
+static const struct i2c_device_id sp2541_id[] = {
+ { "sp2541", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, sp2541_id);
+
+static struct i2c_driver sp2541_battery_driver = {
+ .driver = {
+ .name = "sp2541",
+ .owner = THIS_MODULE,
+ .pm = &sp2541_dev_pm_ops,
+ },
+ .probe = sp2541_battery_probe,
+ .remove = __devexit_p(sp2541_battery_remove),
+ .id_table = sp2541_id,
+};
+
+static struct i2c_board_info sp2541_i2c_info = {
+ I2C_BOARD_INFO("sp2541", 0x14),
+};
+
+static struct i2c_client *i2c_client;
+
+static int __init sp2541_battery_init(void)
+{
+ struct i2c_adapter *i2c_adap;
+ int ret;
+
+ if (parse_battery_param())
+ return -ENODEV;
+
+ i2c_adap = i2c_get_adapter(battery_param.i2c_bus);
+ if (!i2c_adap) {
+ pr_err("get i2c%d adapter failed\n", battery_param.i2c_bus);
+ return -ENODEV;
+ }
+ i2c_client = i2c_new_device(i2c_adap, &sp2541_i2c_info);
+ i2c_put_adapter(i2c_adap);
+ if (!i2c_client) {
+ pr_err("Unable to add I2C device for 0x%x\n", sp2541_i2c_info.addr);
+ return -ENODEV;
+ }
+
+ ret = i2c_add_driver(&sp2541_battery_driver);
+ if (ret) {
+ pr_err("Unable to register sp2541_struct driver\n");
+ }
+
+ return ret;
+}
+
+static void __exit sp2541_battery_exit(void)
+{
+ i2c_del_driver(&sp2541_battery_driver);
+ i2c_unregister_device(i2c_client);
+}
+
+module_init(sp2541_battery_init);
+module_exit(sp2541_battery_exit);
+
+MODULE_AUTHOR("clb");
+MODULE_DESCRIPTION("sp2541_struct battery monitor driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/Makefile b/drivers/power/wmt_battery/gauge/ug31xx/Makefile
new file mode 100755
index 00000000..758c6033
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/Makefile
@@ -0,0 +1,13 @@
+
+s_wmt_batt_ug31xx-objs += \
+ uG31xx_API.o \
+ ug31xx_i2c.o \
+ ug31xx_gauge.o \
+ uG31xx_API_Measurement.o \
+ uG31xx_API_Otp.o \
+ uG31xx_API_System.o \
+ uG31xx_API_Backup.o \
+ uG31xx_API_Capacity.b \
+
+obj-m += s_wmt_batt_ug31xx.o
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_cw500_20130801_103638.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_cw500_20130801_103638.h
new file mode 100755
index 00000000..ee375b15
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_cw500_20130801_103638.h
@@ -0,0 +1,233 @@
+
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 4000
+/// EDVF = 3500
+/// Taper Current = 150
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_cw500 [] = {
+ 0x5f,0x47,0x47,0x5f,0xed,0xc9,0x00,0x00,
+ 0xb6,0xfa,0x3a,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x43,0x57,0x35,0x30,
+ 0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x4d,
+ 0x61,0x78,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x01,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0xa0,0x0f,0xac,0x0d,0x14,0x00,
+ 0x96,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x58,
+ 0x10,0x37,0x10,0x19,0x10,0xfe,0x0f,0xe5,
+ 0x0f,0xcf,0x0f,0xbb,0x0f,0xa9,0x0f,0x98,
+ 0x0f,0x87,0x0f,0x75,0x0f,0x5c,0x0f,0x3e,
+ 0x0f,0x24,0x0f,0xf5,0x0e,0xac,0x0d,0x68,
+ 0x10,0x25,0x10,0xfb,0x0f,0xd3,0x0f,0xab,
+ 0x0f,0x85,0x0f,0x62,0x0f,0x41,0x0f,0x23,
+ 0x0f,0x0a,0x0f,0xf4,0x0e,0xe3,0x0e,0xd0,
+ 0x0e,0xbd,0x0e,0xaa,0x0e,0x96,0x0e,0x80,
+ 0x0e,0x67,0x0e,0x51,0x0e,0x2d,0x0e,0xac,
+ 0x0d,0x68,0x10,0x25,0x10,0xfb,0x0f,0xd3,
+ 0x0f,0xab,0x0f,0x85,0x0f,0x62,0x0f,0x41,
+ 0x0f,0x23,0x0f,0x0a,0x0f,0xf4,0x0e,0xe3,
+ 0x0e,0xd0,0x0e,0xbd,0x0e,0xaa,0x0e,0x96,
+ 0x0e,0x80,0x0e,0x67,0x0e,0x51,0x0e,0x2d,
+ 0x0e,0xac,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x64,
+ 0x10,0x46,0x10,0x2b,0x10,0x11,0x10,0xfc,
+ 0x0f,0xe8,0x0f,0xd6,0x0f,0xc5,0x0f,0xb5,
+ 0x0f,0xa4,0x0f,0x8f,0x0f,0x73,0x0f,0x54,
+ 0x0f,0x2c,0x0f,0xac,0x0d,0x68,0x10,0x1b,
+ 0x10,0xf2,0x0f,0xcb,0x0f,0xa0,0x0f,0x7a,
+ 0x0f,0x58,0x0f,0x38,0x0f,0x1d,0x0f,0x04,
+ 0x0f,0xef,0x0e,0xde,0x0e,0xcc,0x0e,0xba,
+ 0x0e,0xa9,0x0e,0x98,0x0e,0x84,0x0e,0x6d,
+ 0x0e,0x57,0x0e,0x31,0x0e,0xac,0x0d,0x68,
+ 0x10,0x1b,0x10,0xf2,0x0f,0xcb,0x0f,0xa0,
+ 0x0f,0x7a,0x0f,0x58,0x0f,0x38,0x0f,0x1d,
+ 0x0f,0x04,0x0f,0xef,0x0e,0xde,0x0e,0xcc,
+ 0x0e,0xba,0x0e,0xa9,0x0e,0x98,0x0e,0x84,
+ 0x0e,0x6d,0x0e,0x57,0x0e,0x31,0x0e,0xac,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x65,0x10,0x4e,0x10,0x39,
+ 0x10,0x26,0x10,0x14,0x10,0x03,0x10,0xf2,
+ 0x0f,0xdd,0x0f,0xc3,0x0f,0xa2,0x0f,0x69,
+ 0x0f,0xac,0x0d,0x68,0x10,0x00,0x10,0xdb,
+ 0x0f,0xbc,0x0f,0x92,0x0f,0x6e,0x0f,0x4e,
+ 0x0f,0x30,0x0f,0x16,0x0f,0xfe,0x0e,0xe8,
+ 0x0e,0xd8,0x0e,0xc5,0x0e,0xb4,0x0e,0xa3,
+ 0x0e,0x93,0x0e,0x7f,0x0e,0x69,0x0e,0x4e,
+ 0x0e,0x19,0x0e,0xac,0x0d,0x68,0x10,0x00,
+ 0x10,0xdb,0x0f,0xbc,0x0f,0x92,0x0f,0x6e,
+ 0x0f,0x4e,0x0f,0x30,0x0f,0x16,0x0f,0xfe,
+ 0x0e,0xe8,0x0e,0xd8,0x0e,0xc5,0x0e,0xb4,
+ 0x0e,0xa3,0x0e,0x93,0x0e,0x7f,0x0e,0x69,
+ 0x0e,0x4e,0x0e,0x19,0x0e,0xac,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x49,0x10,0x06,0x10,0xac,
+ 0x0d,0x68,0x10,0x2a,0x10,0xfc,0x0f,0xa3,
+ 0x0f,0x59,0x0f,0x35,0x0f,0x1b,0x0f,0xff,
+ 0x0e,0xe6,0x0e,0xd2,0x0e,0xc1,0x0e,0xb5,
+ 0x0e,0xa9,0x0e,0x9e,0x0e,0x92,0x0e,0x81,
+ 0x0e,0x6f,0x0e,0x54,0x0e,0x2f,0x0e,0xf6,
+ 0x0d,0xac,0x0d,0x68,0x10,0x2a,0x10,0xfc,
+ 0x0f,0xa3,0x0f,0x59,0x0f,0x35,0x0f,0x1b,
+ 0x0f,0xff,0x0e,0xe6,0x0e,0xd2,0x0e,0xc1,
+ 0x0e,0xb5,0x0e,0xa9,0x0e,0x9e,0x0e,0x92,
+ 0x0e,0x81,0x0e,0x6f,0x0e,0x54,0x0e,0x2f,
+ 0x0e,0xf6,0x0d,0xac,0x0d,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x4a,0x09,0x00,
+ 0x00,0x54,0x01,0x12,0x03,0xe3,0x04,0x4b,
+ 0x04,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,
+ 0x0c,0xed,0x00,0x02,0x03,0xcd,0x04,0x32,
+ 0x04,0xb9,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x27,0x0d,0xba,0x01,0x46,0x03,0x92,
+ 0x05,0x94,0x02,0x8b,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x99,0x07,0x00,0x00,0x97,
+ 0x00,0xbd,0x02,0x44,0x04,0x9e,0x05,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x86,0x0c,0x7d,
+ 0x00,0xc2,0x02,0x8a,0x04,0xbb,0x04,0xe3,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xea,
+ 0x0c,0x62,0x01,0x1f,0x03,0xb8,0x05,0xaf,
+ 0x02,0x87,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xc1,0x04,0x00,0x00,0xed,0x00,0xe3,
+ 0x00,0xf0,0x02,0x1c,0x07,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xa5,0x0b,0x17,0x00,0x4a,
+ 0x02,0xf9,0x03,0x4a,0x05,0x7e,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x8e,0x0c,0xe2,
+ 0x00,0xf2,0x02,0x76,0x05,0x42,0x03,0xa0,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x08,
+ 0x02,0x00,0x00,0x0e,0x00,0x9e,0x00,0x5b,
+ 0x01,0x06,0x0b,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x6f,0x09,0x60,0x00,0x7a,0x01,0xec,
+ 0x01,0xa7,0x05,0xe0,0x03,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x8d,0x0b,0x1f,0x01,0x3c,
+ 0x01,0x73,0x04,0xbe,0x04,0x3f,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_mp718_20131004_070110.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_mp718_20131004_070110.h
new file mode 100755
index 00000000..1ea6271b
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_mp718_20131004_070110.h
@@ -0,0 +1,233 @@
+
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 6800
+/// EDVF = 3400
+/// Taper Current = 100
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_mp718 [] = {
+ 0x5f,0x47,0x47,0x5f,0x65,0xcb,0x00,0x00,
+ 0xb6,0xd6,0x8d,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x4d,0x37,0x31,0x38,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x6f,
+ 0x6e,0x64,0x65,0x72,0x6d,0x65,0x64,0x69,
+ 0x61,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x01,0x02,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0x90,0x1a,0x48,0x0d,0x14,0x00,
+ 0x64,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x4a,0x10,0x1b,0x10,0xf2,0x0f,0xcc,
+ 0x0f,0xaa,0x0f,0x8c,0x0f,0x71,0x0f,0x58,
+ 0x0f,0x3e,0x0f,0x27,0x0f,0x17,0x0f,0x09,
+ 0x0f,0xfe,0x0e,0xf5,0x0e,0xe3,0x0e,0xc3,
+ 0x0e,0xa0,0x0e,0x87,0x0e,0x48,0x0d,0x68,
+ 0x10,0x2a,0x10,0xf6,0x0f,0xca,0x0f,0xa1,
+ 0x0f,0x7c,0x0f,0x5a,0x0f,0x3b,0x0f,0x1d,
+ 0x0f,0xff,0x0e,0xe5,0x0e,0xd2,0x0e,0xc3,
+ 0x0e,0xb7,0x0e,0xac,0x0e,0x9d,0x0e,0x88,
+ 0x0e,0x68,0x0e,0x49,0x0e,0x1b,0x0e,0x48,
+ 0x0d,0x68,0x10,0x2a,0x10,0xf6,0x0f,0xca,
+ 0x0f,0xa1,0x0f,0x7c,0x0f,0x5a,0x0f,0x3b,
+ 0x0f,0x1d,0x0f,0xff,0x0e,0xe5,0x0e,0xd2,
+ 0x0e,0xc3,0x0e,0xb7,0x0e,0xac,0x0e,0x9d,
+ 0x0e,0x88,0x0e,0x68,0x0e,0x49,0x0e,0x1b,
+ 0x0e,0x48,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x35,0x10,0x09,0x10,0xe3,0x0f,0xc1,
+ 0x0f,0xa3,0x0f,0x89,0x0f,0x70,0x0f,0x59,
+ 0x0f,0x42,0x0f,0x31,0x0f,0x23,0x0f,0x19,
+ 0x0f,0x0f,0x0f,0x00,0x0f,0xe2,0x0e,0xbe,
+ 0x0e,0xa3,0x0e,0x48,0x0d,0x68,0x10,0x26,
+ 0x10,0xf2,0x0f,0xc4,0x0f,0x9c,0x0f,0x77,
+ 0x0f,0x55,0x0f,0x35,0x0f,0x17,0x0f,0xfc,
+ 0x0e,0xe5,0x0e,0xd3,0x0e,0xc5,0x0e,0xba,
+ 0x0e,0xb1,0x0e,0xa5,0x0e,0x91,0x0e,0x71,
+ 0x0e,0x50,0x0e,0x19,0x0e,0x48,0x0d,0x68,
+ 0x10,0x26,0x10,0xf2,0x0f,0xc4,0x0f,0x9c,
+ 0x0f,0x77,0x0f,0x55,0x0f,0x35,0x0f,0x17,
+ 0x0f,0xfc,0x0e,0xe5,0x0e,0xd3,0x0e,0xc5,
+ 0x0e,0xba,0x0e,0xb1,0x0e,0xa5,0x0e,0x91,
+ 0x0e,0x71,0x0e,0x50,0x0e,0x19,0x0e,0x48,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x54,
+ 0x10,0x24,0x10,0xfb,0x0f,0xd8,0x0f,0xba,
+ 0x0f,0x9f,0x0f,0x87,0x0f,0x71,0x0f,0x5c,
+ 0x0f,0x4a,0x0f,0x3c,0x0f,0x31,0x0f,0x26,
+ 0x0f,0x19,0x0f,0xfe,0x0e,0xda,0x0e,0xbe,
+ 0x0e,0x48,0x0d,0x68,0x10,0x23,0x10,0xf1,
+ 0x0f,0xc1,0x0f,0x97,0x0f,0x71,0x0f,0x4e,
+ 0x0f,0x2e,0x0f,0x11,0x0f,0xf8,0x0e,0xe4,
+ 0x0e,0xd2,0x0e,0xc5,0x0e,0xba,0x0e,0xb1,
+ 0x0e,0xa5,0x0e,0x92,0x0e,0x71,0x0e,0x50,
+ 0x0e,0x0c,0x0e,0x48,0x0d,0x68,0x10,0x23,
+ 0x10,0xf1,0x0f,0xc1,0x0f,0x97,0x0f,0x71,
+ 0x0f,0x4e,0x0f,0x2e,0x0f,0x11,0x0f,0xf8,
+ 0x0e,0xe4,0x0e,0xd2,0x0e,0xc5,0x0e,0xba,
+ 0x0e,0xb1,0x0e,0xa5,0x0e,0x92,0x0e,0x71,
+ 0x0e,0x50,0x0e,0x0c,0x0e,0x48,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x64,
+ 0x10,0x36,0x10,0x0d,0x10,0xeb,0x0f,0xcd,
+ 0x0f,0xb4,0x0f,0x9e,0x0f,0x8a,0x0f,0x79,
+ 0x0f,0x6a,0x0f,0x5e,0x0f,0x53,0x0f,0x46,
+ 0x0f,0x2f,0x0f,0x0b,0x0f,0xee,0x0e,0x48,
+ 0x0d,0x68,0x10,0x1e,0x10,0xee,0x0f,0xc0,
+ 0x0f,0x93,0x0f,0x6c,0x0f,0x47,0x0f,0x28,
+ 0x0f,0x0c,0x0f,0xf5,0x0e,0xe1,0x0e,0xd3,
+ 0x0e,0xc6,0x0e,0xbb,0x0e,0xb0,0x0e,0xa4,
+ 0x0e,0x91,0x0e,0x74,0x0e,0x4b,0x0e,0xf5,
+ 0x0d,0x48,0x0d,0x68,0x10,0x1e,0x10,0xee,
+ 0x0f,0xc0,0x0f,0x93,0x0f,0x6c,0x0f,0x47,
+ 0x0f,0x28,0x0f,0x0c,0x0f,0xf5,0x0e,0xe1,
+ 0x0e,0xd3,0x0e,0xc6,0x0e,0xbb,0x0e,0xb0,
+ 0x0e,0xa4,0x0e,0x91,0x0e,0x74,0x0e,0x4b,
+ 0x0e,0xf5,0x0d,0x48,0x0d,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x42,0x1a,0x70,
+ 0x01,0x50,0x06,0x08,0x0d,0x78,0x05,0x20,
+ 0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf7,
+ 0x1a,0x89,0x03,0xd3,0x07,0x7a,0x0d,0x20,
+ 0x02,0xb5,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x2d,0x1b,0x75,0x04,0x57,0x08,0x39,
+ 0x0d,0x27,0x01,0x9a,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xbf,0x18,0xa1,0x00,0x67,
+ 0x05,0xbb,0x0a,0xfa,0x07,0xee,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x4b,0x1a,0xc2,
+ 0x02,0x4f,0x07,0xab,0x0d,0x8e,0x02,0xe2,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xcb,
+ 0x1a,0xee,0x03,0x17,0x08,0x63,0x0d,0x60,
+ 0x01,0xa6,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x84,0x16,0x3a,0x00,0x03,0x04,0x71,
+ 0x08,0xd5,0x09,0xda,0x02,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x79,0x19,0x22,0x02,0xbc,
+ 0x06,0xa3,0x0d,0xf7,0x02,0x35,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x5b,0x1a,0x69,
+ 0x03,0xc1,0x07,0x84,0x0d,0xad,0x01,0xc3,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x22,
+ 0x12,0x0c,0x00,0x1c,0x02,0xf3,0x05,0x05,
+ 0x0a,0x69,0x06,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x09,0x18,0x57,0x01,0xe5,0x05,0xc1,
+ 0x0c,0x0a,0x04,0x05,0x02,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x5d,0x19,0x9a,0x02,0x1e,
+ 0x07,0x67,0x0d,0x3c,0x02,0x35,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_t73v_20131120_001204.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_t73v_20131120_001204.h
new file mode 100755
index 00000000..12e6c67b
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_t73v_20131120_001204.h
@@ -0,0 +1,233 @@
+
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 3000
+/// EDVF = 3400
+/// Taper Current = 150
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_t73v [] = {
+ 0x5f,0x47,0x47,0x5f,0x00,0xc6,0x00,0x00,
+ 0xd4,0x1b,0xca,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x47,0x6f,0x6f,0x64,
+ 0x74,0x69,0x6d,0x65,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x54,0x37,0x33,0x56,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x4c,0x55,
+ 0x53,0x54,0x59,0x2d,0x33,0x37,0x37,0x30,
+ 0x31,0x30,0x30,0x50,0x4c,0x00,0x31,0x33,
+ 0x31,0x30,0x31,0x36,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x01,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0xb8,0x0b,0x48,0x0d,0x14,0x00,
+ 0x96,0x00,0x36,0x10,0x3c,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x74,0x0e,0xd8,0x0e,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x05,0x62,0x8a,0x5d,0x9c,0x58,0x4c,0x53,
+ 0xb3,0x4d,0xeb,0x47,0x15,0x42,0x4b,0x3c,
+ 0xc7,0x36,0x80,0x31,0x86,0x2c,0xe5,0x27,
+ 0xa2,0x23,0xc0,0x1f,0x3d,0x1c,0x17,0x19,
+ 0x47,0x16,0xc6,0x13,0x8e,0x11,0xe8,0x03,
+ 0x84,0x03,0xee,0x02,0x58,0x02,0xc2,0x01,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x21,0x38,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x49,0x10,0x23,
+ 0x10,0x01,0x10,0xe2,0x0f,0xc8,0x0f,0xb0,
+ 0x0f,0x9b,0x0f,0x8a,0x0f,0x7b,0x0f,0x6e,
+ 0x0f,0x63,0x0f,0x58,0x0f,0x44,0x0f,0x27,
+ 0x0f,0x09,0x0f,0xd8,0x0e,0x48,0x0d,0x68,
+ 0x10,0x25,0x10,0xf4,0x0f,0xc7,0x0f,0x9c,
+ 0x0f,0x76,0x0f,0x53,0x0f,0x2f,0x0f,0x0e,
+ 0x0f,0xf3,0x0e,0xdf,0x0e,0xce,0x0e,0xbf,
+ 0x0e,0xb3,0x0e,0xa7,0x0e,0x99,0x0e,0x88,
+ 0x0e,0x6f,0x0e,0x53,0x0e,0x24,0x0e,0x48,
+ 0x0d,0x68,0x10,0x25,0x10,0xf4,0x0f,0xc7,
+ 0x0f,0x9c,0x0f,0x76,0x0f,0x53,0x0f,0x2f,
+ 0x0f,0x0e,0x0f,0xf3,0x0e,0xdf,0x0e,0xce,
+ 0x0e,0xbf,0x0e,0xb3,0x0e,0xa7,0x0e,0x99,
+ 0x0e,0x88,0x0e,0x6f,0x0e,0x53,0x0e,0x24,
+ 0x0e,0x48,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x49,0x10,0x23,0x10,0x01,
+ 0x10,0xe2,0x0f,0xc8,0x0f,0xb0,0x0f,0x9b,
+ 0x0f,0x8a,0x0f,0x7b,0x0f,0x6e,0x0f,0x63,
+ 0x0f,0x58,0x0f,0x44,0x0f,0x27,0x0f,0x09,
+ 0x0f,0xd8,0x0e,0x48,0x0d,0x68,0x10,0x25,
+ 0x10,0xf4,0x0f,0xc7,0x0f,0x9c,0x0f,0x76,
+ 0x0f,0x53,0x0f,0x2f,0x0f,0x0e,0x0f,0xf3,
+ 0x0e,0xdf,0x0e,0xce,0x0e,0xbf,0x0e,0xb3,
+ 0x0e,0xa7,0x0e,0x99,0x0e,0x88,0x0e,0x6f,
+ 0x0e,0x53,0x0e,0x24,0x0e,0x48,0x0d,0x68,
+ 0x10,0x25,0x10,0xf4,0x0f,0xc7,0x0f,0x9c,
+ 0x0f,0x76,0x0f,0x53,0x0f,0x2f,0x0f,0x0e,
+ 0x0f,0xf3,0x0e,0xdf,0x0e,0xce,0x0e,0xbf,
+ 0x0e,0xb3,0x0e,0xa7,0x0e,0x99,0x0e,0x88,
+ 0x0e,0x6f,0x0e,0x53,0x0e,0x24,0x0e,0x48,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x49,0x10,0x23,0x10,0x01,0x10,0xe2,
+ 0x0f,0xc8,0x0f,0xb0,0x0f,0x9b,0x0f,0x8a,
+ 0x0f,0x7b,0x0f,0x6e,0x0f,0x63,0x0f,0x58,
+ 0x0f,0x44,0x0f,0x27,0x0f,0x09,0x0f,0xd8,
+ 0x0e,0x48,0x0d,0x68,0x10,0x25,0x10,0xf4,
+ 0x0f,0xc7,0x0f,0x9c,0x0f,0x76,0x0f,0x53,
+ 0x0f,0x2f,0x0f,0x0e,0x0f,0xf3,0x0e,0xdf,
+ 0x0e,0xce,0x0e,0xbf,0x0e,0xb3,0x0e,0xa7,
+ 0x0e,0x99,0x0e,0x88,0x0e,0x6f,0x0e,0x53,
+ 0x0e,0x24,0x0e,0x48,0x0d,0x68,0x10,0x25,
+ 0x10,0xf4,0x0f,0xc7,0x0f,0x9c,0x0f,0x76,
+ 0x0f,0x53,0x0f,0x2f,0x0f,0x0e,0x0f,0xf3,
+ 0x0e,0xdf,0x0e,0xce,0x0e,0xbf,0x0e,0xb3,
+ 0x0e,0xa7,0x0e,0x99,0x0e,0x88,0x0e,0x6f,
+ 0x0e,0x53,0x0e,0x24,0x0e,0x48,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x49,
+ 0x10,0x23,0x10,0x01,0x10,0xe2,0x0f,0xc8,
+ 0x0f,0xb0,0x0f,0x9b,0x0f,0x8a,0x0f,0x7b,
+ 0x0f,0x6e,0x0f,0x63,0x0f,0x58,0x0f,0x44,
+ 0x0f,0x27,0x0f,0x09,0x0f,0xd8,0x0e,0x48,
+ 0x0d,0x68,0x10,0x25,0x10,0xf4,0x0f,0xc7,
+ 0x0f,0x9c,0x0f,0x76,0x0f,0x53,0x0f,0x2f,
+ 0x0f,0x0e,0x0f,0xf3,0x0e,0xdf,0x0e,0xce,
+ 0x0e,0xbf,0x0e,0xb3,0x0e,0xa7,0x0e,0x99,
+ 0x0e,0x88,0x0e,0x6f,0x0e,0x53,0x0e,0x24,
+ 0x0e,0x48,0x0d,0x68,0x10,0x25,0x10,0xf4,
+ 0x0f,0xc7,0x0f,0x9c,0x0f,0x76,0x0f,0x53,
+ 0x0f,0x2f,0x0f,0x0e,0x0f,0xf3,0x0e,0xdf,
+ 0x0e,0xce,0x0e,0xbf,0x0e,0xb3,0x0e,0xa7,
+ 0x0e,0x99,0x0e,0x88,0x0e,0x6f,0x0e,0x53,
+ 0x0e,0x24,0x0e,0x48,0x0d,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x3b,0x0b,0x66,
+ 0x00,0x7d,0x02,0xe4,0x04,0x72,0x03,0x82,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x82,
+ 0x0b,0x79,0x01,0x1b,0x03,0x3b,0x06,0xb1,
+ 0x00,0x4c,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x96,0x0b,0xe7,0x01,0x69,0x03,0xd9,
+ 0x05,0x6c,0x00,0x41,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x3b,0x0b,0x66,0x00,0x7d,
+ 0x02,0xe4,0x04,0x72,0x03,0x82,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x82,0x0b,0x79,
+ 0x01,0x1b,0x03,0x3b,0x06,0xb1,0x00,0x4c,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x96,
+ 0x0b,0xe7,0x01,0x69,0x03,0xd9,0x05,0x6c,
+ 0x00,0x41,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x3b,0x0b,0x66,0x00,0x7d,0x02,0xe4,
+ 0x04,0x72,0x03,0x82,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x82,0x0b,0x79,0x01,0x1b,
+ 0x03,0x3b,0x06,0xb1,0x00,0x4c,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x96,0x0b,0xe7,
+ 0x01,0x69,0x03,0xd9,0x05,0x6c,0x00,0x41,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3b,
+ 0x0b,0x66,0x00,0x7d,0x02,0xe4,0x04,0x72,
+ 0x03,0x82,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x82,0x0b,0x79,0x01,0x1b,0x03,0x3b,
+ 0x06,0xb1,0x00,0x4c,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x96,0x0b,0xe7,0x01,0x69,
+ 0x03,0xd9,0x05,0x6c,0x00,0x41,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms7320_20130718_200031.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms7320_20130718_200031.h
new file mode 100755
index 00000000..9520f0f7
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms7320_20130718_200031.h
@@ -0,0 +1,233 @@
+
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 4000
+/// EDVF = 3400
+/// Taper Current = 100
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_wms7320 [] = {
+ 0x5f,0x47,0x47,0x5f,0x6d,0xc8,0x00,0x00,
+ 0x5f,0x5b,0x2a,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x4d,0x53,0x37,
+ 0x33,0x32,0x30,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x6f,
+ 0x6e,0x64,0x65,0x72,0x6d,0x65,0x64,0x69,
+ 0x61,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x01,0x02,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0xa0,0x0f,0x48,0x0d,0x14,0x00,
+ 0x64,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x5b,
+ 0x10,0x4d,0x10,0x2d,0x10,0x20,0x10,0x0f,
+ 0x10,0xff,0x0f,0xe1,0x0f,0xcc,0x0f,0xb2,
+ 0x0f,0x99,0x0f,0x4f,0x0f,0x48,0x0d,0x68,
+ 0x10,0xdd,0x0f,0xb0,0x0f,0x85,0x0f,0x5f,
+ 0x0f,0x3d,0x0f,0x1c,0x0f,0xf9,0x0e,0xcf,
+ 0x0e,0xb9,0x0e,0xab,0x0e,0xa4,0x0e,0x9f,
+ 0x0e,0x98,0x0e,0x8b,0x0e,0x72,0x0e,0x45,
+ 0x0e,0x05,0x0e,0xc9,0x0d,0xa2,0x0d,0x48,
+ 0x0d,0x68,0x10,0xdd,0x0f,0xb0,0x0f,0x85,
+ 0x0f,0x5f,0x0f,0x3d,0x0f,0x1c,0x0f,0xf9,
+ 0x0e,0xcf,0x0e,0xb9,0x0e,0xab,0x0e,0xa4,
+ 0x0e,0x9f,0x0e,0x98,0x0e,0x8b,0x0e,0x72,
+ 0x0e,0x45,0x0e,0x05,0x0e,0xc9,0x0d,0xa2,
+ 0x0d,0x48,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x66,
+ 0x10,0x14,0x10,0x48,0x0d,0x68,0x10,0xdd,
+ 0x0f,0xb0,0x0f,0x85,0x0f,0x5f,0x0f,0x3d,
+ 0x0f,0x1c,0x0f,0xf9,0x0e,0xcf,0x0e,0xb9,
+ 0x0e,0xab,0x0e,0xa4,0x0e,0x9f,0x0e,0x98,
+ 0x0e,0x8b,0x0e,0x72,0x0e,0x45,0x0e,0x05,
+ 0x0e,0xc9,0x0d,0xa2,0x0d,0x48,0x0d,0x68,
+ 0x10,0xdd,0x0f,0xb0,0x0f,0x85,0x0f,0x5f,
+ 0x0f,0x3d,0x0f,0x1c,0x0f,0xf9,0x0e,0xcf,
+ 0x0e,0xb9,0x0e,0xab,0x0e,0xa4,0x0e,0x9f,
+ 0x0e,0x98,0x0e,0x8b,0x0e,0x72,0x0e,0x45,
+ 0x0e,0x05,0x0e,0xc9,0x0d,0xa2,0x0d,0x48,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x3f,
+ 0x10,0x48,0x0d,0x68,0x10,0xdd,0x0f,0xb0,
+ 0x0f,0x85,0x0f,0x5f,0x0f,0x3d,0x0f,0x1c,
+ 0x0f,0xf9,0x0e,0xcf,0x0e,0xb9,0x0e,0xab,
+ 0x0e,0xa4,0x0e,0x9f,0x0e,0x98,0x0e,0x8b,
+ 0x0e,0x72,0x0e,0x45,0x0e,0x05,0x0e,0xc9,
+ 0x0d,0xa2,0x0d,0x48,0x0d,0x68,0x10,0xdd,
+ 0x0f,0xb0,0x0f,0x85,0x0f,0x5f,0x0f,0x3d,
+ 0x0f,0x1c,0x0f,0xf9,0x0e,0xcf,0x0e,0xb9,
+ 0x0e,0xab,0x0e,0xa4,0x0e,0x9f,0x0e,0x98,
+ 0x0e,0x8b,0x0e,0x72,0x0e,0x45,0x0e,0x05,
+ 0x0e,0xc9,0x0d,0xa2,0x0d,0x48,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x48,
+ 0x0d,0x68,0x10,0xdd,0x0f,0xb0,0x0f,0x85,
+ 0x0f,0x5f,0x0f,0x3d,0x0f,0x1c,0x0f,0xf9,
+ 0x0e,0xcf,0x0e,0xb9,0x0e,0xab,0x0e,0xa4,
+ 0x0e,0x9f,0x0e,0x98,0x0e,0x8b,0x0e,0x72,
+ 0x0e,0x45,0x0e,0x05,0x0e,0xc9,0x0d,0xa2,
+ 0x0d,0x48,0x0d,0x68,0x10,0xdd,0x0f,0xb0,
+ 0x0f,0x85,0x0f,0x5f,0x0f,0x3d,0x0f,0x1c,
+ 0x0f,0xf9,0x0e,0xcf,0x0e,0xb9,0x0e,0xab,
+ 0x0e,0xa4,0x0e,0x9f,0x0e,0x98,0x0e,0x8b,
+ 0x0e,0x72,0x0e,0x45,0x0e,0x05,0x0e,0xc9,
+ 0x0d,0xa2,0x0d,0x48,0x0d,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xa0,
+ 0x0f,0xd8,0x0e,0x10,0x0e,0x48,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xa0,0x0f,0xd8,0x0e,0x10,0x0e,0x48,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xa0,0x0f,0xd8,0x0e,0x10,
+ 0x0e,0x48,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xa0,0x0f,0xd8,
+ 0x0e,0x10,0x0e,0x48,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xb9,0x07,0x00,
+ 0x00,0x7c,0x00,0xaa,0x02,0x91,0x04,0xa2,
+ 0x04,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xf1,
+ 0x0b,0xb2,0x00,0xde,0x02,0xcf,0x04,0x91,
+ 0x03,0x9c,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xec,0x0b,0x3b,0x01,0xf1,0x02,0x7f,
+ 0x05,0x3f,0x02,0x81,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xe0,0x02,0x00,0x00,0x00,
+ 0x00,0x1b,0x00,0xc5,0x02,0xcf,0x0a,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xe8,0x0b,0x00,
+ 0x00,0x83,0x01,0xdc,0x03,0x88,0x06,0x4f,
+ 0x03,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xbe,
+ 0x0d,0x43,0x00,0x72,0x03,0x24,0x07,0xe3,
+ 0x02,0x85,0x01,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xe2,0x03,0x00,0x00,0x83,0x00,0xe2,
+ 0x02,0x7c,0x00,0x82,0x06,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xa2,0x09,0x49,0x00,0xcc,
+ 0x00,0x57,0x03,0x34,0x05,0xb6,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x8a,0x0a,0x61,
+ 0x00,0xd6,0x01,0x83,0x05,0xcf,0x02,0xa8,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xb3,
+ 0x00,0x00,0x00,0x00,0x00,0x29,0x00,0x89,
+ 0x00,0x17,0x04,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xf4,0x04,0x00,0x00,0x58,0x01,0x7c,
+ 0x00,0x20,0x03,0x1c,0x07,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xa5,0x09,0xab,0x00,0x38,
+ 0x03,0x87,0x00,0x39,0x05,0x6c,0x02,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h
new file mode 100755
index 00000000..7cf4a54a
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h
@@ -0,0 +1,233 @@
+
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 3900
+/// EDVF = 3500
+/// Taper Current = 150
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_wms8309_c7_3900mAh [] = {
+ 0x5f,0x47,0x47,0x5f,0xbb,0xfa,0x00,0x00,
+ 0x1f,0x69,0x33,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x4d,0x53,0x38,
+ 0x33,0x30,0x39,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x4d,
+ 0x61,0x78,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x01,0x02,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0x3c,0x0f,0xac,0x0d,0x14,0x00,
+ 0x96,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x5b,0x10,0x2f,0x10,0x03,0x10,0xde,
+ 0x0f,0xbb,0x0f,0x9c,0x0f,0x7e,0x0f,0x62,
+ 0x0f,0x4a,0x0f,0x36,0x0f,0x28,0x0f,0x1e,
+ 0x0f,0x15,0x0f,0x0a,0x0f,0xf7,0x0e,0xdb,
+ 0x0e,0xbe,0x0e,0xa4,0x0e,0xac,0x0d,0x68,
+ 0x10,0x38,0x10,0x11,0x10,0xed,0x0f,0xca,
+ 0x0f,0xab,0x0f,0x8a,0x0f,0x6e,0x0f,0x52,
+ 0x0f,0x39,0x0f,0x22,0x0f,0x65,0x0f,0x57,
+ 0x0f,0x4c,0x0f,0x40,0x0f,0x31,0x0f,0x1b,
+ 0x0f,0x02,0x0f,0xea,0x0e,0xa0,0x0e,0xac,
+ 0x0d,0x68,0x10,0x38,0x10,0x11,0x10,0xed,
+ 0x0f,0xca,0x0f,0xab,0x0f,0x8a,0x0f,0x6e,
+ 0x0f,0x52,0x0f,0x39,0x0f,0x22,0x0f,0x65,
+ 0x0f,0x57,0x0f,0x4c,0x0f,0x40,0x0f,0x31,
+ 0x0f,0x1b,0x0f,0x02,0x0f,0xea,0x0e,0xa0,
+ 0x0e,0xac,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x4a,0x10,0x10,0x10,0xed,0x0f,0xcd,
+ 0x0f,0xaf,0x0f,0x93,0x0f,0x7b,0x0f,0x65,
+ 0x0f,0x52,0x0f,0x45,0x0f,0x3a,0x0f,0x31,
+ 0x0f,0x27,0x0f,0x14,0x0f,0xf9,0x0e,0xdb,
+ 0x0e,0xbd,0x0e,0xac,0x0d,0x68,0x10,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xac,0x0d,0x68,
+ 0x10,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xac,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x34,0x10,0x0f,0x10,0xee,0x0f,0xcf,
+ 0x0f,0xb4,0x0f,0x9d,0x0f,0x8a,0x0f,0x7a,
+ 0x0f,0x6c,0x0f,0x5f,0x0f,0x53,0x0f,0x47,
+ 0x0f,0x37,0x0f,0x1e,0x0f,0xfe,0x0e,0xd9,
+ 0x0e,0xac,0x0d,0x68,0x10,0x1f,0x10,0xf3,
+ 0x0f,0xc1,0x0f,0x87,0x0f,0x68,0x0f,0x46,
+ 0x0f,0x25,0x0f,0x09,0x0f,0xf2,0x0e,0xde,
+ 0x0e,0xd1,0x0e,0xc2,0x0e,0xb7,0x0e,0xae,
+ 0x0e,0xa3,0x0e,0x92,0x0e,0x7b,0x0e,0x63,
+ 0x0e,0x31,0x0e,0xac,0x0d,0x68,0x10,0x1f,
+ 0x10,0xf3,0x0f,0xc1,0x0f,0x87,0x0f,0x68,
+ 0x0f,0x46,0x0f,0x25,0x0f,0x09,0x0f,0xf2,
+ 0x0e,0xde,0x0e,0xd1,0x0e,0xc2,0x0e,0xb7,
+ 0x0e,0xae,0x0e,0xa3,0x0e,0x92,0x0e,0x7b,
+ 0x0e,0x63,0x0e,0x31,0x0e,0xac,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x51,0x10,0x37,0x10,0x1d,0x10,0x05,
+ 0x10,0xee,0x0f,0xdb,0x0f,0xc8,0x0f,0xb8,
+ 0x0f,0xa8,0x0f,0x99,0x0f,0x8a,0x0f,0x78,
+ 0x0f,0x61,0x0f,0x43,0x0f,0x12,0x0f,0xac,
+ 0x0d,0x68,0x10,0x05,0x10,0xc9,0x0f,0x8a,
+ 0x0f,0x67,0x0f,0x4c,0x0f,0x2d,0x0f,0x17,
+ 0x0f,0xfe,0x0e,0xe8,0x0e,0xd5,0x0e,0xc5,
+ 0x0e,0xb9,0x0e,0xaf,0x0e,0xa7,0x0e,0x9b,
+ 0x0e,0x8b,0x0e,0x75,0x0e,0x5c,0x0e,0x1c,
+ 0x0e,0xac,0x0d,0x68,0x10,0x05,0x10,0xc9,
+ 0x0f,0x8a,0x0f,0x67,0x0f,0x4c,0x0f,0x2d,
+ 0x0f,0x17,0x0f,0xfe,0x0e,0xe8,0x0e,0xd5,
+ 0x0e,0xc5,0x0e,0xb9,0x0e,0xaf,0x0e,0xa7,
+ 0x0e,0x9b,0x0e,0x8b,0x0e,0x75,0x0e,0x5c,
+ 0x0e,0x1c,0x0e,0xac,0x0d,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x1c,0x0e,0x02,
+ 0x01,0xef,0x02,0x18,0x05,0x12,0x05,0xd8,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x62,
+ 0x0e,0xf2,0x01,0x50,0x03,0xcb,0x06,0x53,
+ 0x02,0x9e,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x74,0x0e,0x4c,0x02,0x6c,0x03,0xf3,
+ 0x06,0xc8,0x01,0x95,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x83,0x0d,0x85,0x00,0x83,
+ 0x02,0x8e,0x04,0xec,0x05,0x24,0x01,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x20,0x0e,0xa4,
+ 0x01,0x17,0x03,0x03,0x07,0x60,0x02,0xa1,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x48,
+ 0x0e,0x24,0x02,0x3d,0x03,0x45,0x07,0xa0,
+ 0x01,0x8f,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x76,0x0c,0x2b,0x00,0x0b,0x02,0xd2,
+ 0x03,0x6c,0x06,0xd6,0x01,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xb6,0x0d,0x2c,0x01,0xed,
+ 0x02,0xa8,0x06,0xf3,0x02,0xc5,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xf5,0x0d,0xd1,
+ 0x01,0x12,0x03,0x4b,0x07,0xc5,0x01,0x97,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7d,
+ 0x09,0x0d,0x00,0xf4,0x00,0x9d,0x02,0xde,
+ 0x05,0xfe,0x03,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x95,0x0c,0x7e,0x00,0x1b,0x02,0x32,
+ 0x05,0xc8,0x04,0x37,0x01,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x0f,0x0d,0xef,0x00,0xd3,
+ 0x02,0x15,0x07,0x36,0x02,0xc5,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h
new file mode 100755
index 00000000..aa7d7316
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h
@@ -0,0 +1,233 @@
+
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 3000
+/// EDVF = 3500
+/// Taper Current = 150
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_wms8309_c7_3000mAh [] = {
+ 0x5f,0x47,0x47,0x5f,0x56,0xbe,0x00,0x00,
+ 0x31,0x88,0x6e,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x6f,0x6e,0x64,
+ 0x65,0x72,0x6d,0x65,0x64,0x69,0x61,0x00,
+ 0x00,0x00,0x00,0x00,0x43,0x57,0x35,0x30,
+ 0x30,0x2d,0x33,0x30,0x30,0x30,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x6d,0x4d,
+ 0x61,0x78,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x53,0x5a,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x02,0x01,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0xb8,0x0b,0xac,0x0d,0x14,0x00,
+ 0x96,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x59,0x10,0x44,0x10,0x32,0x10,0x20,
+ 0x10,0x0b,0x10,0xef,0x0f,0xc8,0x0f,0x9c,
+ 0x0f,0x77,0x0f,0x28,0x0f,0xac,0x0d,0x68,
+ 0x10,0x0c,0x10,0xeb,0x0f,0xd5,0x0f,0xc4,
+ 0x0f,0xb4,0x0f,0xa0,0x0f,0x83,0x0f,0x66,
+ 0x0f,0x4d,0x0f,0x37,0x0f,0x28,0x0f,0x16,
+ 0x0f,0x03,0x0f,0xea,0x0e,0xcc,0x0e,0xab,
+ 0x0e,0x84,0x0e,0x5c,0x0e,0x2d,0x0e,0xac,
+ 0x0d,0x68,0x10,0x0c,0x10,0xeb,0x0f,0xd5,
+ 0x0f,0xc4,0x0f,0xb4,0x0f,0xa0,0x0f,0x83,
+ 0x0f,0x66,0x0f,0x4d,0x0f,0x37,0x0f,0x28,
+ 0x0f,0x16,0x0f,0x03,0x0f,0xea,0x0e,0xcc,
+ 0x0e,0xab,0x0e,0x84,0x0e,0x5c,0x0e,0x2d,
+ 0x0e,0xac,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x63,
+ 0x10,0x4b,0x10,0x36,0x10,0x22,0x10,0x0d,
+ 0x10,0xf5,0x0f,0xd4,0x0f,0xac,0x0f,0x84,
+ 0x0f,0x3d,0x0f,0xac,0x0d,0x68,0x10,0x0a,
+ 0x10,0xe9,0x0f,0xd3,0x0f,0xc1,0x0f,0xaf,
+ 0x0f,0x99,0x0f,0x7d,0x0f,0x62,0x0f,0x4b,
+ 0x0f,0x35,0x0f,0x24,0x0f,0x11,0x0f,0xfc,
+ 0x0e,0xe5,0x0e,0xcb,0x0e,0xac,0x0e,0x86,
+ 0x0e,0x61,0x0e,0x34,0x0e,0xac,0x0d,0x68,
+ 0x10,0x0a,0x10,0xe9,0x0f,0xd3,0x0f,0xc1,
+ 0x0f,0xaf,0x0f,0x99,0x0f,0x7d,0x0f,0x62,
+ 0x0f,0x4b,0x0f,0x35,0x0f,0x24,0x0f,0x11,
+ 0x0f,0xfc,0x0e,0xe5,0x0e,0xcb,0x0e,0xac,
+ 0x0e,0x86,0x0e,0x61,0x0e,0x34,0x0e,0xac,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x52,0x10,0x3c,0x10,0x25,0x10,0x0e,
+ 0x10,0xf2,0x0f,0xcd,0x0f,0xa3,0x0f,0x5f,
+ 0x0f,0xac,0x0d,0x68,0x10,0x05,0x10,0xe5,
+ 0x0f,0xcf,0x0f,0xbb,0x0f,0xa6,0x0f,0x8e,
+ 0x0f,0x74,0x0f,0x5c,0x0f,0x45,0x0f,0x30,
+ 0x0f,0x1f,0x0f,0x0b,0x0f,0xf5,0x0e,0xdd,
+ 0x0e,0xc4,0x0e,0xa5,0x0e,0x82,0x0e,0x60,
+ 0x0e,0x24,0x0e,0xac,0x0d,0x68,0x10,0x05,
+ 0x10,0xe5,0x0f,0xcf,0x0f,0xbb,0x0f,0xa6,
+ 0x0f,0x8e,0x0f,0x74,0x0f,0x5c,0x0f,0x45,
+ 0x0f,0x30,0x0f,0x1f,0x0f,0x0b,0x0f,0xf5,
+ 0x0e,0xdd,0x0e,0xc4,0x0e,0xa5,0x0e,0x82,
+ 0x0e,0x60,0x0e,0x24,0x0e,0xac,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x67,0x10,0x4e,0x10,0x34,
+ 0x10,0x16,0x10,0xed,0x0f,0xa6,0x0f,0xac,
+ 0x0d,0x68,0x10,0xf6,0x0f,0xda,0x0f,0xc3,
+ 0x0f,0xac,0x0f,0x95,0x0f,0x7c,0x0f,0x68,
+ 0x0f,0x51,0x0f,0x3b,0x0f,0x26,0x0f,0x11,
+ 0x0f,0xfb,0x0e,0xe3,0x0e,0xcf,0x0e,0xb5,
+ 0x0e,0x99,0x0e,0x79,0x0e,0x4f,0x0e,0x0b,
+ 0x0e,0xac,0x0d,0x68,0x10,0xf6,0x0f,0xda,
+ 0x0f,0xc3,0x0f,0xac,0x0f,0x95,0x0f,0x7c,
+ 0x0f,0x68,0x0f,0x51,0x0f,0x3b,0x0f,0x26,
+ 0x0f,0x11,0x0f,0xfb,0x0e,0xe3,0x0e,0xcf,
+ 0x0e,0xb5,0x0e,0x99,0x0e,0x79,0x0e,0x4f,
+ 0x0e,0x0b,0x0e,0xac,0x0d,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x2a,0x09,0x00,
+ 0x00,0xcf,0x00,0x51,0x04,0x0a,0x04,0x51,
+ 0x02,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xed,
+ 0x0a,0x7d,0x00,0x26,0x04,0x44,0x04,0x05,
+ 0x02,0x9d,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x0e,0x0b,0x6c,0x01,0x7f,0x04,0xc0,
+ 0x03,0x62,0x01,0x7e,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xe5,0x08,0x00,0x00,0xad,
+ 0x00,0x1b,0x04,0x1c,0x04,0x85,0x02,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xe7,0x0a,0x69,
+ 0x00,0x07,0x04,0x5b,0x04,0x19,0x02,0x99,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0e,
+ 0x0b,0x4d,0x01,0x7b,0x04,0xe0,0x03,0x64,
+ 0x01,0x75,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x09,0x08,0x00,0x00,0x59,0x00,0x7e,
+ 0x03,0x31,0x04,0x30,0x03,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x94,0x0a,0x3e,0x00,0xb1,
+ 0x03,0x5a,0x04,0x4a,0x02,0xc4,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xeb,0x0a,0x05,
+ 0x01,0x70,0x04,0xf7,0x03,0x7e,0x01,0x79,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xeb,
+ 0x05,0x00,0x00,0x14,0x00,0xd9,0x01,0xfd,
+ 0x03,0xbf,0x04,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x95,0x09,0x10,0x00,0x99,0x02,0x53,
+ 0x04,0x97,0x02,0x52,0x01,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x53,0x0a,0x79,0x00,0x10,
+ 0x04,0x11,0x04,0xb7,0x01,0xa0,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h
new file mode 100755
index 00000000..c107e274
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h
@@ -0,0 +1,233 @@
+
+/* This file is auto-generated. Don't edit this file. */
+
+/// R-Sense = 20
+/// ILMD = 5000
+/// EDVF = 3500
+/// Taper Current = 200
+/// Taper Voltage = 4150
+/// Taper Time = 60
+
+char FactoryGGBXFile_wms8309_wm8 [] = {
+ 0x5f,0x47,0x47,0x5f,0x1d,0xcc,0x00,0x00,
+ 0xfd,0x0e,0x54,0x95,0x0e,0x00,0x00,0x00,
+ 0xbd,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x4d,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x57,0x4d,0x53,0x38,
+ 0x33,0x30,0x39,0x2d,0x31,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x57,0x4d,
+ 0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x37,
+ 0x31,0x30,0x38,0x31,0x30,0x33,0x50,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x01,0x02,0x00,0x01,0x01,0x00,0x00,
+ 0x70,0x00,0x64,0x00,0x00,0x00,0x00,0x14,
+ 0x00,0x00,0x88,0x13,0xac,0x0d,0x14,0x00,
+ 0xc8,0x00,0x36,0x10,0x3c,0x00,0x06,0x00,
+ 0x00,0x00,0x00,0x00,0xe4,0x0c,0x48,0x0d,
+ 0x00,0x00,0x32,0x00,0x26,0x02,0xf4,0x01,
+ 0x5c,0xff,0x77,0x01,0x63,0x05,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0xf0,0xdf,0xf4,0x67,
+ 0xe8,0xcf,0xee,0x1b,0xe8,0x03,0xe8,0x03,
+ 0x00,0x00,0xe8,0x03,0x00,0x00,0x10,0x27,
+ 0x1e,0x61,0xae,0x5c,0xce,0x57,0x94,0x52,
+ 0x14,0x4d,0x6a,0x47,0xac,0x41,0xff,0x3b,
+ 0x77,0x36,0x2b,0x31,0x2c,0x2c,0x85,0x27,
+ 0x3d,0x23,0x58,0x1f,0xd3,0x1b,0xab,0x18,
+ 0xdb,0x15,0x5c,0x13,0x27,0x11,0xe8,0x03,
+ 0xbc,0x02,0xc2,0x01,0xc8,0x00,0x00,0x00,
+ 0x1e,0x00,0xe8,0x03,0xff,0x77,0x00,0x00,
+ 0x00,0x00,0x01,0x00,0x00,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x41,0x10,0x1a,
+ 0x10,0xf8,0x0f,0xd9,0x0f,0xbe,0x0f,0xa5,
+ 0x0f,0x8d,0x0f,0x77,0x0f,0x65,0x0f,0x56,
+ 0x0f,0x48,0x0f,0x3b,0x0f,0x27,0x0f,0x0a,
+ 0x0f,0xeb,0x0e,0xce,0x0e,0xac,0x0d,0x68,
+ 0x10,0x2f,0x10,0x00,0x10,0xd3,0x0f,0xaa,
+ 0x0f,0x84,0x0f,0x61,0x0f,0x3f,0x0f,0x1c,
+ 0x0f,0xfe,0x0e,0xe8,0x0e,0xd9,0x0e,0xc9,
+ 0x0e,0xbb,0x0e,0xad,0x0e,0x9f,0x0e,0x8c,
+ 0x0e,0x70,0x0e,0x55,0x0e,0x3a,0x0e,0xac,
+ 0x0d,0x68,0x10,0x2f,0x10,0x00,0x10,0xd3,
+ 0x0f,0xaa,0x0f,0x84,0x0f,0x61,0x0f,0x3f,
+ 0x0f,0x1c,0x0f,0xfe,0x0e,0xe8,0x0e,0xd9,
+ 0x0e,0xc9,0x0e,0xbb,0x0e,0xad,0x0e,0x9f,
+ 0x0e,0x8c,0x0e,0x70,0x0e,0x55,0x0e,0x3a,
+ 0x0e,0xac,0x0d,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x62,0x10,0x38,0x10,0x13,
+ 0x10,0xf3,0x0f,0xd7,0x0f,0xbf,0x0f,0xab,
+ 0x0f,0x9a,0x0f,0x8b,0x0f,0x7e,0x0f,0x72,
+ 0x0f,0x67,0x0f,0x57,0x0f,0x3c,0x0f,0x17,
+ 0x0f,0xf7,0x0e,0xac,0x0d,0x68,0x10,0x20,
+ 0x10,0xee,0x0f,0xc0,0x0f,0x95,0x0f,0x6e,
+ 0x0f,0x4b,0x0f,0x29,0x0f,0x09,0x0f,0xef,
+ 0x0e,0xdb,0x0e,0xce,0x0e,0xc1,0x0e,0xb7,
+ 0x0e,0xaf,0x0e,0xa5,0x0e,0x96,0x0e,0x7b,
+ 0x0e,0x59,0x0e,0x3b,0x0e,0xac,0x0d,0x68,
+ 0x10,0x20,0x10,0xee,0x0f,0xc0,0x0f,0x95,
+ 0x0f,0x6e,0x0f,0x4b,0x0f,0x29,0x0f,0x09,
+ 0x0f,0xef,0x0e,0xdb,0x0e,0xce,0x0e,0xc1,
+ 0x0e,0xb7,0x0e,0xaf,0x0e,0xa5,0x0e,0x96,
+ 0x0e,0x7b,0x0e,0x59,0x0e,0x3b,0x0e,0xac,
+ 0x0d,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x53,0x10,0x31,
+ 0x10,0x14,0x10,0xfa,0x0f,0xe4,0x0f,0xd2,
+ 0x0f,0xc1,0x0f,0xb2,0x0f,0xa4,0x0f,0x96,
+ 0x0f,0x86,0x0f,0x6c,0x0f,0x48,0x0f,0x27,
+ 0x0f,0xac,0x0d,0x68,0x10,0x21,0x10,0xf3,
+ 0x0f,0xc8,0x0f,0x9c,0x0f,0x76,0x0f,0x51,
+ 0x0f,0x2e,0x0f,0x10,0x0f,0xf8,0x0e,0xe4,
+ 0x0e,0xd6,0x0e,0xc7,0x0e,0xba,0x0e,0xaf,
+ 0x0e,0xa3,0x0e,0x91,0x0e,0x77,0x0e,0x62,
+ 0x0e,0x3f,0x0e,0xac,0x0d,0x68,0x10,0x21,
+ 0x10,0xf3,0x0f,0xc8,0x0f,0x9c,0x0f,0x76,
+ 0x0f,0x51,0x0f,0x2e,0x0f,0x10,0x0f,0xf8,
+ 0x0e,0xe4,0x0e,0xd6,0x0e,0xc7,0x0e,0xba,
+ 0x0e,0xaf,0x0e,0xa3,0x0e,0x91,0x0e,0x77,
+ 0x0e,0x62,0x0e,0x3f,0x0e,0xac,0x0d,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x68,0x10,0x68,0x10,0x68,0x10,0x68,
+ 0x10,0x5d,0x10,0x51,0x10,0x42,0x10,0x31,
+ 0x10,0x1f,0x10,0x0f,0x10,0xfe,0x0f,0xeb,
+ 0x0f,0xd1,0x0f,0xae,0x0f,0x85,0x0f,0xac,
+ 0x0d,0x68,0x10,0x25,0x10,0xed,0x0f,0xae,
+ 0x0f,0x81,0x0f,0x5c,0x0f,0x3a,0x0f,0x1c,
+ 0x0f,0x03,0x0f,0xee,0x0e,0xdc,0x0e,0xcf,
+ 0x0e,0xc0,0x0e,0xb3,0x0e,0xa7,0x0e,0x99,
+ 0x0e,0x86,0x0e,0x71,0x0e,0x56,0x0e,0x1c,
+ 0x0e,0xac,0x0d,0x68,0x10,0x25,0x10,0xed,
+ 0x0f,0xae,0x0f,0x81,0x0f,0x5c,0x0f,0x3a,
+ 0x0f,0x1c,0x0f,0x03,0x0f,0xee,0x0e,0xdc,
+ 0x0e,0xcf,0x0e,0xc0,0x0e,0xb3,0x0e,0xa7,
+ 0x0e,0x99,0x0e,0x86,0x0e,0x71,0x0e,0x56,
+ 0x0e,0x1c,0x0e,0xac,0x0d,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x68,0x10,0xb9,
+ 0x0f,0x0a,0x0f,0x5b,0x0e,0xac,0x0d,0x00,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x68,
+ 0x10,0xb9,0x0f,0x0a,0x0f,0x5b,0x0e,0xac,
+ 0x0d,0x00,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x68,0x10,0xb9,0x0f,0x0a,0x0f,0x5b,
+ 0x0e,0xac,0x0d,0x00,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x68,0x10,0xb9,0x0f,0x0a,
+ 0x0f,0x5b,0x0e,0xac,0x0d,0x00,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xf9,0x11,0xc1,
+ 0x00,0xb7,0x03,0xc5,0x05,0xba,0x07,0xa4,
+ 0x01,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x0c,
+ 0x13,0x48,0x02,0x75,0x04,0x96,0x08,0xb7,
+ 0x03,0xbd,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x35,0x13,0xdf,0x02,0xb8,0x04,0xd9,
+ 0x08,0xc4,0x02,0xa8,0x00,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x1a,0x12,0x20,0x00,0x23,
+ 0x03,0x2c,0x05,0xaa,0x09,0x97,0x02,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x11,0x14,0xa2,
+ 0x01,0x50,0x04,0xca,0x09,0x53,0x04,0xcb,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x4c,
+ 0x14,0x5e,0x02,0xd0,0x04,0x51,0x0a,0xcc,
+ 0x02,0xaa,0x00,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0x2e,0x0f,0x11,0x00,0x9b,0x02,0x7c,
+ 0x04,0x05,0x08,0x05,0x04,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x35,0x12,0x48,0x01,0xec,
+ 0x03,0xd7,0x07,0x28,0x05,0xec,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0x9b,0x12,0x23,
+ 0x02,0x58,0x04,0x31,0x09,0xef,0x02,0xa3,
+ 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x7a,
+ 0x0a,0x00,0x00,0xb4,0x00,0x89,0x03,0x3d,
+ 0x06,0x96,0x08,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xb9,0x10,0x73,0x01,0xae,0x01,0xe4,
+ 0x05,0xb3,0x07,0xb6,0x01,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0x48,0x12,0xab,0x01,0x0d,
+ 0x04,0xb1,0x08,0xde,0x03,0xdd,0x00,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
+ 0xff,0xff,0xff,0xff,0xff,
+};
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/global.h b/drivers/power/wmt_battery/gauge/ug31xx/global.h
new file mode 100755
index 00000000..e328bec3
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/global.h
@@ -0,0 +1,31 @@
+#ifndef _GLOBAL_H_
+#define _GLOBAL_H_
+
+#define UG31XX_DEBUG_ENABLE
+//#define UG31XX_RESET_DATABASE ///< [AT-PM] : DEFAULT off ; 04/13/2013
+
+#if defined (uG31xx_OS_WINDOWS)
+
+ #pragma pack(push)
+ #pragma pack(1)
+
+ #ifdef UG31XX_DEBUG_ENABLE
+ #define DEBUG_LOG
+ #define uG31xx_NAC_LMD_ADJUST_DEBUG_ENABLE
+ #define TABLE_BACKUP_DEBUG_ENABLE
+ #define DEBUG_LOG_AT_PM
+ #define CALIBRATE_ADC_DEBUG_LOG
+ #endif ///< end of UG31XX_DEBUG_ENABLE
+
+ #define EXPORTS _declspec(dllexport)
+
+#elif defined(uG31xx_OS_ANDROID)
+
+ #define EXPORTS
+
+#endif
+
+#define ENABLE_BQ27520_SW_CMD
+//#define ENABLE_NTC_CHECK
+
+#endif
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/stdafx.h b/drivers/power/wmt_battery/gauge/ug31xx/stdafx.h
new file mode 100755
index 00000000..8db0f146
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/stdafx.h
@@ -0,0 +1,8 @@
+/**
+ * @filename stdafx.h
+ *
+ * Dummy header file
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/typeDefine.h b/drivers/power/wmt_battery/gauge/ug31xx/typeDefine.h
new file mode 100755
index 00000000..d4034026
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/typeDefine.h
@@ -0,0 +1,462 @@
+/// ===========================================
+/// typeDefine.h
+/// ===========================================
+
+#ifndef _TYPE_DEFINE_H_
+#define _TYPE_DEFINE_H_
+
+typedef unsigned char _upi_u8_;
+typedef unsigned short _upi_u16_;
+typedef unsigned int _upi_u32_;
+typedef unsigned long long _upi_u64_;
+typedef char _upi_s8_;
+typedef short _upi_s16_;
+typedef int _upi_s32_;
+typedef long long _upi_s64_;
+typedef char _upi_bool_;
+
+#define _UPI_TRUE_ (1)
+#define _UPI_FALSE_ (0)
+#define _UPI_NULL_ (NULL)
+
+
+#ifdef uG31xx_OS_WINDOWS
+#pragma pack(push)
+#pragma pack(1)
+#endif
+
+#define CELL_PARAMETER_ALARM_EN_UV (1<<0)
+#define CELL_PARAMETER_ALARM_EN_UET (1<<1)
+#define CELL_PARAMETER_ALARM_EN_OET (1<<2)
+
+typedef struct CELL_PARAMETER
+{
+ _upi_u16_ totalSize; //Total struct size
+ _upi_u16_ fw_ver; //CellParameter struct version
+
+ char customer[16]; //Customer name defined by uPI //####2012/08/29#####
+ char project[16]; //Project name defined by uPI
+ _upi_u16_ ggb_version; //0x0102 => 2.1
+
+ char customerSelfDef[16]; //Customer name record by customer
+ char projectSelfDef[16]; //Project name record by customer
+ _upi_u16_ cell_type_code;
+
+ _upi_u8_ ICType; /*[2:0]=000 -> uG3100 [2:0]=001 -> uG3101
+ [2:0]=010 -> uG3102 [2:0]=100 -> uG3103_2
+ [2:0]=101 -> uG3103_3 */
+
+ _upi_u8_ gpio1; /*bit[4] cbc_en32
+ bit[3] cbc_en21
+ bit[2] pwm
+ bit[1] alarm
+ bit[0] gpio */
+ _upi_u8_ gpio2; /*bit[4] cbc_en32
+ bit[3] cbc_en21
+ bit[2] pwm
+ bit[1] alarm
+ bit[0] gpio */
+ _upi_u8_ gpio34; //11/22/2011 -->reg92
+
+ _upi_u8_ cellNumber;
+ _upi_u8_ assignCellOneTo;
+ _upi_u8_ assignCellTwoTo;
+ _upi_u8_ assignCellThreeTo;
+
+ _upi_u16_ i2cAddress; //I2C Address(Hex)
+ _upi_u16_ clock;
+
+ _upi_u8_ tenBitAddressMode;
+ _upi_u8_ highSpeedMode;
+ _upi_u8_ chopCtrl; //11/22/2011 -->regC1
+ _upi_u8_ rSense;
+
+ _upi_s16_ adc1Offset; //11/22/2011 -->reg58/59
+ _upi_u16_ ILMD;
+
+ _upi_u16_ edv1Voltage;
+ _upi_u16_ standbyCurrent;
+
+ _upi_u16_ TPCurrent;
+ _upi_u16_ TPVoltage;
+
+ _upi_u16_ TPTime;
+ _upi_u16_ offsetR;
+
+ _upi_u16_ deltaR;
+ _upi_u16_ TpBypassCurrent; //20121029
+
+ _upi_s16_ uvAlarm;
+ _upi_s16_ uvRelease;
+
+ _upi_s16_ uetAlarm;
+ _upi_s16_ uetRelease;
+
+ _upi_s16_ oetAlarm;
+ _upi_s16_ oetRelease;
+
+ _upi_s16_ oscTuneJ;
+ _upi_s16_ oscTuneK;
+
+ _upi_u8_ maxDeltaQ;
+ _upi_u8_ timeInterval;
+ _upi_u8_ alarm_timer; //11/22/2011 00:*5,01:*10,10:*15,11:*20
+ _upi_u8_ pwm_timer; /*[1:0]=00:32k [1:0]=01:16k
+ [1:0]=10:8k [1:0]=11: 4k */
+
+ _upi_u8_ clkDivA; //11/22/2011
+ _upi_u8_ clkDivB; //11/22/2011
+ _upi_u8_ alarmEnable; /*[7]:COC [6]:DOC [5]:IT [4]:ET
+ [3]:VP [2]:V3 [1]:V2 [0]:V1 */
+ _upi_u8_ cbcEnable; /*[1]:CBC_EN32 [0]:CBC_EN21 */
+
+ _upi_u16_ vBat2_8V_IdealAdcCode; //ideal ADC Code
+ _upi_u16_ vBat2_6V_IdealAdcCode;
+
+ _upi_u16_ vBat3_12V_IdealAdcCode;
+ _upi_u16_ vBat3_9V_IdealAdcCode;
+
+ _upi_s16_ adc1_pgain;
+ _upi_s16_ adc1_ngain;
+
+ _upi_s16_ adc1_pos_offset;
+ _upi_u16_ adc2_gain;
+
+ _upi_s16_ adc2_offset;
+ _upi_u16_ R;
+
+ _upi_u16_ rtTable[ET_NUMS];
+ // SOV_TABLE %
+ _upi_u16_ SOV_TABLE[SOV_NUMS];
+
+ _upi_s16_ adc_d1; //2012/06/06/update for IT25
+ _upi_s16_ adc_d2; //2012/06/06/update for IT80
+
+ _upi_s16_ adc_d3; ///< [AT-PM] : Used for ADC calibration IT code ; 08/15/2012
+ _upi_s16_ adc_d4;
+
+ _upi_s16_ adc_d5;
+ _upi_u16_ NacLmdAdjustCfg;
+
+ _upi_u8_ otp1Scale;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) CELL_PARAMETER;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}CELL_PARAMETER;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct CELL_TABLE
+{
+ _upi_s16_ INIT_OCV[TEMPERATURE_NUMS][OCV_TABLE_IDX_COUNT][OCV_NUMS]; //initial OCV Table,0.1C/0.2C OCV/charge table
+ _upi_s16_ CELL_VOLTAGE_TABLE[TEMPERATURE_NUMS][C_RATE_NUMS][OCV_NUMS]; //cell behavior Model,the voltage data
+ _upi_s16_ CELL_NAC_TABLE[TEMPERATURE_NUMS][C_RATE_NUMS][OCV_NUMS]; //cell behavior Model,the deltaQ
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) CELL_TABLE;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}CELL_TABLE;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct CELL_DATA
+{
+ CELL_PARAMETER cellParameter;
+ CELL_TABLE cellTable1;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) CELL_DATA;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}CELL_DATA;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+#if defined(uG31xx_OS_ANDROID)
+
+ //<ASUS-WAD+>
+ typedef struct _GGBX_FILE_HEADER
+ {
+ _upi_u32_ ggb_tag; //'_GG_'
+ _upi_u32_ sum16; //16 bits checksum, but store as 4 bytes
+ _upi_u64_ time_stamp; //seconds pass since 1970 year, 00:00:00
+ _upi_u64_ length; //size that not only include ggb content. (multi-file support)
+ _upi_u64_ num_ggb; //number of ggb files.
+ } __attribute__((packed)) GGBX_FILE_HEADER;
+ //<ASUS-WAD->
+
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct ADC_CHECK
+{
+ _upi_u16_ regCounter; //check adc counter
+ _upi_u16_ regVbat1Ave; //check average voltage
+ _upi_u16_ lastCounter;
+ _upi_u16_ lastVBat1Ave;
+ _upi_u16_ failCounterCurrent;
+ _upi_u16_ failCounterVoltage;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) ADC_CHECK;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}ADC_CHECK;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct USER_REGISTER
+{
+ _upi_u8_ mode;
+ _upi_u8_ ctrl1;
+ _upi_u8_ charge_low;
+ _upi_u8_ charge_high;
+ _upi_u8_ counter_low;
+ _upi_u8_ counter_high;
+ _upi_u8_ current_low;
+ _upi_u8_ current_high;
+ _upi_u8_ vbat1_low;
+ _upi_u8_ vbat1_high;
+ _upi_u8_ intr_temper_low;
+ _upi_u8_ intr_temper_high;
+ _upi_u8_ ave_current_low;
+ _upi_u8_ ave_current_high;
+ _upi_u8_ extr_temper_low;
+ _upi_u8_ extr_temper_high;
+ _upi_u8_ rid_low;
+ _upi_u8_ rid_high;
+ _upi_u8_ alarm1_status;
+ _upi_u8_ alarm2_status;
+ _upi_u8_ intr_status;
+ _upi_u8_ alram_en;
+ _upi_u8_ ctrl2;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) USER_REGISTER;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}USER_REGISTER;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+//2012/08/24/new add for system suspend
+typedef struct _GG_SUSPEND_INFO{
+ _upi_u16_ LMD; //battery Qmax (maH)
+ _upi_u16_ NAC; //battery NAC(maH)
+ _upi_u16_ RSOC; //Battery Current RSOC(%)
+ _upi_u32_ currentTime; //the time tick
+
+#if defined(uG31xx_OS_ANDROID)
+ }__attribute__((packed)) GG_SUSPEND_INFO,*PGG_SUSPEND_INFO;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}GG_SUSPEND_INFO,*PGG_SUSPEND_INFO;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct _GG_BATTERY_INFO{
+ _upi_u16_ LMD; //battery Qmax (maH)
+ _upi_u16_ NAC; //battery NAC(maH)
+ _upi_u16_ RSOC; //Battery Current RSOC(%)
+#if defined(uG31xx_OS_ANDROID)
+ }__attribute__((packed)) GG_BATTERY_INFO;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}GG_BATTERY_INFO;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/// [AT-PM] : Used for TI bq27520 like command ; 10/11/2012
+typedef struct GG_TI_BQ27520 {
+ _upi_u16_ CntlControlStatus;
+ _upi_u16_ CntlPrevMacWrite;
+
+ _upi_s16_ AR;
+
+ _upi_u16_ Temp;
+
+ _upi_u16_ Flags;
+ _upi_s16_ SINow;
+ _upi_s32_ SIBuf;
+ _upi_s16_ SISample;
+ _upi_u16_ SIWindow;
+
+ _upi_s16_ Mli;
+ _upi_u8_ MliDsgSoc;
+
+ _upi_u16_ AE;
+
+ _upi_s16_ AP;
+ _upi_u16_ APStartChgE;
+ _upi_u16_ APStartDsgE;
+ _upi_u32_ APChgTime;
+ _upi_u32_ APDsgTime;
+
+ _upi_u16_ CC;
+ _upi_u16_ CCBuf;
+ _upi_u16_ CCLastNac;
+
+ _upi_u16_ Dli;
+
+ _upi_u16_ Dlb;
+
+ _upi_s8_ FCSet;
+ _upi_s8_ FCClear;
+ _upi_u8_ Soc1Set;
+ _upi_u8_ Soc1Clear;
+ _upi_s8_ InitSI;
+ _upi_s16_ InitMaxLoadCurrent;
+ _upi_s16_ CCThreshold;
+ _upi_u32_ Opcfg;
+ _upi_u16_ Dcap;
+
+ _upi_u32_ LastTime;
+ _upi_u16_ DeltaSec;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GG_TI_BQ27520, *PGG_TI_BQ27520;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_TI_BQ27520, *PGG_TI_BQ27520;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct GGAdcDeltaCodeMappingST {
+ _upi_s32_ Adc1V100;
+ _upi_s32_ Adc1V200;
+ _upi_s32_ Adc2V100;
+ _upi_s32_ Adc2V200;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GGAdcDeltaCodeMappingType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GGAdcDeltaCodeMappingType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct TableBackupST {
+ _upi_u16_ lowerBound;
+ _upi_u8_ resolution;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) TableBackupType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} TableBackupType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/* define the register of uG31xx */
+typedef struct _GG_USER_REG{
+ _upi_u8_ regMode; ///< [AT-PM] : 0x00 - MODE ; 04/08/2013
+ _upi_u8_ regCtrl1; ///< [AT-PM] : 0x01 - CTRL1 ; 04/08/2013
+ _upi_s16_ regCharge; ///< [AT-PM] : 0x02 - Charge ; 04/08/2013
+ _upi_u16_ regCounter; ///< [AT-PM] : 0x04 - Counter ; 04/08/2013
+ _upi_s16_ regCurrentAve; ///< [AT-PM] : 0x06 - Ave Current ; 04/08/2013
+ _upi_s16_ regVbat1Ave; ///< [AT-PM] : 0x08 - Ave VBat1 ; 04/08/2013
+ _upi_u16_ regITAve; ///< [AT-PM] : 0x0A - Ave IT ; 04/08/2013
+ _upi_s16_ regOffsetCurrentAve; ///< [AT-PM] : 0x0C - Ave Offset Current ; 04/08/2013
+ _upi_u16_ regETAve; ///< [AT-PM] : 0x0E - Ave ET ; 04/08/2013
+ _upi_u16_ regRidAve; ///< [AT-PM] : 0x10 - Ave RID ; 04/08/2013
+ _upi_u8_ regAlarm1Status; ///< [AT-PM] : 0x12 - Alarm1 Status ; 04/08/2013
+ _upi_u8_ regAlarm2Status; ///< [AT-PM] : 0x13 - Alarm2 Status ; 04/08/2013
+ _upi_u8_ regIntrStatus; ///< [AT-PM] : 0x14 - INTR Status ; 04/08/2013
+ _upi_u8_ regAlarmEnable; ///< [AT-PM] : 0x15 - Alarm EN ; 04/08/2013
+ _upi_u8_ regCtrl2; ///< [AT-PM] : 0x16 - CTRL2 ; 04/08/2013
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) GG_USER_REG, *PGG_USER_REG;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_USER_REG, *PGG_USER_REG;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/* define the register of uG31xx */
+typedef struct _GG_USER2_REG{
+ _upi_s16_ regVbat2; //0x40,vBat2
+ _upi_s16_ regVbat3; //0x42,vBat3
+ _upi_s16_ regVbat1; //0x44,vBat1 Average
+ _upi_s16_ regVbat2Ave; //0x46,vBat2 Average
+ _upi_s16_ regVbat3Ave; //0x48,vBat3 Average
+ _upi_u16_ regV1; //0x4a,cell 1 Voltage
+ _upi_u16_ regV2; //0x4c,0xcell 2 Voltage
+ _upi_u16_ regV3; //0x4e,cell 3 Voltage
+ _upi_s16_ regIT; //0x50
+ _upi_s16_ regET; //0x52
+ _upi_u16_ regRID; //0x54
+ _upi_s16_ regCurrent; //0x56
+ _upi_s16_ regAdc1Offset; //0x58ADC1 offset
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GG_USER2_REG, *PGG_USER2_REG;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_USER2_REG, *PGG_USER2_REG;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+#define ALARM_STATUS_UV (1<<0)
+#define ALARM_STATUS_UET (1<<1)
+#define ALARM_STATUS_OET (1<<2)
+
+/* define device information */
+typedef struct _GG_DEVICE_INFO{
+
+ _upi_s16_ oldRegCurrent; //for skip the ADC code Error
+ _upi_s16_ oldRegVbat1; //for skip the ADC code Error
+
+ _upi_s16_ vBat1_AdcCode; //debug use
+ _upi_s16_ vBat1_AveAdcCode;
+
+ _upi_s16_ fwCalAveCurrent_mA; //f/w calculate average current
+ _upi_u32_ lastTime; //
+ _upi_s16_ chargeRegister; //coulomb counter register
+ _upi_u16_ AdcCounter; //ADC convert counter
+
+ _upi_s16_ preChargeRegister; //coulomb counter register
+ _upi_s16_ aveCurrentRegister; //2012/0711/jacky
+ _upi_u16_ preAdcCounter;
+ _upi_s32_ fwChargeData_mAH; //fw calculate maH (Q= I * T)
+
+ _upi_s32_ chargeData_mAh; //maH calculate from charge register
+ _upi_u16_ voltage_mV; //total voltage
+ _upi_s16_ current_mA; // now current
+ _upi_s16_ AveCurrent_mA; // average current
+ _upi_s16_ IT; // internal temperature
+ _upi_s16_ ET; // external temperature
+
+ _upi_u16_ v1_mV; //v1 from hw register
+ _upi_u16_ v2_mV; //v2
+ _upi_u16_ v3_mV; //v3
+
+ _upi_u16_ vBat1Average_mV; //vbat1
+ _upi_u16_ vBat2Average_mV;
+ _upi_u16_ vBat3Average_mV;
+
+
+ _upi_u16_ vCell1_mV; //v Cell1
+ _upi_u16_ vCell2_mV; //v Cell2
+ _upi_u16_ vCell3_mV; //v Cell3
+
+ _upi_s16_ CaliAdc1Code; //2012/08/29/Jacky
+ _upi_s16_ CaliAdc2Code;
+
+ _upi_s16_ CoulombCounter;
+
+ _upi_s32_ CaliChargeReg;
+ _upi_u16_ Adc1ConvTime;
+
+ _upi_u8_ alarmStatus;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) GG_DEVICE_INFO, *PGG_DEVICE_INFO;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_DEVICE_INFO, *PGG_DEVICE_INFO;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+
+/* define battery capacity */
+typedef struct _GG_CAPACITY {
+ _upi_u16_ LMD; //battery Qmax (maH)
+ _upi_u16_ NAC; //battery NAC(maH)
+ _upi_u16_ RSOC; //Battery Current RSOC(%)
+
+ _upi_u8_ Ready;
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__((packed)) GG_CAPACITY, *PGG_CAPACITY;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+}GG_CAPACITY, *PGG_CAPACITY;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct GG_MEAS_PARA_ST
+{
+ _upi_s16_ Adc1Gain;
+ _upi_s16_ Adc1Offset;
+
+ _upi_s16_ Adc2Gain;
+ _upi_s16_ Adc2Offset;
+
+ _upi_s16_ ITOffset;
+ _upi_s16_ ETOffset;
+
+ _upi_u8_ ProductType;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GG_MEAS_PARA_TYPE;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_MEAS_PARA_TYPE;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+#endif
+
+/// ===========================================
+/// End of typeDefine.h
+/// ===========================================
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx.h
new file mode 100755
index 00000000..b3fd8ee3
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx.h
@@ -0,0 +1,127 @@
+/// ===========================================
+/// uG31xx.h
+/// ===========================================
+
+#ifndef _UG31XX_H_
+#define _UG31XX_H_
+
+#define SECURITY 1 //Security Mode enable
+#define NORMAL 0 //Security Mode OFF
+
+#define HIGH_SPEED 1 //HS Mode
+#define FULL_SPEED 0 //FIL speed
+
+#define TEN_BIT_ADDR 1 //10-bit address Mode
+#define SEVEN_BIT_ADDR 0 //7-bit address Mode
+
+#define I2C_SUCESS 1 //
+#define I2C_FAIL 0
+
+/// ===========================================================================
+/// Constant for Calibration
+/// ===========================================================================
+
+#define IT_TARGET_CODE25 (12155)
+#define IT_TARGET_CODE80 (14306)
+
+#define IT_CODE_25 (23171)
+#define IT_CODE_80 (27341)
+
+//constant
+//define IC type
+#define uG3100 0
+#define uG3101 1
+#define uG3102 2
+#define uG3103_2 4
+#define uG3103_3 5
+
+//constant
+//GPIO1/2 define
+#define FUN_GPIO 0x01
+#define FUN_ALARM 0x02
+#define FUN_PWM 0x04
+#define FUN_CBC_EN21 0x08
+#define FUN_CBC_EN32 0x10
+
+#define BIT_MACRO(x) ((_upi_u8_)1 << (x))
+
+#define MAX_CRATE_AVAILABLE (20)
+
+#define I2C_ADDRESS 0x70
+#define I2C_CLOCK 0x100
+
+//const for CELL_TABLE table
+#define TEMPERATURE_NUMS (4)
+#define C_RATE_NUMS (3) ///< [AT-PM] : 0.5, 0.2, 0.1, 0.02 ; 12/17/2013
+#define OCV_NUMS (21) //include the 0% & 100%
+#define SOV_NUMS (5) ///< [AT-PM] : 100%, 70%, 45%, 20%, 0% ; 12/17/2012
+#define ET_NUMS (19) ///< [AT-PM] : -10, -5, 0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80 ; 01/25/2013
+
+#define NAC_LMD_ADJUST_CFG_NO_LMD_UPDATE_BETWEEN_10_90_EN (1<<0)
+#define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_MASK (15<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_00 (0<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_01 (1<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_02 (2<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_03 (3<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_04 (4<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_05 (5<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_06 (6<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_07 (7<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_08 (8<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_09 (9<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_10 (10<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_11 (11<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_12 (12<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_13 (13<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_14 (14<<1)
+ #define NAC_LMD_ADJUST_CFG_LOCK_AND_SPEED_UP_START_SOV_15 (15<<1)
+#define NAC_LMD_ADJUST_CFG_DISPLAY_CC_AS_FCC (1<<5)
+
+#if defined(uG31xx_OS_ANDROID)
+
+ #define GGBX_FILE_TAG 0x5F47475F // _GG_
+ #define GGBX_FACTORY_FILE_TAG 0x5F67675F // _gg_
+
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+enum C_RATE_TABLE_VALUES {
+ C_RATE_TABLE_VALUE_0 = 50,
+ C_RATE_TABLE_VALUE_1 = 20,
+ C_RATE_TABLE_VALUE_2 = 10,
+ C_RATE_TABLE_VALUE_3 = 2
+};
+
+enum OCV_TABLE_IDX {
+ OCV_TABLE_IDX_CHARGE = 0,
+ OCV_TABLE_IDX_STAND_ALONE,
+ OCV_TABLE_IDX_100MA,
+ OCV_TABLE_IDX_COUNT,
+};
+
+#if defined (uG31xx_OS_WINDOWS)
+
+ #define SleepMiniSecond(x) Sleep(x)
+
+ #ifdef DEBUG_LOG
+
+ #define _L(X) __L(X)
+ #define __L(X) L##X
+
+ #endif
+
+#elif defined (uG31xx_OS_ANDROID)
+
+ #define SleepMiniSecond(x) mdelay(x)
+
+#endif
+
+#define CONST_PERCENTAGE (100)
+#define TIME_CONVERT_TIME_TO_MSEC (10)
+#define CONST_CONVERSION_COUNT_THRESHOLD (300)
+
+#endif
+
+/// ===========================================
+/// End of uG31xx.h
+/// ===========================================
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.c
new file mode 100755
index 00000000..e4752190
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.c
@@ -0,0 +1,2307 @@
+/// ===========================================
+/// uG31xx_API.cpp
+/// ===========================================
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+static _upi_bool_ Ug31DebugEnable;
+static _upi_s32_ dsg_charge_before_suspend;
+static _upi_s32_ delta_cap_during_suspend;
+static _upi_u8_ wakeup_predict_rsoc;
+static _upi_u8_ fix_et_at_initial_cnt;
+#if defined (uG31xx_OS_WINDOWS)
+
+ #ifdef DEBUG_LOG
+
+ unsigned int debugViewLines = 0;
+ CString debugViewFileName = _T("uG3100-1");
+ CString BackupFile;
+
+ #endif
+
+#elif defined (uG31xx_OS_ANDROID)
+
+ _upi_u32_ GetTickCount(void)
+ {
+ return jiffies_to_msecs(jiffies); //20121121/jacky
+ }
+
+ _upi_u32_ GetSysTickCount(void)
+ {
+
+ struct timeval current_tick;
+
+ do_gettimeofday(&current_tick);
+
+ return current_tick.tv_sec * 1000 + current_tick.tv_usec/1000;
+ }
+
+#endif
+
+#if defined(uG31xx_OS_ANDROID)
+
+ int ug31_printk(int level, const char *fmt, ...)
+ {
+ #ifdef UG31XX_DEBUG_ENABLE
+ va_list args;
+ int r;
+
+ r = 0;
+ if(Ug31DebugEnable == _UPI_TRUE_)
+ {
+ va_start(args, fmt);
+ r = vprintk(fmt, args);
+ va_end(args);
+ }
+ return (r);
+ #else ///< else of UG31XX_DEBUG_ENABLE
+ return (0);
+ #endif ///< end of UG31XX_DEBUG_ENABLE
+ }
+
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+
+
+/// ===========================================
+/// uG31xx_API.cpp (VAR)
+/// ===========================================
+
+
+/* uPI ug31xx hardware control interface */
+struct ug31xx_data {
+
+ /// [AT-PM] : Following variables are used for uG31xx operation ; 11/01/2012
+ _upi_u8_ totalCellNums;
+ _upi_bool_ bFirstData;
+
+ // Global variable
+ CELL_TABLE cellTable; // data from .GGB file
+ CELL_PARAMETER cellParameter; // data from .GGB file
+ GG_BATTERY_INFO batteryInfo;
+ GG_DEVICE_INFO deviceInfo;
+ GG_USER_REG userReg; //user register 0x00 ~0x10
+ GG_USER2_REG user2Reg; //user register 0x40 ~0x4f
+ GG_TI_BQ27520 bq27520Cmd;
+
+ OtpDataType otpData;
+ MeasDataType measData;
+ CapacityDataType capData;
+ SystemDataType sysData;
+ BackupDataType backupData;
+
+ _upi_u8_ EncriptTableStatus;
+ _upi_u16_ PreviousITAve;
+};
+
+/// ===========================================
+/// End of uG31xx_API.cpp (VAR)
+/// ===========================================
+
+/**
+ * @brief upiGG_GetAlarmStatus
+ *
+ * Get alarm status
+ *
+ * @para pAlarmStatus address of alarm status
+ * @return UG_READ_DEVICE_ALARM_SUCCESS if success
+ */
+GGSTATUS upiGG_GetAlarmStatus(char *pObj, _upi_u8_ *pAlarmStatus)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ UpiMeasAlarmThreshold(&pUg31xx->measData);
+ *pAlarmStatus = UpiAlarmStatus(&pUg31xx->sysData);
+
+ pUg31xx->userReg.regAlarm1Status = (_upi_u8_)(pUg31xx->sysData.alarmSts & 0x00ff);
+ pUg31xx->userReg.regAlarm2Status = (_upi_u8_)(pUg31xx->sysData.alarmSts >> 8);
+
+ return (UG_READ_DEVICE_ALARM_SUCCESS);
+}
+
+// Read GG_USER_REG from device to global variable and output
+GGSTATUS upiGG_ReadAllRegister(char *pObj, GG_USER_REG* pUserReg, GG_USER2_REG* pUser2Reg)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_MODE,
+ sizeof(GG_USER_REG),
+ &pUg31xx->userReg.regMode))
+ {
+ return (UG_READ_REG_FAIL);
+ }
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_VBAT2_LOW,
+ sizeof(GG_USER2_REG),
+ (_upi_u8_* )&pUg31xx->user2Reg.regVbat2)) //read
+ {
+ return (UG_READ_REG_FAIL);
+ }
+
+ return (UG_READ_REG_SUCCESS);
+}
+
+// 07/04/1022/Jacky
+_upi_u16_ CalculateVoltageFromUserReg(struct ug31xx_data *pUg31xx, _upi_s16_ voltageAdcCode, _upi_s16_ curr, _upi_u16_ offsetR, _upi_u16_ deltaR)
+{
+ _upi_u16_ voltage_return;
+
+ voltage_return = (_upi_u16_)voltageAdcCode;
+ if(curr < 0) {
+ voltage_return = voltage_return + offsetR*abs(curr)/1000 + deltaR;
+ } else{
+ voltage_return = voltage_return - offsetR*abs(curr)/1000 + deltaR;
+ }
+ return (voltage_return);
+}
+
+// Read GG_USER_REG from device and calculate GG_DEVICE_INFO, then write to global variable and output
+// TODO: offsetR and deltaR will input from .GGB in the future modify
+GGSTATUS upiGG_ReadDeviceInfo(char *pObj, GG_DEVICE_INFO* pExtDeviceInfo)
+{
+ // Get current user register data
+ GGSTATUS status = UG_READ_DEVICE_INFO_SUCCESS;
+ struct ug31xx_data *pUg31xx;
+ _upi_s32_ tmp;
+ MEAS_RTN_CODE rtn;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_MODE,
+ REG_AVE_RID_HIGH - REG_MODE + 1,
+ &pUg31xx->userReg.regMode))
+ {
+ status = UG_READ_ADC_FAIL;
+ }
+ else
+ {
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_INTR_STATUS,
+ REG_CTRL2 - REG_INTR_STATUS + 1,
+ &pUg31xx->userReg.regIntrStatus))
+ {
+ status = UG_READ_ADC_FAIL;
+ }
+ else
+ {
+ if(!API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_VBAT2_LOW,
+ sizeof(GG_USER2_REG),
+ (_upi_u8_* )&pUg31xx->user2Reg.regVbat2)) //read
+ {
+ status = UG_READ_ADC_FAIL;
+ }
+ }
+ }
+
+ /// [AT-PM] : Check IT AVE code, which should be continuous ; 12/28/2012
+ if(pUg31xx->PreviousITAve != 0)
+ {
+ tmp = (_upi_s32_)pUg31xx->PreviousITAve;
+ tmp = tmp - pUg31xx->userReg.regITAve;
+ if((tmp > 1000) || (tmp < -1000))
+ {
+ #ifdef DEBUG_LOG
+ wDebug::LOGE(debugViewFileName.GetBuffer(),debugViewLines++,_T("[%s]: IT AVE Code abnormal -> %d/%d"),
+ _L(__FUNCTION__), pUg31xx->userReg.regITAve, pUg31xx->PreviousITAve);
+ debugViewFileName.ReleaseBuffer();
+ #endif
+ pUg31xx->userReg.regITAve = pUg31xx->PreviousITAve;
+ }
+ }
+ pUg31xx->PreviousITAve = pUg31xx->userReg.regITAve;
+
+ /// [AT-PM] : Check OTP is empty or not ; 01/25/2013
+ if(pUg31xx->otpData.empty == OTP_IS_EMPTY)
+ {
+ return (UG_OTP_ISEMPTY);
+ }
+
+ /// [AT-PM] : Check product type ; 01/25/2013
+ if(pUg31xx->otpData.productType != UG31XX_PRODUCT_TYPE_0)
+ {
+ return (UG_OTP_PRODUCT_DISMATCH);
+ }
+
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+ rtn = UpiMeasurement(&pUg31xx->measData);
+ if(rtn != MEAS_RTN_PASS)
+ {
+ return ((GGSTATUS)(rtn + UG_MEAS_FAIL));
+ }
+
+ pUg31xx->deviceInfo.chargeRegister = pUg31xx->userReg.regCharge; //coulomb counter
+ pUg31xx->deviceInfo.AdcCounter = pUg31xx->userReg.regCounter; //adc1 convert counter
+ pUg31xx->deviceInfo.aveCurrentRegister = pUg31xx->userReg.regCurrentAve; //2012/07/11
+ pUg31xx->deviceInfo.current_mA = pUg31xx->measData.curr;
+ pUg31xx->deviceInfo.AveCurrent_mA = pUg31xx->measData.curr;
+ pUg31xx->deviceInfo.IT = pUg31xx->measData.intTemperature;
+ pUg31xx->deviceInfo.ET = pUg31xx->measData.extTemperature;
+ pUg31xx->deviceInfo.v1_mV = pUg31xx->measData.bat1Voltage;
+ pUg31xx->deviceInfo.vCell1_mV = pUg31xx->measData.bat1Voltage;
+ pUg31xx->deviceInfo.vBat1Average_mV = CalculateVoltageFromUserReg(pUg31xx,
+ pUg31xx->measData.bat1Voltage,
+ pUg31xx->measData.curr,
+ pUg31xx->cellParameter.offsetR,
+ pUg31xx->cellParameter.deltaR);
+ pUg31xx->deviceInfo.voltage_mV = pUg31xx->deviceInfo.vBat1Average_mV;
+ pUg31xx->deviceInfo.chargeData_mAh = pUg31xx->measData.deltaCap;
+
+ pUg31xx->sysData.otpData = &pUg31xx->otpData;
+ UpiCalibrationOsc(&pUg31xx->sysData); //osc calibration
+
+ UpiAdcStatus(&pUg31xx->sysData);
+
+ if(fix_et_at_initial_cnt > 0)
+ {
+ fix_et_at_initial_cnt = fix_et_at_initial_cnt - 1;
+
+ if(pUg31xx->deviceInfo.ET > UG31XX_MAX_TEMPERATURE_BEFORE_READY)
+ {
+ pUg31xx->deviceInfo.ET = UG31XX_MAX_TEMPERATURE_BEFORE_READY;
+ }
+ if(pUg31xx->deviceInfo.ET < UG31XX_MIN_TEMPERATURE_BEFORE_READY)
+ {
+ pUg31xx->deviceInfo.ET = UG31XX_MIN_TEMPERATURE_BEFORE_READY;
+ }
+ }
+ memcpy(pExtDeviceInfo, &pUg31xx->deviceInfo, sizeof(GG_DEVICE_INFO));
+ return (status);
+}
+
+void dumpInfo(struct ug31xx_data *pUg31xx)
+{
+ int i=0;
+ int j;
+ int k;
+
+/// dump parameter setting
+ UG31_LOGI("/// 2012/12/16/1611====================================\n");
+ UG31_LOGI("/// CELL_PARAMETER\n");
+ UG31_LOGI("/// ====================================2012/12/16/1611\n");
+ UG31_LOGI("Total struct size: %d\n", pUg31xx->cellParameter.totalSize);
+ UG31_LOGI("firmware version: 0x%02X\n", pUg31xx->cellParameter.fw_ver)
+ UG31_LOGI("customer: %s\n", pUg31xx->cellParameter.customer)
+ UG31_LOGI("project: %s\n", pUg31xx->cellParameter.project)
+ UG31_LOGI("ggb version: 0x%02X\n", pUg31xx->cellParameter.ggb_version)
+ UG31_LOGI("customer self-define: %s\n", pUg31xx->cellParameter.customerSelfDef)
+ UG31_LOGI("project self-define: %s\n", pUg31xx->cellParameter.projectSelfDef)
+ UG31_LOGI("cell type : 0x%04X\n", pUg31xx->cellParameter.cell_type_code);
+ UG31_LOGI("ICType: 0x%02X\n", pUg31xx->cellParameter.ICType);
+ UG31_LOGI("gpio1: 0x%02X\n", pUg31xx->cellParameter.gpio1);
+ UG31_LOGI("gpio2: 0x%02X\n", pUg31xx->cellParameter.gpio2);
+ UG31_LOGI("gpio34: 0x%02X\n", pUg31xx->cellParameter.gpio34);
+ UG31_LOGI("Chop control ?? : 0x%02X\n", pUg31xx->cellParameter.chopCtrl);
+ UG31_LOGI("ADC1 offset ?? : %d\n", pUg31xx->cellParameter.adc1Offset);
+ UG31_LOGI("Cell number ?? : %d\n", pUg31xx->cellParameter.cellNumber);
+ UG31_LOGI("Assign cell one to: %d\n", pUg31xx->cellParameter.assignCellOneTo);
+ UG31_LOGI("Assign cell two to: %d\n", pUg31xx->cellParameter.assignCellTwoTo);
+ UG31_LOGI("Assign cell three to: %d\n", pUg31xx->cellParameter.assignCellThreeTo);
+ UG31_LOGI("I2C Address: : 0x%02X\n", pUg31xx->cellParameter.i2cAddress);
+ UG31_LOGI("I2C 10bit address: : 0x%02X\n", pUg31xx->cellParameter.tenBitAddressMode);
+ UG31_LOGI("I2C high speed: 0x%02X\n", pUg31xx->cellParameter.highSpeedMode);
+ UG31_LOGI("clock(kHz): %d\n", pUg31xx->cellParameter.clock);
+ UG31_LOGI("RSense(m ohm): %d\n", pUg31xx->cellParameter.rSense);
+ UG31_LOGI("ILMD(mAH) ?? : %d\n", pUg31xx->cellParameter.ILMD);
+ UG31_LOGI("EDV1 Voltage(mV): %d\n", pUg31xx->cellParameter.edv1Voltage);
+ UG31_LOGI("Standby current ?? : %d\n", pUg31xx->cellParameter.standbyCurrent);
+ UG31_LOGI("TP Current(mA)?? : %d\n", pUg31xx->cellParameter.TPCurrent);
+ UG31_LOGI("TP Voltage(mV)?? : %d\n", pUg31xx->cellParameter.TPVoltage);
+ UG31_LOGI("TP Time ?? : %d\n", pUg31xx->cellParameter.TPTime);
+ UG31_LOGI("Offset R ?? : %d\n", pUg31xx->cellParameter.offsetR);
+ UG31_LOGI("Delta R ?? : %d\n", pUg31xx->cellParameter.deltaR);
+ UG31_LOGI("max delta Q(%%) ?? : %d\n", pUg31xx->cellParameter.maxDeltaQ);
+ UG31_LOGI("TP Bypass Current ?? : %d\n", pUg31xx->cellParameter.TpBypassCurrent); //20121029/Jacky
+ UG31_LOGI("time interval (s) : %d\n", pUg31xx->cellParameter.timeInterval);
+ UG31_LOGI("ADC1 pgain: %d\n", pUg31xx->cellParameter.adc1_pgain);
+ UG31_LOGI("ADC1 ngain: %d\n", pUg31xx->cellParameter.adc1_ngain);
+ UG31_LOGI("ADC1 pos. offset: %d\n", pUg31xx->cellParameter.adc1_pos_offset);
+ UG31_LOGI("ADC2 gain: %d\n", pUg31xx->cellParameter.adc2_gain);
+ UG31_LOGI("ADC2 offset: %d\n", pUg31xx->cellParameter.adc2_offset);
+ UG31_LOGI("R ?? : %d\n", pUg31xx->cellParameter.R);
+ for (i=0; i<sizeof(pUg31xx->cellParameter.rtTable)/sizeof(_upi_u16_); i++) {
+ UG31_LOGI("RTTable[%02d]: %d\n", i, pUg31xx->cellParameter.rtTable[i]);
+ }
+ for (i=0; i<sizeof(pUg31xx->cellParameter.SOV_TABLE)/sizeof(_upi_u16_); i++) {
+ UG31_LOGI("SOV Table[%02d]: %d\n", i, pUg31xx->cellParameter.SOV_TABLE[i]/10);
+ }
+ UG31_LOGI("ADC d1: %d\n", pUg31xx->cellParameter.adc_d1);
+ UG31_LOGI("ADC d2: %d\n", pUg31xx->cellParameter.adc_d2);
+ UG31_LOGI("ADC d3: %d\n", pUg31xx->cellParameter.adc_d3);
+ UG31_LOGI("ADC d4: %d\n", pUg31xx->cellParameter.adc_d4);
+ UG31_LOGI("ADC d5: %d\n", pUg31xx->cellParameter.adc_d5);
+ UG31_LOGI("NacLmdAdjustCfg: %d\n", pUg31xx->cellParameter.NacLmdAdjustCfg); //20121124
+
+ /// [AT-PM] : Dump NAC table ; 01/27/2013
+ i = 0;
+ while(i < TEMPERATURE_NUMS)
+ {
+ j = 0;
+ while(j < C_RATE_NUMS)
+ {
+ k = 0;
+ while(k < SOV_NUMS)
+ {
+ UG31_LOGI("NAC Table [%d][%d][%d] = %d\n", i, j, k, pUg31xx->cellTable.CELL_NAC_TABLE[i][j][k]);
+ k = k + 1;
+ }
+ j = j + 1;
+ }
+ i = i + 1;
+ }
+}
+
+/// count Time Elapsed in suspend/power Off
+_upi_u32_ CountTotalTime(_upi_u32_ savedTimeTag)
+{
+ _upi_u32_ totalTime;
+ _upi_u32_ currentTime;
+
+ totalTime = 0;
+#if defined(uG31xx_OS_ANDROID)
+ currentTime = GetSysTickCount();
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ currentTime = GetTickCount();
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+ if(currentTime > savedTimeTag)
+ {
+ totalTime = currentTime - savedTimeTag; //count the delta Time
+ }
+ else
+ {
+ totalTime = currentTime;
+ }
+ UG31_LOGE("[%s]current time/save Time/totalTime = %d/%d/%d \n",
+ __func__,
+ currentTime,
+ savedTimeTag,
+ totalTime
+ );
+ return(totalTime);
+}
+
+#define MS_IN_A_DAY (86400000)
+#define INIT_CAP_FROM_CC_FACTOR (10)
+
+/**
+ * @brief CheckInitCapacityFromCC
+ *
+ * Check the initial capacity from coulomb counter with time interval
+ * The delta RSOC should be less than n days x 0.1%
+ *
+ * @para pUg31xx address of struct ug31xx_data
+ * @para lastRsoc last RSOC before employ coulomb counter value
+ * @return _UPI_NULL_
+ */
+void CheckInitCapacityFromCC(struct ug31xx_data *pUg31xx, _sys_u8_ lastRsoc)
+{
+ _upi_s32_ tmp32;
+
+ tmp32 = (_upi_s16_)pUg31xx->sysData.rsocFromIC;
+ tmp32 = tmp32 - lastRsoc;
+ if(tmp32 < 0)
+ {
+ tmp32 = (_upi_s32_)CountTotalTime(pUg31xx->sysData.timeTagFromIC)/MS_IN_A_DAY/INIT_CAP_FROM_CC_FACTOR;
+ tmp32 = tmp32*(-1) + lastRsoc;
+ if(tmp32 < 0)
+ {
+ tmp32 = 1;
+ }
+ UG31_LOGI("[%s]: RSOC should be limited to %d (%d <-> %d)\n", __func__,
+ tmp32, lastRsoc, pUg31xx->sysData.rsocFromIC);
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)tmp32;
+ tmp32 = tmp32*pUg31xx->sysData.fccFromIC/CONST_PERCENTAGE;
+ pUg31xx->sysData.rmFromIC = (_sys_u16_)tmp32;
+ }
+}
+
+#define MAX_DELTA_RSOC_THRESHOLD_FOR_WAKEUP (25)
+#define MIN_DELTA_RSOC_THRESHOLD_FOR_WAKEUP (-25)
+#define MAX_DELTA_TIME_THRESHOLD_FOR_WAKEUP (MS_IN_A_DAY*5)
+#define MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE (0)
+#define MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE (-5)
+
+// Read GGB file and initial
+#ifdef uG31xx_OS_WINDOWS
+GGSTATUS upiGG_Initial(char **pObj,const wchar_t* GGBFilename,const wchar_t* OtpFileName, const wchar_t* BackupFileName, char ForceReset, unsigned char MaxETFixCnt)
+#elif defined(uG31xx_OS_ANDROID)
+GGSTATUS upiGG_Initial(char **pObj,GGBX_FILE_HEADER *pGGBXBuf, char ForceReset, unsigned char MaxETFixCnt)
+#endif
+{
+ _upi_bool_ firstPowerOn;
+ struct ug31xx_data *pUg31xx;
+ SYSTEM_RTN_CODE rtn;
+ _upi_s16_ deltaQC = 0;
+ _upi_s32_ tmp32;
+ MEAS_RTN_CODE rtnMeas;
+ _sys_u8_ lastRsocFromIC;
+
+ UG31_LOGI("[%s]: uG31xx API Version = %d.%08x.%04x\n", __func__,
+ UG31XX_API_MAIN_VERSION, UG31XX_API_OTP_VERSION, UG31XX_API_SUB_VERSION);
+
+ Ug31DebugEnable = _UPI_TRUE_;
+ firstPowerOn = _UPI_FALSE_;
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ *pObj = (char *)malloc(sizeof(struct ug31xx_data));
+ #else ///< else of uG31xx_BOOT_LOADER
+ *pObj = (char *)kmalloc(sizeof(struct ug31xx_data),GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ *pObj = (char *)malloc(sizeof(struct ug31xx_data));
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ pUg31xx = (struct ug31xx_data *)(*pObj);
+
+ memset(pUg31xx, 0, sizeof(struct ug31xx_data));
+
+ #ifdef uG31xx_OS_WINDOWS
+ pUg31xx->sysData.ggbFilename = GGBFilename;
+ pUg31xx->sysData.otpFileName = OtpFileName;
+ pUg31xx->sysData.backupFileName = BackupFileName;
+ BackupFile = BackupFileName;
+ #elif defined(uG31xx_OS_ANDROID)
+ pUg31xx->sysData.ggbXBuf = pGGBXBuf;
+ #endif
+ pUg31xx->sysData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->sysData.ggbCellTable = &pUg31xx->cellTable;
+ rtn = UpiInitSystemData(&pUg31xx->sysData);
+ if(rtn != SYSTEM_RTN_PASS)
+ {
+ if(rtn == SYSTEM_RTN_READ_GGB_FAIL)
+ {
+ return (UG_READ_GGB_FAIL);
+ }
+ return (UG_NOT_DEF);
+ }
+
+ // Initial I2C and Open HID
+ #if defined(uG31xx_OS_WINDOWS)
+
+ if(!API_I2C_Init(pUg31xx->cellParameter.clock, pUg31xx->cellParameter.i2cAddress))
+ {
+ return UG_I2C_INIT_FAIL;
+ }
+
+ #endif ///< end of defined(uG31xx_OS_WINDOWS)
+ UpiLoadBatInfoFromIC(&pUg31xx->sysData);
+
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ UpiInitNacTable(&pUg31xx->capData);
+
+ /// [AT-PM] : Check IC is active or not ; 01/28/2013
+ #ifdef UG31XX_RESET_DATABASE
+ firstPowerOn = _UPI_TRUE_;
+ #else ///< else of UG31XX_RESET_DATABASE
+ if(ForceReset == 0)
+ {
+ firstPowerOn = UpiCheckICActive();
+ }
+ else
+ {
+ firstPowerOn = _UPI_TRUE_;
+ UG31_LOGI("[%s]: Force to reset uG3105 driver. (%d)\n", __func__, ForceReset);
+ }
+ #endif ///< end of UG31XX_RESET_DATABASE
+ if(firstPowerOn == _UPI_TRUE_)
+ {
+ UG31_LOGE("[%s]#####firstPowerOn= %d \n",__func__,firstPowerOn);
+ rtn = UpiActiveUg31xx();
+ if(rtn != SYSTEM_RTN_PASS)
+ {
+ return (UG_ACTIVE_FAIL);
+ }
+
+ UpiSetupAdc(&pUg31xx->sysData);
+ UpiSetupSystem(&pUg31xx->sysData);
+
+ pUg31xx->backupData.icDataAvailable = BACKUP_BOOL_FALSE;
+ }
+ else
+ {
+ UG31_LOGE("[%s]#####Last time tag = %d\n", __func__, pUg31xx->sysData.timeTagFromIC);
+ pUg31xx->measData.lastTimeTick = pUg31xx->sysData.timeTagFromIC;
+ pUg31xx->measData.lastDeltaCap = pUg31xx->sysData.deltaCapFromIC;
+ pUg31xx->measData.adc1ConvertTime = pUg31xx->sysData.adc1ConvTime;
+
+ pUg31xx->backupData.icDataAvailable = BACKUP_BOOL_TRUE;
+ }
+
+ /// [AT-PM] : Load OTP data ; 01/31/2013
+ API_I2C_Read(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, OTP1_BYTE1, OTP1_SIZE, pUg31xx->otpData.otp1);
+ API_I2C_Read(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, OTP2_BYTE1, OTP2_SIZE, pUg31xx->otpData.otp2);
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, OTP6_BYTE1, OTP3_SIZE, pUg31xx->otpData.otp3);
+ UpiConvertOtp(&pUg31xx->otpData);
+
+ /// [AT-PM] : Check product type ; 01/25/2013
+ if(pUg31xx->otpData.productType != UG31XX_PRODUCT_TYPE_0)
+ {
+ return (UG_OTP_PRODUCT_DISMATCH);
+ }
+
+ if(firstPowerOn == _UPI_TRUE_)
+ {
+ SleepMiniSecond(1000);
+ fix_et_at_initial_cnt = MaxETFixCnt;
+ }
+
+ UG31_LOGI("[%s]: Do measurement\n", __func__);
+
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+ rtnMeas = UpiMeasurement(&pUg31xx->measData);
+ if(rtnMeas != MEAS_RTN_PASS)
+ {
+ return ((GGSTATUS)(rtnMeas + UG_MEAS_FAIL));
+ }
+ if(firstPowerOn == _UPI_TRUE_)
+ {
+ /// [AT-PM] : Initialize alarm function ; 04/08/2013
+ UpiMeasAlarmThreshold(&pUg31xx->measData);
+ UpiInitAlarm(&pUg31xx->sysData);
+ }
+
+ UG31_LOGI("[%s]: Current Status = %d mV / %d mA / %d 0.1oC\n", __func__,
+ pUg31xx->measData.bat1Voltage, pUg31xx->measData.curr, pUg31xx->measData.intTemperature);
+
+ UpiInitCapacity(&pUg31xx->capData);
+ pUg31xx->capData.rsoc = CalculateRsoc(pUg31xx->capData.rm, pUg31xx->capData.fcc);
+ if((firstPowerOn == _UPI_TRUE_) || (pUg31xx->sysData.fccFromIC == 0))
+ {
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+ UG31_LOGI("[%s]: Init data from table -> %d/%d = %d\n", __func__,
+ pUg31xx->batteryInfo.NAC, pUg31xx->batteryInfo.LMD, pUg31xx->batteryInfo.RSOC);
+ }
+ else
+ {
+ pUg31xx->capData.tableUpdateIdx = pUg31xx->sysData.tableUpdateIdxFromIC;
+
+ /// [AT-PM] : Calculate the RSOC/NAC/LMD from coulomb counter ; 01/27/2013
+ deltaQC = (_upi_s16_)pUg31xx->measData.stepCap;
+ pUg31xx->sysData.voltage = pUg31xx->measData.bat1Voltage;
+ lastRsocFromIC = pUg31xx->sysData.rsocFromIC;
+ UpiUpdateBatInfoFromIC(&pUg31xx->sysData, deltaQC);
+ if(CountTotalTime(pUg31xx->sysData.timeTagFromIC) > MAX_DELTA_TIME_THRESHOLD_FOR_WAKEUP)
+ {
+ /// [AT-PM] : Check the data accuracy ; 01/27/2013
+ deltaQC = (_upi_s16_)pUg31xx->sysData.rsocFromIC;
+ deltaQC = deltaQC - pUg31xx->capData.rsoc;
+ if((deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_WAKEUP) || (deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_WAKEUP))
+ {
+ deltaQC = (_upi_s16_)pUg31xx->capData.rsoc;
+ deltaQC = deltaQC - pUg31xx->sysData.rsocFromICBackup;
+ if((deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE) || (deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE))
+ {
+ if(deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE)
+ {
+ deltaQC = MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE;
+ }
+ if(deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE)
+ {
+ deltaQC = MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE;
+ }
+ deltaQC = deltaQC + pUg31xx->sysData.rsocFromICBackup;
+ pUg31xx->capData.rsoc = (_cap_u8_)deltaQC;
+
+ }
+ pUg31xx->capData.fcc = pUg31xx->sysData.fccFromIC;
+ tmp32 = (_upi_s32_)pUg31xx->capData.fcc;
+ tmp32 = tmp32*pUg31xx->capData.rsoc/CONST_PERCENTAGE;
+ pUg31xx->capData.rm = (_cap_u16_)tmp32;
+ UG31_LOGI("[%s]: Coulomb counter is not available -> Use data from table (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ else
+ {
+ CheckInitCapacityFromCC(pUg31xx, lastRsocFromIC);
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ UG31_LOGI("[%s]: Use data from coulomb counter (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ }
+ else
+ {
+ CheckInitCapacityFromCC(pUg31xx, lastRsocFromIC);
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ UG31_LOGI("[%s]: Use data from coulomb counter (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+ UpiResetCoulombCounter(&pUg31xx->measData);
+ }
+ UpiInitDsgCharge(&pUg31xx->capData);
+
+ /// [AT-PM] : Save battery information to IC ; 01/31/2013
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+
+ UG31_LOGI("[%s]: Driver version = %d\n", __func__, UG31XX_API_RELEASE_VERSION);
+ pUg31xx->backupData.targetFileVer = UG31XX_API_RELEASE_VERSION;
+ pUg31xx->backupData.backupDataIdx = BACKUP_MAX_LOG_SUSPEND_DATA;
+ while(pUg31xx->backupData.backupDataIdx)
+ {
+ pUg31xx->backupData.backupDataIdx = pUg31xx->backupData.backupDataIdx - 1;
+ pUg31xx->backupData.logData[pUg31xx->backupData.backupDataIdx] = (BackupSuspendDataType *)kmalloc(sizeof(BackupSuspendDataType), GFP_KERNEL);
+ memset(pUg31xx->backupData.logData[pUg31xx->backupData.backupDataIdx], 0, sizeof(BackupSuspendDataType));
+ }
+
+ dumpInfo(pUg31xx);
+ return (UG_INIT_SUCCESS);
+}
+
+GGSTATUS upiGG_PreSuspend(char *pObj)
+{
+ GGSTATUS Status = UG_READ_DEVICE_INFO_SUCCESS;
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+ UG31_LOGE("[%s]:*****upiGG_PreSuspend *****\n", __func__);
+
+ UpiResetCoulombCounter(&pUg31xx->measData);
+
+ /// [AT-PM] : Save battery information to IC ; 01/31/2013
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+ pUg31xx->sysData.voltage = pUg31xx->measData.bat1Voltage;
+ UpiUpdateBatInfoFromIC(&pUg31xx->sysData, (_sys_s16_) pUg31xx->measData.stepCap);
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+
+ /// [AT-PM] : Save dsgCharge before suspend ; 08/14/2013
+ dsg_charge_before_suspend = (_upi_s32_)pUg31xx->capData.dsgCharge;
+ if(dsg_charge_before_suspend < 0)
+ {
+ dsg_charge_before_suspend = (_upi_s32_)pUg31xx->sysData.fccFromIC;
+ dsg_charge_before_suspend = dsg_charge_before_suspend - pUg31xx->sysData.rmFromIC;
+ }
+ UG31_LOGI("[%s]: dsg_charge_before_suspend = %d\n", __func__, dsg_charge_before_suspend);
+
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ UpiUpdateSuspendData(&pUg31xx->backupData, _UPI_FALSE_);
+
+ /// [AT-PM] : Set CAP_STS_NAC_UPDATE_DISQ ; 11/08/2013
+ pUg31xx->capData.status = pUg31xx->capData.status | 0x0400;
+
+ #ifdef DEBUG_LOG
+ wDebug::LOGE(debugViewFileName.GetBuffer(),debugViewLines++,_T("[%s]:Reset Coulumb counter"), _L(__FUNCTION__));
+ debugViewFileName.ReleaseBuffer();
+ #endif
+ return(Status);
+}
+
+//====================================================
+//API Call to get the Battery Capacity
+// charge full condition:
+// if((Iav <TP current) && (Voltage >= TP Voltage))
+//====================================================
+void upiGG_ReadCapacity(char *pObj, GG_CAPACITY *pExtCapacity)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ UpiReadCapacity(&pUg31xx->capData);
+ pUg31xx->capData.tableUpdateDelayCnt = 1;
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+
+ // Output result by assign value from global variable
+ pExtCapacity->LMD = pUg31xx->batteryInfo.LMD;
+ pExtCapacity->NAC = pUg31xx->batteryInfo.NAC;
+ pExtCapacity->RSOC = pUg31xx->batteryInfo.RSOC;
+
+ /// [AT-PM] : If fully charged and keeps charging, reset coulomb counter ; 02/11/2013
+ if((pUg31xx->batteryInfo.RSOC == 100) && (pUg31xx->measData.curr >= pUg31xx->cellParameter.standbyCurrent))
+ {
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+ UpiResetCoulombCounter(&pUg31xx->measData);
+ }
+
+ /// [AT-PM] : Save battery information to IC ; 01/31/2013
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+
+ /// [AT-PM] : Check data from IC and file ; 06/19/2013
+ if((pUg31xx->backupData.backupFileSts == BACKUP_FILE_STS_EXIST) &&
+ (pUg31xx->backupData.icDataAvailable == BACKUP_BOOL_FALSE))
+ {
+ pUg31xx->backupData.icDataAvailable = BACKUP_BOOL_TRUE;
+ pUg31xx->sysData.timeTagFromIC = pUg31xx->measData.lastTimeTick;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ pUg31xx->capData.tableUpdateIdx = pUg31xx->sysData.tableUpdateIdxFromIC;
+ UpiSaveNacTable(&pUg31xx->capData);
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+ UpiInitDsgCharge(&pUg31xx->capData);
+ pUg31xx->measData.adc1ConvertTime = pUg31xx->sysData.adc1ConvTime;
+ UG31_LOGI("[%s]: Refresh driver information from file\n", __func__);
+ }
+
+ if(pUg31xx->backupData.icDataAvailable == BACKUP_BOOL_TRUE)
+ {
+ pExtCapacity->Ready = UG_CAP_DATA_READY;
+ }
+ else
+ {
+ pExtCapacity->Ready = UG_CAP_DATA_NOT_READY;
+ }
+}
+
+_upi_u8_ upiGG_CheckBackupFile(char *pObj)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+
+ /// [AT-PM] : Backup data to file routine ; 02/21/2013
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ #ifdef uG31xx_OS_WINDOWS
+ pUg31xx->sysData.backupFileName = BackupFile.GetBuffer();
+ #endif ///< end of uG31xx_OS_WINDOWS
+ UpiBackupData(&pUg31xx->backupData);
+ #ifdef uG31xx_OS_WINDOWS
+ BackupFile.ReleaseBuffer();
+ #endif ///< end of uG31xx_OS_WINDOWS
+
+ if(pUg31xx->backupData.backupFileSts == BACKUP_FILE_STS_CHECKING)
+ {
+ UG31_LOGI("[%s]: Backup File check fail\n", __func__);
+ return (UPI_CHECK_BACKUP_FILE_FAIL);
+ }
+ if(pUg31xx->backupData.backupFileSts == BACKUP_FILE_STS_COMPARE)
+ {
+ UG31_LOGI("[%s]: Backup File Version = %d\n", __func__, pUg31xx->backupData.backupFileVer);
+ if(pUg31xx->backupData.backupFileVer != UG31XX_API_RELEASE_VERSION)
+ {
+ return (UPI_CHECK_BACKUP_FILE_MISMATCH);
+ }
+ return (UPI_CHECK_BACKUP_FILE_EXIST);
+ }
+ return (UPI_CHECK_BACKUP_FILE_FAIL);
+}
+
+//system wakeup
+// to read back the preSuspend information from uG31xx RAM area
+// re-calculate the deltaQmax( the charge/discharge) during the suspend time
+GGSTATUS upiGG_Wakeup(char *pObj, int dc_in_before)
+{
+ GGSTATUS Status = UG_READ_DEVICE_INFO_SUCCESS;
+ _upi_s16_ deltaQC = 0; //coulomb counter's deltaQ
+ _upi_u32_ totalTime;
+ _upi_s32_ tmp32;
+ MEAS_RTN_CODE rtn;
+ _upi_u16_ rmBefore;
+ _upi_u16_ fccBefore;
+ _upi_u16_ rsocBefore;
+
+ struct ug31xx_data *pUg31xx;
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ ///Load the Saved time tag NAC LMD
+ UpiLoadBatInfoFromIC(&pUg31xx->sysData);
+ rmBefore = (_upi_u16_)pUg31xx->sysData.rmFromIC;
+ fccBefore = (_upi_u16_)pUg31xx->sysData.fccFromIC;
+ rsocBefore = (_upi_u16_)pUg31xx->sysData.rsocFromIC;
+
+ /// Count total Time
+ totalTime = CountTotalTime(pUg31xx->sysData.timeTagFromIC);
+ /// count the deltaQ during suspend
+ pUg31xx->measData.sysData = &pUg31xx->sysData;
+ pUg31xx->measData.otp = &pUg31xx->otpData;
+ pUg31xx->measData.lastTimeTick = pUg31xx->sysData.timeTagFromIC;
+ pUg31xx->measData.lastDeltaCap = pUg31xx->sysData.deltaCapFromIC;
+ pUg31xx->measData.adc1ConvertTime = pUg31xx->sysData.adc1ConvTime;
+ rtn = UpiMeasurement(&pUg31xx->measData);
+ pUg31xx->measData.adc1ConvertTime = pUg31xx->sysData.adc1ConvTime;
+ if(rtn != MEAS_RTN_PASS)
+ {
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ UpiUpdateSuspendData(&pUg31xx->backupData, _UPI_TRUE_);
+ return ((GGSTATUS)(rtn + UG_MEAS_FAIL));
+ }
+
+ /// [AT-PM] : Calculate delta capacity; 08/13/2013
+ tmp32 = (_upi_s32_)pUg31xx->measData.deltaCap;
+ tmp32 = tmp32*(pUg31xx->sysData.adc1ConvTime)/(pUg31xx->measData.adc1ConvertTime);
+ pUg31xx->measData.deltaCap = (_upi_s16_)tmp32;
+ pUg31xx->measData.lastDeltaCap = pUg31xx->measData.deltaCap;
+ tmp32 = tmp32 - pUg31xx->sysData.deltaCapFromIC;
+ pUg31xx->measData.stepCap = (_upi_s16_)tmp32;
+ deltaQC = (_upi_s16_)pUg31xx->measData.stepCap;
+ delta_cap_during_suspend = (_upi_s32_)deltaQC;
+
+ /// [AT-PM] : Calculate the RSOC/NAC/LMD from coulomb counter ; 01/27/2013
+ pUg31xx->sysData.voltage = pUg31xx->measData.bat1Voltage;
+ UpiUpdateBatInfoFromIC(&pUg31xx->sysData, deltaQC);
+ UG31_LOGE("[%s]: suspend time = %d ms,deltaQ = %d mAh, RSOC =%d, LMD = %d mAh, NAC=%d mAh\n",
+ __func__,
+ totalTime,
+ deltaQC,
+ pUg31xx->sysData.rsocFromIC,
+ pUg31xx->sysData.fccFromIC,
+ pUg31xx->sysData.rmFromIC);
+ /// [AT-PM] : Calculate the RSOC/NAC/LMD from table ; 01/28/2013
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ pUg31xx->capData.tableUpdateIdx = pUg31xx->sysData.tableUpdateIdxFromIC;
+ UpiTableCapacity(&pUg31xx->capData);
+ pUg31xx->capData.rsoc = CalculateRsoc(pUg31xx->capData.rm, pUg31xx->capData.fcc);
+ wakeup_predict_rsoc = (_upi_u8_)pUg31xx->capData.rsoc;
+ /// [AT-PM] : Check the data accuracy ; 01/27/2013
+ if(totalTime > MAX_DELTA_TIME_THRESHOLD_FOR_WAKEUP)
+ {
+ deltaQC = (_upi_s16_)pUg31xx->sysData.rsocFromIC;
+ deltaQC = deltaQC - pUg31xx->capData.rsoc;
+ if((deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_WAKEUP) || (deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_WAKEUP))
+ {
+ deltaQC = (_upi_s16_)pUg31xx->capData.rsoc;
+ deltaQC = deltaQC - pUg31xx->sysData.rsocFromICBackup;
+ if((deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE) || (deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE))
+ {
+ if(deltaQC > MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE)
+ {
+ deltaQC = MAX_DELTA_RSOC_THRESHOLD_FOR_TABLE;
+ }
+ if(deltaQC < MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE)
+ {
+ deltaQC = MIN_DELTA_RSOC_THRESHOLD_FOR_TABLE;
+ }
+ deltaQC = deltaQC + pUg31xx->sysData.rsocFromICBackup;
+ pUg31xx->capData.rsoc = (_cap_u8_)deltaQC;
+
+ }
+ pUg31xx->capData.fcc = pUg31xx->sysData.fccFromIC;
+ tmp32 = (_upi_s32_)pUg31xx->capData.fcc;
+ tmp32 = tmp32*pUg31xx->capData.rsoc/CONST_PERCENTAGE;
+ pUg31xx->capData.rm = (_cap_u16_)tmp32;
+ UG31_LOGI("[%s]: Coulomb counter is not available -> Use data from table (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ else
+ {
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ UG31_LOGI("[%s]: Use data from coulomb counter (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+ }
+ else
+ {
+ pUg31xx->capData.rm = (_cap_u16_)pUg31xx->sysData.rmFromIC;
+ pUg31xx->capData.fcc = (_cap_u16_)pUg31xx->sysData.fccFromIC;
+ pUg31xx->capData.rsoc = (_cap_u8_)pUg31xx->sysData.rsocFromIC;
+ UG31_LOGI("[%s]: Use data from coulomb counter (%d/%d = %d)\n", __func__,
+ pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+
+ /// [AT-PM] : Check dc in before suspend ; 10/22/2013
+ if(dc_in_before == 0)
+ {
+ if(pUg31xx->batteryInfo.RSOC < pUg31xx->capData.rsoc)
+ {
+ UG31_LOGI("[%s]: Fix the RSOC the same value before suspend = %d (%d)\n", __func__, rsocBefore, pUg31xx->capData.rsoc);
+ pUg31xx->capData.rm = (_cap_u16_)rmBefore;
+ pUg31xx->capData.fcc = (_cap_u16_)fccBefore;
+ pUg31xx->capData.rsoc = (_cap_u16_)rsocBefore;
+ }
+
+ tmp32 = (_upi_s32_)totalTime;
+ tmp32 = tmp32/1000*(pUg31xx->cellParameter.standbyCurrent)/2/3600*(-1);
+ UG31_LOGI("[%s]: Estimated capacity = %d\n", __func__, tmp32);
+
+ if(deltaQC >= tmp32)
+ {
+ UG31_LOGI("[%s]: Apply static discharging current\n", __func__);
+
+ tmp32 = tmp32 + deltaQC;
+ tmp32 = tmp32/2;
+ tmp32 = tmp32 + rmBefore;
+ if(tmp32 < 0)
+ {
+ tmp32 = 0;
+ }
+ UG31_LOGI("[%s]: New RM = %d (%d)\n", __func__, tmp32, rmBefore);
+
+ pUg31xx->capData.rm = (_cap_u16_)tmp32;
+ pUg31xx->capData.rsoc = CalculateRsoc(pUg31xx->capData.rm, pUg31xx->capData.fcc);
+ UG31_LOGI("[%s]: Battery status -> %d / %d = %d\n", __func__, pUg31xx->capData.rm, pUg31xx->capData.fcc, pUg31xx->capData.rsoc);
+ }
+
+ if(pUg31xx->measData.rawCodeCharge > 0)
+ {
+ pUg31xx->measData.ccOffsetAdj = pUg31xx->measData.ccOffsetAdj + 1;
+ if(pUg31xx->measData.ccOffsetAdj > pUg31xx->cellParameter.standbyCurrent)
+ {
+ pUg31xx->measData.ccOffsetAdj = pUg31xx->cellParameter.standbyCurrent;
+ }
+ UG31_LOGI("[%s]: Adjust ADC1 offset = %d\n", __func__, pUg31xx->measData.ccOffsetAdj);
+ }
+ }
+
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+ UpiInitDsgCharge(&pUg31xx->capData);
+
+ UpiResetCoulombCounter(&pUg31xx->measData);
+
+ /// [AT-PM] : Save battery information to IC ; 01/31/2013
+ pUg31xx->sysData.rmFromIC = pUg31xx->batteryInfo.NAC;
+ pUg31xx->sysData.fccFromIC = pUg31xx->batteryInfo.LMD;
+ pUg31xx->sysData.rsocFromIC = (_sys_u8_)pUg31xx->batteryInfo.RSOC;
+ pUg31xx->sysData.tableUpdateIdxFromIC = pUg31xx->capData.tableUpdateIdx;
+ pUg31xx->sysData.deltaCapFromIC = pUg31xx->measData.lastDeltaCap;
+ pUg31xx->sysData.adc1ConvTime = pUg31xx->measData.adc1ConvertTime;
+ UpiSaveBatInfoTOIC(&pUg31xx->sysData);
+
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ UpiUpdateSuspendData(&pUg31xx->backupData, _UPI_TRUE_);
+
+#if 0
+ /// [AT-PM] : Backup data to file routine ; 02/21/2013
+ pUg31xx->backupData.capData = &pUg31xx->capData;
+ pUg31xx->backupData.sysData = &pUg31xx->sysData;
+ pUg31xx->backupData.measData = &pUg31xx->measData;
+ UpiBackupData(&pUg31xx->backupData);
+#endif
+ return (Status);
+}
+
+/**
+ * @brief upiGG_AccessMeasurementParameter
+ *
+ * Access measurement parameter
+ *
+ * @para read set _UPI_TRUE_ to read data from API
+ * @para pMeasPara pointer of GG_MEAS_PARA_TYPE
+ * @return GGSTATUS
+ */
+GGSTATUS upiGG_AccessMeasurementParameter(char *pObj, _upi_bool_ read, GG_MEAS_PARA_TYPE *pMeasPara)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ /// [AT-PM] : Read data ; 08/29/2012
+ if(read == _UPI_TRUE_)
+ {
+ pMeasPara->Adc1Gain = pUg31xx->cellParameter.adc1_ngain;
+ pMeasPara->Adc1Offset = pUg31xx->cellParameter.adc1_pos_offset;
+ pMeasPara->Adc2Gain = pUg31xx->cellParameter.adc2_gain;
+ pMeasPara->Adc2Offset = pUg31xx->cellParameter.adc2_offset;
+ pMeasPara->ITOffset = pUg31xx->cellParameter.adc_d5;
+ pMeasPara->ETOffset = pUg31xx->cellParameter.adc_d4;
+ pMeasPara->ProductType = pUg31xx->otpData.productType;
+ return (UG_SUCCESS);
+ }
+
+ /// [AT-PM] : Write data ; 08/29/2012
+ pUg31xx->cellParameter.adc1_ngain = pMeasPara->Adc1Gain;
+ pUg31xx->cellParameter.adc1_pos_offset = pMeasPara->Adc1Offset;
+ pUg31xx->cellParameter.adc2_gain = pMeasPara->Adc2Gain;
+ pUg31xx->cellParameter.adc2_offset = pMeasPara->Adc2Offset;
+ pUg31xx->cellParameter.adc_d5 = pMeasPara->ITOffset;
+ pUg31xx->cellParameter.adc_d4 = pMeasPara->ETOffset;
+ return (UG_SUCCESS);
+}
+
+#ifdef ENABLE_BQ27520_SW_CMD
+
+/**
+ * @brief TI_Cntl
+ *
+ * Control() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Cntl(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_u16_ CntlData;
+
+ CntlData = *pData;
+ switch(CntlData)
+ {
+ case UG_STD_CMD_CNTL_CONTROL_STATUS:
+ *pData = pUg31xx->bq27520Cmd.CntlControlStatus;
+ break;
+ case UG_STD_CMD_CNTL_DEVICE_TYPE:
+ *pData = 0x3103;
+ break;
+ case UG_STD_CMD_CNTL_FW_VERSION:
+ *pData = 0x0001;
+ break;
+ case UG_STD_CMD_CNTL_PREV_MACWRITE:
+ *pData = pUg31xx->bq27520Cmd.CntlPrevMacWrite;
+ break;
+ case UG_STD_CMD_CNTL_CHEM_ID:
+ *pData = 0x0001;
+ break;
+ case UG_STD_CMD_CNTL_OCV_CMD:
+ break;
+ case UG_STD_CMD_CNTL_BAT_INSERT:
+ if(!(pUg31xx->bq27520Cmd.Opcfg & UG_STD_CMD_OPCFG_BIE))
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_BAT_DET;
+ }
+ break;
+ case UG_STD_CMD_CNTL_BAT_REMOVE:
+ if(!(pUg31xx->bq27520Cmd.Opcfg & UG_STD_CMD_OPCFG_BIE))
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_BAT_DET);
+ }
+ break;
+ case UG_STD_CMD_CNTL_SET_HIBERNATE:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus | UG_STD_CMD_CNTL_CONTROL_STATUS_HIBERNATE;
+ break;
+ case UG_STD_CMD_CNTL_CLEAR_HIBERNATE:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus & (~UG_STD_CMD_CNTL_CONTROL_STATUS_HIBERNATE);
+ break;
+ case UG_STD_CMD_CNTL_SET_SLEEP_PLUS:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus | UG_STD_CMD_CNTL_CONTROL_STATUS_SNOOZE;
+ break;
+ case UG_STD_CMD_CNTL_CLEAR_SLEEP_PLUS:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus & (~UG_STD_CMD_CNTL_CONTROL_STATUS_SNOOZE);
+ break;
+ case UG_STD_CMD_CNTL_FACTORY_RESTORE:
+ break;
+ case UG_STD_CMD_CNTL_ENABLE_DLOG:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus | UG_STD_CMD_CNTL_CONTROL_STATUS_DLOGEN;
+ break;
+ case UG_STD_CMD_CNTL_DISABLE_DLOG:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus & (~UG_STD_CMD_CNTL_CONTROL_STATUS_DLOGEN);
+ break;
+ case UG_STD_CMD_CNTL_DF_VERSION:
+ *pData = 0x0000;
+ break;
+ case UG_STD_CMD_CNTL_SEALED:
+ pUg31xx->bq27520Cmd.CntlControlStatus = pUg31xx->bq27520Cmd.CntlControlStatus | UG_STD_CMD_CNTL_CONTROL_STATUS_SS;
+ break;
+ case UG_STD_CMD_CNTL_RESET:
+ if(!(pUg31xx->bq27520Cmd.CntlControlStatus & UG_STD_CMD_CNTL_CONTROL_STATUS_SS))
+ {
+ }
+ break;
+ default:
+ *pData = 0x0000;
+ break;
+ }
+
+ pUg31xx->bq27520Cmd.CntlPrevMacWrite = ((CntlData) > UG_STD_CMD_CNTL_PREV_MACWRITE) ? UG_STD_CMD_CNTL_PREV_MACWRITE : CntlData;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_AR
+ *
+ * AtRate() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_AR(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s16_ AR;
+
+ AR = (_upi_s16_)(*pData);
+ if(AR != pUg31xx->bq27520Cmd.AR)
+ {
+ pUg31xx->bq27520Cmd.AR = AR;
+ }
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Artte
+ *
+ * AtRateTimeToEmpty() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Artte(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Artte;
+
+ if(pUg31xx->bq27520Cmd.AR >= 0)
+ {
+ *pData = 65535;
+ }
+
+ Artte = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ Artte = Artte*60*(-1)/pUg31xx->bq27520Cmd.AR;
+ *pData = (_upi_u16_)Artte;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Temp
+ *
+ * Temperature() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Temp(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ if(pUg31xx->bq27520Cmd.Opcfg & UG_STD_CMD_OPCFG_WRTEMP)
+ {
+ /// [AT-PM] : Temperature is from host ; 10/11/2012
+ pUg31xx->bq27520Cmd.Temp = *pData;
+ }
+ else
+ {
+ /// [AT-PM] : Temperature is measured by uG31xx ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.Opcfg & UG_STD_CMD_OPCFG_TEMPS)
+ {
+ /// [AT-PM] : Report external temperature ; 10/11/2012
+ *pData = pUg31xx->deviceInfo.ET;
+ }
+ else
+ {
+ /// [AT-PM] : Report internal temperature ; 10/11/2012
+ *pData = pUg31xx->deviceInfo.IT;
+ }
+ }
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Volt
+ *
+ * Voltage() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Volt(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->deviceInfo.voltage_mV;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Flags
+ *
+ * Flags() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Flags(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ /// [AT-PM] : OTC - Overtemperature in charge ; 10/11/2012
+
+ /// [AT-PM] : OTD - Overtemperature in discharge ; 10/11/2012
+
+ /// [AT-PM] : CHG_INH - Charge inhibit ; 10/11/2012
+
+ /// [AT-PM] : XCHG - Charge suspend alert ; 10/11/2012
+
+ /// [AT-PM] : FC - Full-charged ; 10/11/2012
+ if(pUg31xx->batteryInfo.RSOC < pUg31xx->bq27520Cmd.FCClear)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_FC);
+ }
+ if(pUg31xx->bq27520Cmd.FCSet < 0)
+ {
+ if(pUg31xx->batteryInfo.RSOC == 100)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_FC;
+ }
+ }
+ else
+ {
+ if(pUg31xx->batteryInfo.RSOC > pUg31xx->bq27520Cmd.FCSet)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_FC;
+ }
+ }
+
+ /// [AT-PM] : CHG - (Fast) charging allowed ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.Flags & UG_STD_CMD_FLAGS_FC)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_CHG);
+ }
+ else
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_CHG;
+ }
+
+ /// [AT-PM] : OCV_GD - Good OCV measurement taken ; 10/11/2012
+
+ /// [AT-PM] : WAIT_ID - Waiting to identify inserted battery ; 10/11/2012
+
+ /// [AT-PM] : BAT_DET - Battery detected ; 10/11/2012
+ if(pUg31xx->userReg.regAlarm2Status & ALARM2_STATUS_OV1_ALARM)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_BAT_DET;
+ }
+ else
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_BAT_DET);
+ }
+
+ /// [AT-PM] : SOC1 - State-of-charge threshold 1 (SOC1 Set) reached ; 10/11/2012
+ if(pUg31xx->batteryInfo.NAC > pUg31xx->bq27520Cmd.Soc1Clear)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_SOC1);
+ }
+ if(pUg31xx->batteryInfo.NAC < pUg31xx->bq27520Cmd.Soc1Set)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_SOC1;
+ }
+
+ /// [AT-PM] : SYSDOWN - System should shut down ; 10/11/2012
+
+ /// [AT-PM] : DSG - Discharging detected ; 10/11/2012
+ if(pUg31xx->deviceInfo.AveCurrent_mA <= 0)
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags | UG_STD_CMD_FLAGS_DSG;
+ }
+ else
+ {
+ pUg31xx->bq27520Cmd.Flags = pUg31xx->bq27520Cmd.Flags & (~UG_STD_CMD_FLAGS_DSG);
+ }
+
+ *pData = pUg31xx->bq27520Cmd.Flags;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Nac
+ *
+ * NominalAvailableCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Nac(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.NAC;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Fac
+ *
+ * FullAvailableCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Fac(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.LMD;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_RM
+ *
+ * RemainingCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_RM(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.NAC;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Fcc
+ *
+ * FullChargeCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Fcc(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.LMD;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_AI
+ *
+ * AverageCurrent() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_AI(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->deviceInfo.AveCurrent_mA;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Tte
+ *
+ * TimeToEmpty() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Tte(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Tte;
+
+ if(pUg31xx->deviceInfo.AveCurrent_mA >= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Tte = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ Tte = Tte*60*(-1)/pUg31xx->deviceInfo.AveCurrent_mA;
+ *pData = (_upi_u16_)Tte;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Ttf
+ *
+ * TimeToFull() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Ttf(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Ttf;
+
+ if(pUg31xx->deviceInfo.AveCurrent_mA <= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Ttf = (_upi_s32_)pUg31xx->batteryInfo.LMD;
+ Ttf = Ttf - pUg31xx->batteryInfo.NAC;
+ Ttf = Ttf*90/pUg31xx->deviceInfo.AveCurrent_mA;
+ *pData = (_upi_u16_)Ttf;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_SI
+ *
+ * StandbyCurrent() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_SI(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s16_ LowerBound;
+ _upi_s32_ NewSI;
+
+ /// [AT-PM] : Set initial SI ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.SINow == 0)
+ {
+ pUg31xx->bq27520Cmd.SINow = pUg31xx->bq27520Cmd.InitSI;
+ }
+
+ LowerBound = pUg31xx->bq27520Cmd.InitSI*2;
+ if(LowerBound > 0)
+ {
+ LowerBound = LowerBound*(-1);
+ }
+
+ /// [AT-PM] : SI criteria - 2 x InitSI < Current < 0 ; 10/11/2012
+ if((pUg31xx->deviceInfo.AveCurrent_mA < 0) && (pUg31xx->deviceInfo.AveCurrent_mA > LowerBound))
+ {
+ /// [AT-PM] : Update SI every 1 minute ; 10/11/2012
+ if((pUg31xx->bq27520Cmd.SIWindow >= 60) && (pUg31xx->bq27520Cmd.SISample > 0))
+ {
+ NewSI = pUg31xx->bq27520Cmd.SIBuf/pUg31xx->bq27520Cmd.SISample;
+ NewSI = NewSI*7 + pUg31xx->bq27520Cmd.SINow*93;
+ NewSI = NewSI/100;
+ pUg31xx->bq27520Cmd.SINow = (_upi_s16_)NewSI;
+ pUg31xx->bq27520Cmd.SISample = -1;
+ pUg31xx->bq27520Cmd.SIBuf = 0;
+ pUg31xx->bq27520Cmd.SIWindow = 0;
+ }
+ else
+ {
+ pUg31xx->bq27520Cmd.SISample = pUg31xx->bq27520Cmd.SISample + 1;
+ pUg31xx->bq27520Cmd.SIWindow = pUg31xx->bq27520Cmd.SIWindow + pUg31xx->bq27520Cmd.DeltaSec;
+
+ /// [AT-PM] : Ignore the first sample ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.SISample > 0)
+ {
+ pUg31xx->bq27520Cmd.SIBuf = pUg31xx->bq27520Cmd.SIBuf + pUg31xx->deviceInfo.AveCurrent_mA;
+ }
+ }
+ }
+ else
+ {
+ /// [AT-PM] : Ignore the last sample ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.SISample > 0)
+ {
+ NewSI = pUg31xx->bq27520Cmd.SIBuf/pUg31xx->bq27520Cmd.SISample;
+ NewSI = NewSI*7 + pUg31xx->bq27520Cmd.SINow*93;
+ NewSI = NewSI/100;
+ pUg31xx->bq27520Cmd.SINow = (_upi_s16_)NewSI;
+ }
+ pUg31xx->bq27520Cmd.SISample = -1;
+ pUg31xx->bq27520Cmd.SIBuf = 0;
+ pUg31xx->bq27520Cmd.SIWindow = 0;
+ }
+
+ *pData = pUg31xx->bq27520Cmd.SINow;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Stte
+ *
+ * StandbyTimeToEmpty() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Stte(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Stte;
+
+ if(pUg31xx->bq27520Cmd.SINow >= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Stte = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ Stte = Stte*60*(-1)/pUg31xx->bq27520Cmd.SINow;
+ *pData = (_upi_u16_)Stte;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Mli
+ *
+ * MaxLoadCurrent() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Mli(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ NewMli;
+
+ /// [AT-PM] : Set initial MLI ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.Mli == 0)
+ {
+ pUg31xx->bq27520Cmd.Mli = pUg31xx->bq27520Cmd.InitMaxLoadCurrent;
+ }
+
+ /// [AT-PM] : Get the start charging SOC ; 10/11/2012
+ if(pUg31xx->bq27520Cmd.Flags & UG_STD_CMD_FLAGS_DSG)
+ {
+ pUg31xx->bq27520Cmd.MliDsgSoc = (_upi_u8_)pUg31xx->batteryInfo.RSOC;
+ }
+
+ /// [AT-PM] : MLI criteria - Current < MLI ; 10/11/2012
+ if(pUg31xx->deviceInfo.AveCurrent_mA < pUg31xx->bq27520Cmd.Mli)
+ {
+ pUg31xx->bq27520Cmd.Mli = pUg31xx->deviceInfo.AveCurrent_mA;
+ }
+
+ /// [AT-PM] : Reduce MLI at FC ; 10/11/2012
+ if((pUg31xx->bq27520Cmd.Flags & UG_STD_CMD_FLAGS_FC) && (pUg31xx->bq27520Cmd.MliDsgSoc < 50))
+ {
+ NewMli = (_upi_s32_)pUg31xx->bq27520Cmd.InitMaxLoadCurrent;
+ NewMli = NewMli + pUg31xx->bq27520Cmd.Mli;
+ NewMli = NewMli/2;
+ pUg31xx->bq27520Cmd.Mli = (_upi_s16_)NewMli;
+ }
+
+ *pData = (_upi_u16_)pUg31xx->bq27520Cmd.Mli;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Stte
+ *
+ * MaxLoadTimeToEmpty() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Mltte(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Mltte;
+
+ if(pUg31xx->deviceInfo.AveCurrent_mA >= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Mltte = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ Mltte = Mltte*60*(-1)/pUg31xx->bq27520Cmd.Mli;
+ *pData = (_upi_u16_)Mltte;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_AE
+ *
+ * AvailableEnergy() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_AE(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_u32_ AE;
+
+ AE = (_upi_u32_)pUg31xx->batteryInfo.NAC;
+ AE = AE*pUg31xx->deviceInfo.voltage_mV/1000;
+ pUg31xx->bq27520Cmd.AE = (_upi_u16_)AE;
+ *pData = pUg31xx->bq27520Cmd.AE;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_AP
+ *
+ * AveragePower() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_AP(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ AP;
+
+ if((pUg31xx->deviceInfo.AveCurrent_mA == 0) || (pUg31xx->bq27520Cmd.APDsgTime == 0))
+ {
+ pUg31xx->bq27520Cmd.AP = 0;
+ *pData = 0;
+ return (UG_SUCCESS);
+ }
+
+ AP = (_upi_s32_)pUg31xx->batteryInfo.NAC;
+ AP = AP*pUg31xx->deviceInfo.voltage_mV/1000;
+
+ /// [AT-PM] : Average discharging power ; 10/11/2012
+ if(pUg31xx->deviceInfo.AveCurrent_mA > 0)
+ {
+ pUg31xx->bq27520Cmd.APStartDsgE = AP;
+ pUg31xx->bq27520Cmd.APDsgTime = 0;
+
+ pUg31xx->bq27520Cmd.APChgTime = pUg31xx->bq27520Cmd.APChgTime + pUg31xx->bq27520Cmd.DeltaSec;
+ AP = AP - pUg31xx->bq27520Cmd.APStartChgE;
+ AP = AP*3600/pUg31xx->bq27520Cmd.APChgTime;
+ pUg31xx->bq27520Cmd.AP = (_upi_s16_)AP;
+ }
+
+ /// [AT-PM] : Average charging power ; 10/11/2012
+ if(pUg31xx->deviceInfo.AveCurrent_mA < 0)
+ {
+ pUg31xx->bq27520Cmd.APStartChgE = AP;
+ pUg31xx->bq27520Cmd.APChgTime = 0;
+
+ pUg31xx->bq27520Cmd.APDsgTime = pUg31xx->bq27520Cmd.APDsgTime + pUg31xx->bq27520Cmd.DeltaSec;
+ AP = AP - pUg31xx->bq27520Cmd.APStartDsgE;
+ AP = AP*3600/pUg31xx->bq27520Cmd.APDsgTime;
+ pUg31xx->bq27520Cmd.AP = (_upi_s16_)AP;
+ }
+
+ *pData = (_upi_u16_)pUg31xx->bq27520Cmd.AP;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Ttecp
+ *
+ * TimeToEmptyAtConstantPower() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Ttecp(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_s32_ Ttecp;
+
+ if(pUg31xx->bq27520Cmd.AP >= 0)
+ {
+ *pData = 65535;
+ return (UG_SUCCESS);
+ }
+
+ Ttecp = (_upi_s32_)pUg31xx->bq27520Cmd.AE;
+ Ttecp = Ttecp*60*(-1)/pUg31xx->bq27520Cmd.AP;
+ *pData = (_upi_u16_)Ttecp;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Soh
+ *
+ * StateOfHealth() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Soh(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ _upi_u32_ Soh;
+
+ Soh = (_upi_u32_)pUg31xx->batteryInfo.LMD;
+ Soh = Soh*100/pUg31xx->bq27520Cmd.Dcap;
+
+ Soh = Soh & UG_STD_CMD_SOH_VALUE_MASK;
+ Soh = Soh | UG_STD_CMD_SOH_STATUS_READY;
+ *pData = (_upi_u16_)Soh;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_CC
+ *
+ * CycleCount() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_CC(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ if(pUg31xx->deviceInfo.AveCurrent_mA < 0)
+ {
+ pUg31xx->bq27520Cmd.CCBuf = pUg31xx->bq27520Cmd.CCBuf + pUg31xx->bq27520Cmd.CCLastNac - pUg31xx->batteryInfo.NAC;
+ }
+ pUg31xx->bq27520Cmd.CCLastNac = pUg31xx->batteryInfo.NAC;
+
+ if(pUg31xx->bq27520Cmd.CCBuf >= pUg31xx->bq27520Cmd.CCThreshold)
+ {
+ pUg31xx->bq27520Cmd.CC = pUg31xx->bq27520Cmd.CC + 1;
+ pUg31xx->bq27520Cmd.CCBuf = pUg31xx->bq27520Cmd.CCBuf - pUg31xx->bq27520Cmd.CCThreshold;
+ }
+
+ *pData = pUg31xx->bq27520Cmd.CC;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Soc
+ *
+ * StateOfCharge() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Soc(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->batteryInfo.RSOC;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Nic
+ *
+ * NormalizedImpedanceCal() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Nic(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = 0;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Icr
+ *
+ * InstantaneousCurrentReading() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Icr(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->deviceInfo.current_mA;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Dli
+ *
+ * DataLogIndex() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Dli(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->bq27520Cmd.Dli;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Dlb
+ *
+ * DataLogBuffer() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Dlb(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->bq27520Cmd.Dlb;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Itemp
+ *
+ * InternalTemperature() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Itemp(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->deviceInfo.IT;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Opcfg
+ *
+ * OperationConfiguration() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Opcfg(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = (_upi_u16_)pUg31xx->bq27520Cmd.Opcfg;
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief TI_Dcap
+ *
+ * DesignCapacity() command
+ *
+ * @para pData address of data
+ * @return GGSTATUS
+ */
+GGSTATUS TI_Dcap(struct ug31xx_data *pUg31xx, _upi_u16_ *pData)
+{
+ *pData = pUg31xx->bq27520Cmd.Dcap;
+ return (UG_SUCCESS);
+}
+
+typedef GGSTATUS (*TIBq27520FuncPtr)(struct ug31xx_data *pUg31xx, _upi_u16_ *pData);
+typedef struct TIBq27520FuncTableST {
+ TIBq27520FuncPtr pFunc;
+ _upi_u8_ CmdCode;
+} TIBq27520FuncTableType;
+
+
+TIBq27520FuncTableType TI_Command[] = {
+ { TI_Cntl, UG_STD_CMD_CNTL, },
+ { TI_AR, UG_STD_CMD_AR, },
+ { TI_Artte, UG_STD_CMD_ARTTE, },
+ { TI_Temp, UG_STD_CMD_TEMP, },
+ { TI_Volt, UG_STD_CMD_VOLT, },
+ { TI_Flags, UG_STD_CMD_FLAGS, },
+ { TI_Nac, UG_STD_CMD_NAC, },
+ { TI_Fac, UG_STD_CMD_FAC, },
+ { TI_RM, UG_STD_CMD_RM, },
+ { TI_Fcc, UG_STD_CMD_FCC, },
+ { TI_AI, UG_STD_CMD_AI, },
+ { TI_Tte, UG_STD_CMD_TTE, },
+ { TI_Ttf, UG_STD_CMD_TTF, },
+ { TI_SI, UG_STD_CMD_SI, },
+ { TI_Stte, UG_STD_CMD_STTE, },
+ { TI_Mli, UG_STD_CMD_MLI, },
+ { TI_Mltte, UG_STD_CMD_MLTTE, },
+ { TI_AE, UG_STD_CMD_AE, },
+ { TI_AP, UG_STD_CMD_AP, },
+ { TI_Ttecp, UG_STD_CMD_TTECP, },
+ { TI_Soh, UG_STD_CMD_SOH, },
+ { TI_CC, UG_STD_CMD_CC, },
+ { TI_Soc, UG_STD_CMD_SOC, },
+ { TI_Nic, UG_STD_CMD_NIC, },
+ { TI_Icr, UG_STD_CMD_ICR, },
+ { TI_Dli, UG_STD_CMD_DLI, },
+ { TI_Dlb, UG_STD_CMD_DLB, },
+ { TI_Itemp, UG_STD_CMD_ITEMP, },
+ { TI_Opcfg, UG_STD_CMD_OPCFG, },
+ { TI_Dcap, UG_EXT_CMD_DCAP, },
+};
+
+/**
+ * @brief upiGG_FetchDataCommand
+ *
+ * Read the gas gauge status following TI bq27520's interface
+ *
+ * @para CommandCode command code
+ * @para pData address of returned data
+ * @return GGSTATUS
+ */
+GGSTATUS upiGG_FetchDataCommand(char *pObj, _upi_u8_ CommandCode, _upi_u16_ *pData)
+{
+ GGSTATUS Rtn;
+ int TotalCmd;
+ GG_DEVICE_INFO DevInfo;
+ GG_CAPACITY CapData;
+ _upi_u32_ DeltaT;
+ int CmdIdx;
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ Rtn = upiGG_ReadDeviceInfo(pObj, &DevInfo);
+ if(Rtn != UG_READ_DEVICE_INFO_SUCCESS)
+ {
+ return (Rtn);
+ }
+
+ DeltaT = pUg31xx->measData.deltaTime;
+ if(DeltaT >= 5000000)
+ {
+ upiGG_ReadCapacity(pObj, &CapData);
+ }
+
+ DeltaT = GetTickCount();
+ DeltaT = DeltaT - pUg31xx->bq27520Cmd.LastTime;
+ pUg31xx->bq27520Cmd.LastTime = GetTickCount();
+ pUg31xx->bq27520Cmd.DeltaSec = (_upi_u16_)(DeltaT/1000);
+
+ Rtn = UG_SUCCESS;
+ TotalCmd = sizeof(TI_Command)/sizeof(TIBq27520FuncTableType);
+ CmdIdx = 0;
+ while(1)
+ {
+ if(TI_Command[CmdIdx].CmdCode == CommandCode)
+ {
+ Rtn = (*TI_Command[CmdIdx].pFunc)(pUg31xx, pData);
+ break;
+ }
+
+ CmdIdx = CmdIdx + 1;
+ if(CmdIdx >= TotalCmd)
+ {
+ Rtn = UG_TI_CMD_OVERFLOW;
+ }
+ }
+
+ return (Rtn);
+}
+
+/**
+ * @brief upiGG_FetchDataParameter
+ *
+ * Set the parameter for bq27520 like command
+ *
+ * @para data parameters of GG_FETCH_DATA_PARA_TYPE
+ * @return GGSTATUS
+ */
+GGSTATUS upiGG_FetchDataParameter(char *pObj, GG_FETCH_DATA_PARA_TYPE data)
+{
+ GGSTATUS Rtn;
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ Rtn = UG_SUCCESS;
+
+ pUg31xx->bq27520Cmd.FCSet = data.FCSet;
+ pUg31xx->bq27520Cmd.FCClear = data.FCClear;
+ pUg31xx->bq27520Cmd.Soc1Set = data.Soc1Set;
+ pUg31xx->bq27520Cmd.Soc1Clear = data.Soc1Clear;
+ pUg31xx->bq27520Cmd.InitSI = data.InitSI;
+ pUg31xx->bq27520Cmd.InitMaxLoadCurrent = data.InitMaxLoadCurrent;
+ pUg31xx->bq27520Cmd.CCThreshold = data.CCThreshold;
+ pUg31xx->bq27520Cmd.Opcfg = data.Opcfg;
+ pUg31xx->bq27520Cmd.Dcap = data.Dcap;
+ return (Rtn);
+}
+
+#endif ///< end of ENABLE_BQ27520_SW_CMD
+
+/**
+ * @brief upiGG_DumpRegister
+ *
+ * Dump whole register value
+ *
+ * @para pBuf address of register value buffer
+ * @return data size
+ */
+_upi_u16_ upiGG_DumpRegister(char *pObj, _upi_u8_ * pBuf)
+{
+ _upi_u16_ idx;
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ idx = 0;
+ memcpy(pBuf + idx, pUg31xx->otpData.otp1, OTP1_SIZE);
+ idx = idx + OTP1_SIZE;
+ memcpy(pBuf + idx, pUg31xx->otpData.otp2, OTP2_SIZE);
+ idx = idx + OTP2_SIZE;
+ memcpy(pBuf + idx, pUg31xx->otpData.otp3, OTP3_SIZE);
+ idx = idx + OTP3_SIZE;
+ memcpy(pBuf + idx, &pUg31xx->userReg, sizeof(GG_USER_REG));
+ idx = idx + sizeof(GG_USER_REG);
+ memcpy(pBuf + idx, &pUg31xx->user2Reg, sizeof(GG_USER2_REG));
+ idx = idx + sizeof(GG_USER2_REG);
+ return (idx);
+}
+
+/**
+ * @brief upiGG_DumpCellTable
+ *
+ * Dump cell NAC table
+ *
+ * @para pTable address of cell table
+ * @return _UPI_NULL_
+ */
+void upiGG_DumpCellTable(char *pObj, CELL_TABLE *pTable)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+ memcpy(pTable, &pUg31xx->cellTable, sizeof(CELL_TABLE));
+}
+
+/**
+ * @brief upiGG_UnInitial
+ *
+ * Un-initialize uG31xx
+ *
+ * @para pObj address of memory buffer allocated for uG31xx
+ * @return GGSTATUS
+ */
+GGSTATUS upiGG_UnInitial(char **pObj)
+{
+ struct ug31xx_data *pUg31xx;
+ pUg31xx = (struct ug31xx_data *)(*pObj);
+
+ UG31_LOGE("[%s]***** upiGG_UnInitial() to free memory ***** \n", __func__);
+
+ pUg31xx->backupData.backupDataIdx = BACKUP_MAX_LOG_SUSPEND_DATA;
+ while(pUg31xx->backupData.backupDataIdx)
+ {
+ pUg31xx->backupData.backupDataIdx = pUg31xx->backupData.backupDataIdx - 1;
+ kfree(pUg31xx->backupData.logData[pUg31xx->backupData.backupDataIdx]);
+ }
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(*pObj);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(*pObj);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(*pObj);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ return (UG_SUCCESS);
+}
+
+/**
+ * @brief upiGG_DumpParameter
+ *
+ * Dump all parameter setting
+ *
+ * @para pTable address of cell parameter
+ * @return _UPI_NULL_
+ */
+void upiGG_DumpParameter(char *pObj, CELL_PARAMETER *pTable)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+ memcpy(pTable, &pUg31xx->cellParameter, sizeof(CELL_PARAMETER));
+
+ // memcpy(&pTable->CELL_NAC_TABLE, &pUg31xx->realData, sizeof(GG_REAL_DATA));
+}
+
+/**
+ * @brief upiGG_FetchDebugData
+ *
+ * Fetch debug information data
+ *
+ * @para pObj address of memory buffer
+ * @para data address of GG_FETCH_CAP_DATA_TYPE
+ * @return _UPI_NULL_
+ */
+void upiGG_FetchDebugData(char *pObj, GG_FETCH_DEBUG_DATA_TYPE *data)
+{
+ struct ug31xx_data *pUg31xx;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+
+ data->versionMain = UG31XX_API_MAIN_VERSION;
+ data->versionOtp = UG31XX_API_OTP_VERSION;
+ data->versionSub = UG31XX_API_SUB_VERSION;
+
+ data->capDelta = pUg31xx->measData.stepCap;
+ data->capDsgCharge = pUg31xx->capData.dsgCharge;
+ data->capDsgChargeStart = pUg31xx->capData.dsgChargeStart;
+ data->capDsgChargeTime = pUg31xx->capData.dsgChargeTime;
+ data->capPreDsgCharge = pUg31xx->capData.preDsgCharge;
+ data->capSelfHour = pUg31xx->capData.selfDsgHour;
+ data->capSelfMilliSec = pUg31xx->capData.selfDsgMilliSec;
+ data->capSelfMin = pUg31xx->capData.selfDsgMin;
+ data->capSelfSec = pUg31xx->capData.selfDsgSec;
+ data->capStatus = pUg31xx->capData.status;
+ data->capTableUpdateIdx = pUg31xx->capData.tableUpdateIdx;
+ data->capTPTime = pUg31xx->capData.tpTime;
+
+ data->measAdc1ConvertTime = pUg31xx->measData.adc1ConvertTime;
+ data->measAdc1Gain = pUg31xx->measData.adc1Gain;
+ data->measAdc1Offset = pUg31xx->measData.adc1Offset;
+ data->measAdc2Gain = pUg31xx->measData.adc2Gain;
+ data->measAdc2Offset = pUg31xx->measData.adc2Offset;
+ data->measCCOffset = pUg31xx->measData.ccOffset;
+ data->measCharge = pUg31xx->measData.codeCharge;
+ data->measCodeBat1 = pUg31xx->measData.codeBat1;
+ data->measCodeCurrent = pUg31xx->measData.codeCurrent;
+ data->measCodeET = pUg31xx->measData.codeExtTemperature;
+ data->measCodeIT = pUg31xx->measData.codeIntTemperature;
+ data->measLastCounter = pUg31xx->measData.lastCounter;
+ data->measLastDeltaQ = pUg31xx->measData.lastDeltaCap;
+ data->measLastTimeTick = pUg31xx->measData.lastTimeTick;
+}
+
+/**
+ * @brief upiGG_DebugSwitch
+ *
+ * Enable/disable debug information to UART
+ *
+ * @para Enable set _UPI_TRUE_ to enable it
+ * @return NULL
+ */
+void upiGG_DebugSwitch(_upi_bool_ enable)
+{
+ Ug31DebugEnable = enable;
+}
+
+/**
+ * @brief upiGG_ForceTaper
+ *
+ * Force taper condition reached
+ *
+ * @para pObj address of memory buffer
+ * @para charger_full 1 if charger detects full
+ * @para dc_in_before 1 if dc in before suspend mode
+ * @para dc_in_now 1 if dc in after suspend mode
+ * @return NULL
+ */
+void upiGG_ForceTaper(char *pObj, int charger_full, int dc_in_before, int dc_in_now)
+{
+ struct ug31xx_data *pUg31xx;
+ _meas_s16_ now_step_cap;
+ _meas_s16_ now_current;
+ _meas_u16_ now_voltage;
+ _meas_u32_ now_delta_time;
+ _upi_u8_ cnt;
+ _upi_s32_ tmp32;
+
+ pUg31xx = (struct ug31xx_data *)pObj;
+ pUg31xx->capData.ggbTable = &pUg31xx->cellTable;
+ pUg31xx->capData.ggbParameter = &pUg31xx->cellParameter;
+ pUg31xx->capData.measurement = &pUg31xx->measData;
+ pUg31xx->capData.status = pUg31xx->capData.status | 0x0100;
+
+ if(dc_in_before == 0)
+ {
+ UG31_LOGI("[%s]: Enter suspend without charger -> no force taper\n", __func__);
+ return;
+ }
+
+ tmp32 = dsg_charge_before_suspend - delta_cap_during_suspend;
+ if(dc_in_now == 0)
+ {
+ now_current = pUg31xx->measData.curr;
+ now_current = now_current/10;
+ now_voltage = pUg31xx->measData.bat1Voltage;
+ now_voltage = now_voltage - now_current;
+
+ UG31_LOGI("[%s]: No dc in now (%d)\n", __func__, now_voltage);
+ tmp32 = tmp32*100;
+ if((pUg31xx->capData.status & 0x0000000c) == 0x00000008) ///< [AT-PM] : Check current status is dischargign ; 10/11/2013
+ {
+ if((wakeup_predict_rsoc < 95) &&
+ (tmp32 > pUg31xx->cellParameter.ILMD) &&
+ (now_voltage < pUg31xx->cellParameter.TPVoltage))
+ {
+ UG31_LOGI("[%s]: Predicted RSOC < 95 and charged capacity is not enough (%d < %d) and voltage is low (%d < %d ) in discharging -> no force taper\n",
+ __func__, delta_cap_during_suspend, dsg_charge_before_suspend, now_voltage, pUg31xx->cellParameter.TPVoltage);
+ return;
+ }
+ }
+ else
+ {
+ if((wakeup_predict_rsoc < 98) && (tmp32 > pUg31xx->cellParameter.ILMD))
+ {
+ UG31_LOGI("[%s]: Predicted RSOC (%d) < 98 and charged capacity is not enough (%d < %d) -> no force taper\n",
+ __func__, wakeup_predict_rsoc, delta_cap_during_suspend, dsg_charge_before_suspend);
+ return;
+ }
+ }
+ }
+ else
+ {
+ UG31_LOGI("[%s]: DC in now\n", __func__);
+ if(charger_full == 0)
+ {
+ UG31_LOGI("[%s]: No charger detect full event -> no force taper\n", __func__);
+ return;
+ }
+ if(wakeup_predict_rsoc < 90)
+ {
+ UG31_LOGI("[%s]: Predicted RSOC (%d) < 90 -> no force taper\n", __func__, wakeup_predict_rsoc);
+ return;
+ }
+ }
+
+ now_step_cap = pUg31xx->measData.stepCap;
+ now_current = pUg31xx->measData.curr;
+ now_voltage = pUg31xx->measData.bat1Voltage;
+ now_delta_time = pUg31xx->measData.deltaTime;
+
+ UG31_LOGI("[%s]: Force to taper START\n", __func__);
+
+ cnt = 100;
+ while(cnt)
+ {
+ /// [AT-PM] : Enter charging mode ; 08/14/2013
+ pUg31xx->measData.stepCap = 0;
+ pUg31xx->measData.deltaTime = 0;
+ pUg31xx->measData.curr = (_meas_s16_)pUg31xx->cellParameter.TPCurrent - 1;
+
+ UpiReadCapacity(&pUg31xx->capData);
+ pUg31xx->capData.tableUpdateDelayCnt = 1;
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+
+ /// [AT-PM] : Force to TP ; 08/14/2013
+ pUg31xx->measData.bat1Voltage = (_meas_u16_)pUg31xx->cellParameter.TPVoltage + 1;
+ pUg31xx->capData.tpTime = (_cap_u32_)pUg31xx->cellParameter.TPTime + 1;
+ pUg31xx->capData.tpTime = pUg31xx->capData.tpTime*1000;
+
+ UpiReadCapacity(&pUg31xx->capData);
+ pUg31xx->capData.tableUpdateDelayCnt = 1;
+ pUg31xx->capData.rm = pUg31xx->capData.fcc;
+ pUg31xx->capData.rsoc = 100;
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+
+ /// [AT-PM] : Recover to initial state ; 08/14/2013
+ pUg31xx->measData.curr = now_current;
+ pUg31xx->measData.bat1Voltage = now_voltage;
+ pUg31xx->measData.deltaTime = now_delta_time;
+
+ UpiReadCapacity(&pUg31xx->capData);
+ pUg31xx->capData.tableUpdateDelayCnt = 1;
+ pUg31xx->batteryInfo.NAC = (_upi_u16_)pUg31xx->capData.rm;
+ pUg31xx->batteryInfo.LMD = (_upi_u16_)pUg31xx->capData.fcc;
+ pUg31xx->batteryInfo.RSOC = (_upi_u16_)pUg31xx->capData.rsoc;
+
+ UG31_LOGI("[%s]: Force to taper loop %d -> %d / %d = %d\n", __func__,
+ cnt, pUg31xx->batteryInfo.NAC, pUg31xx->batteryInfo.LMD, pUg31xx->batteryInfo.RSOC);
+ if(pUg31xx->capData.rsoc >= 100)
+ {
+ break;
+ }
+
+ cnt = cnt - 1;
+ }
+
+ pUg31xx->measData.stepCap = now_step_cap;
+
+ UG31_LOGI("[%s]: Force to taper END (%d / %d = %d)\n", __func__,
+ pUg31xx->batteryInfo.NAC, pUg31xx->batteryInfo.LMD, pUg31xx->batteryInfo.RSOC);
+}
+
+/// ===========================================
+/// End of uG31xx_API.cpp
+/// ===========================================
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.h
new file mode 100755
index 00000000..c601852c
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API.h
@@ -0,0 +1,521 @@
+/// ===========================================
+/// uG31xx_API.h
+/// ===========================================
+
+#ifndef _UG31XXAPI_H_
+#define _UG31XXAPI_H_
+
+#include "uG31xx_Platform.h"
+
+#if defined (uG31xx_OS_WINDOWS)
+
+ #include <windows.h>
+
+#endif
+
+#include "global.h"
+#include "uG31xx.h"
+
+#if defined (uG31xx_OS_WINDOWS)
+
+ #include "../../uG31xx_I2C_DLL/uG3100Dll/uG31xx_I2C.h"
+ #include <assert.h>
+
+ #ifdef DEBUG_LOG
+
+ #include "wDebug.h"
+
+ #endif
+
+#elif defined (uG31xx_OS_ANDROID)
+
+ #ifdef uG31xx_BOOT_LOADER
+
+ #include <sys/types.h>
+ #include <platform/timer.h>
+
+ #include "ug31xx_boot_i2c.h"
+
+ #else ///< else of uG31xx_BOOT_LOADER
+
+ #include <linux/module.h>
+ #include <linux/slab.h>
+ #include <linux/delay.h>
+ #include <linux/time.h>
+ #include <asm/div64.h>
+ #include <linux/jiffies.h>
+ #include <linux/types.h>
+ #include <linux/fs.h>
+ #include <asm/uaccess.h>
+
+ #include "ug31xx_i2c.h"
+
+ #endif ///< end of uG31xx_BOOT_LOADER
+
+#endif
+
+#include "typeDefine.h"
+#include "uG31xx_Reg_Def.h"
+#include "uG31xx_API_Otp.h"
+#include "uG31xx_API_System.h"
+#include "uG31xx_API_Measurement.h"
+#include "uG31xx_API_Capacity.h"
+#include "uG31xx_API_Backup.h"
+
+#define UG31XX_API_RELEASE_VERSION (20131108)
+
+#define UG31XX_API_MAIN_VERSION (13)
+#define UG31XX_API_OTP_VERSION ((UG31XX_OTP_VERSION_MAIN << 16) | UG31XX_OTP_VERSION_SUB)
+#define UG31XX_API_SUB_VERSION ((UG31XX_CAP_VERSION << 16) | (UG31XX_MEAS_VERSION << 8) | UG31XX_SYSTEM_VERSION)
+
+#define UG31XX_I2C_HIGH_SPEED_MODE (_UPI_FALSE_)
+#define UG31XX_I2C_TEM_BITS_MODE (_UPI_FALSE_)
+
+#define UG31XX_MAX_TEMPERATURE_BEFORE_READY (395)
+#define UG31XX_MIN_TEMPERATURE_BEFORE_READY (105)
+
+#ifdef UG31XX_DEBUG_ENABLE
+
+ #if defined(uG31xx_OS_ANDROID)
+
+ #ifdef uG31xx_BOOT_LOADER
+
+ #define __func__
+
+ #define UG31_LOGE(...)
+ #define UG31_LOGI(...)
+ #define UG31_LOGD(...)
+ #define UG31_LOGV(...)
+
+ extern int ug31_dprintk(int level, const char *fmt, ...);
+
+ #else ///< else of uG31xx_BOOT_LOADER
+
+ #define LOG_NORMAL (0)
+ #define LOG_ERROR (1)
+ #define LOG_DATA (2)
+ #define LOG_VERBOSE (3)
+
+ #define UG31_TAG "UG31"
+ #define UG31_LOGE(...) ug31_printk(LOG_ERROR, "<"UG31_TAG"/E>" __VA_ARGS__);
+ #define UG31_LOGI(...) ug31_printk(LOG_NORMAL, "<"UG31_TAG"/I>" __VA_ARGS__);
+ #define UG31_LOGD(...) ug31_printk(LOG_DATA, "<"UG31_TAG"/D>" __VA_ARGS__);
+ #define UG31_LOGV(...) ug31_printk(LOG_VERBOSE, "<"UG31_TAG"/V>" __VA_ARGS__);
+
+ extern int ug31_printk(int level, const char *fmt, ...);
+
+ #endif ///< end of uG31xx_BOOT_LOADER
+
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+
+ #if defined (uG31xx_OS_WINDOWS)
+
+ #define DEBUG_FILE (_T("uG3105"))
+ #define __func__ (_T(__FUNCTION__))
+
+ #define UG31_LOGE(...) wDebug::LOGE(DEBUG_FILE, 0, _T(__VA_ARGS__));
+ #define UG31_LOGI(...) wDebug::LOGE(DEBUG_FILE, 0, _T(__VA_ARGS__));
+ #define UG31_LOGD(...) wDebug::LOGE(DEBUG_FILE, 0, _T(__VA_ARGS__));
+ #define UG31_LOGV(...) wDebug::LOGE(DEBUG_FILE, 0, _T(__VA_ARGS__));
+
+ #endif ///< end of defined (uG31xx_OS_WINDOWS)
+
+#else ///< else of UG31XX_DEBUG_ENABLE
+
+ #define UG31_LOGE(...)
+ #define UG31_LOGI(...)
+ #define UG31_LOGD(...)
+ #define UG31_LOGV(...)
+
+#endif ///< end of UG31XX_DEBUG_ENABLE
+
+#if defined(uG31xx_OS_ANDROID)
+
+ extern _upi_u32_ GetTickCount(void);
+ extern _upi_u32_ GetSysTickCount(void);
+
+#endif
+
+/* data struct */
+typedef enum _GGSTATUS{
+ UG_SUCCESS = 0x00,
+ UG_FAIL = 0x01,
+ UG_NOT_DEF = 0x02,
+ UG_INIT_OCV_FAIL = 0x03,
+ UG_READ_GGB_FAIL = 0x04,
+ UG_ACTIVE_FAIL = 0x05,
+ UG_INIT_SUCCESS = 0x06,
+ UG_OTP_ISEMPTY = 0x07,
+ UG_OTP_PRODUCT_DISMATCH = 0x08,
+
+ UG_I2C_INIT_FAIL = 0x10,
+ UG_I2C_READ_SUCCESS = 0x11,
+ UG_I2C_READ_FAIL = 0x12,
+ UG_I2C_WRITE_SUCCESS = 0x13,
+ UG_I2C_WRITE_FAIL = 0x14,
+
+ UG_READ_REG_SUCCESS = 0x20,
+ UG_READ_REG_FAIL = 0x21,
+
+ UG_READ_DEVICE_INFO_SUCCESS = 0x22,
+ UG_READ_DEVICE_INFO_FAIL = 0x23,
+ UG_READ_DEVICE_ALARM_SUCCESS = 0x24,
+ UG_READ_DEVICE_ALARM_FAIL = 0x25,
+ UG_READ_DEVICE_RID_SUCCESS = 0x26,
+ UG_READ_DEVICE_RID_FAIL = 0x27,
+ UG_READ_ADC_FAIL = 0x28, //new add for filter ADC Error Code
+
+ UG_TI_CMD_OVERFLOW = 0x30,
+
+ UG_MEAS_FAIL = 0x40,
+ UG_MEAS_FAIL_BATTERY_REMOVED = 0x41,
+ UG_MEAS_FAIL_ADC_ABNORMAL = 0x42,
+ UG_MEAS_FAIL_NTC_SHORT = 0x43,
+
+ UG_CAP_DATA_READY = 0x50,
+ UG_CAP_DATA_NOT_READY = 0x51,
+}GGSTATUS;
+
+/*
+ GGSTATUS upiGG_Initial
+ Description: Initial and active uG31xx function
+ Input: .GGB(gas gauge battery) setting filename, need include complete path
+ Output: UG_INIT_SUCCESS -> initial uG31xx success
+ UG_READ_GGB_FAIL -> read GGB file fail
+ UG_INIT_I2C_FAIL -> initial I2C to open HID fail
+ UG_ACTIVE_FAIL -> active uG31xx fail
+*/
+#if defined (uG31xx_OS_WINDOWS)
+
+ EXPORTS GGSTATUS upiGG_Initial(char **pObj, const wchar_t* GGBFilename, const wchar_t* OtpFileName, const wchar_t* BackupFileName, char reset, unsigned char MaxETFixCnt);
+
+#endif
+
+#if defined(uG31xx_OS_ANDROID)
+
+ GGSTATUS upiGG_Initial(char **pObj, GGBX_FILE_HEADER *pGGBXBuf, char reset, unsigned char MaxETFixCnt);
+
+#endif
+
+/*
+ GGSTATUS upiGG_CountInitQmax
+ Description:
+ Input: None
+ Output: None
+*/
+//EXPORTS void upiGG_CountInitQmax(void);
+
+/*
+ GGSTATUS upiGG_ReadDevieRegister
+ Description: Read GG_USER_REG from device to global variable and output
+ Input: Pointer of sturct GG_USER_REG
+ Output: UG_READ_REG_SUCCESS -> read success
+ UG_READ_REG_FAIL -> read fail
+*/
+EXPORTS GGSTATUS upiGG_ReadAllRegister(char *pObj,GG_USER_REG* pExtUserReg, GG_USER2_REG* pExtUserReg2);
+
+/*
+ GGSTATUS upiGG_ReadDeviceInfo
+ Description: Read GG_USER_REG from device and calculate GG_DEVICE_INFO, then write to global variable and output
+ Input: Pointer of struct GG_DEVICE_INFO
+ Output: UG_READ_DEVICE_INFO_SUCCESS -> calculate derive information sucess
+ UG_READ_DEVICE_INFO_FAIL -> calculate derive information fail
+*/
+EXPORTS GGSTATUS upiGG_ReadDeviceInfo(char *pObj,GG_DEVICE_INFO* pExtDeviceInfo);
+
+/* GGSTATUS upiGG_ReadCapacity
+ Description:
+ Input:
+ Output: None
+*/
+EXPORTS void upiGG_ReadCapacity(char *pObj,GG_CAPACITY *pExtCapacity);
+
+/**
+ * @brief upiGG_GetAlarmStatus
+ *
+ * Get alarm status
+ *
+ * @para pAlarmStatus address of alarm status
+ * @return UG_READ_DEVICE_ALARM_SUCCESS if success
+ */
+EXPORTS GGSTATUS upiGG_GetAlarmStatus(char *pObj, _upi_u8_ *pAlarmStatus);
+
+/*
+new add function for System suspend & wakeup
+
+*/
+EXPORTS GGSTATUS upiGG_PreSuspend(char *pObj);
+EXPORTS GGSTATUS upiGG_Wakeup(char *pObj, int dc_in_before);
+
+/**
+ * @brief upiGG_DumpRegister
+ *
+ * Dump whole register value
+ *
+ * @para pBuf address of register value buffer
+ * @return data size
+ */
+EXPORTS _upi_u16_ upiGG_DumpRegister(char *pObj, _upi_u8_ *pBuf);
+
+/**
+ * @brief upiGG_DumpCellTable
+ *
+ * Dump cell NAC table
+ *
+ * @para pTable address of cell table
+ * @return _UPI_NULL_
+ */
+EXPORTS void upiGG_DumpCellTable(char *pObj, CELL_TABLE *pTable);
+EXPORTS GGSTATUS upiGG_UnInitial(char **pObj);
+EXPORTS void upiGG_DumpParameter(char *pObj, CELL_PARAMETER *pTable);
+
+#define upiGG_PrePowerOff (upiGG_PreSuspend)
+
+#ifdef ENABLE_BQ27520_SW_CMD
+
+/**
+ * @brief upiGG_AccessMeasurementParameter
+ *
+ * Access measurement parameter
+ *
+ * @para read set _UPI_TRUE_ to read data from API
+ * @para pMeasPara pointer of GG_MEAS_PARA_TYPE
+ * @return GGSTATUS
+ */
+EXPORTS GGSTATUS upiGG_AccessMeasurementParameter(char *pObj, _upi_bool_ read, GG_MEAS_PARA_TYPE *pMeasPara);
+
+#define UG_STD_CMD_CNTL (0x00)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS (0x0000)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_DLOGEN (1<<15)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_FAS (1<<14)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_SS (1<<13)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_CSV (1<<12)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_CCA (1<<11)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_BCA (1<<10)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_OCVCMDCOMP (1<<9)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_OCVFAIL (1<<8)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_INITCOMP (1<<7)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_HIBERNATE (1<<6)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_SNOOZE (1<<5)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_SLEEP (1<<4)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_LDMD (1<<3)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_RUP_DIS (1<<2)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_VOK (1<<1)
+ #define UG_STD_CMD_CNTL_CONTROL_STATUS_QEN (1<<0)
+ #define UG_STD_CMD_CNTL_DEVICE_TYPE (0x0001)
+ #define UG_STD_CMD_CNTL_FW_VERSION (0x0002)
+ #define UG_STD_CMD_CNTL_PREV_MACWRITE (0x0007)
+ #define UG_STD_CMD_CNTL_CHEM_ID (0x0008)
+ #define UG_STD_CMD_CNTL_OCV_CMD (0x000C) ///< [AT-PM] : Not implemented ; 10/11/2012
+ #define UG_STD_CMD_CNTL_BAT_INSERT (0x000D)
+ #define UG_STD_CMD_CNTL_BAT_REMOVE (0x000E)
+ #define UG_STD_CMD_CNTL_SET_HIBERNATE (0x0011)
+ #define UG_STD_CMD_CNTL_CLEAR_HIBERNATE (0x0012)
+ #define UG_STD_CMD_CNTL_SET_SLEEP_PLUS (0x0013)
+ #define UG_STD_CMD_CNTL_CLEAR_SLEEP_PLUS (0x0014)
+ #define UG_STD_CMD_CNTL_FACTORY_RESTORE (0x0015)
+ #define UG_STD_CMD_CNTL_ENABLE_DLOG (0x0018)
+ #define UG_STD_CMD_CNTL_DISABLE_DLOG (0x0019)
+ #define UG_STD_CMD_CNTL_DF_VERSION (0x001F)
+ #define UG_STD_CMD_CNTL_SEALED (0x0020)
+ #define UG_STD_CMD_CNTL_RESET (0x0041)
+#define UG_STD_CMD_AR (0x02)
+#define UG_STD_CMD_ARTTE (0x04)
+#define UG_STD_CMD_TEMP (0x06)
+#define UG_STD_CMD_VOLT (0x08)
+#define UG_STD_CMD_FLAGS (0x0A)
+ #define UG_STD_CMD_FLAGS_OTC (1<<15)
+ #define UG_STD_CMD_FLAGS_OTD (1<<14)
+ #define UG_STD_CMD_FLAGS_RSVD13 (1<<13)
+ #define UG_STD_CMD_FLAGS_RSVD12 (1<<12)
+ #define UG_STD_CMD_FLAGS_CHG_INH (1<<11)
+ #define UG_STD_CMD_FLAGS_XCHG (1<<10)
+ #define UG_STD_CMD_FLAGS_FC (1<<9)
+ #define UG_STD_CMD_FLAGS_CHG (1<<8)
+ #define UG_STD_CMD_FLAGS_RSVD7 (1<<7)
+ #define UG_STD_CMD_FLAGS_RSVD6 (1<<6)
+ #define UG_STD_CMD_FLAGS_OCV_GD (1<<5)
+ #define UG_STD_CMD_FLAGS_WAIT_ID (1<<4)
+ #define UG_STD_CMD_FLAGS_BAT_DET (1<<3)
+ #define UG_STD_CMD_FLAGS_SOC1 (1<<2)
+ #define UG_STD_CMD_FLAGS_SYSDOWN (1<<1)
+ #define UG_STD_CMD_FLAGS_DSG (1<<0)
+#define UG_STD_CMD_NAC (0x0C)
+#define UG_STD_CMD_FAC (0x0E)
+#define UG_STD_CMD_RM (0x10)
+#define UG_STD_CMD_FCC (0x12)
+#define UG_STD_CMD_AI (0x14)
+#define UG_STD_CMD_TTE (0x16)
+#define UG_STD_CMD_TTF (0x18)
+#define UG_STD_CMD_SI (0x1A)
+#define UG_STD_CMD_STTE (0x1C)
+#define UG_STD_CMD_MLI (0x1E)
+#define UG_STD_CMD_MLTTE (0x20)
+#define UG_STD_CMD_AE (0x22)
+#define UG_STD_CMD_AP (0x24)
+#define UG_STD_CMD_TTECP (0x26)
+#define UG_STD_CMD_SOH (0x28)
+ #define UG_STD_CMD_SOH_VALUE_MASK (0x00FF)
+ #define UG_STD_CMD_SOH_STATUS_MASK (0xFF00)
+ #define UG_STD_CMD_SOH_STATUS_NOT_VALID (0x0000)
+ #define UG_STD_CMD_SOH_STATUS_INSTANT_READY (0x0100)
+ #define UG_STD_CMD_SOH_STATUS_INITIAL_READY (0x0200)
+ #define UG_STD_CMD_SOH_STATUS_READY (0x0300)
+#define UG_STD_CMD_CC (0x2A)
+#define UG_STD_CMD_SOC (0x2C)
+#define UG_STD_CMD_NIC (0x2E) ///< [AT-PM] : Not implemented ; 10/11/2012
+#define UG_STD_CMD_ICR (0x30)
+#define UG_STD_CMD_DLI (0x32)
+#define UG_STD_CMD_DLB (0x34)
+#define UG_STD_CMD_ITEMP (0x36)
+#define UG_STD_CMD_OPCFG (0x3A)
+ #define UG_STD_CMD_OPCFG_RESCAP (1<<31)
+ #define UG_STD_CMD_OPCFG_BATG_OVR (1<<30)
+ #define UG_STD_CMD_OPCFG_INT_BERM (1<<29)
+ #define UG_STD_CMD_OPCFG_PFC_CFG1 (1<<28)
+ #define UG_STD_CMD_OPCFG_PFC_CFG0 (1<<27)
+ #define UG_STD_CMD_OPCFG_IWAKE (1<<26)
+ #define UG_STD_CMD_OPCFG_RSNS1 (1<<25)
+ #define UG_STD_CMD_OPCFG_RSNS0 (1<<24)
+ #define UG_STD_CMD_OPCFG_INT_FOCV (1<<23)
+ #define UG_STD_CMD_OPCFG_IDSELEN (1<<22)
+ #define UG_STD_CMD_OPCFG_SLEEP (1<<21)
+ #define UG_STD_CMD_OPCFG_RMFCC (1<<20)
+ #define UG_STD_CMD_OPCFG_SOCI_POL (1<<19)
+ #define UG_STD_CMD_OPCFG_BATG_POL (1<<18)
+ #define UG_STD_CMD_OPCFG_BATL_POL (1<<17)
+ #define UG_STD_CMD_OPCFG_TEMPS (1<<16)
+ #define UG_STD_CMD_OPCFG_WRTEMP (1<<15)
+ #define UG_STD_CMD_OPCFG_BIE (1<<14)
+ #define UG_STD_CMD_OPCFG_BL_INT (1<<13)
+ #define UG_STD_CMD_OPCFG_GNDSEL (1<<12)
+ #define UG_STD_CMD_OPCFG_FCE (1<<11)
+ #define UG_STD_CMD_OPCFG_DFWRINDBL (1<<10)
+ #define UG_STD_CMD_OPCFG_RFACTSTEP (1<<9)
+ #define UG_STD_CMD_OPCFG_INDFACRES (1<<8)
+ #define UG_STD_CMD_OPCFG_BATGSPUEN (1<<7)
+ #define UG_STD_CMD_OPCFG_BATGWPUEN (1<<6)
+ #define UG_STD_CMD_OPCFG_BATLSPUEN (1<<5)
+ #define UG_STD_CMD_OPCFG_BATLWSPUEN (1<<4)
+ #define UG_STD_CMD_OPCFG_RSVD3 (1<<3)
+ #define UG_STD_CMD_OPCFG_SLPWKCHG (1<<2)
+ #define UG_STD_CMD_OPCFG_DELTAVOPT1 (1<<1)
+ #define UG_STD_CMD_OPCFG_DELTAVOPT0 (1<<0)
+#define UG_EXT_CMD_DCAP (0x3C)
+
+/**
+ * @brief upiGG_FetchDataCommand
+ *
+ * Fetch bq27520 like command
+ *
+ * @para read set _UPI_TRUE_ to read data from API
+ * @para pMeasPara pointer of GG_MEAS_PARA_TYPE
+ * @return GGSTATUS
+ */
+EXPORTS GGSTATUS upiGG_FetchDataCommand(char *pObj, _upi_u8_ CommandCode, _upi_u16_ *pData);
+
+typedef struct GG_FETCH_DATA_PARA_ST {
+ _upi_s8_ FCSet;
+ _upi_s8_ FCClear;
+ _upi_u8_ Soc1Set;
+ _upi_u8_ Soc1Clear;
+ _upi_s8_ InitSI;
+ _upi_s16_ InitMaxLoadCurrent;
+ _upi_u16_ CCThreshold;
+ _upi_u32_ Opcfg;
+ _upi_u16_ Dcap;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) GG_FETCH_DATA_PARA_TYPE;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} GG_FETCH_DATA_PARA_TYPE;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief upiGG_FetchDataParameter
+ *
+ * Set the parameter for bq27520 like command
+ *
+ * @para data parameters of GG_FETCH_DATA_PARA_TYPE
+ * @return GGSTATUS
+ */
+EXPORTS GGSTATUS upiGG_FetchDataParameter(char *pObj, GG_FETCH_DATA_PARA_TYPE data);
+
+#endif //endif ENABLE_BQ27520_SW_CMD
+
+typedef struct GG_FETCH_DEBUG_DATA_ST {
+ /// [AT-PM] : Driver version ; 01/30/2013
+ int versionMain;
+ int versionOtp;
+ int versionSub;
+
+ /// [AT-PM] : Capacity related ; 01/30/2013
+ int capStatus;
+ int capSelfHour;
+ int capSelfMin;
+ int capSelfSec;
+ int capSelfMilliSec;
+ int capTPTime;
+ int capDelta;
+ int capDsgCharge;
+ int capDsgChargeStart;
+ int capDsgChargeTime;
+ int capPreDsgCharge;
+ int capTableUpdateIdx;
+
+ /// [AT-PM] : Measurement related ; 01/30/2013
+ int measCodeBat1;
+ int measCodeCurrent;
+ int measCodeIT;
+ int measCodeET;
+ int measCharge;
+ int measCCOffset;
+ int measAdc1ConvertTime;
+ int measAdc1Gain;
+ int measAdc1Offset;
+ int measAdc2Gain;
+ int measAdc2Offset;
+ int measLastCounter;
+ int measLastTimeTick;
+ int measLastDeltaQ;
+
+#if defined(uG31xx_OS_ANDROID)
+ }__attribute__((packed)) GG_FETCH_DEBUG_DATA_TYPE;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } GG_FETCH_DEBUG_DATA_TYPE;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief upiGG_FetchDebugData
+ *
+ * Fetch debug information data
+ *
+ * @para pObj address of memory buffer
+ * @para data address of GG_FETCH_CAP_DATA_TYPE
+ * @return _UPI_NULL_
+ */
+EXPORTS void upiGG_FetchDebugData(char *pObj, GG_FETCH_DEBUG_DATA_TYPE *data);
+
+/**
+ * @brief upiGG_DebugSwitch
+ *
+ * Enable/disable debug information to UART
+ *
+ * @para Enable set _UPI_TRUE_ to enable it
+ * @return NULL
+ */
+EXPORTS void upiGG_DebugSwitch(_upi_bool_ enable);
+
+#define UPI_CHECK_BACKUP_FILE_FAIL (0)
+#define UPI_CHECK_BACKUP_FILE_EXIST (1)
+#define UPI_CHECK_BACKUP_FILE_MISMATCH (2)
+
+EXPORTS _upi_u8_ upiGG_CheckBackupFile(char *pObj);
+
+EXPORTS void upiGG_ForceTaper(char *pObj, int charger_full, int dc_in_before, int dc_in_now);
+
+#endif ///< end of _UG31XXAPI_H_
+
+/// ===========================================
+/// End of uG31xx_API.h
+/// ===========================================
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.c
new file mode 100755
index 00000000..90a82633
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.c
@@ -0,0 +1,601 @@
+/**
+ * @filename uG31xx_API_Backup.cpp
+ *
+ * Backup data on uG31xx to a file in system
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+#define UG31XX_BACKUP_FILE_ENABLE
+
+#ifdef uG31xx_OS_WINDOWS
+
+typedef char mm_segment_t;
+typedef char loff_t;
+
+typedef struct file
+{
+ FILE *fp;
+} fileType;
+
+#define O_RDONLY (1<<0)
+#define O_WRONLY (1<<1)
+#define O_RDWR (3<<0)
+#define O_CREAT (1<<2)
+#define O_APPEND (1<<3)
+
+static struct file BackupFile;
+
+/**
+ * @brief filp_open
+ *
+ * Open file
+ *
+ * @para path address of file
+ * @para cntl FILE_CNTL parameter
+ * @para misc dummy parameter
+ * @return address of BackupFile
+ */
+struct file * filp_open(const wchar_t *path, int cntl, int misc)
+{
+ if(BackupFile.fp != _UPI_NULL_)
+ {
+ fclose(BackupFile.fp);
+ }
+ BackupFile.fp = _UPI_NULL_;
+
+ switch(cntl & O_RDWR)
+ {
+ case O_RDONLY:
+ _wfopen_s(&BackupFile.fp, path, _T("rb, ccs=UTF-8"));
+ break;
+ case O_WRONLY:
+ _wfopen_s(&BackupFile.fp, path, _T("wb, ccs=UTF-8"));
+ break;
+ case O_RDWR:
+ _wfopen_s(&BackupFile.fp, path, _T("r+b, ccs=UTF-8"));
+ break;
+ default:
+ _wfopen_s(&BackupFile.fp, path, _T("rb, ccs=UTF-8"));
+ break;
+ }
+ return (&BackupFile);
+}
+
+/**
+ * @brief filp_close
+ *
+ * Close file
+ *
+ * @para fp address of struct file
+ * @para misc dummy value
+ * @return _UPI_NULL_
+ */
+void filp_close(struct file *fp, int misc)
+{
+ if(fp->fp != _UPI_NULL_)
+ {
+ fclose(fp->fp);
+ }
+}
+
+/**
+ * @brief vfs_write
+ *
+ * Write data to binary file
+ *
+ * @para fp address of struct file
+ * @para data address of data to be written
+ * @para size size to be written
+ * @para pos start position in the file
+ * @return size be written
+ */
+size_t vfs_write(struct file *fp, char *data, int size, loff_t *pos)
+{
+ return (fwrite(data, sizeof(char), size, fp->fp));
+}
+
+/**
+ * @brief vfs_read
+ *
+ * Read data from binary file
+ *
+ * @para fp address of struct file
+ * @para data address of data to be read
+ * @para size size to be read
+ * @para pos start position in the file
+ * @return size be read
+ */
+size_t vfs_read(struct file *fp, char *data, int size, loff_t *pos)
+{
+ return (fread(data, sizeof(char), size, fp->fp));
+}
+
+/**
+ * @brief IS_ERR
+ *
+ * Check file is opened or not
+ *
+ * @para fp address of struct file
+ * @return _UPI_TRUE_ if file is opened
+ */
+_upi_bool_ IS_ERR(struct file *fp)
+{
+ return ((fp->fp == _UPI_NULL_) ? _UPI_TRUE_ : _UPI_FALSE_);
+}
+
+/**
+ * @brief get_fs
+ *
+ * Dummy function
+ *
+ * @return 0
+ */
+mm_segment_t get_fs(void)
+{
+ return (0);
+}
+
+/**
+ * @brief set_fs
+ *
+ * Dummy function
+ *
+ * @para value mm_segment_t value
+ * @return _UPI_NULL_
+ */
+void set_fs(mm_segment_t value)
+{
+}
+
+/**
+ * @brief get_ds
+ *
+ * Dummy function
+ *
+ * @return 0
+ */
+mm_segment_t get_ds(void)
+{
+ return (0);
+}
+
+#endif ///< end of uG31xx_OS_WINDOWS
+
+/**
+ * @brief ISFileExist
+ *
+ * Check file existed or not
+ *
+ * @para data address of BackupDataType
+ * @return BACKUP_BOOL_TRUE if file existed
+ */
+_backup_bool_ ISFileExist(BackupDataType *data)
+{
+ struct file *fp;
+
+ fp = filp_open(data->backupFileName, O_RDONLY, 0);
+ if(IS_ERR(fp))
+ {
+ UG31_LOGI("[%s]: Backup file is not existed\n", __func__);
+ return (BACKUP_BOOL_FALSE);
+ }
+ UG31_LOGI("[%s]: Backup file is existed\n", __func__);
+ filp_close(fp, _UPI_NULL_);
+ return (BACKUP_BOOL_TRUE);
+}
+
+/**
+ * @brief WriteFile
+ *
+ * Write data to file
+ *
+ * @para data address of BackupDataType
+ * @para fp address of struct fp
+ */
+void WriteFile(BackupDataType *data, struct file *fp)
+{
+ mm_segment_t oldFS;
+ loff_t pos;
+ size_t size;
+
+ oldFS = get_fs();
+ set_fs(get_ds());
+
+ pos = 0;
+ size = vfs_write(fp, (char *)data->capData->encriptTable, sizeof(data->capData->encriptTable), &pos);
+ UG31_LOGI("[%s]: Write table %d bytes\n", __func__, size);
+ size = vfs_write(fp, (char *)(&data->sysData->rmFromIC), sizeof(data->sysData->rmFromIC), &pos);
+ UG31_LOGI("[%s]: Write RM (%d) %d bytes\n", __func__, data->sysData->rmFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->fccFromIC), sizeof(data->sysData->fccFromIC), &pos);
+ UG31_LOGI("[%s]: Write FCC (%d) %d bytes\n", __func__, data->sysData->fccFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->timeTagFromIC), sizeof(data->sysData->timeTagFromIC), &pos);
+ UG31_LOGI("[%s]: Write Time Tag (%d) %d bytes\n", __func__, data->sysData->timeTagFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->tableUpdateIdxFromIC), sizeof(data->sysData->tableUpdateIdxFromIC), &pos);
+ UG31_LOGI("[%s]: Write Table Update Index (%d) %d bytes\n", __func__, data->sysData->tableUpdateIdxFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->deltaCapFromIC), sizeof(data->sysData->deltaCapFromIC), &pos);
+ UG31_LOGI("[%s]: Write Delta Capacity (%d) %d bytes\n", __func__, data->sysData->deltaCapFromIC, size);
+ size = vfs_write(fp, (char *)(&data->sysData->adc1ConvTime), sizeof(data->sysData->adc1ConvTime), &pos);
+ UG31_LOGI("[%s]: Write ADC1 Conversion Time (%d) %d bytes\n", __func__, data->sysData->adc1ConvTime, size);
+ size = vfs_write(fp, (char *)(&data->sysData->rsocFromIC), sizeof(data->sysData->rsocFromIC), &pos);
+ UG31_LOGI("[%s]: Write RSOC (%d) %d bytes\n", __func__, data->sysData->rsocFromIC, size);
+ size = vfs_write(fp, (char *)(&data->targetFileVer), sizeof(_backup_u32_), &pos);
+ UG31_LOGI("[%s]: Write version (%d) %d bytes\n", __func__, data->targetFileVer, size);
+
+ set_fs(oldFS);
+}
+
+/**
+ * @brief CreateBackupFile
+ *
+ * Create backup file in system
+ *
+ * @para data address of BackupDataType
+ * @return BACKUP_BOOL_TRUE if success
+ */
+_backup_bool_ CreateBackupFile(BackupDataType *data)
+{
+ struct file *fp;
+
+ fp = filp_open(data->backupFileName, O_CREAT | O_RDWR, 0);
+ if(IS_ERR(fp))
+ {
+ UG31_LOGI("[%s]: Create backup file fail\n", __func__);
+ return (BACKUP_BOOL_FALSE);
+ }
+
+ /// [AT-PM] : Write data to file ; 02/21/2013
+ WriteFile(data, fp);
+
+ filp_close(fp, _UPI_NULL_);
+ return (BACKUP_BOOL_TRUE);
+}
+
+/**
+ * @brief ReadFile
+ *
+ * Read data from file
+ *
+ * @para data address of BackupDataType
+ * @para fp address of struct fp
+ */
+void ReadFile(BackupDataType *data, struct file *fp)
+{
+ mm_segment_t oldFS;
+ loff_t pos;
+ size_t size;
+
+ oldFS = get_fs();
+ set_fs(get_ds());
+
+ pos = 0;
+ size = vfs_read(fp, (char *)&data->capData->encriptTable, sizeof(data->capData->encriptTable), &pos);
+ UG31_LOGI("[%s]: Read table %d bytes\n", __func__, size);
+ size = vfs_read(fp, (char *)(&data->sysData->rmFromIC), sizeof(data->sysData->rmFromIC), &pos);
+ UG31_LOGI("[%s]: Read RM (%d) %d bytes\n", __func__, data->sysData->rmFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->fccFromIC), sizeof(data->sysData->fccFromIC), &pos);
+ UG31_LOGI("[%s]: Read FCC (%d) %d bytes\n", __func__, data->sysData->fccFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->timeTagFromIC), sizeof(data->sysData->timeTagFromIC), &pos);
+ UG31_LOGI("[%s]: Read Time Tag (%d) %d bytes\n", __func__, data->sysData->timeTagFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->tableUpdateIdxFromIC), sizeof(data->sysData->tableUpdateIdxFromIC), &pos);
+ UG31_LOGI("[%s]: Read Table Update Index (%d) %d bytes\n", __func__, data->sysData->tableUpdateIdxFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->deltaCapFromIC), sizeof(data->sysData->deltaCapFromIC), &pos);
+ UG31_LOGI("[%s]: Read Delta Capacity (%d) %d bytes\n", __func__, data->sysData->deltaCapFromIC, size);
+ size = vfs_read(fp, (char *)(&data->sysData->adc1ConvTime), sizeof(data->sysData->adc1ConvTime), &pos);
+ UG31_LOGI("[%s]: Read ADC1 Conversion Time (%d) %d bytes\n", __func__, data->sysData->adc1ConvTime, size);
+ size = vfs_read(fp, (char *)(&data->sysData->rsocFromIC), sizeof(data->sysData->rsocFromIC), &pos);
+ UG31_LOGI("[%s]: Read RSOC (%d) %d bytes\n", __func__, data->sysData->rsocFromIC, size);
+ size = vfs_read(fp, (char *)(&data->backupFileVer), sizeof(_backup_u32_), &pos);
+ UG31_LOGI("[%s]: Read version (%d) %d bytes\n", __func__, data->backupFileVer, size);
+
+ set_fs(oldFS);
+}
+
+/**
+ * @brief CheckBackupFile
+ *
+ * Check backup file is consisted with IC or not
+ *
+ * @para data address of BackupDataType
+ * @return BACKUP_BOOL_TRUE if success
+ */
+_backup_bool_ CheckBackupFile(BackupDataType *data)
+{
+ struct file *fp;
+ CapacityDataType *bufCapData;
+ SystemDataType *bufSysData;
+
+ fp = filp_open(data->backupFileName, O_RDWR, 0);
+ if(IS_ERR(fp))
+ {
+ UG31_LOGI("[%s]: Open backup file fail\n", __func__);
+ return (BACKUP_BOOL_FALSE);
+ }
+
+ /// [AT-PM] : Create buffer ; 02/21/2013
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ bufCapData = (CapacityDataType *)malloc(sizeof(CapacityDataType));
+ bufSysData = (SystemDataType *)malloc(sizeof(SystemDataType));
+ #else ///< else of uG31xx_BOOT_LOADER
+ bufCapData = (CapacityDataType *)kmalloc(sizeof(CapacityDataType), GFP_KERNEL);
+ bufSysData = (SystemDataType *)kmalloc(sizeof(SystemDataType), GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ bufCapData = (CapacityDataType *)malloc(sizeof(CapacityDataType));
+ bufSysData = (SystemDataType *)malloc(sizeof(SystemDataType));
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ memcpy(bufCapData, data->capData, sizeof(CapacityDataType));
+ memcpy(bufSysData, data->sysData, sizeof(SystemDataType));
+
+ /// [AT-PM] : Get data from file ; 02/21/2013
+ ReadFile(data, fp);
+
+ /// [AT-PM] : Following information is not checked ; 02/21/2013
+ data->sysData->rmFromIC = bufSysData->rmFromIC;
+ data->sysData->timeTagFromIC = bufSysData->timeTagFromIC;
+ data->sysData->deltaCapFromIC = bufSysData->deltaCapFromIC;
+ data->sysData->adc1ConvTime = bufSysData->adc1ConvTime;
+
+ /// [AT-PM] : Check data ; 02/21/2013
+ if((memcmp(bufCapData, data->capData, sizeof(CapacityDataType)) != 0) ||
+ (memcmp(bufSysData, data->sysData, sizeof(SystemDataType)) != 0) ||
+ (data->backupFileVer != data->targetFileVer))
+ {
+ UG31_LOGI("[%s]: Backup file needs to be updated\n", __func__);
+ /// [AT-PM] : Write data to file ; 02/21/2013
+ memcpy(data->capData, bufCapData, sizeof(CapacityDataType));
+ memcpy(data->sysData, bufSysData, sizeof(SystemDataType));
+ WriteFile(data, fp);
+ }
+
+ filp_close(fp, _UPI_NULL_);
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(bufCapData);
+ free(bufSysData);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(bufCapData);
+ kfree(bufSysData);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(bufCapData);
+ free(bufSysData);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ return (BACKUP_BOOL_TRUE);
+}
+
+/**
+ * @brief WriteSuspendBackupData
+ *
+ * Write suspend backup data to file
+ *
+ * @para data address of BackupDataType
+ * @return BACKUP_BOOL_TRUE if success
+ */
+_backup_bool_ WriteSuspendBackupData(BackupDataType *data)
+{
+ struct file *fp;
+ _backup_u8_ idx;
+ mm_segment_t oldFS;
+ loff_t pos;
+ size_t size;
+ _backup_u8_ *buf;
+
+ /// [AT-PM] : Check backup file content ; 10/12/2013
+ fp = filp_open("/sdcard/upi_table", O_RDWR, 0);
+ if(!IS_ERR(fp))
+ {
+ oldFS = get_fs();
+ set_fs(get_ds());
+
+ idx = 0;
+ pos = 0;
+ buf = (_backup_u8_ *)kmalloc(sizeof(BackupSuspendDataType), GFP_KERNEL);
+ while(idx < BACKUP_MAX_LOG_SUSPEND_DATA)
+ {
+ size = vfs_read(fp, (char *)buf, sizeof(BackupSuspendDataType), &pos);
+
+ if(memcmp((char *)buf, (char *)data->logData[idx], sizeof(BackupSuspendDataType)) != 0)
+ {
+ UG31_LOGI("[%s]: Need to write suspend data to file (%d)\n", __func__, idx);
+ break;
+ }
+
+ idx = idx + 1;
+ }
+ kfree(buf);
+
+ set_fs(oldFS);
+
+ filp_close(fp, _UPI_NULL_);
+
+ if(idx >= BACKUP_MAX_LOG_SUSPEND_DATA)
+ {
+ UG31_LOGI("[%s]: No need to update suspend data.\n", __func__, idx);
+ return (BACKUP_BOOL_TRUE);
+ }
+ }
+
+ fp = filp_open("/sdcard/upi_table", O_CREAT | O_RDWR, 0);
+ if(IS_ERR(fp))
+ {
+ UG31_LOGI("[%s]: Open backup file fail\n", __func__);
+ return (BACKUP_BOOL_FALSE);
+ }
+
+ oldFS = get_fs();
+ set_fs(get_ds());
+
+ idx = 0;
+ pos = 0;
+ while(idx < BACKUP_MAX_LOG_SUSPEND_DATA)
+ {
+ size = vfs_write(fp, (char *)data->logData[idx], sizeof(BackupSuspendDataType), &pos);
+ UG31_LOGI("[%s]: Write suspend data %d with %d bytes\n", __func__, idx, size);
+
+ idx = idx + 1;
+ }
+
+ set_fs(oldFS);
+
+ filp_close(fp, _UPI_NULL_);
+ return (BACKUP_BOOL_TRUE);
+}
+
+/// =============================================
+/// Extern Function Region
+/// =============================================
+
+#define RETRY_CHECKING_THRESHOLD (CONST_CONVERSION_COUNT_THRESHOLD)
+
+/**
+ * @brief UpiBackupData
+ *
+ * Backup data from IC to system routine
+ *
+ * @para data address of BackupDataType
+ * @return _UPI_NULL_
+ */
+void UpiBackupData(BackupDataType *data)
+{
+ _backup_bool_ rtnBool;
+
+ #ifndef UG31XX_BACKUP_FILE_ENABLE
+ rtnBool = _UPI_TRUE_;
+ data->backupFileSts = BACKUP_FILE_STS_EXIST;
+ #endif ///< end of UG31XX_BACKUP_FILE_ENABLE
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ data->backupFileName = (char *)malloc(strlen(BACKUP_FILE_PATH));
+ #else ///< else of uG31xx_BOOT_LOADER
+ data->backupFileName = (char *)kmalloc(strlen(BACKUP_FILE_PATH), GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ sprintf(data->backupFileName, "%s", BACKUP_FILE_PATH);
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ data->backupFileName = data->sysData->backupFileName;
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+
+ switch(data->backupFileSts)
+ {
+ case BACKUP_FILE_STS_CHECKING:
+ /// [AT-PM] : Check backup file existed or not ; 02/21/2013
+ rtnBool = ISFileExist(data);
+ if(rtnBool == BACKUP_BOOL_TRUE)
+ {
+ #ifdef BACKUP_FILE_STS_NOT_EXIST
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ #else ///< else of BACKUP_FILE_STS_NOT_EXIST
+ data->backupFileSts = BACKUP_FILE_STS_EXIST;
+ #endif ///< end of BACKUP_FILE_STS_NOT_EXIST
+ }
+ else
+ {
+ if(data->measData->lastCounter > RETRY_CHECKING_THRESHOLD)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ }
+ }
+ break;
+ case BACKUP_FILE_STS_NOT_EXIST:
+ /// [AT-PM] : Create backup file ; 02/21/2013
+ rtnBool = CreateBackupFile(data);
+ if(rtnBool == BACKUP_BOOL_TRUE)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_EXIST;
+ }
+ break;
+ case BACKUP_FILE_STS_EXIST:
+ rtnBool = CheckBackupFile(data);
+ if(rtnBool != BACKUP_BOOL_TRUE)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ }
+ else
+ {
+ data->backupFileSts = BACKUP_FILE_STS_COMPARE;
+ }
+ break;
+ case BACKUP_FILE_STS_COMPARE:
+ if(data->icDataAvailable == BACKUP_BOOL_TRUE)
+ {
+ /// [AT-PM] : Check content of file is consist with IC or not ; 02/21/2013
+ rtnBool = CheckBackupFile(data);
+ if(rtnBool != BACKUP_BOOL_TRUE)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ }
+ rtnBool = WriteSuspendBackupData(data);
+ if(rtnBool != BACKUP_BOOL_TRUE)
+ {
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ }
+ }
+ else
+ {
+ data->backupFileSts = BACKUP_FILE_STS_CHECKING;
+ }
+ break;
+ default:
+ /// [AT-PM] : Un-known state ; 02/21/2013
+ data->backupFileSts = BACKUP_FILE_STS_NOT_EXIST;
+ break;
+ }
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(data->backupFileName);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(data->backupFileName);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+
+ UG31_LOGI("[%s]: Backp file status = %d\n", __func__, data->backupFileSts);
+}
+
+/**
+ * @brief UpiUpdateSuspendData
+ *
+ * Update data for suspend backup
+ *
+ * @para data address of BackupDataType
+ * @para is_resume UPI_TRUE if called in resume
+ * @return NULL
+ */
+void UpiUpdateSuspendData(BackupDataType *data, _backup_bool_ is_resume)
+{
+ if(is_resume == _UPI_TRUE_)
+ {
+ data->logData[data->backupDataIdx]->afterCapData = *(data->capData);
+ data->logData[data->backupDataIdx]->afterMeasData = *(data->measData);
+ UG31_LOGI("[%s]: Backup resume data to buffer %d\n", __func__, data->backupDataIdx);
+
+ data->backupDataIdx = data->backupDataIdx + 1;
+ if(data->backupDataIdx >= BACKUP_MAX_LOG_SUSPEND_DATA)
+ {
+ data->backupDataIdx = BACKUP_MAX_LOG_SUSPEND_DATA;
+ }
+ }
+ else
+ {
+ if((data->backupDataIdx >= BACKUP_MAX_LOG_SUSPEND_DATA) && (is_resume == _UPI_FALSE_))
+ {
+ data->backupDataIdx = 0;
+ while(data->backupDataIdx < (BACKUP_MAX_LOG_SUSPEND_DATA - 1))
+ {
+ memcpy(data->logData[data->backupDataIdx], data->logData[data->backupDataIdx + 1], sizeof(BackupSuspendDataType));
+ data->backupDataIdx = data->backupDataIdx + 1;
+ }
+ }
+
+ data->logData[data->backupDataIdx]->beforeCapData = *(data->capData);
+ data->logData[data->backupDataIdx]->beforeMeasData = *(data->measData);
+ UG31_LOGI("[%s]: Backup suspend data to buffer %d\n", __func__, data->backupDataIdx);
+ }
+}
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.h
new file mode 100755
index 00000000..03564f3f
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Backup.h
@@ -0,0 +1,79 @@
+/**
+ * @filename uG31xx_API_Backup.h
+ *
+ * Header of uG31xx_API_Backup.cpp
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#define BACKUP_BOOL_TRUE (1)
+#define BACKUP_BOOL_FALSE (0)
+
+#define BACKUP_FILE_PATH ("/sdcard/upi_gg")
+
+enum BACKUP_FILE_STS {
+ BACKUP_FILE_STS_CHECKING = 0,
+ BACKUP_FILE_STS_NOT_EXIST,
+ BACKUP_FILE_STS_EXIST,
+ BACKUP_FILE_STS_COMPARE,
+};
+
+typedef unsigned char _backup_bool_;
+typedef unsigned char _backup_u8_;
+typedef unsigned long _backup_u32_;
+
+typedef struct BackupSuspendDataST {
+ CapacityDataType beforeCapData;
+ MeasDataType beforeMeasData;
+
+ CapacityDataType afterCapData;
+ MeasDataType afterMeasData;
+} __attribute__ ((packed)) BackupSuspendDataType;
+
+#define BACKUP_MAX_LOG_SUSPEND_DATA (8)
+
+typedef struct BackupDataST {
+
+ CapacityDataType *capData;
+ SystemDataType *sysData;
+ MeasDataType *measData;
+
+ _backup_bool_ icDataAvailable;
+ _backup_u8_ backupFileSts;
+ _backup_u32_ backupFileVer;
+ _backup_u32_ targetFileVer;
+
+ _backup_u8_ backupDataIdx;
+ BackupSuspendDataType *logData[BACKUP_MAX_LOG_SUSPEND_DATA];
+
+ #if defined (uG31xx_OS_WINDOWS)
+ const wchar_t* backupFileName;
+ #elif defined(uG31xx_OS_ANDROID)
+ char *backupFileName;
+ #endif
+
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) BackupDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } BackupDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiBackupData
+ *
+ * Backup data from IC to system routine
+ *
+ * @para data address of BackupDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiBackupData(BackupDataType *data);
+
+/**
+ * @brief UpiUpdateSuspendData
+ *
+ * Update data for suspend backup
+ *
+ * @para data address of BackupDataType
+ * @return NULL
+ */
+extern void UpiUpdateSuspendData(BackupDataType *data, _backup_bool_ is_resume);
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.b b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.b
new file mode 100755
index 00000000..52317121
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.b
Binary files differ
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.h
new file mode 100755
index 00000000..a45ea7b4
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Capacity.h
@@ -0,0 +1,143 @@
+/**
+ * @filename uG31xx_API_Capacity.h
+ *
+ * Header of uG31xx capacity algorithm
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+typedef _upi_bool_ _cap_bool_;
+typedef unsigned char _cap_u8_;
+typedef signed char _cap_s8_;
+typedef unsigned short _cap_u16_;
+typedef signed short _cap_s16_;
+typedef unsigned long _cap_u32_;
+typedef signed long _cap_s32_;
+
+#define CAP_FC_RELEASE_RSOC (99)
+#define UG31XX_CAP_VERSION (36)
+#define CAP_ENCRIPT_TABLE_SIZE (TEMPERATURE_NUMS*C_RATE_NUMS*(SOV_NUMS - 1))
+
+typedef struct CapacityDataST {
+
+ /// [AT-PM] : Data from GGB file ; 01/25/2013
+ CELL_PARAMETER *ggbParameter;
+ CELL_TABLE *ggbTable;
+
+ /// [AT-PM] : Measurement data ; 01/25/2013
+ MeasDataType *measurement;
+
+ /// [AT-PM] : Data for table backup ; 01/31/2013
+ TableBackupType tableBackup[SOV_NUMS];
+ _cap_u8_ encriptTable[CAP_ENCRIPT_TABLE_SIZE];
+
+ /// [AT-PM] : Capacity information ; 01/25/2013
+ _cap_u16_ rm;
+ _cap_u16_ fcc;
+ _cap_u16_ fccBackup;
+ _cap_u8_ rsoc;
+
+ /// [AT-PM] : Capacity operation variables ; 01/25/2013
+ _cap_u32_ status;
+
+ _cap_u32_ selfDsgMilliSec;
+ _cap_u8_ selfDsgSec;
+ _cap_u8_ selfDsgMin;
+ _cap_u8_ selfDsgHour;
+ _cap_u8_ selfDsgResidual;
+
+ _cap_u8_ lastRsoc;
+
+ _cap_u32_ tpTime;
+
+ _cap_s32_ dsgCharge;
+ _cap_s32_ dsgChargeStart;
+ _cap_u32_ dsgChargeTime;
+ _cap_s32_ preDsgCharge;
+
+ _cap_u8_ tableUpdateIdx;
+ _cap_u32_ tableUpdateDisqTime;
+ _cap_u8_ tableUpdateDelayCnt;
+
+ _cap_s8_ parseRMResidual;
+
+ _cap_s16_ reverseCap;
+ _cap_u8_ avgCRate;
+
+ _cap_s16_ ccRecord[SOV_NUMS];
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) CapacityDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } CapacityDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiInitCapacity
+ *
+ * Initial capacity algorithm
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiInitCapacity(CapacityDataType *data);
+
+/**
+ * @brief UpiReadCapacity
+ *
+ * Read capacity information
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiReadCapacity(CapacityDataType *data);
+
+/**
+ * @brief UpiTableCapacity
+ *
+ * Look up capacity from table
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiTableCapacity(CapacityDataType *data);
+
+/**
+ * @brief UpiInitDsgCharge
+ *
+ * Initialize data->dsgCharge value
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiInitDsgCharge(CapacityDataType *data);
+
+/**
+ * @brief UpiInitNacTable
+ *
+ * Initialize NAC table
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiInitNacTable(CapacityDataType *data);
+
+/**
+ * @brief UpiSaveNacTable
+ *
+ * Save NAC table to IC
+ *
+ * @para data address of CapacityDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiSaveNacTable(CapacityDataType *data);
+
+/**
+ * @brief CalculateRsoc
+ *
+ * RSOC = RM x 100 / FCC
+ *
+ * @para rm remaining capacity
+ * @para fcc full charged capacity
+ * @return relative state of charge
+ */
+extern _cap_u8_ CalculateRsoc(_cap_u16_ rm, _cap_u16_ fcc);
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.c
new file mode 100755
index 00000000..29f5072c
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.c
@@ -0,0 +1,1389 @@
+/**
+ * @filename uG31xx_API_Measurement.cpp
+ *
+ * guG31xx measurement API
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+//#define MEAS_FAKE_INT_TEMP
+#ifdef MEAS_FAKE_INT_TEMP
+ #define MEAS_FAKE_INT_TEMP_OFFSET (200)
+#endif ///< end of MEAS_FAKE_INT_TEMP
+
+typedef struct MeasDataInternalST {
+
+ MeasDataType *info;
+
+ _meas_s16_ adc1CodeT25V100;
+ _meas_s16_ adc1CodeT25V200;
+ _meas_s16_ adc1CodeT80V100;
+ _meas_s16_ adc1CodeT80V200;
+
+ _meas_s16_ adc2CodeT25V100;
+ _meas_s16_ adc2CodeT25V200;
+ _meas_s16_ adc2CodeT80V100;
+ _meas_s16_ adc2CodeT80V200;
+
+ _meas_u32_ currTime;
+
+ _meas_u16_ codeBat1;
+ _meas_s16_ codeCurrent;
+ _meas_u16_ codeIntTemperature;
+ _meas_u16_ codeExtTemperature;
+ _meas_s16_ codeCharge;
+ _meas_u16_ codeCounter;
+ _meas_s16_ ccOffset;
+
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) MeasDataInternalType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } MeasDataInternalType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct AdcDeltaCodeMappingST {
+ _meas_s32_ Adc1V100;
+ _meas_s32_ Adc1V200;
+ _meas_s32_ Adc2V100;
+ _meas_s32_ Adc2V200;
+#if defined(uG31xx_OS_ANDROID)
+}__attribute__((packed)) AdcDeltaCodeMappingType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} AdcDeltaCodeMappingType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+static AdcDeltaCodeMappingType AdcDeltaCodeMapping[] =
+{
+ { -12800, -25600, 1536, 0 }, ///< Index = 0
+ { -12544, -25088, 1600, 128 }, ///< Index = 1
+ { -13056, -26112, 1472, -128 }, ///< Index = 2
+ { -12288, -24576, 1664, 256 }, ///< Index = 3
+ { -13312, -26624, 1408, -256 }, ///< Index = 4
+ { -12032, -24064, 1728, 384 }, ///< Index = 5
+ { -13568, -27136, 1344, -384 }, ///< Index = 6
+ { -11776, -23552, 1792, 512 }, ///< Index = 7
+ { -13824, -27648, 1280, -512 }, ///< Index = 8
+ { -11520, -23040, 1856, 640 }, ///< Index = 9
+ { -14080, -28160, 1216, -640 }, ///< Index = 10
+ { -11264, -22528, 1920, 768 }, ///< Index = 11
+ { -14336, -28672, 1152, -768 }, ///< Index = 12
+ { -11008, -22016, 1984, 896 }, ///< Index = 13
+ { -14592, -29184, 1088, -896 }, ///< Index = 14
+ { -10752, -21504, 2048, 1024 }, ///< Index = 15
+ { -14848, -29696, 1024, -1024 }, ///< Index = 16
+ { -10496, -20992, 2112, 1152 }, ///< Index = 17
+ { -15104, -30208, 960, -1152 }, ///< Index = 18
+ { -10240, -20480, 2176, 1280 }, ///< Index = 19
+ { -15360, -30720, 896, -1280 }, ///< Index = 20
+ { -9984, -19968, 2240, 1408 }, ///< Index = 21
+ { -15616, -31232, 832, -1408 }, ///< Index = 22
+ { -9728, -19456, 2304, 1536 }, ///< Index = 23
+ { -15872, -31744, 768, -1536 }, ///< Index = 24
+ { -9472, -18944, 2368, 1664 }, ///< Index = 25
+ { -16128, -32256, 704, -1664 }, ///< Index = 26
+ { -9216, -18432, 2432, 1792 }, ///< Index = 27
+ { -16384, -32768, 640, -1792 }, ///< Index = 28
+ { -8960, -17920, 2496, 1920 }, ///< Index = 29
+ { -16640, -33280, 576, -1920 }, ///< Index = 30
+ { 0, 0, 0, 0 }, ///< Index = 31
+};
+
+#define ADC_TEMPERATURE_GAIN_CONST (1000)
+
+#define ADC1_CODE_100MV_NEGATIVE (0xFF00)
+#define ADC1_CODE_200MV_NEGATIVE (0xFE00)
+#define ADC1_CP_CODE_25_100MV (12288)
+#define ADC1_CP_CODE_25_200MV (24576)
+#define ADC1_DELTA_CODE_25_100MV_SIGN_BIT (1<<8)
+#define ADC1_DELTA_CODE_25_200MV_SIGN_BIT (1<<9)
+#define ADC1_TEMPERATURE_GAIN_100MV (869600)
+#define ADC1_TEMPERATURE_GAIN_200MV (-695680)
+
+/**
+ * @brief ConvertAdc1Data
+ *
+ * Convert ADC1 data from OTP
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertAdc1Data(MeasDataInternalType *obj)
+{
+ _meas_u16_ tmp16;
+ _meas_s32_ tmp32;
+
+ /// [AT-PM] : Get code T25 100mV ; 01/23/2013
+ tmp16 = obj->info->otp->adc1DeltaCodeT25V100;
+ if(tmp16 & ADC1_DELTA_CODE_25_100MV_SIGN_BIT)
+ {
+ tmp16 = tmp16 & (~ADC1_DELTA_CODE_25_100MV_SIGN_BIT);
+ if(tmp16 != 0)
+ {
+ tmp16 = tmp16 + ADC1_CODE_100MV_NEGATIVE;
+ }
+ }
+ tmp16 = tmp16 + ADC1_CP_CODE_25_100MV;
+ tmp32 = (_meas_s32_)(_meas_s16_)tmp16;
+ tmp32 = tmp32 + AdcDeltaCodeMapping[obj->info->otp->indexAdc1V100T25].Adc1V100;
+ obj->adc1CodeT25V100 = (_meas_s16_)tmp32;
+
+ /// [AT-PM] : Get code T25 200mV ; 01/23/2013
+ tmp16 = obj->info->otp->adc1DeltaCodeT25V200;
+ if(tmp16 & ADC1_DELTA_CODE_25_200MV_SIGN_BIT)
+ {
+ tmp16 = tmp16 & (~ADC1_DELTA_CODE_25_200MV_SIGN_BIT);
+ if(tmp16 != 0)
+ {
+ tmp16 = tmp16 + ADC1_CODE_200MV_NEGATIVE;
+ }
+ }
+ tmp16 = tmp16 + ADC1_CP_CODE_25_200MV;
+ tmp32 = (_meas_s32_)(_meas_s16_)tmp16;
+ tmp32 = tmp32 + AdcDeltaCodeMapping[obj->info->otp->indexAdc1V200T25].Adc1V200;
+ obj->adc1CodeT25V200 = (_meas_s16_)tmp32;
+
+ tmp32 = (_meas_s32_)obj->info->otp->aveIT80;
+ tmp32 = (tmp32 - obj->info->otp->aveIT25)*ADC_TEMPERATURE_GAIN_CONST;
+
+ /// [AT-PM] : Get code T80 100mV ; 01/23/2013
+ obj->adc1CodeT80V100 = (_meas_s16_)(tmp32/ADC1_TEMPERATURE_GAIN_100MV + obj->adc1CodeT25V100);
+
+ /// [AT-PM] : Get code T80 200mV ; 01/23/2013
+ obj->adc1CodeT80V200 = (_meas_s16_)(tmp32/ADC1_TEMPERATURE_GAIN_200MV + obj->adc1CodeT25V200);
+}
+
+#define ADC2_CODE_100MV_NEGATIVE (0xFFC0)
+#define ADC2_CODE_200MV_NEGATIVE (0xFF80)
+#define ADC2_CP_CODE_25_100MV (3072)
+#define ADC2_CP_CODE_25_200MV (6144)
+#define ADC2_DELTA_CODE_25_100MV_SIGN_BIT (1<<6)
+#define ADC2_DELTA_CODE_25_200MV_SIGN_BIT (1<<7)
+#define ADC2_TEMPERATURE_GAIN_100MV (-149130)
+#define ADC2_TEMPERATURE_GAIN_200MV (-136937)
+
+/**
+ * @brief ConvertAdc2Data
+ *
+ * Convert ADC2 data from OTP
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertAdc2Data(MeasDataInternalType *obj)
+{
+ _meas_u16_ tmp16;
+ _meas_s32_ tmp32;
+
+ /// [AT-PM] : Get code T25 100mV ; 01/23/2013
+ tmp16 = obj->info->otp->adc2DeltaCodeT25V100;
+ if(tmp16 & ADC2_DELTA_CODE_25_100MV_SIGN_BIT)
+ {
+ tmp16 = tmp16 & (~ADC2_DELTA_CODE_25_100MV_SIGN_BIT);
+ tmp16 = tmp16 + ADC2_CODE_100MV_NEGATIVE;
+ }
+ tmp16 = tmp16 + ADC2_CP_CODE_25_100MV;
+ tmp32 = (_meas_s32_)(_meas_s16_)tmp16;
+ tmp32 = tmp32 + AdcDeltaCodeMapping[obj->info->otp->indexAdc2V100T25].Adc2V100;
+ obj->adc2CodeT25V100 = (_meas_s16_)tmp32;
+
+ /// [AT-PM] : Get code T25 200mV ; 01/23/2013
+ tmp16 = obj->info->otp->adc2DeltaCodeT25V200;
+ if(tmp16 & ADC2_DELTA_CODE_25_200MV_SIGN_BIT)
+ {
+ tmp16 = tmp16 & (~ADC2_DELTA_CODE_25_200MV_SIGN_BIT);
+ tmp16 = tmp16 + ADC2_CODE_200MV_NEGATIVE;
+ }
+ tmp16 = tmp16 + ADC2_CP_CODE_25_200MV;
+ tmp32 = (_meas_s32_)(_meas_s16_)tmp16;
+ tmp32 = tmp32 + AdcDeltaCodeMapping[obj->info->otp->indexAdc2V200T25].Adc2V200;
+ obj->adc2CodeT25V200 = (_meas_s16_)tmp32;
+
+ tmp32 = (_meas_s32_)obj->info->otp->aveIT80;
+ tmp32 = (tmp32 - obj->info->otp->aveIT25)*ADC_TEMPERATURE_GAIN_CONST;
+
+ /// [AT-PM] : Get code T80 100mV ; 01/23/2013
+ obj->adc2CodeT80V100 = (_meas_s16_)(tmp32/ADC2_TEMPERATURE_GAIN_100MV + obj->adc2CodeT25V100);
+
+ /// [AT-PM] : Get code T80 200mV ; 01/23/2013
+ obj->adc2CodeT80V200 = (_meas_s16_)(tmp32/ADC2_TEMPERATURE_GAIN_200MV + obj->adc2CodeT25V200);
+}
+
+/**
+ * @brief CalAdc1Factors
+ *
+ * Calculate ADC1 gain slope and factor B
+ * Calculate ADC1 offset slope and factor O
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void CalAdc1Factors(MeasDataInternalType *obj)
+{
+ _meas_s32_ delta25;
+ _meas_s32_ delta80;
+ _meas_s64_ tmp64;
+
+ /// [AT-PM] : Calculate gain slope and factor B ; 01/23/2013
+ delta25 = (_meas_s32_)obj->adc1CodeT25V200;
+ delta25 = delta25 - obj->adc1CodeT25V100;
+ delta80 = (_meas_s32_)obj->adc1CodeT80V200;
+ delta80 = delta80 - obj->adc1CodeT80V100;
+
+ obj->info->adc1GainSlope = delta80 - delta25;
+ obj->info->adc1GainFactorB = delta25*(obj->info->otp->aveIT80) - delta80*(obj->info->otp->aveIT25);
+
+ /// [AT-PM] : Calculate offset slope and factor O ; 01/23/2013
+ delta25 = (_meas_s32_)obj->adc1CodeT25V100;
+ delta25 = delta25*2 - obj->adc1CodeT25V200;
+ delta80 = (_meas_s32_)obj->adc1CodeT80V100;
+ delta80 = delta80*2 - obj->adc1CodeT80V200;
+
+ obj->info->adc1OffsetSlope = delta80 - delta25;
+ obj->info->adc1OffsetFactorO = delta25*(obj->info->otp->aveIT80) - delta80*(obj->info->otp->aveIT25);
+
+ /// [AT-PM] : Calculate current ADC1 gain ; 01/23/2013
+ tmp64 = (_meas_s64_)obj->info->adc1GainSlope;
+ tmp64 = tmp64*(obj->codeIntTemperature) + obj->info->adc1GainFactorB;
+ obj->info->adc1Gain = (_meas_s32_)tmp64;
+
+ /// [AT-PM] : Calculate current ADC1 offset ; 01/23/2013
+ tmp64 = (_meas_s64_)obj->info->adc1OffsetSlope;
+ tmp64 = tmp64*(obj->codeIntTemperature) + obj->info->adc1OffsetFactorO;
+ obj->info->adc1Offset = (_meas_s32_)tmp64;
+}
+
+/**
+ * @brief CalAdc2Factors
+ *
+ * Calculate ADC2 gain slope and factor B
+ * Calculate ADC2 offset slope and factor O
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void CalAdc2Factors(MeasDataInternalType *obj)
+{
+ _meas_s32_ delta25;
+ _meas_s32_ delta80;
+ _meas_s64_ tmp64;
+
+ /// [AT-PM] : Calculate gain slope and factor B ; 01/23/2013
+ delta25 = (_meas_s32_)obj->adc2CodeT25V200;
+ delta25 = delta25 - obj->adc2CodeT25V100;
+ delta80 = (_meas_s32_)obj->adc2CodeT80V200;
+ delta80 = delta80 - obj->adc2CodeT80V100;
+
+ obj->info->adc2GainSlope = delta80 - delta25;
+ obj->info->adc2GainFactorB = delta25*(obj->info->otp->aveIT80) - delta80*(obj->info->otp->aveIT25);
+
+ /// [AT-PM] : Calculate offset slope and factor O ; 01/23/2013
+ delta25 = (_meas_s32_)obj->adc2CodeT25V100;
+ delta25 = delta25*2 - obj->adc2CodeT25V200;
+ delta80 = (_meas_s32_)obj->adc2CodeT80V100;
+ delta80 = delta80*2 - obj->adc2CodeT80V200;
+
+ obj->info->adc2OffsetSlope = delta80 - delta25;
+ obj->info->adc2OffsetFactorO = delta25*(obj->info->otp->aveIT80) - delta80*(obj->info->otp->aveIT25);
+
+ /// [AT-PM] : Calculate current ADC1 gain ; 01/23/2013
+ tmp64 = (_meas_s64_)obj->info->adc2GainSlope;
+ tmp64 = tmp64*(obj->codeIntTemperature) + obj->info->adc2GainFactorB;
+ obj->info->adc2Gain = (_meas_s32_)tmp64;
+
+ /// [AT-PM] : Calculate current ADC1 offset ; 01/23/2013
+ tmp64 = (_meas_s64_)obj->info->adc2OffsetSlope;
+ tmp64 = tmp64*(obj->codeIntTemperature) + obj->info->adc2OffsetFactorO;
+ obj->info->adc2Offset = (_meas_s32_)tmp64;
+}
+
+#define ADC1_IDEAL_CODE_100MV (614)
+#define ADC1_IDEAL_CODE_200MV (1229)
+#define ADC1_IDEAL_CODE_DELTA (ADC1_IDEAL_CODE_200MV - ADC1_IDEAL_CODE_100MV)
+
+/**
+ * @brief CalibrateAdc1Code
+ *
+ * Calibrate ADC1 code
+ *
+ * @para obj address of MeasDataInternalType
+ * @para code ADC1 code to be calibrated
+ * @return calibrated code
+ */
+_meas_s32_ CalibrateAdc1Code(MeasDataInternalType *obj, _meas_s32_ code)
+{
+ _meas_s64_ tmp64;
+ _meas_s32_ tmp32;
+ _meas_s32_ deltaIT;
+ _meas_s32_ gain;
+ _meas_s32_ offset;
+
+ deltaIT = (_meas_s32_)obj->info->otp->aveIT80;
+ deltaIT = deltaIT - obj->info->otp->aveIT25;
+
+ /// [AT-PM] : Pre-operation to avoid 64-bit division ; 01/23/2013
+ gain = obj->info->adc1Gain;
+ offset = obj->info->adc1Offset;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)code;
+ tmp64 = tmp64*deltaIT - offset;
+ tmp64 = tmp64*ADC1_IDEAL_CODE_DELTA;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ code = code/2;
+ deltaIT = deltaIT/2;
+ gain = gain/4;
+ offset = offset/4;
+ }
+
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/gain;
+ return (tmp32);
+}
+
+#define ADC2_IDEAL_CODE_100MV (ADC2_CP_CODE_25_100MV)
+#define ADC2_IDEAL_CODE_200MV (ADC2_CP_CODE_25_200MV)
+#define ADC2_IDEAL_CODE_DELTA (ADC2_IDEAL_CODE_200MV - ADC2_IDEAL_CODE_100MV)
+
+/**
+ * @brief CalibrateAdc2Code
+ *
+ * Calibrate ADC2 code
+ *
+ * @para obj address of MeasDataInternalType
+ * @para code ADC2 code to be calibrated
+ * @return calibrated code
+ */
+_meas_s32_ CalibrateAdc2Code(MeasDataInternalType *obj, _meas_s32_ code)
+{
+ _meas_s64_ tmp64;
+ _meas_s32_ tmp32;
+ _meas_s32_ deltaIT;
+ _meas_s32_ gain;
+ _meas_s32_ offset;
+
+ deltaIT = (_meas_s32_)obj->info->otp->aveIT80;
+ deltaIT = deltaIT - obj->info->otp->aveIT25;
+
+ /// [AT-PM] : Pre-operation to avoid 64-bit division ; 01/23/2013
+ gain = obj->info->adc2Gain;
+ offset = obj->info->adc2Offset;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)code;
+ tmp64 = tmp64*deltaIT - offset;
+ tmp64 = tmp64*ADC2_IDEAL_CODE_DELTA;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ code = code/2;
+ deltaIT = deltaIT/2;
+ gain = gain/4;
+ offset = offset/4;
+ }
+
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/gain;
+ return (tmp32);
+}
+
+#define IT_IDEAL_CODE_25 (24310)
+#define IT_IDEAL_CODE_80 (28612)
+#define IT_IDEAL_CODE_DELTA (IT_IDEAL_CODE_80 - IT_IDEAL_CODE_25)
+
+/**
+ * @brief CalibrateITCode
+ *
+ * Calibrate internal temperature code
+ *
+ * @para obj address of MeasDataInternalType
+ * @para itCode raw IT code
+ * @return calibrated IT code
+ */
+_meas_u16_ CalibrateITCode(MeasDataInternalType *obj, _meas_u16_ itCode)
+{
+ _meas_s32_ tmp32;
+
+ tmp32 = (_meas_s32_)itCode;
+ tmp32 = tmp32 - obj->info->otp->aveIT25;
+ tmp32 = tmp32*IT_IDEAL_CODE_DELTA;
+ tmp32 = tmp32/(obj->info->otp->aveIT80 - obj->info->otp->aveIT25);
+ tmp32 = tmp32 + IT_IDEAL_CODE_25;
+ return ((_meas_u16_)tmp32);
+}
+
+#define NORMAL_REGISTER (NORMAL)
+
+/**
+ * @brief CalibrateChargeCode
+ *
+ * Calibrate charge code
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void CalibrateChargeCode(MeasDataInternalType *obj)
+{
+ _meas_s32_ tmp32;
+ _meas_s64_ tmp64;
+ _meas_s32_ gain;
+ _meas_s32_ offset;
+
+ UG31_LOGI("[%s]: Raw Code = %d\n", __func__, obj->codeCharge);
+
+ /// [AT-PM] : Calibrate charge code ; 01/23/2013
+ obj->info->rawCodeCharge = obj->codeCharge;
+ obj->info->codeCharge = CalibrateAdc1Code(obj, ((_meas_s32_)obj->codeCharge)*2);
+
+ /// [AT-PM] : Calculate coulomb counter offset ; 01/23/2013
+ tmp32 = obj->info->adc1Offset/(obj->info->otp->aveIT80 - obj->info->otp->aveIT25)*(-1);
+ tmp32 = tmp32 + obj->info->ccOffsetAdj;
+ obj->info->ccOffset = (_meas_s16_)tmp32;
+
+ UG31_LOGI("[%s]: Calibrated Code = %d, Offset = %d (%d)\n", __func__, obj->info->codeCharge, obj->info->ccOffset, obj->info->ccOffsetAdj);
+
+ /// [AT-PM] : Set coulomb counter offset ; 01/27/2013
+ API_I2C_Write(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_ADC1_OFFSET_LOW,
+ REG_ADC1_OFFSET_HIGH - REG_ADC1_OFFSET_LOW + 1,
+ (unsigned char *)&obj->info->ccOffset);
+
+ /// [AT-PM] : Remove the offset in calibrated charge code ; 01/23/2013
+ gain = obj->info->adc1Gain;
+ offset = obj->info->adc1Offset;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)offset;
+ tmp64 = tmp64*ADC1_IDEAL_CODE_DELTA;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ gain = gain/2;
+ offset = offset/2;
+ }
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/gain;
+ UG31_LOGI("[%s]: Compensation = %d x %d / %d\n", __func__,
+ obj->info->adc1Offset, ADC1_IDEAL_CODE_DELTA, obj->info->adc1Gain);
+ obj->info->codeCharge = obj->info->codeCharge + tmp32;
+ UG31_LOGI("[%s]: Charge = %d\n", __func__, obj->info->codeCharge);
+}
+
+#define ADC2_VOLTAGE_100MV (3000) ///< [AT-PM] : Unit in mV ; 01/25/2013
+#define ADC2_VOLTAGE_200MV (4000) ///< [AT-PM] : Unit in mV ; 01/25/2013
+#define ADC2_VOLTAGE_DELTA (ADC2_VOLTAGE_200MV - ADC2_VOLTAGE_100MV)
+
+/**
+ * @brief ConvertBat1
+ *
+ * Convert code of BAT1
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertBat1(MeasDataInternalType *obj)
+{
+ _meas_s32_ tmp32;
+
+ /// [AT-PM] : Convert from calibrated ADC code ; 01/25/2013
+ tmp32 = (_meas_s32_)obj->info->codeBat1;
+ tmp32 = tmp32 - ADC2_IDEAL_CODE_100MV;
+ tmp32 = tmp32*ADC2_VOLTAGE_DELTA/ADC2_IDEAL_CODE_DELTA;
+ tmp32 = tmp32 + ADC2_VOLTAGE_100MV;
+
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_VOLTAGE_OFFSET;
+ tmp32 = tmp32*BOARD_FACTOR_CONST/BOARD_FACTOR_VOLTAGE_GAIN;
+
+ /// [AT-PM] : Apply calibration parameter ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc2_offset;
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc2_gain;
+ obj->info->bat1Voltage = (_meas_u16_)tmp32;
+}
+
+#define ADC1_VOLTAGE_100MV (-5000) ///< [AT-PM] : Unit in uV ; 01/25/2013
+#define ADC1_VOLTAGE_200MV (-10000) ///< [AT-PM] : Unit in uV ; 01/25/2013
+#define ADC1_VOLTAGE_DELTA (ADC1_VOLTAGE_200MV - ADC1_VOLTAGE_100MV)
+/**
+ * @brief ConvertCurrent
+ *
+ * Convert code of Current
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertCurrent(MeasDataInternalType *obj)
+{
+ _meas_s32_ tmp32;
+
+ /// [AT-PM] : Convert from calibrated ADC code ; 01/25/2013
+ tmp32 = (_meas_s32_)obj->info->codeCurrent;
+ tmp32 = tmp32 - ADC1_IDEAL_CODE_100MV;
+ tmp32 = tmp32*ADC1_VOLTAGE_DELTA/ADC1_IDEAL_CODE_DELTA;
+ tmp32 = tmp32 + ADC1_VOLTAGE_100MV;
+ tmp32 = tmp32/obj->info->sysData->ggbParameter->rSense;
+
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_CURR_OFFSET;
+ tmp32 = tmp32*BOARD_FACTOR_CONST/BOARD_FACTOR_CURR_GAIN;
+
+ /// [AT-PM] : Apply calibration factor ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc1_pos_offset;
+ if(tmp32 > 0)
+ {
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc1_pgain;
+ }
+ else
+ {
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc1_ngain;
+ }
+ obj->info->curr = (_meas_s16_)tmp32;
+}
+
+#define AMBIENT_TEMPERATURE_IN_FT (220)
+#define IT_CONST (100)
+#define IT_GAIN (392)
+#define IT_OFFSET (11172)
+
+static _meas_s16_ FTAmbientMappingTable[] =
+{
+ AMBIENT_TEMPERATURE_IN_FT, ///< Index = 0
+ AMBIENT_TEMPERATURE_IN_FT + 10, ///< Index = 1
+ AMBIENT_TEMPERATURE_IN_FT - 10, ///< Index = 2
+ AMBIENT_TEMPERATURE_IN_FT + 20, ///< Index = 3
+ AMBIENT_TEMPERATURE_IN_FT - 20, ///< Index = 4
+ AMBIENT_TEMPERATURE_IN_FT + 30, ///< Index = 5
+ AMBIENT_TEMPERATURE_IN_FT - 30, ///< Index = 6
+ AMBIENT_TEMPERATURE_IN_FT + 40, ///< Index = 7
+ AMBIENT_TEMPERATURE_IN_FT - 40, ///< Index = 8
+ AMBIENT_TEMPERATURE_IN_FT + 50, ///< Index = 9
+ AMBIENT_TEMPERATURE_IN_FT - 50, ///< Index = 10
+ AMBIENT_TEMPERATURE_IN_FT + 60, ///< Index = 11
+ AMBIENT_TEMPERATURE_IN_FT - 60, ///< Index = 12
+ AMBIENT_TEMPERATURE_IN_FT + 70, ///< Index = 13
+ AMBIENT_TEMPERATURE_IN_FT - 70, ///< Index = 14
+ AMBIENT_TEMPERATURE_IN_FT, ///< Index = 15
+};
+
+/**
+ * @brief ConvertIntTemperature
+ *
+ * Convert code of internal temperature
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertIntTemperature(MeasDataInternalType *obj)
+{
+ _meas_s32_ tmp32;
+ _meas_s32_ ftIT;
+
+ /// [AT-PM] : Convert from calibrated ADC code ; 01/25/2013
+ tmp32 = (_meas_s32_)obj->info->codeIntTemperature;
+ tmp32 = tmp32/2;
+ tmp32 = tmp32 - IT_OFFSET;
+ tmp32 = tmp32*IT_CONST/IT_GAIN;
+
+ /// [AT-PM] : Apply FT information ; 01/25/2013
+ ftIT = (_meas_s32_)CalibrateITCode(obj, obj->info->otp->ftIT);
+ ftIT = ftIT/2;
+ ftIT = ftIT - IT_OFFSET;
+ ftIT = ftIT*IT_CONST/IT_GAIN;
+ tmp32 = tmp32 - (ftIT - FTAmbientMappingTable[obj->info->otp->deltaET]);
+
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_INTT_OFFSET;
+
+ /// [AT-PM] : Apply calibration factor ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc_d5;
+ obj->info->intTemperature = (_meas_s16_)tmp32;
+}
+
+static _meas_s16_ ExtTemperatureTable[] = {
+ -100, ///< Index = 0
+ -50, ///< Index = 1
+ 0, ///< Index = 2
+ 50, ///< Index = 3
+ 100, ///< Index = 4
+ 150, ///< Index = 5
+ 200, ///< Index = 6
+ 250, ///< Index = 7
+ 300, ///< Index = 8
+ 350, ///< Index = 9
+ 400, ///< Index = 10
+ 450, ///< Index = 11
+ 500, ///< Index = 12
+ 550, ///< Index = 13
+ 600, ///< Index = 14
+ 650, ///< Index = 15
+ 700, ///< Index = 16
+ 750, ///< Index = 17
+ 800, ///< Index = 18
+};
+
+/**
+ * @brief ConvertExtTemperature
+ *
+ * Convert code of external temperature
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertExtTemperature(MeasDataInternalType *obj)
+{
+ _meas_u8_ idx;
+ _meas_s32_ tmp32;
+
+ idx = 0;
+ while(idx < ET_NUMS)
+ {
+ if(obj->info->codeExtTemperature >= obj->info->sysData->ggbParameter->rtTable[idx])
+ {
+ break;
+ }
+ idx = idx + 1;
+ }
+
+ if(idx == 0)
+ {
+ /// [AT-PM] : Minimum measurable temperature ; 01/25/2013
+ tmp32 = (_meas_s32_)ExtTemperatureTable[0];
+ }
+ else if(idx == ET_NUMS)
+ {
+ /// [AT-PM] : Maximum measurable temperature ; 01/25/2013
+ tmp32 = (_meas_s32_)ExtTemperatureTable[ET_NUMS - 1];
+ }
+ else
+ {
+ /// [AT-PM] : Calculate external temperature ; 01/25/2013
+ tmp32 = (_meas_s32_)obj->info->codeExtTemperature;
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->rtTable[idx];
+ tmp32 = tmp32*(ExtTemperatureTable[idx - 1] - ExtTemperatureTable[idx]);
+ tmp32 = tmp32/(obj->info->sysData->ggbParameter->rtTable[idx - 1] - obj->info->sysData->ggbParameter->rtTable[idx]);
+ tmp32 = tmp32 + ExtTemperatureTable[idx];
+ }
+
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_EXTT_OFFSET;
+
+ /// [AT-PM] : Apply calibration factor ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc_d4;
+ obj->info->extTemperature = (_meas_s16_)tmp32;
+}
+
+#define TIME_DEFAULT_ADC1_CONVERT_TIME (1253)
+#define MINIMUM_ADC1_COUNTER_FOR_CONVERT_TIME (10)
+#define MAXIMUM_ADC1_CONVERSION_TIME (0xf8)
+#define MINIMUM_ADC1_CONVERSION_TIME (0x08)
+
+/**
+ * @brief CalculateAdc1ConvertTime
+ *
+ * Calculate ADC1 conversion time
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void CalculateAdc1ConvertTime(MeasDataInternalType *obj)
+{
+ _meas_u32_ tmp32;
+
+ UG31_LOGI("[%s]: Initial conversion time = %d\n", __func__, obj->info->adc1ConvertTime);
+
+ /// [AT-PM] : First time to calculate ADC1 conversion time ; 01/25/2013
+ if(obj->info->adc1ConvertTime == 0)
+ {
+ obj->info->adc1ConvertTime = TIME_DEFAULT_ADC1_CONVERT_TIME;
+ obj->info->lastCounter = obj->codeCounter;
+ return;
+ }
+
+ #ifdef uG31xx_BOOT_LOADER
+ /// [AT-PM] : In bootloader, ADC1 converstion time is not calculated ; 02/12/2013
+ return;
+ #endif ///< end of uG31xx_BOOT_LOADER
+
+ /// [AT-PM] : Check counter overflow or time overflow; 01/25/2013
+ if((obj->codeCounter <= obj->info->lastCounter) || (obj->info->deltaTime == 0))
+ {
+ obj->info->lastCounter = obj->codeCounter;
+ return;
+ }
+
+ /// [AT-PM] : Limit the minimum counter ; 02/11/2013
+ tmp32 = (_meas_u32_)obj->codeCounter;
+ tmp32 = tmp32 - obj->info->lastCounter;
+ if(tmp32 < MINIMUM_ADC1_COUNTER_FOR_CONVERT_TIME)
+ {
+ obj->info->lastCounter = obj->codeCounter;
+ return;
+ }
+
+ /// [AT-PM] : Average ADC1 conversion time ; 01/25/2013
+ tmp32 = obj->info->deltaTime;
+ tmp32 = tmp32*TIME_CONVERT_TIME_TO_MSEC/(obj->codeCounter - obj->info->lastCounter);
+ tmp32 = tmp32 + obj->info->adc1ConvertTime;
+ tmp32 = tmp32/2;
+
+ /// [AT-PM] : Check conversion time is valid or not ; 02/13/2013
+ if((tmp32 > (MAXIMUM_ADC1_CONVERSION_TIME*TIME_CONVERT_TIME_TO_MSEC)) ||
+ (tmp32 < (MINIMUM_ADC1_CONVERSION_TIME*TIME_CONVERT_TIME_TO_MSEC)))
+ {
+ UG31_LOGI("[%s]: ***************************************************************************************\n", __func__);
+ UG31_LOGI("[%s]: ***************************************************************************************\n", __func__);
+ UG31_LOGI("[%s]: #### ##### ## ## #### ##### ## ## #### ## ###### ###### ## ## ######\n", __func__);
+ UG31_LOGI("[%s]: ## ## ## ## ### ## ## ## ## ## ### ### ## ## ## ###### ## ### ### ##\n", __func__);
+ UG31_LOGI("[%s]: ###### ##### ###### ## ## ##### ####### ###### ## ## ## ####### ###\n", __func__);
+ UG31_LOGI("[%s]: ## ## ## ## ## ### ## ## ## ## ## ## ## ## ## ## ## ## ## ##\n", __func__);
+ UG31_LOGI("[%s]: ## ## ##### ## ## #### ## ## ## ## ## ## ###### ## ###### ## ## ######\n", __func__);
+ UG31_LOGI("[%s]:\n", __func__);
+ UG31_LOGI("[%s]: Previous Time Tag = %d\n", __func__, obj->info->lastTimeTick - obj->info->deltaTime);
+ UG31_LOGI("[%s]: Current Time Tag = %d\n", __func__, obj->info->lastTimeTick);
+ UG31_LOGI("[%s]: Delta Time = %d\n", __func__, obj->info->deltaTime);
+ UG31_LOGI("[%s]: Previous ADC Count = %d\n", __func__, obj->info->lastCounter);
+ UG31_LOGI("[%s]: Current ADC Count = %d\n", __func__, obj->codeCounter);
+ UG31_LOGI("[%s]: Delta ADC Count = %d\n", __func__, obj->codeCounter - obj->info->lastCounter);
+ UG31_LOGI("[%s]: Old ADC Convert Time = %d\n", __func__, obj->info->adc1ConvertTime);
+ UG31_LOGI("[%s]: New ADC Convert Time = %d\n", __func__, tmp32);
+ UG31_LOGI("[%s]: ***************************************************************************************\n", __func__);
+ UG31_LOGI("[%s]: ***************************************************************************************\n", __func__);
+ tmp32 = (_meas_u32_)obj->info->adc1ConvertTime;
+ }
+ UG31_LOGI("[%s]: Conversion Time = %d ((%d - %d)/%d)\n", __func__,
+ tmp32, obj->codeCounter, obj->info->lastCounter, obj->info->deltaTime);
+ obj->info->adc1ConvertTime = (_meas_u16_)tmp32;
+ obj->info->lastCounter = obj->codeCounter;
+}
+
+#define TIME_MSEC_TO_SEC (1000)
+#define TIME_SEC_TO_HOUR (3600)
+#define COULOMB_COUNTER_LSB (4096)
+
+/**
+ * @brief ConvertCharge
+ *
+ * Convert code of charge
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ConvertCharge(MeasDataInternalType *obj)
+{
+ _meas_s16_ tmp16;
+ _meas_s32_ tmp32;
+ _meas_s64_ tmp64;
+
+ /// [AT-PM] : Convert from calibrated ADC code ; 01/25/2013
+ tmp16 = ADC1_IDEAL_CODE_DELTA;
+ tmp32 = (_meas_s32_)obj->info->codeCharge;
+ tmp32 = tmp32 - ADC1_IDEAL_CODE_100MV;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)tmp32;
+ tmp64 = tmp64*ADC1_VOLTAGE_DELTA;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ tmp16 = tmp16/2;
+ tmp32 = tmp32/2;
+ }
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/tmp16;
+ tmp32 = tmp32 + ADC1_VOLTAGE_100MV;
+ tmp32 = tmp32/obj->info->sysData->ggbParameter->rSense;
+ UG31_LOGI("[%s]: ((%d - %d) x %d / %d + %d) / %d = %d\n", __func__,
+ obj->info->codeCharge, ADC1_IDEAL_CODE_100MV, ADC1_VOLTAGE_DELTA,
+ ADC1_IDEAL_CODE_DELTA, ADC1_VOLTAGE_100MV, obj->info->sysData->ggbParameter->rSense, tmp32);
+ /// [AT-PM] : Apply board factor ; 01/25/2013
+ tmp32 = tmp32 - BOARD_FACTOR_CURR_OFFSET;
+ tmp32 = tmp32*BOARD_FACTOR_CONST/BOARD_FACTOR_CURR_GAIN;
+ UG31_LOGI("[%s]: Board Factor (%d/%d) -> %d\n", __func__,
+ BOARD_FACTOR_CURR_GAIN, BOARD_FACTOR_CURR_OFFSET, tmp32);
+
+ /// [AT-PM] : Apply calibration factor ; 01/25/2013
+ tmp32 = tmp32 - obj->info->sysData->ggbParameter->adc1_pos_offset;
+ if(tmp32 > 0)
+ {
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc1_pgain;
+ }
+ else
+ {
+ tmp32 = tmp32*CALIBRATION_FACTOR_CONST/obj->info->sysData->ggbParameter->adc1_ngain;
+ }
+ UG31_LOGI("[%s]: Calibration Factor (%d|%d/%d) -> %d\n", __func__,
+ obj->info->sysData->ggbParameter->adc1_pgain, obj->info->sysData->ggbParameter->adc1_ngain,
+ obj->info->sysData->ggbParameter->adc1_pos_offset, tmp32);
+
+ /// [AT-PM] : Apply time information ; 01/25/2013
+ CalculateAdc1ConvertTime(obj);
+ tmp32 = tmp32*(obj->info->adc1ConvertTime)/TIME_MSEC_TO_SEC*COULOMB_COUNTER_LSB/TIME_SEC_TO_HOUR;
+ tmp32 = tmp32/TIME_CONVERT_TIME_TO_MSEC;
+
+ /// [AT-PM] : Update capacity information ; 01/25/2013
+ obj->info->deltaCap = (_meas_s16_)tmp32;
+ obj->info->stepCap = obj->info->deltaCap - obj->info->lastDeltaCap;
+ obj->info->lastDeltaCap = obj->info->deltaCap;
+ UG31_LOGI("[%s]: Capacity = %d (%d)\n", __func__, obj->info->deltaCap, obj->info->stepCap);
+}
+
+/**
+ * @brief TimeTick
+ *
+ * Get the time tick and calculate delta time
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void TimeTick(MeasDataInternalType *obj)
+{
+ obj->currTime = GetTickCount();
+
+ /// [AT-PM] : Prevent time tick overflow ; 01/25/2013
+ if(obj->currTime <= obj->info->lastTimeTick)
+ {
+ obj->info->deltaTime = 0;
+ obj->info->lastTimeTick = obj->currTime;
+ UG31_LOGI("[%s]: OVERFLOW -> %d < %d\n", __func__,
+ obj->currTime, obj->info->lastTimeTick);
+ return;
+ }
+
+ /// [AT-PM] : Calculate delta time ; 01/25/2013
+ obj->info->deltaTime = obj->currTime - obj->info->lastTimeTick;
+ UG31_LOGI("[%s]: Delta Time = %d - %d = %d\n", __func__,
+ obj->currTime, obj->info->lastTimeTick, obj->info->deltaTime);
+ obj->info->lastTimeTick = obj->currTime;
+}
+
+/**
+ * @brief ReadRegister
+ *
+ * Read measurement registers
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ReadRegister(MeasDataInternalType *obj)
+{
+ /// [AT-PM] : Read VBat1Ave ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_VBAT1_LOW,
+ REG_AVE_VBAT1_HIGH - REG_AVE_VBAT1_LOW + 1,
+ (unsigned char *)&obj->codeBat1);
+
+ /// [AT-PM] : Read CurrentAve ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_CURRENT_LOW,
+ REG_AVE_CURRENT_HIGH - REG_AVE_CURRENT_LOW + 1,
+ (unsigned char *)&obj->codeCurrent);
+
+ /// [AT-PM] : Read ITAve ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_IT_LOW,
+ REG_AVE_IT_HIGH - REG_AVE_IT_LOW + 1,
+ (unsigned char *)&obj->codeIntTemperature);
+
+ /// [AT-PM] : Read ETAve ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_ET_LOW,
+ REG_AVE_ET_HIGH - REG_AVE_ET_LOW + 1,
+ (unsigned char *)&obj->codeExtTemperature);
+
+ /// [AT-PM] : Read Charge ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CHARGE_LOW,
+ REG_CHARGE_HIGH - REG_CHARGE_LOW + 1,
+ (unsigned char *)&obj->codeCharge);
+
+ /// [AT-PM] : Read Counter ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_COUNTER_LOW,
+ REG_COUNTER_HIGH - REG_COUNTER_LOW + 1,
+ (unsigned char *)&obj->codeCounter);
+
+ /// [AT-PM] : Read Offset ; 01/27/2013
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_ADC1_OFFSET_LOW,
+ REG_ADC1_OFFSET_HIGH - REG_ADC1_OFFSET_LOW + 1,
+ (unsigned char *)&obj->ccOffset);
+}
+
+/**
+ * @brief ResetCoulombCounter
+ *
+ * Reset coulomb counter
+ *
+ * @para obj address of MeasDataInternalType
+ * @return _UPI_NULL_
+ */
+void ResetCoulombCounter(MeasDataInternalType *obj)
+{
+ _meas_u8_ tmp8;
+
+ API_I2C_Read(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CTRL1,
+ 1,
+ &tmp8);
+ tmp8 = tmp8 | CTRL1_GG_RST;
+ API_I2C_Write(NORMAL_REGISTER,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CTRL1,
+ 1,
+ &tmp8);
+}
+
+/**
+ * @brief RevertCalibrateAdc2Code
+ *
+ * Revert calibrated ADC2 code
+ *
+ * @para data address of MeasDataType
+ * @para caliCode calibrated ADC2 code
+ * @return raw ADC2 code
+ */
+_meas_s32_ RevertCalibrateAdc2Code(MeasDataType *data, _meas_s32_ caliCode)
+{
+ _meas_s64_ tmp64;
+ _meas_s32_ tmp32;
+ _meas_s32_ deltaIT;
+ _meas_s32_ gain;
+ _meas_s32_ offset;
+ _meas_s32_ constant;
+
+ /// [AT-PM] : tmp32 = ( caliCode x gain / constant + offset ) / deltaIT ; 04/08/2013
+ gain = data->adc2Gain;
+ offset = data->adc2Offset;
+ deltaIT = (_meas_s32_)data->otp->aveIT80;
+ deltaIT = deltaIT - data->otp->aveIT25;
+ constant = ADC2_IDEAL_CODE_DELTA;
+ while(1)
+ {
+ tmp64 = (_meas_s64_)caliCode;
+ tmp64 = tmp64*gain;
+ if((tmp64 < 2147483647) && (tmp64 > -2147483647))
+ {
+ break;
+ }
+ caliCode = caliCode/2;
+ gain = gain/2;
+ constant = constant/4;
+ }
+ tmp32 = (_meas_s32_)tmp64;
+ tmp32 = tmp32/constant;
+ tmp32 = tmp32 + offset;
+ tmp32 = tmp32/deltaIT;
+ return (tmp32);
+}
+
+/**
+ * @brief RevertBat1Code
+ *
+ * Revert VBat1 code
+ *
+ * @para data address of MeasDataType
+ * @para volt voltage in mV to be reverted
+ * @return adc2 vbat1 code
+ */
+_meas_u16_ RevertBat1Code(MeasDataType *data, _upi_s16_ volt)
+{
+ _meas_s32_ tmp32;
+
+ tmp32 = (_meas_s32_)volt;
+
+ /// [AT-PM] : Revert calibration parameter ; 04/08/2013
+ tmp32 = tmp32*data->sysData->ggbParameter->adc2_gain/CALIBRATION_FACTOR_CONST;
+ tmp32 = tmp32 + data->sysData->ggbParameter->adc2_offset;
+
+ /// [AT-PM] : Revert board factor ; 04/08/2013
+ tmp32 = tmp32*BOARD_FACTOR_VOLTAGE_GAIN/BOARD_FACTOR_CONST;
+ tmp32 = tmp32 + BOARD_FACTOR_VOLTAGE_OFFSET;
+
+ /// [AT-PM] : Revert to calibrated ADC code ; 04/08/2013
+ tmp32 = tmp32 - ADC2_VOLTAGE_100MV;
+ tmp32 = tmp32*ADC2_IDEAL_CODE_DELTA/ADC2_VOLTAGE_DELTA;
+ tmp32 = tmp32 + ADC2_IDEAL_CODE_100MV;
+
+ /// [AT-PM] : Revert to raw code ; 04/08/2013
+ tmp32 = RevertCalibrateAdc2Code(data, tmp32);
+ return ((_meas_u16_)tmp32);
+}
+
+/**
+ * @brief RevertETCode
+ *
+ * Revert ET code
+ *
+ * @para data address of MeasDataType
+ * @para et external temperature in 0.1oC to be reverted
+ * @return adc1 et code
+ */
+_meas_u16_ RevertETCode(MeasDataType *data, _upi_s16_ et)
+{
+ _meas_s32_ tmp32;
+ _meas_u8_ idx;
+
+ tmp32 = (_meas_s32_)et;
+
+ /// [AT-PM] : Revert calibration factor ; 04/08/2013
+ tmp32 = tmp32 + data->sysData->ggbParameter->adc_d4;
+
+ /// [AT-PM] : Revert board factor ; 04/08/2013
+ tmp32 = tmp32 + BOARD_FACTOR_EXTT_OFFSET;
+
+ /// [AT-PM] : Revert external temperature calculation ; 04/08/2013
+ idx = 0;
+ while(idx < ET_NUMS)
+ {
+ if(tmp32 < ExtTemperatureTable[idx])
+ {
+ break;
+ }
+ idx = idx + 1;
+ }
+ if(idx == 0)
+ {
+ tmp32 = (_meas_s32_)data->sysData->ggbParameter->rtTable[0];
+ }
+ else if(idx >= ET_NUMS)
+ {
+ tmp32 = (_meas_s32_)data->sysData->ggbParameter->rtTable[ET_NUMS - 1];
+ }
+ else
+ {
+ tmp32 = tmp32 - ExtTemperatureTable[idx - 1];
+ tmp32 = tmp32*(data->sysData->ggbParameter->rtTable[idx] - data->sysData->ggbParameter->rtTable[idx - 1]);
+ tmp32 = tmp32/(ExtTemperatureTable[idx] - ExtTemperatureTable[idx - 1]);
+ tmp32 = tmp32 + data->sysData->ggbParameter->rtTable[idx - 1];
+ }
+ return ((_meas_u16_)tmp32);
+}
+
+/// =============================================
+/// [AT-PM] : Extern function region
+/// =============================================
+
+/**
+ * @brief UpiResetCoulombCounter
+ *
+ * Reset coulomb counter
+ *
+ * @para data address of MeasDataType
+ * @return _UPI_NULL_
+ */
+void UpiResetCoulombCounter(MeasDataType *data)
+{
+ MeasDataInternalType *obj;
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ obj = (MeasDataInternalType *)malloc(sizeof(MeasDataInternalType));
+ #else ///< else of uG31xx_BOOT_LOADER
+ obj = (MeasDataInternalType *)kmalloc(sizeof(MeasDataInternalType), GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ obj = (MeasDataInternalType *)malloc(sizeof(MeasDataInternalType));
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ memset(obj, 0x00, sizeof(MeasDataInternalType));
+
+ obj->info = data;
+
+ /// [AT-PM] : Get delta time ; 01/25/2013
+ TimeTick(obj);
+
+ /// [AT-PM] : Read ADC code ; 01/27/2013
+ ReadRegister(obj);
+
+ /// [AT-PM] : Reset coulomb counter ; 01/30/2013
+ ResetCoulombCounter(obj);
+
+ /// [AT-PM] : Convert ADC characteristic from OTP ; 01/23/2013
+ ConvertAdc1Data(obj);
+
+ /// [AT-PM] : Calculate ADC gain and offset ; 01/23/2013
+ CalAdc1Factors(obj);
+
+ /// [AT-PM] : Calibrate ADC code ; 01/23/2013
+ data->codeCurrent = (_meas_s16_)CalibrateAdc1Code(obj, (_meas_s32_)obj->codeCurrent);
+ CalibrateChargeCode(obj);
+
+ /// [AT-PM] : Convert into physical value ; 01/23/2013
+ ConvertCurrent(obj);
+ ConvertCharge(obj);
+ data->lastDeltaCap = 0;
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(obj);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(obj);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(obj);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+}
+
+#define MAXIMUM_RETRY_CNT (5)
+#define MINIMUM_VBAT1_CODE (ADC2_IDEAL_CODE_100MV/2)
+#define MAXIMUM_CURRENT_CODE (ADC1_IDEAL_CODE_200MV*6)
+#define MINIMUM_CURRENT_CODE (ADC1_IDEAL_CODE_200MV*(-6))
+#define MINIMUM_IT_CODE (IT_IDEAL_CODE_25/2)
+#define MAXIMUM_IT_CODE (IT_IDEAL_CODE_80*11/10)
+#define MINIMUM_ET_CODE (1000)
+#define MAXIMUM_ET_CODE (28000)
+#define RESET_CC_CURRENT_MAGIC_NUMBER (2)
+
+/**
+ * @brief UpiMeasurement
+ *
+ * Measurement routine
+ *
+ * @para data address of MeasDataType
+ * @return MEAS_RTN_CODE
+ */
+MEAS_RTN_CODE UpiMeasurement(MeasDataType *data)
+{
+ MeasDataInternalType *obj;
+ _meas_u8_ retry;
+ MEAS_RTN_CODE rtn;
+ _meas_s16_ standbyUpper;
+ _meas_s16_ standbyLower;
+ _meas_s16_ tmp16;
+
+ UG31_LOGI("[%s]: Measurement version : %d\n", __func__, UG31XX_MEAS_VERSION);
+
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ obj = (MeasDataInternalType *)malloc(sizeof(MeasDataInternalType));
+ #else ///< else of uG31xx_BOOT_LOADER
+ obj = (MeasDataInternalType *)kmalloc(sizeof(MeasDataInternalType), GFP_KERNEL);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ obj = (MeasDataInternalType *)malloc(sizeof(MeasDataInternalType));
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ memset(obj, 0x00, sizeof(MeasDataInternalType));
+
+ obj->info = data;
+ rtn = MEAS_RTN_PASS;
+
+ /// [AT-PM] : Get delta time ; 01/25/2013
+ TimeTick(obj);
+
+ /// [AT-PM] : Read ADC code ; 01/27/2013
+ retry = 0;
+ while(retry < MAXIMUM_RETRY_CNT)
+ {
+ ReadRegister(obj);
+
+ if(obj->codeBat1 < MINIMUM_VBAT1_CODE)
+ {
+ UG31_LOGE("[%s]: Voltage code %d < %d -> Retry %d\n", __func__,
+ obj->codeBat1, MINIMUM_VBAT1_CODE, retry);
+ rtn = MEAS_RTN_BATTERY_REMOVED;
+ }
+ else if(obj->codeCurrent < MINIMUM_CURRENT_CODE)
+ {
+ UG31_LOGE("[%s]: Current code %d < %d\n", __func__,
+ obj->codeCurrent, MINIMUM_CURRENT_CODE);
+ obj->codeCurrent = MINIMUM_CURRENT_CODE;
+ break;
+ }
+ else if(obj->codeCurrent > MAXIMUM_CURRENT_CODE)
+ {
+ UG31_LOGE("[%s]: Current code %d > %d\n", __func__,
+ obj->codeCurrent, MAXIMUM_CURRENT_CODE);
+ obj->codeCurrent = MAXIMUM_CURRENT_CODE;
+ break;
+ }
+ else if(obj->codeIntTemperature < MINIMUM_IT_CODE)
+ {
+ UG31_LOGE("[%s]: Internal Temperature code %d < %d -> Retry %d\n", __func__,
+ obj->codeIntTemperature, MINIMUM_IT_CODE, retry);
+ rtn = MEAS_RTN_ADC_ABNORMAL;
+ }
+ else if(obj->codeIntTemperature > MAXIMUM_IT_CODE)
+ {
+ UG31_LOGE("[%s]: Internal Temperature code %d > %d -> Retry %d\n", __func__,
+ obj->codeIntTemperature, MAXIMUM_IT_CODE, retry);
+ rtn = MEAS_RTN_ADC_ABNORMAL;
+ }
+ else if(obj->codeExtTemperature < MINIMUM_ET_CODE)
+ {
+ UG31_LOGE("[%s]: External Temperature code %d < %d -> Retry %d\n", __func__,
+ obj->codeExtTemperature, MINIMUM_ET_CODE, retry);
+ rtn = MEAS_RTN_NTC_SHORT;
+ #ifndef ENABLE_NTC_CHECK
+ break;
+ #endif ///< end of ENABLE_NTC_CHECK
+ }
+ else if(obj->codeExtTemperature > MAXIMUM_ET_CODE)
+ {
+ UG31_LOGE("[%s]: External Temperature code %d > %d -> Retry %d\n", __func__,
+ obj->codeExtTemperature, MAXIMUM_ET_CODE, retry);
+ rtn = MEAS_RTN_BATTERY_REMOVED;
+ #ifndef ENABLE_NTC_CHECK
+ break;
+ #endif ///< end of ENABLE_NTC_CHECK
+ }
+ else
+ {
+ break;
+ }
+ retry = retry + 1;
+ SleepMiniSecond(1000);
+ }
+ if(retry >= MAXIMUM_RETRY_CNT)
+ {
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(obj);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(obj);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(obj);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ return (rtn);
+ }
+ rtn = MEAS_RTN_PASS;
+
+ /// [AT-PM] : Convert ADC characteristic from OTP ; 01/23/2013
+ ConvertAdc1Data(obj);
+ ConvertAdc2Data(obj);
+
+ /// [AT-PM] : Calculate ADC gain and offset ; 01/23/2013
+ CalAdc1Factors(obj);
+ CalAdc2Factors(obj);
+
+ /// [AT-PM] : Calibrate ADC code ; 01/23/2013
+ data->codeBat1 = (_meas_u16_)CalibrateAdc2Code(obj, (_meas_s32_)obj->codeBat1);
+ UG31_LOGI("[%s]: VBat1 Code = %d -> %d\n", __func__, obj->codeBat1, data->codeBat1);
+ data->codeCurrent = (_meas_s16_)CalibrateAdc1Code(obj, (_meas_s32_)obj->codeCurrent);
+ UG31_LOGI("[%s]: Current Code = %d -> %d\n", __func__, obj->codeCurrent, data->codeCurrent);
+ CalibrateChargeCode(obj);
+ data->codeIntTemperature = CalibrateITCode(obj, obj->codeIntTemperature);
+ UG31_LOGI("[%s]: Internal Temperature Code = %d -> %d\n", __func__,
+ obj->codeIntTemperature, data->codeIntTemperature);
+ /// [AT-PM] : External temperature code ; 06/19/2013
+ tmp16 = (_meas_s16_)obj->codeExtTemperature;
+ tmp16 = tmp16 - (obj->codeCurrent + obj->ccOffset);
+ if(data->codeExtTemperature != 0)
+ {
+ tmp16 = tmp16 - data->codeExtTemperature;
+ if(tmp16 < -200)
+ {
+ tmp16 = -200;
+ }
+ if(tmp16 > 200)
+ {
+ tmp16 = 200;
+ }
+ tmp16 = tmp16 + data->codeExtTemperature;
+ }
+ data->codeExtTemperature = (_meas_u16_)tmp16;
+ UG31_LOGI("[%s]: External Temperature Code = %d -> %d\n", __func__,
+ obj->codeExtTemperature, data->codeExtTemperature);
+
+ /// [AT-PM] : Convert into physical value ; 01/23/2013
+ ConvertBat1(obj);
+ ConvertCurrent(obj);
+ ConvertIntTemperature(obj);
+ ConvertExtTemperature(obj);
+ ConvertCharge(obj);
+
+ /// [AT-PM] : Reset coulomb counter if necessary ; 01/27/2013
+ standbyUpper = (_meas_s16_)obj->info->sysData->ggbParameter->standbyCurrent;
+ standbyUpper = standbyUpper/RESET_CC_CURRENT_MAGIC_NUMBER;
+ standbyLower = standbyUpper*(-1);
+ if((obj->codeCounter > COULOMB_COUNTER_RESET_THRESHOLD_COUNTER) ||
+ (obj->codeCharge > COULOMB_COUNTER_RESET_THRESHOLD_CHARGE_CHG) ||
+ (obj->codeCharge < COULOMB_COUNTER_RESET_THREDHOLD_CHARGE_DSG) ||
+ ((obj->info->curr < standbyUpper) &&
+ (obj->info->curr > standbyLower) &&
+ (obj->codeCounter > CONST_CONVERSION_COUNT_THRESHOLD*RESET_CC_CURRENT_MAGIC_NUMBER)))
+ {
+ ResetCoulombCounter(obj);
+ data->lastDeltaCap = 0;
+ }
+
+ #ifdef MEAS_FAKE_INT_TEMP
+ data->extTemperature = data->intTemperature;
+ data->intTemperature = MEAS_FAKE_INT_TEMP_OFFSET + data->intTemperature%100;
+ #endif ///< end of MEAS_FAKE_INT_TEMP
+
+ UG31_LOGI("[%s]: %d mV / %d mA / %d 0.1oC / %d 0.1oC / %d mAh\n", __func__,
+ data->bat1Voltage, data->curr, data->intTemperature, data->extTemperature, data->deltaCap);
+ #if defined(uG31xx_OS_ANDROID)
+ #ifdef uG31xx_BOOT_LOADER
+ free(obj);
+ #else ///< else of uG31xx_BOOT_LOADER
+ kfree(obj);
+ #endif ///< end of uG31xx_BOOT_LOADER
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ free(obj);
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ return (rtn);
+}
+
+/**
+ * @brief UpiMeasAlarmThreshold
+ *
+ * Get alarm threshold
+ *
+ * @para data address of MeasDataType
+ * @return MEAS_RTN_CODE
+ */
+MEAS_RTN_CODE UpiMeasAlarmThreshold(MeasDataType *data)
+{
+ MEAS_RTN_CODE rtn;
+
+ rtn = MEAS_RTN_PASS;
+
+ /// [AT-PM] : Calculate UV alarm and release threshold ; 04/08/2013
+ data->sysData->uvAlarm.alarmThrd = RevertBat1Code(data, data->sysData->ggbParameter->uvAlarm);
+ data->sysData->uvAlarm.releaseThrd = RevertBat1Code(data, data->sysData->ggbParameter->uvRelease);
+ UG31_LOGI("[%s]: UV Alarm -> %d / %d\n", __func__,
+ data->sysData->uvAlarm.alarmThrd, data->sysData->uvAlarm.releaseThrd);
+
+ /// [AT-PM] : Calculate UET alarm and release threshold ; 04/08/2013
+ data->sysData->uetAlarm.alarmThrd = RevertETCode(data, data->sysData->ggbParameter->uetAlarm);
+ data->sysData->uetAlarm.releaseThrd = RevertETCode(data, data->sysData->ggbParameter->uetRelease);
+ UG31_LOGI("[%s]: UET Alarm -> %d / %d\n", __func__,
+ data->sysData->uetAlarm.alarmThrd, data->sysData->uetAlarm.releaseThrd);
+
+ /// [AT-PM] : Calculate OET alarm and release threshold ; 04/08/2013
+ data->sysData->oetAlarm.alarmThrd = RevertETCode(data, data->sysData->ggbParameter->oetAlarm);
+ data->sysData->oetAlarm.releaseThrd = RevertETCode(data, data->sysData->ggbParameter->oetRelease);
+ UG31_LOGI("[%s]: OET Alarm -> %d / %d\n", __func__,
+ data->sysData->oetAlarm.alarmThrd, data->sysData->oetAlarm.releaseThrd);
+
+ return (rtn);
+}
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.h
new file mode 100755
index 00000000..c6bcc76e
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Measurement.h
@@ -0,0 +1,127 @@
+/**
+ * @filename uG31xx_API_Measurement.h
+ *
+ * Header for uG31xx measurement API
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+typedef signed char _meas_s8_;
+typedef unsigned char _meas_u8_;
+typedef signed short _meas_s16_;
+typedef unsigned short _meas_u16_;
+typedef signed long _meas_s32_;
+typedef unsigned long _meas_u32_;
+typedef signed long long _meas_s64_;
+
+#define UG31XX_MEAS_VERSION (9)
+
+#define BOARD_FACTOR_CONST (1000)
+#define BOARD_FACTOR_VOLTAGE_GAIN (1000) ///< [AT-PM] : VBat1 board factor - gain ; 01/25/2013
+#define BOARD_FACTOR_VOLTAGE_OFFSET (0) ///< [AT-PM] : VBat1 board factor - offset ; 01/25/2013
+#define BOARD_FACTOR_CURR_GAIN (1014) ///< [AT-PM] : Current board factor - gain ; 01/25/2013
+#define BOARD_FACTOR_CURR_OFFSET (-7) ///< [AT-PM] : Current board factor - offset ; 01/25/2013
+#define BOARD_FACTOR_INTT_OFFSET (-23) ///< [AT-PM] : Internal Temperature board factor - offset ; 01/25/2013
+#define BOARD_FACTOR_EXTT_OFFSET (13) ///< [AT-PM] : External Temperature board factor - offset ; 01/25/2013
+
+#define CALIBRATION_FACTOR_CONST (1000)
+
+#define COULOMB_COUNTER_RESET_THRESHOLD_COUNTER (10000)
+#define COULOMB_COUNTER_RESET_THRESHOLD_CHARGE_CHG (30000)
+#define COULOMB_COUNTER_RESET_THREDHOLD_CHARGE_DSG (-30000)
+
+typedef enum _MEAS_RTN_CODE {
+ MEAS_RTN_PASS = 0,
+ MEAS_RTN_BATTERY_REMOVED,
+ MEAS_RTN_ADC_ABNORMAL,
+ MEAS_RTN_NTC_SHORT,
+} MEAS_RTN_CODE;
+
+typedef struct MeasDataST {
+
+ /// [AT-PM] : System data ; 04/08/2013
+ SystemDataType *sysData;
+
+ /// [AT-PM] : OTP data ; 01/23/2013
+ OtpDataType *otp;
+
+ /// [AT-PM] : Physical value ; 01/23/2013
+ _meas_u16_ bat1Voltage;
+ _meas_s16_ curr;
+ _meas_s16_ intTemperature;
+ _meas_s16_ extTemperature;
+ _meas_s16_ deltaCap;
+ _meas_s16_ stepCap;
+ _meas_u32_ deltaTime;
+
+
+ /// [AT-PM] : ADC code ; 01/23/2013
+ _meas_u16_ codeBat1;
+ _meas_s16_ codeCurrent;
+ _meas_u16_ codeIntTemperature;
+ _meas_u16_ codeExtTemperature;
+ _meas_s32_ codeCharge;
+ _meas_s16_ rawCodeCharge;
+
+ /// [AT-PM] : Coulomb counter offset ; 01/23/2013
+ _meas_s16_ ccOffset;
+ _meas_s16_ ccOffsetAdj;
+
+ /// [AT-PM] : ADC1 characteristic ; 01/23/2013
+ _meas_u16_ adc1ConvertTime;
+ _meas_s32_ adc1Gain;
+ _meas_s32_ adc1GainSlope;
+ _meas_s32_ adc1GainFactorB;
+ _meas_s32_ adc1Offset;
+ _meas_s32_ adc1OffsetSlope;
+ _meas_s32_ adc1OffsetFactorO;
+
+ /// [AT-PM] : ADC2 characteristic ; 01/23/2013
+ _meas_s32_ adc2Gain;
+ _meas_s32_ adc2GainSlope;
+ _meas_s32_ adc2GainFactorB;
+ _meas_s32_ adc2Offset;
+ _meas_s32_ adc2OffsetSlope;
+ _meas_s32_ adc2OffsetFactorO;
+
+ /// [AT-PM] : Previous information ; 01/25/2013
+ _meas_u16_ lastCounter;
+ _meas_u32_ lastTimeTick;
+ _meas_s16_ lastDeltaCap;
+
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__ ((packed)) MeasDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} MeasDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiResetCoulombCounter
+ *
+ * Reset coulomb counter
+ *
+ * @para data address of MeasDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiResetCoulombCounter(MeasDataType *data);
+
+/**
+ * @brief UpiMeasurement
+ *
+ * Measurement routine
+ *
+ * @para data address of MeasDataType
+ * @return MEAS_RTN_CODE
+ */
+extern MEAS_RTN_CODE UpiMeasurement(MeasDataType *data);
+
+/**
+ * @brief UpiMeasAlarmThreshold
+ *
+ * Get alarm threshold
+ *
+ * @para data address of MeasDataType
+ * @return MEAS_RTN_CODE
+ */
+extern MEAS_RTN_CODE UpiMeasAlarmThreshold(MeasDataType *data);
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.c
new file mode 100755
index 00000000..0b1b2d1a
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.c
@@ -0,0 +1,876 @@
+/**
+ * @filename uG31xx_API_Otp.cpp
+ *
+ * Convert OTP registers into readable value
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+/// =============================================
+/// [AT-PM] : OTP register definition
+/// =============================================
+
+#define OTP1_OFFSET_E0 (0)
+ #define INDEX_ADC1_200_25_0 (1<<0)
+ #define INDEX_ADC1_200_25_1 (1<<1)
+ #define INDEX_ADC1_200_25_2 (1<<2)
+ #define INDEX_ADC1_200_25_3 (1<<3)
+ #define DELTA_VREF_0 (1<<4)
+ #define DELTA_VREF_1 (1<<5)
+ #define DELTA_VREF_2 (1<<6)
+ #define DELTA_VREF_3 (1<<7)
+
+#define OTP1_OFFSET_E1 (OTP1_OFFSET_E0 + 1)
+ #define INDEX_ADC1_100_25_0 (1<<0)
+ #define INDEX_ADC1_100_25_1 (1<<1)
+ #define INDEX_ADC1_100_25_2 (1<<2)
+ #define INDEX_ADC1_100_25_3 (1<<3)
+ #define FT_IT_3 (1<<4)
+ #define FT_IT_4 (1<<5)
+ #define FT_IT_5 (1<<6)
+ #define FT_IT_6 (1<<7)
+
+#define OTP1_OFFSET_E2 (OTP1_OFFSET_E1 + 1)
+ #define INDEX_ADC2_200_25_0 (1<<0)
+ #define INDEX_ADC2_200_25_1 (1<<1)
+ #define INDEX_ADC2_200_25_2 (1<<2)
+ #define INDEX_ADC2_200_25_3 (1<<3)
+ #define FT_IT_7 (1<<4)
+ #define FT_IT_8 (1<<5)
+ #define FT_IT_9 (1<<6)
+ #define FT_IT_10 (1<<7)
+
+#define OTP1_OFFSET_E3 (OTP1_OFFSET_E2 + 1)
+ #define INDEX_ADC2_100_25_0 (1<<0)
+ #define INDEX_ADC2_100_25_1 (1<<1)
+ #define INDEX_ADC2_100_25_2 (1<<2)
+ #define INDEX_ADC2_100_25_3 (1<<3)
+ #define FT_IT_11 (1<<4)
+ #define FT_IT_12 (1<<5)
+ #define FT_IT_13 (1<<6)
+ #define FT_IT_14 (1<<7)
+
+#define OTP2_OFFSET_F0 (0)
+ #define OTP2_OFFSET_F0_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F0_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F0_RSVD_2 (1<<2)
+ #define PRODUCT_TYPE_0 (1<<3)
+ #define PRODUCT_TYPE_1 (1<<4)
+ #define DELTA_ET_0 (1<<5)
+ #define INDEX_ADC2_100_25_4 (1<<6)
+ #define DELTA_ET_1 (1<<7)
+
+#define OTP2_OFFSET_F1 (OTP2_OFFSET_F0 + 1)
+ #define OTP2_OFFSET_F1_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F1_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F1_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_F1_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_F1_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_F1_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_F1_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_F1_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_F2 (OTP2_OFFSET_F1 + 1)
+ #define OTP2_OFFSET_F2_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F2_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F2_RSVD_2 (1<<2)
+ #define OTP_CELL_EN_0 (1<<3)
+ #define OTP_CELL_EN_1 (1<<4)
+ #define OTP_CELL_EN_2 (1<<5)
+ #define OTP_CELL_EN_3 (1<<6)
+ #define OTP_CELL_EN_4 (1<<7)
+
+#define OTP2_OFFSET_F3 (OTP2_OFFSET_F2 + 1)
+ #define OTP2_OFFSET_F3_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F3_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F3_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_F3_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_F3_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_F3_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_F3_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_F3_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_F4 (OTP2_OFFSET_F3 + 1)
+ #define ADC1_DELTA_CODE_25_200MV_8 (1<<0)
+ #define ADC1_DELTA_CODE_25_200MV_9 (1<<1)
+ #define DEV_ADDR_0 (1<<2)
+ #define DEV_ADDR_1 (1<<3)
+ #define DEV_ADDR_2 (1<<4)
+ #define DEV_ADDR_7 (1<<5)
+ #define DEV_ADDR_8 (1<<6)
+ #define DEV_ADDR_9 (1<<7)
+
+#define OTP2_OFFSET_F5 (OTP2_OFFSET_F4 + 1)
+ #define OTP2_OFFSET_F5_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F5_RSVD_1 (1<<1)
+ #define BGR_TUNE_0 (1<<2)
+ #define BGR_TUNE_1 (1<<3)
+ #define BGR_TUNE_2 (1<<4)
+ #define BGR_TUNE_3 (1<<5)
+ #define BGR_TUNE_4 (1<<6)
+ #define BGR_TUNE_5 (1<<7)
+
+#define OTP2_OFFSET_F6 (OTP2_OFFSET_F5 + 1)
+ #define OSC_DELTA_CODE_25_0 (1<<0)
+ #define OSC_DELTA_CODE_25_1 (1<<1)
+ #define OSC_DELTA_CODE_25_2 (1<<2)
+ #define OSC_DELTA_CODE_25_3 (1<<3)
+ #define OSC_DELTA_CODE_25_4 (1<<4)
+ #define OSC_DELTA_CODE_25_5 (1<<5)
+ #define OSC_DELTA_CODE_25_6 (1<<6)
+ #define OSC_DELTA_CODE_25_7 (1<<7)
+
+#define OTP2_OFFSET_F7 (OTP2_OFFSET_F6 + 1)
+ #define OSC_DELTA_CODE_80_0 (1<<0)
+ #define OSC_DELTA_CODE_80_1 (1<<1)
+ #define OSC_DELTA_CODE_80_2 (1<<2)
+ #define OSC_DELTA_CODE_80_3 (1<<3)
+ #define OSC_DELTA_CODE_80_4 (1<<4)
+ #define OSC_DELTA_CODE_80_5 (1<<5)
+ #define OSC_DELTA_CODE_80_6 (1<<6)
+ #define OSC_DELTA_CODE_80_7 (1<<7)
+
+#define OTP2_OFFSET_F8 (OTP2_OFFSET_F7 + 1)
+ #define ADC1_DELTA_CODE_25_200MV_0 (1<<0)
+ #define ADC1_DELTA_CODE_25_200MV_1 (1<<1)
+ #define ADC1_DELTA_CODE_25_200MV_2 (1<<2)
+ #define ADC1_DELTA_CODE_25_200MV_3 (1<<3)
+ #define ADC1_DELTA_CODE_25_200MV_4 (1<<4)
+ #define ADC1_DELTA_CODE_25_200MV_5 (1<<5)
+ #define ADC1_DELTA_CODE_25_200MV_6 (1<<6)
+ #define ADC1_DELTA_CODE_25_200MV_7 (1<<7)
+
+#define OTP2_OFFSET_F9 (OTP2_OFFSET_F8 + 1)
+ #define OTP2_OFFSET_F9_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_F9_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_F9_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_F9_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_F9_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_F9_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_F9_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_F9_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_FA (OTP2_OFFSET_F9 + 1)
+ #define ADC1_DELTA_CODE_25_100MV_0 (1<<0)
+ #define ADC1_DELTA_CODE_25_100MV_1 (1<<1)
+ #define ADC1_DELTA_CODE_25_100MV_2 (1<<2)
+ #define ADC1_DELTA_CODE_25_100MV_3 (1<<3)
+ #define ADC1_DELTA_CODE_25_100MV_4 (1<<4)
+ #define ADC1_DELTA_CODE_25_100MV_5 (1<<5)
+ #define ADC1_DELTA_CODE_25_100MV_6 (1<<6)
+ #define ADC1_DELTA_CODE_25_100MV_7 (1<<7)
+
+#define OTP2_OFFSET_FB (OTP2_OFFSET_FA + 1)
+ #define OTP2_OFFSET_FB_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_FB_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_FB_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_FB_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_FB_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_FB_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_FB_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_FB_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_FC (OTP2_OFFSET_FB + 1)
+ #define ADC1_DELTA_CODE_25_100MV_8 (1<<0)
+ #define ADC2_DELTA_CODE_25_100MV_0 (1<<1)
+ #define ADC2_DELTA_CODE_25_100MV_1 (1<<2)
+ #define ADC2_DELTA_CODE_25_100MV_2 (1<<3)
+ #define ADC2_DELTA_CODE_25_100MV_3 (1<<4)
+ #define ADC2_DELTA_CODE_25_100MV_4 (1<<5)
+ #define ADC2_DELTA_CODE_25_100MV_5 (1<<6)
+ #define ADC2_DELTA_CODE_25_100MV_6 (1<<7)
+
+#define OTP2_OFFSET_FD (OTP2_OFFSET_FC + 1)
+ #define OTP2_OFFSET_FD_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_FD_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_FD_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_FD_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_FD_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_FD_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_FD_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_FD_RSVD_7 (1<<7)
+
+#define OTP2_OFFSET_FE (OTP2_OFFSET_FD + 1)
+ #define ADC2_DELTA_CODE_25_200MV_0 (1<<0)
+ #define ADC2_DELTA_CODE_25_200MV_1 (1<<1)
+ #define ADC2_DELTA_CODE_25_200MV_2 (1<<2)
+ #define ADC2_DELTA_CODE_25_200MV_3 (1<<3)
+ #define ADC2_DELTA_CODE_25_200MV_4 (1<<4)
+ #define ADC2_DELTA_CODE_25_200MV_5 (1<<5)
+ #define ADC2_DELTA_CODE_25_200MV_6 (1<<6)
+ #define ADC2_DELTA_CODE_25_200MV_7 (1<<7)
+
+#define OTP2_OFFSET_FF (OTP2_OFFSET_FE + 1)
+ #define OTP2_OFFSET_FF_RSVD_0 (1<<0)
+ #define OTP2_OFFSET_FF_RSVD_1 (1<<1)
+ #define OTP2_OFFSET_FF_RSVD_2 (1<<2)
+ #define OTP2_OFFSET_FF_RSVD_3 (1<<3)
+ #define OTP2_OFFSET_FF_RSVD_4 (1<<4)
+ #define OTP2_OFFSET_FF_RSVD_5 (1<<5)
+ #define OTP2_OFFSET_FF_RSVD_6 (1<<6)
+ #define OTP2_OFFSET_FF_RSVD_7 (1<<7)
+
+
+#define OTP3_OFFSET_70 (0)
+ #define DELTA_VREF_4 (1<<0)
+ #define DELTA_ET_2 (1<<1)
+ #define DELTA_ET_3 (1<<2)
+ #define AVE_IT_25_3 (1<<3)
+ #define AVE_IT_25_4 (1<<4)
+ #define AVE_IT_25_5 (1<<5)
+ #define AVE_IT_25_6 (1<<6)
+ #define AVE_IT_25_7 (1<<7)
+
+#define OTP3_OFFSET_71 (OTP3_OFFSET_70 + 1)
+ #define AVE_IT_25_8 (1<<0)
+ #define AVE_IT_25_9 (1<<1)
+ #define AVE_IT_25_10 (1<<2)
+ #define AVE_IT_25_11 (1<<3)
+ #define AVE_IT_25_12 (1<<4)
+ #define AVE_IT_25_13 (1<<5)
+ #define AVE_IT_25_14 (1<<6)
+ #define AVE_IT_25_15 (1<<7)
+
+#define OTP3_OFFSET_72 (OTP3_OFFSET_71 + 1)
+ #define INDEX_ADC2_200_25_4 (1<<0)
+ #define INDEX_ADC1_100_25_4 (1<<1)
+ #define INDEX_ADC1_200_25_4 (1<<2)
+ #define AVE_IT_80_3 (1<<3)
+ #define AVE_IT_80_4 (1<<4)
+ #define AVE_IT_80_5 (1<<5)
+ #define AVE_IT_80_6 (1<<6)
+ #define AVE_IT_80_7 (1<<7)
+
+#define OTP3_OFFSET_73 (OTP3_OFFSET_72 + 1)
+ #define AVE_IT_80_8 (1<<0)
+ #define AVE_IT_80_9 (1<<1)
+ #define AVE_IT_80_10 (1<<2)
+ #define AVE_IT_80_11 (1<<3)
+ #define AVE_IT_80_12 (1<<4)
+ #define AVE_IT_80_13 (1<<5)
+ #define AVE_IT_80_14 (1<<6)
+ #define AVE_IT_80_15 (1<<7)
+
+/// =============================================
+/// [AT-PM] : OTP register conversion routine
+/// =============================================
+
+/**
+ * @brief ConvOtp1E0
+ *
+ * Convert OTP1 0xE0
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp1E0(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp1[OTP1_OFFSET_E0];
+
+ obj->indexAdc1V200T25 = obj->indexAdc1V200T25 + (value & (INDEX_ADC1_200_25_0 |
+ INDEX_ADC1_200_25_1 |
+ INDEX_ADC1_200_25_2 |
+ INDEX_ADC1_200_25_3));
+
+ obj->deltaVref = obj->deltaVref + ((value & (DELTA_VREF_0| DELTA_VREF_1 | DELTA_VREF_2 | DELTA_VREF_3)) >> 4);
+}
+
+/**
+ * @brief ConvOtp1E1
+ *
+ * Convert OTP1 0xE1
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp1E1(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp1[OTP1_OFFSET_E1];
+
+ obj->indexAdc1V100T25 = obj->indexAdc1V100T25 + (value & (INDEX_ADC1_100_25_0 |
+ INDEX_ADC1_100_25_1 |
+ INDEX_ADC1_100_25_2 |
+ INDEX_ADC1_100_25_3));
+
+ obj->ftIT = obj->ftIT + ((value & (FT_IT_3 | FT_IT_4 | FT_IT_5 | FT_IT_6)) >> 1);
+}
+
+/**
+ * @brief ConvOtp1E2
+ *
+ * Convert OTP1 0xE2
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp1E2(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp1[OTP1_OFFSET_E2];
+
+ obj->indexAdc2V200T25 = obj->indexAdc2V200T25 + (value & (INDEX_ADC2_200_25_0 |
+ INDEX_ADC2_200_25_1 |
+ INDEX_ADC2_200_25_2 |
+ INDEX_ADC2_200_25_3));
+
+ tmp = (value & (FT_IT_7 | FT_IT_8 | FT_IT_9 | FT_IT_10));
+ obj->ftIT = obj->ftIT + (tmp << 3);
+}
+
+/**
+ * @brief ConvOtp1E3
+ *
+ * Convert OTP1 0xE3
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp1E3(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp1[OTP1_OFFSET_E3];
+
+ obj->indexAdc2V100T25 = obj->indexAdc2V100T25 + (value & (INDEX_ADC2_100_25_0 |
+ INDEX_ADC2_100_25_1 |
+ INDEX_ADC2_100_25_2 |
+ INDEX_ADC2_100_25_3));
+
+ tmp = (value & (FT_IT_11 | FT_IT_12 | FT_IT_13 | FT_IT_14));
+ obj->ftIT = obj->ftIT + (tmp << 7);
+}
+
+/**
+ * @brief ConvOtp2F0
+ *
+ * Convert OTP2 0xF0
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F0(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F0];
+
+ obj->productType = (value & (PRODUCT_TYPE_0 | PRODUCT_TYPE_1)) >> 3;
+
+ obj->deltaET = obj->deltaET + ((value & DELTA_ET_0) >> 5) + ((value & DELTA_ET_1) >> 6);
+
+ obj->indexAdc2V100T25 = obj->indexAdc2V100T25 + ((value & INDEX_ADC2_100_25_4) >> 2);
+}
+
+/**
+ * @brief ConvOtp2F1
+ *
+ * Convert OTP2 0xF1
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F1(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2F2
+ *
+ * Convert OTP2 0xF2
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F2(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F2];
+
+ obj->otpCellEN = obj->otpCellEN + ((value & (OTP_CELL_EN_0 |
+ OTP_CELL_EN_1 |
+ OTP_CELL_EN_2 |
+ OTP_CELL_EN_3 |
+ OTP_CELL_EN_4)) >> 3);
+}
+
+/**
+ * @brief ConvOtp2F3
+ *
+ * Convert OTP2 0xF3
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F3(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2F4
+ *
+ * Convert OTP2 0xF4
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F4(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp2[OTP2_OFFSET_F4];
+
+ tmp = value & (ADC1_DELTA_CODE_25_200MV_8| ADC1_DELTA_CODE_25_200MV_9);
+ obj->adc1DeltaCodeT25V200 = obj->adc1DeltaCodeT25V200 + (tmp << 8);
+
+ tmp = value & (DEV_ADDR_7 | DEV_ADDR_8 | DEV_ADDR_9);
+ obj->devAddr = (tmp << 2) + ((value & (DEV_ADDR_0 | DEV_ADDR_1 | DEV_ADDR_2)) >> 2);
+}
+
+/**
+ * @brief ConvOtp2F5
+ *
+ * Convert OTP2 0xF5
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F5(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F5];
+
+ obj->bgrTune = obj->bgrTune + ((value & (BGR_TUNE_0 |
+ BGR_TUNE_1 |
+ BGR_TUNE_2 |
+ BGR_TUNE_3 |
+ BGR_TUNE_4 |
+ BGR_TUNE_5)) >> 2);
+}
+
+/**
+ * @brief ConvOtp2F6
+ *
+ * Convert OTP2 0xF6
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F6(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F6];
+
+ obj->oscDeltaCode25 = obj->oscDeltaCode25 + (value & (OSC_DELTA_CODE_25_0 |
+ OSC_DELTA_CODE_25_1 |
+ OSC_DELTA_CODE_25_2 |
+ OSC_DELTA_CODE_25_3 |
+ OSC_DELTA_CODE_25_4 |
+ OSC_DELTA_CODE_25_5 |
+ OSC_DELTA_CODE_25_6 |
+ OSC_DELTA_CODE_25_7));
+}
+
+/**
+ * @brief ConvOtp2F7
+ *
+ * Convert OTP2 0xF7
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F7(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F7];
+
+ obj->oscDeltaCode80 = obj->oscDeltaCode80 + (value & (OSC_DELTA_CODE_80_0 |
+ OSC_DELTA_CODE_80_1 |
+ OSC_DELTA_CODE_80_2 |
+ OSC_DELTA_CODE_80_3 |
+ OSC_DELTA_CODE_80_4 |
+ OSC_DELTA_CODE_80_5 |
+ OSC_DELTA_CODE_80_6 |
+ OSC_DELTA_CODE_80_7));
+}
+
+/**
+ * @brief ConvOtp2F8
+ *
+ * Convert OTP2 0xF8
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F8(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_F8];
+
+ obj->adc1DeltaCodeT25V200 = obj->adc1DeltaCodeT25V200 + (value & (ADC1_DELTA_CODE_25_200MV_0 |
+ ADC1_DELTA_CODE_25_200MV_1 |
+ ADC1_DELTA_CODE_25_200MV_2 |
+ ADC1_DELTA_CODE_25_200MV_3 |
+ ADC1_DELTA_CODE_25_200MV_4 |
+ ADC1_DELTA_CODE_25_200MV_5 |
+ ADC1_DELTA_CODE_25_200MV_6 |
+ ADC1_DELTA_CODE_25_200MV_7));
+}
+
+/**
+ * @brief ConvOtp2F9
+ *
+ * Convert OTP2 0xF9
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2F9(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2FA
+ *
+ * Convert OTP2 0xFA
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FA(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_FA];
+
+ obj->adc1DeltaCodeT25V100 = obj->adc1DeltaCodeT25V100 + (value & (ADC1_DELTA_CODE_25_100MV_0 |
+ ADC1_DELTA_CODE_25_100MV_1 |
+ ADC1_DELTA_CODE_25_100MV_2 |
+ ADC1_DELTA_CODE_25_100MV_3 |
+ ADC1_DELTA_CODE_25_100MV_4 |
+ ADC1_DELTA_CODE_25_100MV_5 |
+ ADC1_DELTA_CODE_25_100MV_6 |
+ ADC1_DELTA_CODE_25_100MV_7));
+}
+
+/**
+ * @brief ConvOtp2FB
+ *
+ * Convert OTP2 0xFB
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FB(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2FC
+ *
+ * Convert OTP2 0xFC
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FC(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp2[OTP2_OFFSET_FC];
+
+ tmp = value & ADC1_DELTA_CODE_25_100MV_8;
+ obj->adc1DeltaCodeT25V100 = obj->adc1DeltaCodeT25V100 + (tmp << 8);
+
+ obj->adc2DeltaCodeT25V100 = obj->adc2DeltaCodeT25V100 + ((value & (ADC2_DELTA_CODE_25_100MV_0 |
+ ADC2_DELTA_CODE_25_100MV_1 |
+ ADC2_DELTA_CODE_25_100MV_2 |
+ ADC2_DELTA_CODE_25_100MV_3 |
+ ADC2_DELTA_CODE_25_100MV_4 |
+ ADC2_DELTA_CODE_25_100MV_5 |
+ ADC2_DELTA_CODE_25_100MV_6)) >> 1);
+}
+
+/**
+ * @brief ConvOtp2FD
+ *
+ * Convert OTP2 0xFD
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FD(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp2FE
+ *
+ * Convert OTP2 0xFE
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FE(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp2[OTP2_OFFSET_FE];
+
+ obj->adc2DeltaCodeT25V200 = obj->adc2DeltaCodeT25V200 + (value & (ADC2_DELTA_CODE_25_200MV_0 |
+ ADC2_DELTA_CODE_25_200MV_1 |
+ ADC2_DELTA_CODE_25_200MV_2 |
+ ADC2_DELTA_CODE_25_200MV_3 |
+ ADC2_DELTA_CODE_25_200MV_4 |
+ ADC2_DELTA_CODE_25_200MV_5 |
+ ADC2_DELTA_CODE_25_200MV_6 |
+ ADC2_DELTA_CODE_25_200MV_7));
+}
+
+/**
+ * @brief ConvOtp2FF
+ *
+ * Convert OTP2 0xFF
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp2FF(OtpDataType *obj)
+{
+}
+
+/**
+ * @brief ConvOtp370
+ *
+ * Convert OTP3 0x70
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp370(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp3[OTP3_OFFSET_70];
+
+ obj->deltaVref = obj->deltaVref + ((value & DELTA_VREF_4) << 4);
+
+ obj->deltaET = obj->deltaET + ((value & (DELTA_ET_2 | DELTA_ET_3)) << 1);
+
+ obj->aveIT25 = obj->aveIT25 + (value & (AVE_IT_25_3 | AVE_IT_25_4 | AVE_IT_25_5 | AVE_IT_25_6 | AVE_IT_25_7));
+}
+
+/**
+ * @brief ConvOtp371
+ *
+ * Convert OTP3 0x71
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp371(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp3[OTP3_OFFSET_71];
+
+ tmp = value & (AVE_IT_25_8 |
+ AVE_IT_25_9 |
+ AVE_IT_25_10 |
+ AVE_IT_25_11 |
+ AVE_IT_25_12 |
+ AVE_IT_25_13 |
+ AVE_IT_25_14 |
+ AVE_IT_25_15);
+ obj->aveIT25 = obj->aveIT25 + (tmp << 8);
+}
+
+/**
+ * @brief ConvOtp372
+ *
+ * Convert OTP3 0x72
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp372(OtpDataType *obj)
+{
+ _otp_u8_ value;
+
+ value = obj->otp3[OTP3_OFFSET_72];
+
+ obj->indexAdc2V200T25 = obj->indexAdc2V200T25 + ((value & INDEX_ADC2_200_25_4) << 4);
+
+ obj->indexAdc1V100T25 = obj->indexAdc1V100T25 + ((value & INDEX_ADC1_100_25_4) << 3);
+
+ obj->indexAdc1V200T25 = obj->indexAdc1V200T25 + ((value & INDEX_ADC1_200_25_4) << 2);
+
+ obj->aveIT80 = obj->aveIT80 + (value & (AVE_IT_80_3 | AVE_IT_80_4 | AVE_IT_80_5 | AVE_IT_80_6 | AVE_IT_80_7));
+}
+
+/**
+ * @brief ConvOtp373
+ *
+ * Convert OTP3 0x73
+ *
+ * @para obj address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void ConvOtp373(OtpDataType *obj)
+{
+ _otp_u8_ value;
+ _otp_u16_ tmp;
+
+ value = obj->otp3[OTP3_OFFSET_73];
+
+ tmp = value & (AVE_IT_80_8 |
+ AVE_IT_80_9 |
+ AVE_IT_80_10 |
+ AVE_IT_80_11 |
+ AVE_IT_80_12 |
+ AVE_IT_80_13 |
+ AVE_IT_80_14 |
+ AVE_IT_80_15);
+ obj->aveIT80 = obj->aveIT80 + (tmp << 8);
+}
+
+#define CONV_FUNC_PTR_NULL (0)
+
+typedef void (*ConvFuncPtr)(OtpDataType *obj);
+
+static ConvFuncPtr ConvFuncTable[] = {
+ ConvOtp1E0,
+ ConvOtp1E1,
+ ConvOtp1E2,
+ ConvOtp1E3,
+
+ ConvOtp2F0,
+ ConvOtp2F1,
+ ConvOtp2F2,
+ ConvOtp2F3,
+ ConvOtp2F4,
+ ConvOtp2F5,
+ ConvOtp2F6,
+ ConvOtp2F7,
+ ConvOtp2F8,
+ ConvOtp2F9,
+ ConvOtp2FA,
+ ConvOtp2FB,
+ ConvOtp2FC,
+ ConvOtp2FD,
+ ConvOtp2FE,
+ ConvOtp2FF,
+
+ ConvOtp370,
+ ConvOtp371,
+ ConvOtp372,
+ ConvOtp373,
+
+ CONV_FUNC_PTR_NULL,
+};
+
+/**
+ * @brief CheckOtpISEmpty
+ *
+ * Check OTP is empty or not
+ *
+ * @para data address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void CheckOtpISEmpty(OtpDataType *data)
+{
+ _otp_u8_ idx;
+
+ /// [AT-PM] : Check OTP1 ; 01/25/2013
+ idx = 0;
+ while(idx < OTP1_SIZE)
+ {
+ if(data->otp1[idx] != 0)
+ {
+ data->empty = OTP_IS_NOT_EMPTY;
+ return;
+ }
+ }
+
+ /// [AT-PM] : Check OTP2 ; 01/25/2013
+ idx = 0;
+ while(idx < OTP2_SIZE)
+ {
+ if(data->otp2[idx] != 0)
+ {
+ data->empty = OTP_IS_NOT_EMPTY;
+ return;
+ }
+ }
+
+ /// [AT-PM] : Check OTP3 ; 01/25/2013
+ idx = 0;
+ while(idx < OTP3_SIZE)
+ {
+ if(data->otp3[idx] != 0)
+ {
+ data->empty = OTP_IS_NOT_EMPTY;
+ return;
+ }
+ }
+
+ /// [AT-PM] : Set OTP is empty ; 01/25/2013
+ data->empty = OTP_IS_EMPTY;
+}
+
+/// =============================================
+/// [AT-PM] : Extern function region
+/// =============================================
+
+/**
+ * @brief UpiConvertOtp
+ *
+ * Convert OTP register value to readable value
+ *
+ * @para data address of OtpDataType
+ * @return _UPI_NULL_
+ */
+void UpiConvertOtp(OtpDataType *data)
+{
+ _otp_u8_ idx;
+
+ UG31_LOGI("[%s]: OTP version : %d.%02x\n", __func__, UG31XX_OTP_VERSION_MAIN, UG31XX_OTP_VERSION_SUB);
+
+ /// [AT-PM] : Set version ; 01/25/2013
+ data->versionMain = UG31XX_OTP_VERSION_MAIN;
+ data->versionSub = UG31XX_OTP_VERSION_SUB;
+
+ /// [AT-PM] : Conversion ; 01/23/2013
+ idx = 0;
+ while(1)
+ {
+ (*ConvFuncTable[idx])(data);
+
+ idx = idx + 1;
+ if(ConvFuncTable[idx] == CONV_FUNC_PTR_NULL)
+ {
+ break;
+ }
+ }
+
+ /// [AT-PM] : Check OTP is empty ; 01/25/2013
+ CheckOtpISEmpty(data);
+}
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.h
new file mode 100755
index 00000000..fffdc867
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_Otp.h
@@ -0,0 +1,84 @@
+/**
+ * @filename uG31xx_API_Otp.h
+ *
+ * Header of OTP conversion module
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+/// [AT-PM] : Product Type definition in OTP ; 01/23/2013
+enum UG31XX_PRODUCT_TYPE {
+ UG31XX_PRODUCT_TYPE_0 = 0,
+ UG31XX_PRODUCT_TYPE_1 = 1,
+ UG31XX_PRODUCT_TYPE_2 = 2,
+ UG31XX_PRODUCT_TYPE_3 = 3,
+};
+
+#define UG31XX_OTP_VERSION_MAIN (0x2013)
+#define UG31XX_OTP_VERSION_SUB (0x0110)
+
+#define OTP_IS_EMPTY (1)
+#define OTP_IS_NOT_EMPTY (0)
+
+#define OTP1_SIZE (4)
+#define OTP2_SIZE (16)
+#define OTP3_SIZE (4)
+
+typedef unsigned char _otp_u8_;
+typedef unsigned short _otp_u16_;
+
+typedef struct OtpDataST {
+
+ /// [AT-PM] : Version ; 01/23/2013
+ _otp_u16_ versionMain;
+ _otp_u16_ versionSub;
+ _otp_u8_ empty;
+
+ /// [AT-PM] : Raw data ; 01/23/2013
+ _otp_u8_ otp1[OTP1_SIZE];
+ _otp_u8_ otp2[OTP2_SIZE];
+ _otp_u8_ otp3[OTP3_SIZE];
+
+ /// [AT-PM] : Converted value ; 01/23/2013
+ _otp_u16_ adc1DeltaCodeT25V100;
+ _otp_u16_ adc1DeltaCodeT25V200;
+ _otp_u16_ adc2DeltaCodeT25V100;
+ _otp_u16_ adc2DeltaCodeT25V200;
+ _otp_u16_ aveIT25;
+ _otp_u16_ aveIT80;
+
+ _otp_u8_ bgrTune;
+
+ _otp_u8_ deltaET;
+ _otp_u8_ deltaVref;
+ _otp_u16_ devAddr;
+
+ _otp_u16_ ftIT;
+
+ _otp_u8_ indexAdc1V100T25;
+ _otp_u8_ indexAdc1V200T25;
+ _otp_u8_ indexAdc2V100T25;
+ _otp_u8_ indexAdc2V200T25;
+
+ _otp_u8_ oscDeltaCode25;
+ _otp_u8_ oscDeltaCode80;
+ _otp_u8_ otpCellEN;
+
+ _otp_u8_ productType;
+
+#if defined(uG31xx_OS_ANDROID)
+} __attribute__ ((packed)) OtpDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+} OtpDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiConvertOtp
+ *
+ * Convert OTP register value to readable value
+ *
+ * @para data address of OtpDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiConvertOtp(OtpDataType *data);
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.c b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.c
new file mode 100755
index 00000000..e23f858e
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.c
@@ -0,0 +1,1196 @@
+/**
+ * @filename uG31xx_API_System.cpp
+ *
+ * uG31xx system control
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#include "stdafx.h" //windows need this??
+#include "uG31xx_API.h"
+
+#if defined(uG31xx_OS_ANDROID)
+
+_upi_bool_ ReadGGBXFileToCellDataAndInitSetting(SystemDataType *obj)
+{
+ _sys_u8_ *p_start = _UPI_NULL_;
+ _sys_u8_ *p_end = _UPI_NULL_;
+ _sys_u16_ sum16=0;
+ _sys_s32_ i=0;
+
+ /*
+ * check GGBX_FILE tag
+ */
+ if(obj->ggbXBuf->ggb_tag != GGBX_FILE_TAG)
+ {
+ UG31_LOGE("[%s] GGBX file tag not correct. tag: %08X\n", __func__, obj->ggbXBuf->ggb_tag);
+ return (_UPI_FALSE_);
+ }
+
+ /*
+ * check GGBX_FILE checksum
+ */
+ p_start = (_sys_u8_ *)obj->ggbXBuf + sizeof(GGBX_FILE_HEADER);
+ p_end = p_start + obj->ggbXBuf->length - 1;
+ for (; p_start <= p_end; p_start++)
+ {
+ sum16 += *p_start;
+ }
+
+ /* check done. prepare copy data */
+ memset(obj->ggbCellTable, 0x00, sizeof(CELL_TABLE));
+ memset(obj->ggbParameter, 0x00, sizeof(CELL_PARAMETER));
+
+ p_start = (_sys_u8_ *)obj->ggbXBuf + sizeof(GGBX_FILE_HEADER);
+ for (i=0; i<obj->ggbXBuf->num_ggb; i++)
+ {
+ /* TODO: boundary checking */
+ /* TODO: select right ggb content by sku */
+ memcpy(obj->ggbParameter, p_start, sizeof(CELL_PARAMETER));
+ memcpy(obj->ggbCellTable, p_start + sizeof(CELL_PARAMETER), sizeof(CELL_TABLE));
+ p_start += (sizeof(CELL_PARAMETER) + sizeof(CELL_TABLE));
+ }
+ return (_UPI_TRUE_);
+}
+
+#else ///< else of defined(uG31xx_OS_ANDROID)
+
+ _upi_bool_ ReadGGBFileToCellDataAndInitSetting(SystemDataType *obj)
+{
+ FILE* stream;
+ _wfopen_s(&stream, obj->ggbFilename, _T("rb, ccs=UTF-8"));
+
+ memset(obj->ggbCellTable, 0x00, sizeof(CELL_TABLE));
+ memset(obj->ggbParameter, 0x00, sizeof(CELL_PARAMETER));
+
+ if(!stream)
+ {
+ return (_UPI_FALSE_);
+ }
+ if(fread(obj->ggbParameter, sizeof(char), sizeof(CELL_PARAMETER), stream) != sizeof(CELL_PARAMETER))
+ {
+ fclose(stream);
+ return (_UPI_FALSE_);
+ }
+ if(fread(obj->ggbCellTable, sizeof(char), sizeof(CELL_TABLE), stream) != sizeof(CELL_TABLE))
+ {
+ fclose(stream);
+ return (_UPI_FALSE_);
+ }
+
+ fclose(stream);
+
+ return (_UPI_TRUE_);
+}
+
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief GetCellNum
+ *
+ * Get cell number from ggbParameter->ICType
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void GetCellNum(SystemDataType *data)
+{
+ _sys_u8_ cellNum[6] = {1, 1, 2, 0, 2, 3};
+
+ data->cellNum = cellNum[data->ggbParameter->ICType];
+}
+
+/**
+ * @brief SetupAdcChopFunction
+ *
+ * Setup ADC chop function
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void SetupAdcChopFunction(SystemDataType *data)
+{
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_FW_CTRL,
+ data->ggbParameter->chopCtrl);
+}
+
+/**
+ * @brief SetupAdc1Queue
+ *
+ * Setup ADC1 conversion queue
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void SetupAdc1Queue(SystemDataType *data)
+{
+ _cap_u8_ adcQueue[4];
+
+ adcQueue[0] = SET_A_IT | SET_B_IT | SET_C_CURRENT | SET_D_CURRENT;
+ adcQueue[1] = SET_E_CURRENT | SET_F_CURRENT | SET_G_CURRENT | SET_H_CURRENT;
+ adcQueue[2] = SET_I_ET | SET_J_ET | SET_K_CURRENT | SET_L_CURRENT;
+ adcQueue[3] = SET_M_CURRENT | SET_N_CURRENT | SET_O_CURRENT | SET_P_CURRENT;
+
+ API_I2C_Write(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_ADC_CTR_A,
+ 4,
+ &adcQueue[0]);
+}
+
+/**
+ * @brief SetupAdc2Queue
+ *
+ * Set ADC2 conversion queue
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void SetupAdc2Quene(SystemDataType *data)
+{
+ _sys_u8_ adc2Queue[3];
+
+ /// [AT-PM] : Set sell type ; 01/31/2013
+ if(data->cellNum == 1)
+ {
+ adc2Queue[0] = SET_V1_VBAT1 | SET_V2_VBAT1 | SET_V3_VBAT1 | SET_V4_VBAT1;
+ adc2Queue[1] = SET_V5_VBAT1 | SET_V6_VBAT1 | SET_V7_VBAT1 | SET_V8_VBAT1;
+ adc2Queue[2] = SET_V9_VBAT1 | SET_V10_VBAT1 | SET_V11_VBAT1 | SET_V12_VBAT1;
+ }
+ else if(data->cellNum == 2)
+ {
+ adc2Queue[0] = SET_V1_VBAT1 | SET_V2_VBAT1 | SET_V3_VBAT2 | SET_V4_VBAT2;
+ adc2Queue[1] = SET_V5_VBAT1 | SET_V6_VBAT1 | SET_V7_VBAT2 | SET_V8_VBAT2;
+ adc2Queue[2] = SET_V9_VBAT1 | SET_V10_VBAT1 | SET_V11_VBAT2 | SET_V12_VBAT2;
+ }
+ else if(data->cellNum == 3)
+ {
+ adc2Queue[0] = SET_V1_VBAT1 | SET_V2_VBAT1 | SET_V3_VBAT2 | SET_V4_VBAT2;
+ adc2Queue[1] = SET_V5_VBAT3 | SET_V6_VBAT3 | SET_V7_VBAT1 | SET_V8_VBAT1;
+ adc2Queue[2] = SET_V9_VBAT2 | SET_V10_VBAT2 | SET_V11_VBAT3 | SET_V12_VBAT3;
+ }
+ else
+ {
+ /// [AT-PM] : 1-cell ; 01/31/2013
+ adc2Queue[0] = SET_V1_VBAT1 | SET_V2_VBAT1 | SET_V3_VBAT1 | SET_V4_VBAT1;
+ adc2Queue[1] = SET_V5_VBAT1 | SET_V6_VBAT1 | SET_V7_VBAT1 | SET_V8_VBAT1;
+ adc2Queue[2] = SET_V9_VBAT1 | SET_V10_VBAT1 | SET_V11_VBAT1 | SET_V12_VBAT1;
+ }
+ API_I2C_Write(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ADC_V1, 3, &adc2Queue[0]);
+}
+
+/**
+ * @brief EnableCbc
+ *
+ * Enable CBC function
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void EnableCbc(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_INTR_CTRL_B, &tmp8);
+ tmp8 = tmp8 & (~(INTR_CTRL_B_CBC_32_EN | INTR_CTRL_B_CBC_21_EN));
+ tmp8 = tmp8 | (INTR_CTRL_B_ET_EN | INTR_CTRL_B_IT_EN | INTR_CTRL_B_RID_EN);
+ tmp8 = tmp8 | (data->ggbParameter->cbcEnable << 4);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_INTR_CTRL_B, tmp8);
+}
+
+/**
+ * @brief EnableICType
+ *
+ * Enable IC type
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void EnableICType(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CELL_EN, &tmp8);
+ tmp8 = tmp8 & (~CELL_EN_APPLICATION);
+ tmp8 = tmp8 | (data->ggbParameter->ICType << 2);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CELL_EN, tmp8);
+}
+
+/**
+ * @brief ConfigGpioFunction
+ *
+ * Configure GPIO1/2 function
+ *
+ * @para setting GPIO1/2 setting
+ * @return register value
+ */
+_sys_u8_ ConfigGpioFunction(_sys_u8_ setting)
+{
+ _sys_u8_ gpioSelData = 0;
+
+ if(setting & FUN_GPIO)
+ {
+ gpioSelData = 0;
+ }
+ if(setting & FUN_ALARM) //select Alarm function
+ {
+ gpioSelData = 1;
+ }
+ if(setting & FUN_CBC_EN21) //cbc21 enable
+ {
+ gpioSelData = 2;
+ }if(setting & FUN_CBC_EN32) //cbc32 Enable
+ {
+ gpioSelData = 3;
+ }
+ if(setting & FUN_PWM) //PWM function, set PWM cycle
+ {
+ gpioSelData = 4;
+ }
+ return (gpioSelData);
+}
+
+/**
+ * @brief ConfigureGpio
+ *
+ * Configure GPIO function
+ *
+ * @para data SystemDataType
+ * @return _UPI_NULL_
+ */
+void ConfigureGpio(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_SingleRead(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_INTR_CTRL_A,
+ &tmp8);
+ tmp8 = tmp8 | (ConfigGpioFunction(data->ggbParameter->gpio1) << 2);
+ tmp8 = tmp8 | (ConfigGpioFunction(data->ggbParameter->gpio2) << 5);
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_INTR_CTRL_A,
+ tmp8);
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_INTR_CTRL_D,
+ data->ggbParameter->gpio34);
+}
+
+#define ADC_FAIL_CRITERIA (10)
+
+/**
+ * @brief CheckAdcStatusFail
+ *
+ * Check ADC status is fail or not
+ *
+ * @para pUg31xx address of SystemDataType
+ * @return _UPI_TRUE_ if fail
+ */
+_upi_bool_ CheckAdcStatusFail(SystemDataType *data)
+{
+ API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_COUNTER_LOW,
+ REG_COUNTER_HIGH - REG_COUNTER_LOW + 1,
+ (unsigned char *)&data->adcCheckData.regCounter);
+
+ API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_VBAT1_LOW,
+ REG_AVE_VBAT1_HIGH - REG_AVE_VBAT1_LOW + 1,
+ (unsigned char *)&data->adcCheckData.regVbat1Ave);
+
+ /// [AT-PM] : Compare counter register ; 01/27/2013
+ if(data->adcCheckData.regCounter == data->adcCheckData.lastCounter)
+ {
+ data->adcCheckData.failCounterCurrent = data->adcCheckData.failCounterCurrent + 1;
+ UG31_LOGI("[%s]: Counter fixed (%d) ... %d\n", __func__,
+ data->adcCheckData.regCounter, data->adcCheckData.failCounterCurrent);
+ }
+ else
+ {
+ data->adcCheckData.failCounterCurrent = 0;
+ }
+ data->adcCheckData.lastCounter = data->adcCheckData.regCounter;
+
+ /// [AT-PM] : Compre VBat1 register ; 01/27/2013
+ if(data->adcCheckData.regVbat1Ave == data->adcCheckData.lastVBat1Ave)
+ {
+ data->adcCheckData.failCounterVoltage = data->adcCheckData.failCounterVoltage + 1;
+ UG31_LOGI("[%s]: VBat1 fixed (%d) ... %d\n", __func__,
+ data->adcCheckData.regVbat1Ave, data->adcCheckData.failCounterVoltage);
+ }
+ else
+ {
+ data->adcCheckData.failCounterVoltage = 0;
+ }
+ data->adcCheckData.lastVBat1Ave = data->adcCheckData.regVbat1Ave;
+
+ /// [AT-PM] : Check ADC fail criteria ; 01/27/2013
+ if(data->adcCheckData.failCounterCurrent > ADC_FAIL_CRITERIA)
+ {
+ data->adcCheckData.failCounterCurrent = 0;
+ return (_UPI_TRUE_);
+ }
+ if(data->adcCheckData.failCounterVoltage > ADC_FAIL_CRITERIA)
+ {
+ data->adcCheckData.failCounterVoltage = 0;
+ return (_UPI_TRUE_);
+ }
+ return (_UPI_FALSE_);
+}
+
+/**
+ * @brief DecimateRst
+ *
+ * Decimate reset filter of ADC
+ *
+ * @return _UPI_NULL_
+ */
+void DecimateRst(void)
+{
+ _sys_u8_ tmp8;
+
+ tmp8 = 0x00;
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ tmp8 = tmp8 & (~ALARM_EN_DECIMATE_RST);
+ API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ tmp8 = tmp8 | ALARM_EN_DECIMATE_RST;
+ API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ UG31_LOGI("[%s]: DECIMATE_RST\n", __func__);
+}
+
+/**
+ * @brief AlarmEnable
+ *
+ * Enable alarm
+ *
+ * @para alarm REG_ALARM_EN bits
+ * @return NULL
+ */
+void AlarmEnable(_sys_u8_ alarm)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ tmp8 = tmp8 | alarm;
+ API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+}
+
+/**
+ * @brief AlarmDisable
+ *
+ * Disable alarm
+ *
+ * @para alarm REG_ALARM_EN bits
+ * @return NULL
+ */
+void AlarmDisable(_sys_u8_ alarm)
+{
+ _sys_u8_ tmp8;
+
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+ tmp8 = tmp8 & (~alarm);
+ API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM_EN, 1, &tmp8);
+}
+
+#define SYS_ALARM_STS_UV1 (ALARM2_STATUS_UV1_ALARM)
+#define SYS_ALARM_STS_OV1 (ALARM2_STATUS_OV1_ALARM)
+#define SYS_ALARM_STS_UV2 (ALARM2_STATUS_UV2_ALARM)
+#define SYS_ALARM_STS_OV2 (ALARM2_STATUS_OV2_ALARM)
+#define SYS_ALARM_STS_UV3 (ALARM2_STATUS_UV3_ALARM)
+#define SYS_ALARM_STS_OV3 (ALARM2_STATUS_OV3_ALARM)
+#define SYS_ALARM_STS_UET (ALARM1_STATUS_UET_ALARM<<8)
+#define SYS_ALARM_STS_OET (ALARM1_STATUS_OET_ALARM<<8)
+#define SYS_ALARM_STS_UIT (ALARM1_STATUS_UIT_ALARM<<8)
+#define SYS_ALARM_STS_OIT (ALARM1_STATUS_OIT_ALARM<<8)
+#define SYS_ALARM_STS_DOC (ALARM1_STATUS_DOC_ALARM<<8)
+#define SYS_ALARM_STS_COC (ALARM1_STATUS_COC_ALARM<<8)
+
+/**
+ * @brief ProcUVAlarm
+ *
+ * UV alarm function
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+void ProcUVAlarm(SystemDataType *data)
+{
+ _sys_u8_ tmp8[4];
+
+ /// [AT-PM] : Check alarm is enable or not ; 04/08/2013
+ if(!(data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_UV))
+ {
+ /// [AT-PM] : Disable UV and OV alarm ; 04/08/2013
+ AlarmDisable(ALARM_EN_V1_ALARM_EN);
+ return;
+ }
+
+ if(data->uvAlarm.state == _UPI_TRUE_)
+ {
+ /// [AT-PM] : UV alarm has been set -> Wait for OV alarm ; 04/08/2013
+ if(data->alarmSts & SYS_ALARM_STS_OV1)
+ {
+ data->uvAlarm.state = _UPI_FALSE_;
+
+ /// [AT-PM] : Release UV alarm by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_V1_ALARM_EN);
+
+ /// [AT-PM] : UV release threshold reached -> set alarm threshold ; 04/08/2013
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ tmp8[2] = (_sys_u8_)(data->uvAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uvAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ /// [AT-PM] : UV state -> set release threshold ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->uvAlarm.releaseThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->uvAlarm.releaseThrd >> 8);
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ }
+ else
+ {
+ /// [AT-PM] : Normal state ; 04/08/2013
+ if(data->alarmSts & SYS_ALARM_STS_UV1)
+ {
+ data->uvAlarm.state = _UPI_TRUE_;
+
+ /// [AT-PM] : Release UV alarm by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_V1_ALARM_EN);
+
+ /// [AT-PM] : UV alarm reached -> set release threshold ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->uvAlarm.releaseThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->uvAlarm.releaseThrd >> 8);
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ else
+ {
+ /// [AT-PM] : Normal state -> set alarm threshold ; 04/08/2013
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ tmp8[2] = (_sys_u8_)(data->uvAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uvAlarm.alarmThrd >> 8);
+ }
+ }
+
+ /// [AT-PM] : Set alarm threshold ; 04/08/2013
+ API_I2C_Write(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_OV1_LOW, 4, &tmp8[0]);
+
+ /// [AT-PM] : Enable UV and OV alarm ; 04/08/2013
+ AlarmEnable(ALARM_EN_V1_ALARM_EN);
+}
+
+/**
+ * @brief ProcETAlarm
+ *
+ * UET and OET alarm function
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+void ProcETAlarm(SystemDataType *data)
+{
+ _sys_u8_ tmp8[4];
+
+ /// [AT-PM] : Check alarm is enable or not ; 04/08/2013
+ if(!(data->ggbParameter->alarmEnable & (CELL_PARAMETER_ALARM_EN_UET | CELL_PARAMETER_ALARM_EN_OET)))
+ {
+ /// [AT-PM] : Disable UV and OV alarm ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+ return;
+ }
+
+ if(data->uetAlarm.state == _UPI_TRUE_)
+ {
+ /// [AT-PM] : UET alarm state -> wait for OET alarm ; 04/08/2013
+ if(data->alarmSts & SYS_ALARM_STS_OET)
+ {
+ data->uetAlarm.state = _UPI_FALSE_;
+
+ /// [AT-PM] : Release by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+
+ /// [AT-PM] : UET release met -> set UET and OET alarm ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->oetAlarm.alarmThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->oetAlarm.alarmThrd >> 8);
+ tmp8[2] = (_sys_u8_)(data->uetAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uetAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ /// [AT-PM] : Wait OET alarm ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->uetAlarm.releaseThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->uetAlarm.releaseThrd >> 8);
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ }
+ else if(data->oetAlarm.state == _UPI_TRUE_)
+ {
+ /// [AT-PM] : OET alarm state -> wait for UET alarm ; 04/08/2013
+ if(data->alarmSts & SYS_ALARM_STS_UET)
+ {
+ data->oetAlarm.state = _UPI_FALSE_;
+
+ /// [AT-PM] : Release by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+
+ /// [AT-PM] : OET release met -> set UET and OET alarm ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->oetAlarm.alarmThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->oetAlarm.alarmThrd >> 8);
+ tmp8[2] = (_sys_u8_)(data->uetAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uetAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ /// [AT-PM] : Wait UET alarm ; 04/08/2013
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ tmp8[2] = (_sys_u8_)(data->oetAlarm.releaseThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->oetAlarm.releaseThrd >> 8);
+ }
+ }
+ else
+ {
+ /// [AT-PM] : Normal state ; 04/08/2013
+ if((data->alarmSts & SYS_ALARM_STS_UET) &&
+ (data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_UET))
+ {
+ data->uetAlarm.state = _UPI_TRUE_;
+
+ /// [AT-PM] : Release by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+
+ /// [AT-PM] : UET is set -> set UET release threshold ; 04/08/2013
+ tmp8[0] = (_sys_u8_)(data->uetAlarm.releaseThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->uetAlarm.releaseThrd >> 8);
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ else if((data->alarmSts & SYS_ALARM_STS_OET) &&
+ (data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_OET))
+ {
+ data->oetAlarm.state = _UPI_TRUE_;
+
+ /// [AT-PM] : Release by disable ; 04/08/2013
+ AlarmDisable(ALARM_EN_ET_ALARM_EN);
+
+ /// [AT-PM] : OET is set -> set OET release threshold ; 04/08/2013
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ tmp8[2] = (_sys_u8_)(data->oetAlarm.releaseThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->oetAlarm.releaseThrd >> 8);
+ }
+ else
+ {
+ /// [AT-PM] : Set OET alarm threshold ; 04/08/2013
+ if(data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_OET)
+ {
+ tmp8[0] = (_sys_u8_)(data->oetAlarm.alarmThrd & 0x00ff);
+ tmp8[1] = (_sys_u8_)(data->oetAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ tmp8[0] = 0xff;
+ tmp8[1] = 0x7f;
+ }
+ /// [AT-PM] : Set UET alarm threshold ; 04/11/2013
+ if(data->ggbParameter->alarmEnable & CELL_PARAMETER_ALARM_EN_UET)
+ {
+ tmp8[2] = (_sys_u8_)(data->uetAlarm.alarmThrd & 0x00ff);
+ tmp8[3] = (_sys_u8_)(data->uetAlarm.alarmThrd >> 8);
+ }
+ else
+ {
+ tmp8[2] = 0x00;
+ tmp8[3] = 0x00;
+ }
+ }
+ }
+
+ /// [AT-PM] : Set alarm threshold ; 04/08/2013
+ API_I2C_Write(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_EXTR_OVER_TEMP_LOW, 4, &tmp8[0]);
+
+ /// [AT-PM] : Enable UV and OV alarm ; 04/08/2013
+ AlarmEnable(ALARM_EN_ET_ALARM_EN);
+}
+
+/**
+ * @brief EnableAlarm
+ *
+ * Set UV, UET, and OET alarm functions
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+void EnableAlarm(SystemDataType *data)
+{
+ /// [AT-PM] : UV alarm ; 04/08/2013
+ ProcUVAlarm(data);
+
+ /// [AT-PM] : UET and OET alarm ; 04/08/2013
+ ProcETAlarm(data);
+}
+
+/// =============================================
+/// [AT-PM] : Extern function region
+/// =============================================
+
+/**
+ * @brief UpiInitSystemData
+ *
+ * Initialize system variables
+ *
+ * @para data address of SystemDataType
+ * @return SYSTEM_RTN_CODE
+ */
+SYSTEM_RTN_CODE UpiInitSystemData(SystemDataType *data)
+{
+ /// [AT-PM] : Initialize variables ; 01/30/2013
+ data->preITAve = 0;
+ data->cellNum = 0;
+
+ /// [AT-PM] : Load GGB file ; 01/30/2013
+ UG31_LOGI("[%s]: Read GGB\n", __func__);
+ #if defined(uG31xx_OS_ANDROID)
+ if(!ReadGGBXFileToCellDataAndInitSetting(data))
+ #else
+ if(!ReadGGBFileToCellDataAndInitSetting(data))
+ #endif
+ {
+ return (SYSTEM_RTN_READ_GGB_FAIL);
+ }
+
+ /// [AT-PM] : Set cell number ; 01/31/2013
+ GetCellNum(data);
+ return (SYSTEM_RTN_PASS);
+}
+
+/**
+ * @brief UpiCheckICActive
+ *
+ * Check IC is actived or not
+ *
+ * @return _UPI_TRUE_ if uG31xx is not actived
+ */
+_upi_bool_ UpiCheckICActive(void)
+{
+ _upi_u8_ tmp;
+
+ if(!API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_MODE, 1, &tmp))
+ {
+ UG31_LOGI("[%s]: Get GG_RUN fail.\n", __func__);
+ return (_UPI_TRUE_);
+ }
+
+ if((tmp & MODE_GG_RUN) == GG_RUN_OPERATION_MODE)
+ {
+ UG31_LOGI("[%s]: uG31xx is actived.\n", __func__);
+ return (_UPI_FALSE_);
+ }
+ UG31_LOGI("[%s]: uG31xx is NOT actived.\n", __func__);
+ return (_UPI_TRUE_);
+}
+
+/**
+ * @brief UpiActiveUg31xx
+ *
+ * Active uG31xx
+ *
+ * @return SYSTEM_RTN_CODE
+ */
+SYSTEM_RTN_CODE UpiActiveUg31xx(void)
+{
+ _sys_u8_ tmp8;
+
+ /// [AT-PM] : Reset uG31xx ; 01/31/2013
+ tmp8 = PORDET_W_SOFTRESET | IO1DATA_W_HIGH;
+ if(!API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CTRL1, 1, &tmp8))
+ {
+ return (SYSTEM_RTN_I2C_FAIL);
+ }
+ tmp8 = IO1DATA_W_HIGH;
+ if(!API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CTRL1, 1, &tmp8))
+ {
+ return (SYSTEM_RTN_I2C_FAIL);
+ }
+
+ /// [AT-PM] : Active uG31xx ; 01/31/2013
+ tmp8 = CTRL1_GG_RST | IO1DATA_W_HIGH;
+ if(!API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CTRL1, 1, &tmp8))
+ {
+ return (SYSTEM_RTN_I2C_FAIL);
+ }
+ tmp8 = GG_RUN_OPERATION_MODE;
+ if(!API_I2C_Write(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_MODE, 1, &tmp8))
+ {
+ return (SYSTEM_RTN_I2C_FAIL);
+ }
+
+ /// [AT-PM] : Delay 255mS for system stable ; 01/31/2013
+ SleepMiniSecond(255); //2012/08/29/Jacky, need wait 255 ms
+ return (SYSTEM_RTN_PASS);
+}
+
+/**
+ * @brief UpiSetupAdc
+ *
+ * Setup ADC configurations
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiSetupAdc(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ /// [AT-PM] : Set ADC chop function ; 01/31/2013
+ SetupAdcChopFunction(data);
+
+ /// [AT-PM] : Set ADC1 queue ; 01/31/2013
+ SetupAdc1Queue(data);
+
+ /// [AT-PM] : Set ADC2 queue ; 01/31/2013
+ SetupAdc2Quene(data);
+
+ /// [AT-PM] : Enable ADC ; 01/31/2013
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_INTR_CTRL_A, &tmp8);
+ tmp8 = tmp8 | (INTR_CTRL_A_ADC2_EN | INTR_CTRL_A_ADC1_EN);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_INTR_CTRL_A, tmp8);
+
+ /// [AT-PM] : Decimate reset ; 01/31/2013
+ DecimateRst();
+
+ /// [AT-PM] : Enable CBC function ; 01/31/2013
+ EnableCbc(data);
+}
+
+/**
+ * @brief UpiSetupSystem
+ *
+ * Setup uG31xx system
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiSetupSystem(SystemDataType *data)
+{
+ _sys_u8_ tmp8;
+
+ /// [AT-PM] : Enable IC type ; 01/31/2013
+ EnableICType(data);
+
+ /// [AT-PM] : Configure GPIO ; 01/31/2013
+ ConfigureGpio(data);
+
+ /// [AT-PM] : Enable cell ; 01/31/2013
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CELL_EN, &tmp8);
+ tmp8 = tmp8 | (CELL_EN1 | CELL_EN0);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_CELL_EN, tmp8);
+}
+
+#define OSC_CNT_TARG 512 //oscCntTarg[9:0]
+
+/**
+ * @brief UpiCalibrationOsc
+ *
+ * OSC calibration
+ * oscCnt25[9:0] = oscCntTarg[9:0] + oscDeltaCode25[7:0]
+ * oscCnt80[9:0] = oscCntTarg[9:0] + oscDeltaCode80[7:0]
+ * oscCnt[9:0] = m*ITcode[15:8] + C[9:0]
+ * m = (oscCnt80[9:0]-oscCnt25[9:0])/(iTcode80[7:0]-iTcode25[7:0])
+ * c = oscCnt25[9:0] - m*ITcode25[7:0]
+ * write oscCnt[9:0] to register 0x97-98
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiCalibrationOsc(SystemDataType *data)
+{
+ _sys_u16_ u16Temp;
+
+ _sys_u16_ oscCnt25;
+ _sys_u16_ oscCnt80; //10 bits
+ _sys_u16_ oscDeltaCode25;
+ _sys_u16_ oscDeltaCode80; //
+ _sys_u16_ targetOscCnt; //target osc
+
+ _sys_u16_ varM;
+ _sys_u16_ varC;
+
+ _sys_u16_ aveIT;
+
+ /// [AT-PM] : Calculate m & C ; 01/25/2013
+ oscDeltaCode25 = (_sys_u16_)data->otpData->oscDeltaCode25;
+ oscDeltaCode80 = (_sys_u16_)data->otpData->oscDeltaCode80;
+
+ oscCnt25 = OSC_CNT_TARG + oscDeltaCode25;
+ oscCnt80 = OSC_CNT_TARG + oscDeltaCode80;
+
+ varM = (oscCnt80 - oscCnt25)/(data->otpData->aveIT80 - data->otpData->aveIT25);
+ varC = oscCnt25 - varM*(data->otpData->aveIT25);
+
+ /// [AT-PM] : Read ITAve ; 01/27/2013
+ API_I2C_Read(NORMAL,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_AVE_IT_LOW,
+ REG_AVE_IT_HIGH - REG_AVE_IT_LOW + 1,
+ (_sys_u8_ *)&aveIT);
+
+ /// [AT-PM] : Calculate target OSC cnt ; 01/25/2013
+ targetOscCnt = varM*(aveIT/256) + varC;
+ if(targetOscCnt & 0x8000) //check +/-
+ {
+ u16Temp = (_sys_u16_)(targetOscCnt & 0x1fff);
+ u16Temp |= 0x0200; // minus
+ } else{
+ u16Temp = (_sys_u16_)targetOscCnt; //positive data
+ }
+
+ /// [AT-PM] : Write to register 0x97-98 ; 01/25/2013
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ OSCTUNE_CNTB,
+ (_sys_u8_)(u16Temp >> 8));
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ OSCTUNE_CNTA,
+ (_sys_u8_)u16Temp );
+}
+
+/**
+ * @brief UpiAdcStatus
+ *
+ * Check ADC status
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiAdcStatus(SystemDataType *data)
+{
+ if(CheckAdcStatusFail(data) == _UPI_TRUE_) //check ADC Code frozen
+ {
+ DecimateRst();
+ }
+}
+
+#define BACKUP_TIME_BYTE3 (REG_COC_LOW)
+#define BACKUP_TIME_BYTE2 (REG_OTP_CTRL)
+#define BACKUP_NAC_HIGH (REG_CBC21_LOW)
+#define BACKUP_NAC_LOW (REG_CBC21_HIGH)
+#define BACKUP_LMD_HIGH (REG_CBC32_LOW)
+#define BACKUP_LMD_LOW (REG_CBC32_HIGH)
+#define BACKUP_TABLE_UPDATE_IDX (REG_COC_HIGH)
+#define BACKUP_DELTA_CAP_HIGH (REG_DOC_LOW)
+#define BACKUP_DELTA_CAP_LOW (REG_DOC_HIGH)
+#define BACKUP_ADC1_CONV_TIME (REG_COC_HIGH)
+
+/**
+ * @brief UpiLoadBatInfoFromIC
+ *
+ * Load battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiLoadBatInfoFromIC(SystemDataType *data)
+{
+ _sys_u8_ *u8Ptr;
+ _sys_u8_ u8Temp;
+ _sys_u8_ u8TempHigh;
+ _sys_u16_ u16Temp;
+
+ //Load the time tag
+ u8Ptr = (_sys_u8_ *)&data->timeTagFromIC;
+ *u8Ptr = 0;
+ *(u8Ptr + 1) = 0;
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TIME_BYTE2, &u8Temp);
+ *(u8Ptr + 2) = u8Temp;
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TIME_BYTE3, &u8Temp);
+ *(u8Ptr + 3) = u8Temp;
+
+ //Load the NAC
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_NAC_HIGH, &u8TempHigh);
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_NAC_LOW, &u8Temp);
+ data->rmFromIC = (_sys_u16_)u8TempHigh;
+ data->rmFromIC = data->rmFromIC*256 + u8Temp;
+
+ // Load LMD
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_LMD_HIGH, &u8TempHigh);
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_LMD_LOW, &u8Temp);
+ data->fccFromIC = (_sys_u16_)u8TempHigh;
+ data->fccFromIC = data->fccFromIC*256 + u8Temp;
+ UG31_LOGE("[%s]:timeTag =%u/%x ms,NAC = %d mAh,LMD = %dmAh\n",
+ __func__,
+ data->timeTagFromIC,
+ data->timeTagFromIC,
+ data->rmFromIC,
+ data->fccFromIC);
+
+ /// [AT-PM] : Load table update index ; 02/10/2013
+ API_I2C_SingleRead(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TABLE_UPDATE_IDX, &u8Temp);
+ data->tableUpdateIdxFromIC = u8Temp & 0x07;
+ UG31_LOGI("[%s]: Table Update Index From IC = %d (0x%02x)\n", __func__, data->tableUpdateIdxFromIC, u8Temp);
+
+ /// [AT-PM] : Load delta capacity ; 02/10/2013
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_DELTA_CAP_HIGH, &u8TempHigh);
+ API_I2C_SingleRead(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_DELTA_CAP_LOW, &u8Temp);
+ data->deltaCapFromIC = (_sys_u16_)u8TempHigh;
+ data->deltaCapFromIC = data->deltaCapFromIC*256 + u8Temp;
+ UG31_LOGI("[%s]: Delta Capacity From IC = %d (0x%02x%02x)\n", __func__, data->deltaCapFromIC, u8TempHigh, u8Temp);
+
+ /// [AT-PM] : Load ADC1 conversion time ; 02/10/2013
+ API_I2C_SingleRead(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_ADC1_CONV_TIME, &u8Temp);
+ u16Temp = (_sys_u16_)(u8Temp & 0xf8);
+ data->adc1ConvTime = u16Temp*TIME_CONVERT_TIME_TO_MSEC;
+ UG31_LOGI("[%s]: ADC1 Conversion Time From IC = %d (0x%02x)\n", __func__, data->adc1ConvTime, u8Temp);
+
+ /// [AT-PM] : Get RSOC ; 01/31/2013
+ if(data->fccFromIC == 0)
+ {
+ data->rsocFromIC = 0;
+ }
+ else
+ {
+ data->rsocFromIC = CalculateRsoc(data->rmFromIC, data->fccFromIC);
+ }
+
+ data->rmFromICBackup = data->rmFromIC;
+ data->fccFromICBackup = data->fccFromIC;
+ data->rsocFromICBackup = data->rsocFromIC;
+}
+
+/**
+ * @brief UpiUpdateBatInfoFromIC
+ *
+ * Update battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @para deltaQ delta capacity from coulomb counter
+ * @return _UPI_NULL_
+ */
+void UpiUpdateBatInfoFromIC(SystemDataType *data, _sys_s16_ deltaQ)
+{
+ _sys_s32_ tmp32;
+ _sys_u16_ oldRM;
+
+ oldRM = data->rmFromIC;
+
+ tmp32 = (_sys_s32_)data->rmFromIC;
+ tmp32 = tmp32 + deltaQ;
+ if(tmp32 < 0)
+ {
+ tmp32 = 0;
+ }
+ if(tmp32 > data->fccFromIC)
+ {
+ tmp32 = (_sys_s32_)data->fccFromIC;
+ }
+ UG31_LOGI("[%s]: RM = %d + %d = %d\n", __func__,
+ data->rmFromIC, deltaQ, tmp32);
+ data->rmFromIC = (_sys_u16_)tmp32;
+
+ if(data->fccFromIC == 0)
+ {
+ data->rsocFromIC = 0;
+ }
+ else
+ {
+ data->rsocFromIC = CalculateRsoc(data->rmFromIC, data->fccFromIC);
+
+ if(oldRM != 0)
+ {
+ /// [AT-PM] : EDVF is not reached in last log data ; 02/13/2013
+ if(data->rsocFromIC == 0)
+ {
+ /// [AT-PM] : Check EDVF threshold ; 02/13/2013
+ if(data->voltage < data->ggbParameter->edv1Voltage)
+ {
+ /// [AT-PM] : Set capacity to 0 when EDVF reached ; 02/13/2013
+ data->rmFromIC = 0;
+ data->rsocFromIC = 0;
+ }
+ else
+ {
+ /// [AT-PM] : Capacity should not be 0 before EDVF ; 02/13/2013
+ tmp32 = (_sys_s32_)data->fccFromIC;
+ tmp32 = tmp32/CONST_PERCENTAGE;
+ data->rmFromIC = (_sys_u16_)tmp32;
+ data->rsocFromIC = 1;
+ }
+ }
+ else
+ {
+ /// [AT-PM] : Check EDVF threshold ; 02/13/2013
+ if(data->voltage < data->ggbParameter->edv1Voltage)
+ {
+ /// [AT-PM] : Set capacity to 1% when EDVF reached in initialization ; 02/13/2013
+ tmp32 = (_sys_s32_)data->fccFromIC;
+ tmp32 = tmp32/CONST_PERCENTAGE;
+ data->rmFromIC = (_sys_u16_)tmp32;
+ data->rsocFromIC = 1;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @brief UpiSaveBatInfoTOIC
+ *
+ * Save battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+void UpiSaveBatInfoTOIC(SystemDataType *data)
+{
+ _sys_u8_ *u8Ptr;
+ _sys_u8_ u8Temp;
+ _sys_u8_ u8Temp1;
+ _sys_u16_ u16Temp;
+
+ #if defined(uG31xx_OS_ANDROID)
+ data->timeTagFromIC = GetSysTickCount();
+ #else ///< else of defined(uG31xx_OS_ANDROID)
+ data->timeTagFromIC = GetTickCount();
+ #endif ///< end of defined(uG31xx_OS_ANDROID)
+ UG31_LOGE("[%s]:timeTag =%u/%x ms,NAC = %d maH,LMD = %d maH\n",
+ __func__,
+ data->timeTagFromIC,
+ data->timeTagFromIC,
+ data->rmFromIC,
+ data->fccFromIC);
+
+ //save the time tag
+ u8Ptr = (_sys_u8_ *)&data->timeTagFromIC;
+ u8Temp = (*(u8Ptr + 2)) & 0xf8;
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TIME_BYTE2, u8Temp);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TIME_BYTE3, *(u8Ptr + 3));
+
+ //save the NAC
+ u8Temp = (_sys_u8_)((data->rmFromIC & 0xff00)/256);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_NAC_HIGH, u8Temp);
+ u8Temp = (_sys_u8_)(data->rmFromIC & 0x00ff);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_NAC_LOW, u8Temp);
+
+ // save LMD
+ u8Temp = (_sys_u8_)((data->fccFromIC & 0xff00)/256);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_LMD_HIGH, u8Temp);
+ u8Temp = (_sys_u8_)(data->fccFromIC & 0x00ff);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_LMD_LOW, u8Temp);
+
+ /// [AT-PM] : Save table update index ; 02/10/2013
+ API_I2C_SingleRead(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TABLE_UPDATE_IDX, &u8Temp);
+ u8Temp = u8Temp & 0xf8;
+ u8Temp = u8Temp | (data->tableUpdateIdxFromIC & 0x07);
+ API_I2C_SingleWrite(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_TABLE_UPDATE_IDX, u8Temp);
+ UG31_LOGI("[%s]: Save Table Update Index = %d - 0x%02x\n", __func__, data->tableUpdateIdxFromIC, u8Temp);
+
+ /// [AT-PM] : Save delta capacity ; 02/10/2013
+ u16Temp = (_sys_u16_)data->deltaCapFromIC;
+ u8Temp = (_sys_u8_)(u16Temp >> 8);
+ API_I2C_SingleWrite(SECURITY, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_DELTA_CAP_HIGH, u8Temp);
+ u8Temp1 = (_sys_u8_)(u16Temp & 0x00ff);
+ API_I2C_SingleWrite(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_DELTA_CAP_LOW, u8Temp1);
+ UG31_LOGI("[%s]: Save Delta Capacity = %d - 0x%02x%02x\n", __func__, data->deltaCapFromIC, u8Temp, u8Temp1);
+
+ /// [AT-PM] : Save adc1 conversion time ; 02/10/2013
+ u16Temp = data->adc1ConvTime/TIME_CONVERT_TIME_TO_MSEC;
+ API_I2C_SingleRead(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_ADC1_CONV_TIME, &u8Temp);
+ u8Temp = u8Temp & 0x07;
+ u8Temp = u8Temp | (u16Temp & 0xf8);
+ API_I2C_SingleWrite(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, BACKUP_ADC1_CONV_TIME, u8Temp);
+ UG31_LOGI("[%s]: Save ADC1 Conversion Time = %d - 0x%02x\n", __func__, data->adc1ConvTime, u8Temp);
+}
+
+/**
+ * @brief UpiInitAlarm
+ *
+ * Initialize alarm function of uG3105
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+void UpiInitAlarm(SystemDataType *data)
+{
+ /// [AT-PM] : Set GPIO as alarm pin ; 04/08/2013
+ ConfigureGpio(data);
+
+ /// [AT-PM] : Set delay time ; 04/08/2013
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_TIMER,
+ data->ggbParameter->alarm_timer);
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CLK_DIVA,
+ data->ggbParameter->clkDivA);
+ API_I2C_SingleWrite(SECURITY,
+ UG31XX_I2C_HIGH_SPEED_MODE,
+ UG31XX_I2C_TEM_BITS_MODE,
+ REG_CLK_DIVB,
+ data->ggbParameter->clkDivB);
+
+ /// [AT-PM] : Enable alarm ; 04/08/2013
+ data->alarmSts = 0;
+ data->uvAlarm.state = _UPI_FALSE_;
+ data->uetAlarm.state = _UPI_FALSE_;
+ data->oetAlarm.state = _UPI_FALSE_;
+ EnableAlarm(data);
+}
+
+/**
+ * @brief UpiAlarmStatus
+ *
+ * Get alarm status
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+_sys_u8_ UpiAlarmStatus(SystemDataType *data)
+{
+ _sys_u8_ sts;
+ _sys_u8_ tmp8[2];
+
+ sts = 0;
+
+ /// [AT-PM] : Read alarm status from uG3105 ; 04/08/2013
+ API_I2C_Read(NORMAL, UG31XX_I2C_HIGH_SPEED_MODE, UG31XX_I2C_TEM_BITS_MODE, REG_ALARM1_STATUS, 2, &tmp8[0]);
+ data->alarmSts = (_sys_u16_)tmp8[0];
+ data->alarmSts = data->alarmSts*256 + tmp8[1];
+
+ /// [AT-PM] : Enable alarm ; 04/08/2013
+ EnableAlarm(data);
+
+ /// [AT-PM] : Update current alarm status ; 04/08/2013
+ tmp8[0] = data->uvAlarm.state == _UPI_TRUE_ ? ALARM_STATUS_UV : 0;
+ sts = sts | tmp8[0];
+ tmp8[0] = data->uetAlarm.state == _UPI_TRUE_ ? ALARM_STATUS_UET : 0;
+ sts = sts | tmp8[0];
+ tmp8[0] = data->oetAlarm.state == _UPI_TRUE_ ? ALARM_STATUS_OET : 0;
+ sts = sts | tmp8[0];
+ return (sts);
+}
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.h
new file mode 100755
index 00000000..cb3ea775
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_API_System.h
@@ -0,0 +1,211 @@
+/**
+ * @filename uG31xx_API_System.h
+ *
+ * Interface of ug31xx system control
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+#define UG31XX_SYSTEM_VERSION (6)
+
+typedef unsigned char _sys_u8_;
+typedef signed char _sys_s8_;
+typedef unsigned short _sys_u16_;
+typedef signed short _sys_s16_;
+typedef unsigned long _sys_u32_;
+typedef signed long _sys_s32_;
+typedef char _sys_bool_;
+
+typedef enum _SYSTEM_RTN_CODE {
+ SYSTEM_RTN_PASS = 0,
+ SYSTEM_RTN_READ_GGB_FAIL,
+ SYSTEM_RTN_I2C_FAIL,
+} SYSTEM_RTN_CODE;
+
+typedef struct AlarmDataST {
+ _sys_u16_ alarmThrd;
+ _sys_u16_ releaseThrd;
+ _sys_bool_ state;
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) AlarmDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } AlarmDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+typedef struct SystemDataST {
+
+ #if defined (uG31xx_OS_WINDOWS)
+ const wchar_t* ggbFilename;
+ const wchar_t* otpFileName;
+ const wchar_t* backupFileName;
+ #elif defined(uG31xx_OS_ANDROID)
+ GGBX_FILE_HEADER *ggbXBuf;
+ #endif
+
+ CELL_PARAMETER *ggbParameter;
+ CELL_TABLE *ggbCellTable;
+ OtpDataType *otpData;
+
+ ADC_CHECK adcCheckData; //add for adc error check 20121025/jacky
+
+ _sys_u16_ voltage;
+
+ _sys_u16_ preITAve;
+ _sys_u8_ cellNum;
+
+ _sys_u16_ rmFromIC;
+ _sys_u16_ fccFromIC;
+ _sys_u8_ rsocFromIC;
+ _sys_u32_ timeTagFromIC;
+ _sys_u8_ tableUpdateIdxFromIC;
+ _sys_s16_ deltaCapFromIC;
+ _sys_u16_ adc1ConvTime;
+
+ _sys_u16_ rmFromICBackup;
+ _sys_u16_ fccFromICBackup;
+ _sys_u8_ rsocFromICBackup;
+
+ AlarmDataType uvAlarm;
+ AlarmDataType oetAlarm;
+ AlarmDataType uetAlarm;
+ _sys_u16_ alarmSts;
+#if defined(uG31xx_OS_ANDROID)
+ } __attribute__ ((packed)) SystemDataType;
+#else ///< else of defined(uG31xx_OS_ANDROID)
+ } SystemDataType;
+#endif ///< end of defined(uG31xx_OS_ANDROID)
+
+/**
+ * @brief UpiInitSystemData
+ *
+ * Initialize system data
+ *
+ * @para data address of BootDataType
+ * @return _UPI_NULL_
+ */
+extern SYSTEM_RTN_CODE UpiInitSystemData(SystemDataType *data);
+
+/**
+ * @brief UpiCheckICActive
+ *
+ * Check IC is actived or not
+ *
+ * @return _UPI_TRUE_ if uG31xx is not actived
+ */
+extern _upi_bool_ UpiCheckICActive(void);
+
+/**
+ * @brief UpiActiveUg31xx
+ *
+ * Active uG31xx
+ *
+ * @return SYSTEM_RTN_CODE
+ */
+extern SYSTEM_RTN_CODE UpiActiveUg31xx(void);
+
+/**
+ * @brief UpiSetupAdc
+ *
+ * Setup ADC configurations
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiSetupAdc(SystemDataType *data);
+
+/**
+ * @brief UpiDecimateRst
+ *
+ * Decimate reset filter of ADC
+ *
+ * @return _UPI_NULL_
+ */
+extern void UpiDecimateRst(void);
+
+/**
+ * @brief UpiSetupSystem
+ *
+ * Setup uG31xx system
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiSetupSystem(SystemDataType *data);
+
+/**
+ * @brief UpiCalibrationOsc
+ *
+ * OSC calibration
+ * oscCnt25[9:0] = oscCntTarg[9:0] + oscDeltaCode25[7:0]
+ * oscCnt80[9:0] = oscCntTarg[9:0] + oscDeltaCode80[7:0]
+ * oscCnt[9:0] = m*ITcode[15:8] + C[9:0]
+ * m = (oscCnt80[9:0]-oscCnt25[9:0])/(iTcode80[7:0]-iTcode25[7:0])
+ * c = oscCnt25[9:0] - m*ITcode25[7:0]
+ * write oscCnt[9:0] to register 0x97-98
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiCalibrationOsc(SystemDataType *data);
+
+/**
+ * @brief UpiAdcStatus
+ *
+ * Check ADC status
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiAdcStatus(SystemDataType *data);
+
+/**
+ * @brief UpiLoadBatInfoFromIC
+ *
+ * Load battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiLoadBatInfoFromIC(SystemDataType *data);
+
+/**
+ * @brief UpiUpdateBatInfoFromIC
+ *
+ * Update battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @para deltaQ delta capacity from coulomb counter
+ * @return _UPI_NULL_
+ */
+extern void UpiUpdateBatInfoFromIC(SystemDataType *data, _sys_s16_ deltaQ);
+
+/**
+ * @brief UpiSaveBatInfoTOIC
+ *
+ * Save battery information from uG31xx
+ *
+ * @para data address of SystemDataType
+ * @return _UPI_NULL_
+ */
+extern void UpiSaveBatInfoTOIC(SystemDataType *data);
+
+/**
+ * @brief UpiInitAlarm
+ *
+ * Initialize alarm function of uG3105
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+extern void UpiInitAlarm(SystemDataType *data);
+
+/**
+ * @brief UpiAlarmStatus
+ *
+ * Get alarm status
+ *
+ * @para data address of SystemDataType
+ * @return NULL
+ */
+extern _sys_u8_ UpiAlarmStatus(SystemDataType *data);
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Platform.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Platform.h
new file mode 100755
index 00000000..9c1e7325
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Platform.h
@@ -0,0 +1,17 @@
+/**
+ * @filename uG31xx_Platform.h
+ *
+ * Define the platform for uG31xx driver
+ *
+ * @author AllenTeng <allen_teng@upi-semi.com>
+ */
+
+//#define uG31xx_OS_WINDOWS
+#define uG31xx_OS_ANDROID
+
+#ifdef uG31xx_OS_ANDROID
+
+ //#define uG31xx_BOOT_LOADER
+
+#endif ///< end of uG31xx_OS_ANDROID
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Reg_Def.h b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Reg_Def.h
new file mode 100755
index 00000000..4de941ae
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/uG31xx_Reg_Def.h
@@ -0,0 +1,667 @@
+/**
+ * @filename RegDef.h
+ *
+ * uG31xx register definition
+ *
+ * @author AllenTeng <allen.kuoliang.teng@gmail.com>
+ * @note register table release date 2012/11/05
+ */
+
+#define INTERNAL_REGISTER_GROUP_A (0x00)
+ #define REG_MODE (INTERNAL_REGISTER_GROUP_A + 0) ///< 0x00
+ #define MODE_GG_RUN (1<<4)
+ #define GG_RUN_STANDBY_MODE (0<<4)
+ #define GG_RUN_OPERATION_MODE (1<<4)
+ #define REG_CTRL1 (INTERNAL_REGISTER_GROUP_A + 1) ///< 0x01
+ #define CTRL1_PORDET (1<<4)
+ #define PORDET_R_NO_POR (0<<4)
+ #define PORDET_R_POR (1<<4)
+ #define PORDET_W_RELEASE (0<<4)
+ #define PORDET_W_SOFTRESET (1<<4)
+ #define CTRL1_VTM_EOC (1<<3)
+ #define CTRL1_GG_EOC (1<<2)
+ #define CTRL1_GG_RST (1<<1)
+ #define CTRL1_IO1DATA (1<<0)
+ #define IO1DATA_R_LOW (0<<0)
+ #define IO1DATA_R_HIGH (1<<0)
+ #define IO1DATA_W_LOW (0<<0)
+ #define IO1DATA_W_HIGH (1<<0)
+
+#define INTERNAL_REGISTER_GROUP_B (0x02)
+ #define REG_CHARGE_LOW (INTERNAL_REGISTER_GROUP_B + 0) ///< 0x02
+ #define REG_CHARGE_HIGH (INTERNAL_REGISTER_GROUP_B + 1) ///< 0x03
+ #define REG_COUNTER_LOW (INTERNAL_REGISTER_GROUP_B + 2) ///< 0x04
+ #define REG_COUNTER_HIGH (INTERNAL_REGISTER_GROUP_B + 3) ///< 0x05
+ #define REG_AVE_CURRENT_LOW (INTERNAL_REGISTER_GROUP_B + 4) ///< 0x06
+ #define REG_AVE_CURRENT_HIGH (INTERNAL_REGISTER_GROUP_B + 5) ///< 0x07
+ #define REG_AVE_VBAT1_LOW (INTERNAL_REGISTER_GROUP_B + 6) ///< 0x08
+ #define REG_AVE_VBAT1_HIGH (INTERNAL_REGISTER_GROUP_B + 7) ///< 0x09
+ #define REG_AVE_IT_LOW (INTERNAL_REGISTER_GROUP_B + 8) ///< 0x0A
+ #define REG_AVE_IT_HIGH (INTERNAL_REGISTER_GROUP_B + 9) ///< 0x0B
+ #define REG_AVE_OFFSET_CURRENT_LOW (INTERNAL_REGISTER_GROUP_B + 10) ///< 0x0C
+ #define REG_AVE_OFFSET_CURRENT_HIGH (INTERNAL_REGISTER_GROUP_B + 11) ///< 0x0D
+ #define REG_AVE_ET_LOW (INTERNAL_REGISTER_GROUP_B + 12) ///< 0x0E
+ #define REG_AVE_ET_HIGH (INTERNAL_REGISTER_GROUP_B + 13) ///< 0x0F
+ #define REG_AVE_RID_LOW (INTERNAL_REGISTER_GROUP_B + 14) ///< 0x10
+ #define REG_AVE_RID_HIGH (INTERNAL_REGISTER_GROUP_B + 15) ///< 0x11
+ #define REG_ALARM1_STATUS (INTERNAL_REGISTER_GROUP_B + 16) ///< 0x12
+ #define ALARM1_STATUS_COC_ALARM (1<<5)
+ #define ALARM1_STATUS_DOC_ALARM (1<<4)
+ #define ALARM1_STATUS_OIT_ALARM (1<<3)
+ #define ALARM1_STATUS_UIT_ALARM (1<<2)
+ #define ALARM1_STATUS_OET_ALARM (1<<1)
+ #define ALARM1_STATUS_UET_ALARM (1<<0)
+ #define REG_ALARM2_STATUS (INTERNAL_REGISTER_GROUP_B + 17) ///< 0x13
+ #define ALARM2_STATUS_OV3_ALARM (1<<5)
+ #define ALARM2_STATUS_UV3_ALARM (1<<4)
+ #define ALARM2_STATUS_OV2_ALARM (1<<3)
+ #define ALARM2_STATUS_UV2_ALARM (1<<2)
+ #define ALARM2_STATUS_OV1_ALARM (1<<1)
+ #define ALARM2_STATUS_UV1_ALARM (1<<0)
+ #define REG_INTR_STATUS (INTERNAL_REGISTER_GROUP_B + 18) ///< 0x14
+ #define INTR_STATUS_CBC_STS32 (1<<7)
+ #define CBC_STS32_ENABLE (0<<7)
+ #define CBC_STS32_DISABLE (1<<7)
+ #define INTR_STATUS_CBC_STS21 (1<<6)
+ #define CBC_STS21_ENABLE (0<<6)
+ #define CBC_STS21_DISABLE (1<<6)
+ #define INTR_STATUS_STB_STS (1<<5)
+ #define INTR_STATUS_ET_STS (1<<4)
+ #define INTR_STATUS_IT_STS (1<<3)
+ #define INTR_STATUS_RID_STS (1<<2)
+ #define INTR_STATUS_LVD_STS (1<<1)
+ #define INTR_STATUS_AL_STS (1<<0)
+ #define REG_ALARM_EN (INTERNAL_REGISTER_GROUP_B + 19) ///< 0x15
+ #define ALARM_EN_COC_ALARM_EN (1<<7)
+ #define ALARM_EN_DOC_ALARM_EN (1<<6)
+ #define ALARM_EN_IT_ALARM_EN (1<<5)
+ #define ALARM_EN_ET_ALARM_EN (1<<4)
+ #define ALARM_EN_DECIMATE_RST (1<<3)
+ #define ALARM_EN_V3_ALARM_EN (1<<2)
+ #define ALARM_EN_V2_ALARM_EN (1<<1)
+ #define ALARM_EN_V1_ALARM_EN (1<<0)
+ #define REG_CTRL2 (INTERNAL_REGISTER_GROUP_B + 20) ///< 0x16
+ #define CTRL2_IO4DATA (1<<2)
+ #define IO4DATA_R_LOW (0<<2)
+ #define IO4DATA_R_HIGH (1<<2)
+ #define IO4DATA_W_LOW (0<<2)
+ #define IO4DATA_W_HIGH (1<<2)
+ #define CTRL2_IO3DATA (1<<1)
+ #define IO3DATA_R_LOW (0<<1)
+ #define IO3DATA_R_HIGH (1<<1)
+ #define IO3DATA_W_LOW (0<<1)
+ #define IO3DATA_W_HIGH (1<<1)
+ #define CTRL2_IO2DATA (1<<0)
+ #define IO2DATA_R_LOW (0<<0)
+ #define IO2DATA_R_HIGH (1<<0)
+ #define IO2DATA_W_LOW (0<<0)
+ #define IO2DATA_W_HIGH (1<<0)
+
+#define INTERNAL_REGISTER_GROUP_C (0x20)
+ #define REG_RAM0 (INTERNAL_REGISTER_GROUP_C + 0) ///< 0x20
+ #define REG_RAM1 (INTERNAL_REGISTER_GROUP_C + 1) ///< 0x21
+ #define REG_RAM2 (INTERNAL_REGISTER_GROUP_C + 2) ///< 0x22
+ #define REG_RAM3 (INTERNAL_REGISTER_GROUP_C + 3) ///< 0x23
+ #define REG_RAM4 (INTERNAL_REGISTER_GROUP_C + 4) ///< 0x24
+ #define REG_RAM5 (INTERNAL_REGISTER_GROUP_C + 5) ///< 0x25
+ #define REG_RAM6 (INTERNAL_REGISTER_GROUP_C + 6) ///< 0x26
+ #define REG_RAM7 (INTERNAL_REGISTER_GROUP_C + 7) ///< 0x27
+ #define REG_RAM8 (INTERNAL_REGISTER_GROUP_C + 8) ///< 0x28
+ #define REG_RAM9 (INTERNAL_REGISTER_GROUP_C + 9) ///< 0x29
+ #define REG_RAM10 (INTERNAL_REGISTER_GROUP_C + 10) ///< 0x2A
+ #define REG_RAM11 (INTERNAL_REGISTER_GROUP_C + 11) ///< 0x2B
+ #define REG_RAM12 (INTERNAL_REGISTER_GROUP_C + 12) ///< 0x2C
+ #define REG_RAM13 (INTERNAL_REGISTER_GROUP_C + 13) ///< 0x2D
+ #define REG_RAM14 (INTERNAL_REGISTER_GROUP_C + 14) ///< 0x2E
+ #define REG_RAM15 (INTERNAL_REGISTER_GROUP_C + 15) ///< 0x2F
+ #define REG_RAM16 (INTERNAL_REGISTER_GROUP_C + 16) ///< 0x30
+ #define REG_RAM17 (INTERNAL_REGISTER_GROUP_C + 17) ///< 0x31
+ #define REG_RAM18 (INTERNAL_REGISTER_GROUP_C + 18) ///< 0x32
+ #define REG_RAM19 (INTERNAL_REGISTER_GROUP_C + 19) ///< 0x33
+ #define REG_RAM20 (INTERNAL_REGISTER_GROUP_C + 20) ///< 0x34
+ #define REG_RAM21 (INTERNAL_REGISTER_GROUP_C + 21) ///< 0x35
+ #define REG_RAM22 (INTERNAL_REGISTER_GROUP_C + 22) ///< 0x36
+ #define REG_RAM23 (INTERNAL_REGISTER_GROUP_C + 23) ///< 0x37
+ #define REG_RAM24 (INTERNAL_REGISTER_GROUP_C + 24) ///< 0x38
+ #define REG_RAM25 (INTERNAL_REGISTER_GROUP_C + 25) ///< 0x39
+ #define REG_RAM26 (INTERNAL_REGISTER_GROUP_C + 26) ///< 0x3A
+ #define REG_RAM27 (INTERNAL_REGISTER_GROUP_C + 27) ///< 0x3B
+ #define REG_RAM28 (INTERNAL_REGISTER_GROUP_C + 28) ///< 0x3C
+ #define REG_RAM29 (INTERNAL_REGISTER_GROUP_C + 29) ///< 0x3D
+ #define REG_RAM30 (INTERNAL_REGISTER_GROUP_C + 30) ///< 0x3E
+ #define REG_RAM31 (INTERNAL_REGISTER_GROUP_C + 31) ///< 0x3F
+
+#define INTERNAL_REGISTER_GROUP_D (0x40)
+ #define REG_VBAT2_LOW (INTERNAL_REGISTER_GROUP_D + 0) ///< 0x40
+ #define REG_VBAT2_HIGH (INTERNAL_REGISTER_GROUP_D + 1) ///< 0x41
+ #define REG_VBAT3_LOW (INTERNAL_REGISTER_GROUP_D + 2) ///< 0x42
+ #define REG_VBAT3_HIGH (INTERNAL_REGISTER_GROUP_D + 3) ///< 0x43
+ #define REG_VBAT1_LOW (INTERNAL_REGISTER_GROUP_D + 4) ///< 0x44
+ #define REG_VBAT1_HIGH (INTERNAL_REGISTER_GROUP_D + 5) ///< 0x45
+ #define REG_VBAT2_AVE_LOW (INTERNAL_REGISTER_GROUP_D + 6) ///< 0x46
+ #define REG_VBAT2_AVE_HIGH (INTERNAL_REGISTER_GROUP_D + 7) ///< 0x47
+ #define REG_VBAT3_AVE_LOW (INTERNAL_REGISTER_GROUP_D + 8) ///< 0x48
+ #define REG_VBAT3_AVE_HIGH (INTERNAL_REGISTER_GROUP_D + 9) ///< 0x49
+ #define REG_V1_LOW (INTERNAL_REGISTER_GROUP_D + 10) ///< 0x4A
+ #define REG_V1_HIGH (INTERNAL_REGISTER_GROUP_D + 11) ///< 0x4B
+ #define REG_V2_LOW (INTERNAL_REGISTER_GROUP_D + 12) ///< 0x4C
+ #define REG_V2_HIGH (INTERNAL_REGISTER_GROUP_D + 13) ///< 0x4D
+ #define REG_V3_LOW (INTERNAL_REGISTER_GROUP_D + 14) ///< 0x4E
+ #define REG_V3_HIGH (INTERNAL_REGISTER_GROUP_D + 15) ///< 0x4F
+ #define REG_INTR_TEMPER_LOW (INTERNAL_REGISTER_GROUP_D + 16) ///< 0x50
+ #define REG_INTR_TEMPER_HIGH (INTERNAL_REGISTER_GROUP_D + 17) ///< 0x51
+ #define REG_EXTR_TEMPER_LOW (INTERNAL_REGISTER_GROUP_D + 18) ///< 0x52
+ #define REG_EXTR_TEMPER_HIGH (INTERNAL_REGISTER_GROUP_D + 19) ///< 0x53
+ #define REG_RID_LOW (INTERNAL_REGISTER_GROUP_D + 20) ///< 0x54
+ #define REG_RID_HIGH (INTERNAL_REGISTER_GROUP_D + 21) ///< 0x55
+ #define REG_CURRENT_LOW (INTERNAL_REGISTER_GROUP_D + 22) ///< 0x56
+ #define REG_CURRENT_HIGH (INTERNAL_REGISTER_GROUP_D + 23) ///< 0x57
+ #define REG_ADC1_OFFSET_LOW (INTERNAL_REGISTER_GROUP_D + 24) ///< 0x58
+ #define REG_ADC1_OFFSET_HIGH (INTERNAL_REGISTER_GROUP_D + 25) ///< 0x59
+
+ #define OTP6_BYTE1 (INTERNAL_REGISTER_GROUP_D + 48) ///< 0x70
+ #define AVE_IT25_7_3 (63<<3)
+ #define DELTA_ET_3_2 (3<<1)
+ #define DELTA_VREF_4 (1<<0)
+ #define OTP6_BYTE2 (INTERNAL_REGISTER_GROUP_D + 49) ///< 0x71
+ #define AVE_IT25_15_8 (255<<0)
+ #define OTP6_BYTE3 (INTERNAL_REGISTER_GROUP_D + 50) ///< 0x72
+ #define AVE_IT80_7_3 (63<<3)
+ #define INDEX_ADC1_200_25_4 (1<<2)
+ #define INDEX_ADC1_100_25_4 (1<<1)
+ #define INDEX_ADC2_200_25_4 (1<<0)
+ #define OTP6_BYTE4 (INTERNAL_REGISTER_GROUP_D + 51) ///< 0x73
+ #define AVE_IT80_15_8 (255<<0)
+
+#define INTERNAL_REGISTER_GROUP_E (0x8F)
+ #define REG_TIMER (INTERNAL_REGISTER_GROUP_E + 0) ///< 0x8F
+ #define TIMER_TIMER_ITSET (3<<6)
+ #define TIMER_ITSET_4 (0<<6)
+ #define TIMER_ITSET_9 (1<<6)
+ #define TIMER_ITSET_14 (2<<6)
+ #define TIMER_ITSET_19 (3<<6)
+ #define TIMER_TIMER_ETSET (3<<4)
+ #define TIMER_ETSET_4 (0<<4)
+ #define TIMER_ETSET_9 (1<<4)
+ #define TIMER_ETSET_14 (2<<4)
+ #define TIMER_ETSET_19 (3<<4)
+ #define TIMER_TIMER_VSET (3<<2)
+ #define TIMER_VSET_4 (0<<2)
+ #define TIMER_VSET_9 (1<<2)
+ #define TIMER_VSET_14 (2<<2)
+ #define TIMER_VSET_19 (3<<2)
+ #define TIMER_TIMER_CSET (3<<0)
+ #define TIMER_CSET_4 (0<<0)
+ #define TIMER_CSET_9 (1<<0)
+ #define TIMER_CSET_14 (2<<0)
+ #define TIMER_CSET_19 (3<<0)
+ #define REG_CLK_DIVA (INTERNAL_REGISTER_GROUP_E + 1) ///< 0x90
+ #define CLK_DIVA_FW_CLK_CDIV (15<<4)
+ #define FW_CLK_CDIV_31US (0<<4)
+ #define FW_CLK_CDIV_61US (1<<4)
+ #define FW_CLK_CDIV_122US (2<<4)
+ #define FW_CLK_CDIV_244US (3<<4)
+ #define FW_CLK_CDIV_488US (4<<4)
+ #define FW_CLK_CDIV_977US (5<<4)
+ #define FW_CLK_CDIV_2MS (6<<4)
+ #define FW_CLK_CDIV_4MS (7<<4)
+ #define FW_CLK_CDIV_8MS (8<<4)
+ #define FW_CLK_CDIV_16MS (9<<4)
+ #define FW_CLK_CDIV_31MS (10<<4)
+ #define FW_CLK_CDIV_62MS (11<<4)
+ #define FW_CLK_CDIV_125MS (12<<4)
+ #define FW_CLK_CDIV_250MS (13<<4)
+ #define FW_CLK_CDIV_500MS (14<<4)
+ #define FW_CLK_CDIV_1S (15<<4)
+ #define CLK_DIVA_FW_CLK_VDIV (15<<0)
+ #define FW_CLK_VDIV_31US (0<<0)
+ #define FW_CLK_VDIV_61US (1<<0)
+ #define FW_CLK_VDIV_122US (2<<0)
+ #define FW_CLK_VDIV_244US (3<<0)
+ #define FW_CLK_VDIV_488US (4<<0)
+ #define FW_CLK_VDIV_977US (5<<0)
+ #define FW_CLK_VDIV_2MS (6<<0)
+ #define FW_CLK_VDIV_4MS (7<<0)
+ #define FW_CLK_VDIV_8MS (8<<0)
+ #define FW_CLK_VDIV_16MS (9<<0)
+ #define FW_CLK_VDIV_31MS (10<<0)
+ #define FW_CLK_VDIV_62MS (11<<0)
+ #define FW_CLK_VDIV_125MS (12<<0)
+ #define FW_CLK_VDIV_250MS (13<<0)
+ #define FW_CLK_VDIV_500MS (14<<0)
+ #define FW_CLK_VDIV_1S (15<<0)
+ #define REG_CLK_DIVB (INTERNAL_REGISTER_GROUP_E + 2) ///< 0x91
+ #define CLK_DIVB_FW_CLK_ETDIV (15<<4)
+ #define FW_CLK_ETDIV_31US (0<<4)
+ #define FW_CLK_ETDIV_61US (1<<4)
+ #define FW_CLK_ETDIV_122US (2<<4)
+ #define FW_CLK_ETDIV_244US (3<<4)
+ #define FW_CLK_ETDIV_488US (4<<4)
+ #define FW_CLK_ETDIV_977US (5<<4)
+ #define FW_CLK_ETDIV_2MS (6<<4)
+ #define FW_CLK_ETDIV_4MS (7<<4)
+ #define FW_CLK_ETDIV_8MS (8<<4)
+ #define FW_CLK_ETDIV_16MS (9<<4)
+ #define FW_CLK_ETDIV_31MS (10<<4)
+ #define FW_CLK_ETDIV_62MS (11<<4)
+ #define FW_CLK_ETDIV_125MS (12<<4)
+ #define FW_CLK_ETDIV_250MS (13<<4)
+ #define FW_CLK_ETDIV_500MS (14<<4)
+ #define FW_CLK_ETDIV_1S (15<<4)
+ #define CLK_DIVB_FW_CLK_ITDIV (15<<0)
+ #define FW_CLK_ITDIV_31US (0<<0)
+ #define FW_CLK_ITDIV_61US (1<<0)
+ #define FW_CLK_ITDIV_122US (2<<0)
+ #define FW_CLK_ITDIV_244US (3<<0)
+ #define FW_CLK_ITDIV_488US (4<<0)
+ #define FW_CLK_ITDIV_977US (5<<0)
+ #define FW_CLK_ITDIV_2MS (6<<0)
+ #define FW_CLK_ITDIV_4MS (7<<0)
+ #define FW_CLK_ITDIV_8MS (8<<0)
+ #define FW_CLK_ITDIV_16MS (9<<0)
+ #define FW_CLK_ITDIV_31MS (10<<0)
+ #define FW_CLK_ITDIV_62MS (11<<0)
+ #define FW_CLK_ITDIV_125MS (12<<0)
+ #define FW_CLK_ITDIV_250MS (13<<0)
+ #define FW_CLK_ITDIV_500MS (14<<0)
+ #define FW_CLK_ITDIV_1S (15<<0)
+ #define REG_INTR_CTRL_D (INTERNAL_REGISTER_GROUP_E + 3) ///< 0x92
+ #define INTR_CTRL_D_GPIO4_SEL (7<<3)
+ #define GPIO4_SEL_GPIO1 (0<<3)
+ #define GPIO4_SEL_ALARM (1<<3)
+ #define GPIO4_SEL_CBC_EN21 (2<<3)
+ #define GPIO4_SEL_CBC_EN32 (3<<3)
+ #define GPIO4_SEL_PWM (4<<3)
+ #define GPIO4_SEL_ADC1_D (6<<3)
+ #define GPIO4_SEL_ADC2_D (7<<3)
+ #define INTR_CTRL_D_GPIO3_SEL (7<<0)
+ #define GPIO3_SEL_GPIO1 (0<<0)
+ #define GPIO3_SEL_ALARM (1<<0)
+ #define GPIO3_SEL_CBC_EN21 (2<<0)
+ #define GPIO3_SEL_CBC_EN32 (3<<0)
+ #define GPIO3_SEL_PWM (4<<0)
+ #define GPIO3_SEL_ADC1_D (6<<0)
+ #define GPIO3_SEL_ADC2_D (7<<0)
+ #define OSCTUNE_J1 (INTERNAL_REGISTER_GROUP_E + 4) ///< 0x93
+ #define OSCTUNE_J_LOW (255<<0)
+ #define OSCTUNE_J2 (INTERNAL_REGISTER_GROUP_E + 5) ///< 0x94
+ #define OSCTUNE_J_HIGH (3<<0)
+ #define OSCTUNE_K1 (INTERNAL_REGISTER_GROUP_E + 6) ///< 0x95
+ #define OSCTUNE_K_LOW (255<<0)
+ #define OSCTUNE_K2 (INTERNAL_REGISTER_GROUP_E + 7) ///< 0x96
+ #define OSCTUNE_K_HIGH (3<<0)
+ #define OSCTUNE_CNTA (INTERNAL_REGISTER_GROUP_E + 8) ///< 0x97
+ #define OSCTUNE_CNT_LOW (255<<0)
+ #define OSCTUNE_CNTB (INTERNAL_REGISTER_GROUP_E + 9) ///< 0x98
+ #define OSCTUNE_CNT_HIGH (3<<0)
+ #define ADC1_TARGET_A (INTERNAL_REGISTER_GROUP_E + 10) ///< 0x99
+ #define TARGET_A_LOW (255<<0)
+ #define ADC1_TARGET_B (INTERNAL_REGISTER_GROUP_E + 11) ///< 0x9A
+ #define TARGET_A_HIGH (3<<0)
+ #define REG_INTR_CTRL_A (INTERNAL_REGISTER_GROUP_E + 12) ///< 0x9B
+ #define INTR_CTRL_A_GPIO2_SEL (7<<5)
+ #define GPIO2_SEL_GPIO1 (0<<5)
+ #define GPIO2_SEL_ALARM (1<<5)
+ #define GPIO2_SEL_CBC_EN21 (2<<5)
+ #define GPIO2_SEL_CBC_EN32 (3<<5)
+ #define GPIO2_SEL_PWM (4<<5)
+ #define INTR_CTRL_A_GPIO1_SEL (7<<2)
+ #define GPIO1_SEL_GPIO1 (0<<2)
+ #define GPIO1_SEL_ALARM (1<<2)
+ #define GPIO1_SEL_CBC_EN21 (2<<2)
+ #define GPIO1_SEL_CBC_EN32 (3<<2)
+ #define GPIO1_SEL_PWM (4<<2)
+ #define GPIO1_SEL_OSCOUT (7<<2)
+ #define INTR_CTRL_A_ADC2_EN (1<<1)
+ #define INTR_CTRL_A_ADC1_EN (1<<0)
+ #define REG_INTR_CTRL_B (INTERNAL_REGISTER_GROUP_E + 13) ///< 0x9C
+ #define INTR_CTRL_B_OSC_CNT_EN (1<<7)
+ #define INTR_CTRL_B_PWM_EN (1<<6)
+ #define INTR_CTRL_B_CBC_32_EN (1<<5)
+ #define INTR_CTRL_B_CBC_21_EN (1<<4)
+ #define INTR_CTRL_B_STB_EN (1<<3)
+ #define INTR_CTRL_B_ET_EN (1<<2)
+ #define INTR_CTRL_B_IT_EN (1<<1)
+ #define INTR_CTRL_B_RID_EN (1<<0)
+ #define REG_ITNR_CTRL_C (INTERNAL_REGISTER_GROUP_E + 14) ///< 0x9D
+ #define INTR_CTRL_C_BGRCAL_FINISH (1<<7)
+ #define INTR_CTRL_C_FW_V_DIVIDE (1<<6)
+ #define INTR_CTRL_C_BGRCAL_START (1<<5)
+ #define INTR_CTRL_C_BGR_CALEN (1<<4)
+ #define INTR_CTRL_C_PWM_SET (3<<2)
+ #define PWM_SET_32K (0<<2)
+ #define PWM_SET_16K (1<<2)
+ #define PWM_SET_8K (2<<2)
+ #define PWM_SET_4K (3<<2)
+ #define INTR_CTRL_C_TIMER_SET (3<<0)
+ #define TIMER_SET_OV_UV (0<<0)
+ #define TIMER_SET_OC_UC (1<<0)
+ #define TIMER_SET_OIT_UIT (2<<0)
+ #define TIMER_SET_OET_UET (3<<0)
+ #define REG_CELL_EN (INTERNAL_REGISTER_GROUP_E + 15) ///< 0x9E
+ #define CELL_EN_APPLICATION (7<<2)
+ #define APPLICATION_UG3100 (0<<2)
+ #define APPLICATION_UG3101 (1<<2)
+ #define APPLICATION_UG3102 (2<<2)
+ #define APPLICATION_UG3103_2 (4<<2)
+ #define APPLICATION_UG3103_3 (5<<2)
+ #define CELL_EN1 (1<<1)
+ #define CELL_EN0 (1<<0)
+
+#define INTERNAL_REGISTER_GROUP_F (0x9F)
+ #define REG_COC_LOW (INTERNAL_REGISTER_GROUP_F + 0) ///< 0x9F
+ #define REG_COC_HIGH (INTERNAL_REGISTER_GROUP_F + 1) ///< 0xA0
+ #define REG_DOC_LOW (INTERNAL_REGISTER_GROUP_F + 2) ///< 0xA1
+ #define REG_DOC_HIGH (INTERNAL_REGISTER_GROUP_F + 3) ///< 0xA2
+ #define REG_UC_LOW (INTERNAL_REGISTER_GROUP_F + 4) ///< 0xA3
+ #define REG_UC_HIGH (INTERNAL_REGISTER_GROUP_F + 5) ///< 0xA4
+ #define REG_OV1_LOW (INTERNAL_REGISTER_GROUP_F + 6) ///< 0xA5
+ #define REG_OV1_HIGH (INTERNAL_REGISTER_GROUP_F + 7) ///< 0xA6
+ #define REG_UV1_LOW (INTERNAL_REGISTER_GROUP_F + 8) ///< 0xA7
+ #define REG_UV1_HIGH (INTERNAL_REGISTER_GROUP_F + 9) ///< 0xA8
+ #define REG_OV2_LOW (INTERNAL_REGISTER_GROUP_F + 10) ///< 0xA9
+ #define REG_OV2_HIGH (INTERNAL_REGISTER_GROUP_F + 11) ///< 0xAA
+ #define REG_UV2_LOW (INTERNAL_REGISTER_GROUP_F + 12) ///< 0xAB
+ #define REG_UV2_HIGH (INTERNAL_REGISTER_GROUP_F + 13) ///< 0xAC
+ #define REG_OV3_LOW (INTERNAL_REGISTER_GROUP_F + 14) ///< 0xAD
+ #define REG_OV3_HIGH (INTERNAL_REGISTER_GROUP_F + 15) ///< 0xAE
+ #define REG_UV3_LOW (INTERNAL_REGISTER_GROUP_F + 16) ///< 0xAF
+ #define REG_UV3_HIGH (INTERNAL_REGISTER_GROUP_F + 17) ///< 0xB0
+ #define REG_OVP_LOW (INTERNAL_REGISTER_GROUP_F + 18) ///< 0xB1
+ #define REG_OVP_HIGH (INTERNAL_REGISTER_GROUP_F + 19) ///< 0xB2
+ #define REG_UVP_LOW (INTERNAL_REGISTER_GROUP_F + 20) ///< 0xB3
+ #define REG_UVP_HIGH (INTERNAL_REGISTER_GROUP_F + 21) ///< 0xB4
+ #define REG_INTR_OVER_TEMP_LOW (INTERNAL_REGISTER_GROUP_F + 22) ///< 0xB5
+ #define REG_INTR_OVER_TEMP_HIGH (INTERNAL_REGISTER_GROUP_F + 23) ///< 0xB6
+ #define REG_INTR_UNDER_TEMP_LOW (INTERNAL_REGISTER_GROUP_F + 24) ///< 0xB7
+ #define REG_INTR_UNDER_TEMP_HIGH (INTERNAL_REGISTER_GROUP_F + 25) ///< 0xB8
+ #define REG_EXTR_OVER_TEMP_LOW (INTERNAL_REGISTER_GROUP_F + 26) ///< 0xB9
+ #define REG_EXTR_OVER_TEMP_HIGH (INTERNAL_REGISTER_GROUP_F + 27) ///< 0xBA
+ #define REG_EXTR_UNDER_TEMP_LOW (INTERNAL_REGISTER_GROUP_F + 28) ///< 0xBB
+ #define REG_EXTR_UNDER_TEMP_HIGH (INTERNAL_REGISTER_GROUP_F + 29) ///< 0xBC
+ #define REG_CBC21_LOW (INTERNAL_REGISTER_GROUP_F + 30) ///< 0xBD
+ #define REG_CBC21_HIGH (INTERNAL_REGISTER_GROUP_F + 31) ///< 0xBE
+ #define REG_CBC32_LOW (INTERNAL_REGISTER_GROUP_F + 32) ///< 0xBF
+ #define REG_CBC32_HIGH (INTERNAL_REGISTER_GROUP_F + 33) ///< 0xC0
+
+#define INTERNAL_REGISTER_GROUP_G (0xC1)
+ #define REG_FW_CTRL (INTERNAL_REGISTER_GROUP_G + 0) ///< 0xC1
+ #define FW_CTRL_CHOP2_EN (1<<3)
+ #define FW_CTRL_CHOPPING2 (1<<2)
+ #define FW_CTRL_CHOP1_EN (1<<1)
+ #define FW_CTRL_CHOPPING1 (1<<0)
+ #define REG_OTP_CTRL (INTERNAL_REGISTER_GROUP_G + 1) ///< 0xC2
+ #define OTP_CTRL_IT_CAL80 (1<<7)
+ #define OTP_CTRL_IT_CAL25 (1<<6)
+ #define OTP_CTRL_ADC2_200MV (1<<5)
+ #define OTP_CTRL_ADC2_100MV (1<<4)
+ #define OTP_CTRL_ADC1_200MV (1<<3)
+ #define OTP_CTRL_ADC1_100MV (1<<2)
+ #define OTP_CTRL_OTP_PTM (3<<0)
+ #define REG_OTP_PPROG_ON (INTERNAL_REGISTER_GROUP_G + 2) ///< 0xC3
+ #define OTP_PPROG_ON_VALUE (0xDD)
+ #define REG_OTP_PPROG_OFF (INTERNAL_REGISTER_GROUP_G + 3) ///< 0xC4
+ #define OTP_PPROG_OFF_VALUE (0xDE)
+
+#define INTERNAL_REGISTER_GROUP_H (0xC5)
+ #define REG_ADC_CTR_A (INTERNAL_REGISTER_GROUP_H + 0) ///< 0xC5
+ #define ADC_CTR_A_SET_A (3<<6)
+ #define SET_A_CURRENT (0<<6)
+ #define SET_A_ET (1<<6)
+ #define SET_A_RID_IN (2<<6)
+ #define SET_A_IT (3<<6)
+ #define ADC_CTR_A_SET_B (3<<4)
+ #define SET_B_CURRENT (0<<4)
+ #define SET_B_ET (1<<4)
+ #define SET_B_RID_IN (2<<4)
+ #define SET_B_IT (3<<4)
+ #define ADC_CTR_A_SET_C (3<<2)
+ #define SET_C_CURRENT (0<<2)
+ #define SET_C_ET (1<<2)
+ #define SET_C_RID_IN (2<<2)
+ #define SET_C_IT (3<<2)
+ #define ADC_CTR_A_SET_D (3<<0)
+ #define SET_D_CURRENT (0<<0)
+ #define SET_D_ET (1<<0)
+ #define SET_D_RID_IN (2<<0)
+ #define SET_D_IT (3<<0)
+ #define REG_ADC_CTR_B (INTERNAL_REGISTER_GROUP_H + 1) ///< 0xC6
+ #define ADC_CTR_B_SET_E (3<<6)
+ #define SET_E_CURRENT (0<<6)
+ #define SET_E_ET (1<<6)
+ #define SET_E_RID_IN (2<<6)
+ #define SET_E_IT (3<<6)
+ #define ADC_CTR_B_SET_F (3<<4)
+ #define SET_F_CURRENT (0<<4)
+ #define SET_F_ET (1<<4)
+ #define SET_F_RID_IN (2<<4)
+ #define SET_F_IT (3<<4)
+ #define ADC_CTR_B_SET_G (3<<2)
+ #define SET_G_CURRENT (0<<2)
+ #define SET_G_ET (1<<2)
+ #define SET_G_RID_IN (2<<2)
+ #define SET_G_IT (3<<2)
+ #define ADC_CTR_B_SET_H (3<<0)
+ #define SET_H_CURRENT (0<<0)
+ #define SET_H_ET (1<<0)
+ #define SET_H_RID_IN (2<<0)
+ #define SET_H_IT (3<<0)
+ #define REG_ADC_CTR_C (INTERNAL_REGISTER_GROUP_H + 2) ///< 0xC7
+ #define ADC_CTR_C_SET_I (3<<6)
+ #define SET_I_CURRENT (0<<6)
+ #define SET_I_ET (1<<6)
+ #define SET_I_RID_IN (2<<6)
+ #define SET_I_IT (3<<6)
+ #define ADC_CTR_C_SET_J (3<<4)
+ #define SET_J_CURRENT (0<<4)
+ #define SET_J_ET (1<<4)
+ #define SET_J_RID_IN (2<<4)
+ #define SET_J_IT (3<<4)
+ #define ADC_CTR_C_SET_K (3<<2)
+ #define SET_K_CURRENT (0<<2)
+ #define SET_K_ET (1<<2)
+ #define SET_K_RID_IN (2<<2)
+ #define SET_K_IT (3<<2)
+ #define ADC_CTR_C_SET_L (3<<0)
+ #define SET_L_CURRENT (0<<0)
+ #define SET_L_ET (1<<0)
+ #define SET_L_RID_IN (2<<0)
+ #define SET_L_IT (3<<0)
+ #define REG_ADC_CTR_D (INTERNAL_REGISTER_GROUP_H + 3) ///< 0xC8
+ #define ADC_CTR_D_SET_M (3<<6)
+ #define SET_M_CURRENT (0<<6)
+ #define SET_M_ET (1<<6)
+ #define SET_M_RID_IN (2<<6)
+ #define SET_M_IT (3<<6)
+ #define ADC_CTR_D_SET_N (3<<4)
+ #define SET_N_CURRENT (0<<4)
+ #define SET_N_ET (1<<4)
+ #define SET_N_RID_IN (2<<4)
+ #define SET_N_IT (3<<4)
+ #define ADC_CTR_D_SET_O (3<<2)
+ #define SET_O_CURRENT (0<<2)
+ #define SET_O_ET (1<<2)
+ #define SET_O_RID_IN (2<<2)
+ #define SET_O_IT (3<<2)
+ #define ADC_CTR_D_SET_P (3<<0)
+ #define SET_P_CURRENT (0<<0)
+ #define SET_P_ET (1<<0)
+ #define SET_P_RID_IN (2<<0)
+ #define SET_P_IT (3<<0)
+ #define REG_ADC_V1 (INTERNAL_REGISTER_GROUP_H + 4) ///< 0xC9
+ #define ADC_CTR_V1_SET_V1 (3<<6)
+ #define SET_V1_GND (0<<6)
+ #define SET_V1_VBAT1 (1<<6)
+ #define SET_V1_VBAT2 (2<<6)
+ #define SET_V1_VBAT3 (3<<6)
+ #define ADC_CTR_V1_SET_V2 (3<<4)
+ #define SET_V2_GND (0<<4)
+ #define SET_V2_VBAT1 (1<<4)
+ #define SET_V2_VBAT2 (2<<4)
+ #define SET_V2_VBAT3 (3<<4)
+ #define ADC_CTR_V1_SET_V3 (3<<2)
+ #define SET_V3_GND (0<<2)
+ #define SET_V3_VBAT1 (1<<2)
+ #define SET_V3_VBAT2 (2<<2)
+ #define SET_V3_VBAT3 (3<<2)
+ #define ADC_CTR_V1_SET_V4 (3<<0)
+ #define SET_V4_GND (0<<0)
+ #define SET_V4_VBAT1 (1<<0)
+ #define SET_V4_VBAT2 (2<<0)
+ #define SET_V4_VBAT3 (3<<0)
+ #define REG_ADC_V2 (INTERNAL_REGISTER_GROUP_H + 5) ///< 0xCA
+ #define ADC_CTR_V2_SET_V5 (3<<6)
+ #define SET_V5_GND (0<<6)
+ #define SET_V5_VBAT1 (1<<6)
+ #define SET_V5_VBAT2 (2<<6)
+ #define SET_V5_VBAT3 (3<<6)
+ #define ADC_CTR_V2_SET_V6 (3<<4)
+ #define SET_V6_GND (0<<4)
+ #define SET_V6_VBAT1 (1<<4)
+ #define SET_V6_VBAT2 (2<<4)
+ #define SET_V6_VBAT3 (3<<4)
+ #define ADC_CTR_V2_SET_V7 (3<<2)
+ #define SET_V7_GND (0<<2)
+ #define SET_V7_VBAT1 (1<<2)
+ #define SET_V7_VBAT2 (2<<2)
+ #define SET_V7_VBAT3 (3<<2)
+ #define ADC_CTR_V2_SET_V8 (3<<0)
+ #define SET_V8_GND (0<<0)
+ #define SET_V8_VBAT1 (1<<0)
+ #define SET_V8_VBAT2 (2<<0)
+ #define SET_V8_VBAT3 (3<<0)
+ #define REG_ADC_V3 (INTERNAL_REGISTER_GROUP_H + 6) ///< 0xCB
+ #define ADC_CTR_V3_SET_V9 (3<<6)
+ #define SET_V9_GND (0<<6)
+ #define SET_V9_VBAT1 (1<<6)
+ #define SET_V9_VBAT2 (2<<6)
+ #define SET_V9_VBAT3 (3<<6)
+ #define ADC_CTR_V3_SET_V10 (3<<4)
+ #define SET_V10_GND (0<<4)
+ #define SET_V10_VBAT1 (1<<4)
+ #define SET_V10_VBAT2 (2<<4)
+ #define SET_V10_VBAT3 (3<<4)
+ #define ADC_CTR_V3_SET_V11 (3<<2)
+ #define SET_V11_GND (0<<2)
+ #define SET_V11_VBAT1 (1<<2)
+ #define SET_V11_VBAT2 (2<<2)
+ #define SET_V11_VBAT3 (3<<2)
+ #define ADC_CTR_V3_SET_V12 (3<<0)
+ #define SET_V12_GND (0<<0)
+ #define SET_V12_VBAT1 (1<<0)
+ #define SET_V12_VBAT2 (2<<0)
+ #define SET_V12_VBAT3 (3<<0)
+
+#define INTERNAL_REGISTER_GROUP_I (0xCC)
+ #define KCONFIG_D1 (INTERNAL_REGISTER_GROUP_I + 0) ///< 0xCC
+ #define KCONFIG_D_LOW (255<<0)
+ #define KCONFIG_D2 (INTERNAL_REGISTER_GROUP_I + 1) ///< 0xCD
+ #define KCONFIG_D_HIGH (255<<0)
+
+#define INTERNAL_REGISTER_GROUP_J (0xCE)
+ #define KCONFIG_A1 (INTERNAL_REGISTER_GROUP_J + 0) ///< 0xCE
+ #define KCONFIG_A1_KGG1_OSC (255<<0)
+ #define KCONFIG_A2 (INTERNAL_REGISTER_GROUP_J + 1) ///< 0xCF
+ #define KCONFIG_A2_KGG1_DSM2_L (255<<0)
+ #define KCONFIG_A3 (INTERNAL_REGISTER_GROUP_J + 2) ///< 0xD0
+ #define KCONFIG_A3_KGG1_DSM2_M (255<<0)
+ #define KCONFIG_A4 (INTERNAL_REGISTER_GROUP_J + 3) ///< 0xD1
+ #define KCONFIG_A4_KGG1_DSM2_H (255<<0)
+ #define KCONFIG_A5 (INTERNAL_REGISTER_GROUP_J + 4) ///< 0xD2
+ #define KCONFIG_A5_KGG1_DSM1_L (255<<0)
+ #define KCONFIG_A6 (INTERNAL_REGISTER_GROUP_J + 5) ///< 0xD3
+ #define KCONFIG_A6_KGG1_DSM1_M (255<<0)
+ #define KCONFIG_A7 (INTERNAL_REGISTER_GROUP_J + 6) ///< 0xD4
+ #define KCONFIG_A7_KGG1_DSM1_H (255<<0)
+ #define KCONFIG_A8 (INTERNAL_REGISTER_GROUP_J + 7) ///< 0xD5
+ #define KCONFIG_A8_KGG1_MBIAS_L (255<<0)
+ #define KCONFIG_A9 (INTERNAL_REGISTER_GROUP_J + 8) ///< 0xD6
+ #define KCONFIG_A9_KGG1_MBIAS_H (3<<0)
+
+ #define KCONFIG_H1 (INTERNAL_REGISTER_GROUP_J + 9) ///< 0xD7
+ #define KCONFIG_H1_KGG1_IDO_LOW (15<<4)
+ #define KCONFIG_H1_KGG1_MBIAS (3<<2)
+ #define KCONFIG_H1_KGG1_OSC (3<<2)
+ #define KCONFIG_H2 (INTERNAL_REGISTER_GROUP_J + 10) ///< 0xD8
+ #define KCONFIG_H2_KGG1_IDO_HIGH (255<<0)
+ #define KCONFIG_H3 (INTERNAL_REGISTER_GROUP_J + 11) ///< 0xD9
+ #define KCONFIG_H3_KGG1_BGAP_LOW (63<<2)
+ #define KCONFIG_H3_KGG1_DSM2 (3<<0)
+ #define KCONFIG_H4 (INTERNAL_REGISTER_GROUP_J + 12) ///< 0xDA
+ #define KCONFIG_H3_KGG1_BGAP_HIGH (127<<0)
+ #define KCONFIG_H5 (INTERNAL_REGISTER_GROUP_J + 13) ///< 0xDB
+ #define KCONFIG_H5_WIRE_V_DIVIDE (1<<6)
+ #define KCONFIG_H5_KGG1_BGAP_CAL (63<<0)
+
+#define INTERNAL_REGISTER_GROUP_K (0xDC)
+ #define KCONFIG_CAL1 (INTERNAL_REGISTER_GROUP_K + 0) ///< 0xDC
+ #define KCONFIG_CAL1_KGG1_OSC_L (255<<0)
+ #define KCONFIG_CAL2 (INTERNAL_REGISTER_GROUP_K + 1) ///< 0xDD
+ #define KCONFIG_CAL2_KGG1_OSC_H (255<<0)
+ #define KCONFIG_CAL3 (INTERNAL_REGISTER_GROUP_K + 2) ///< 0xDE
+ #define KCONFIG_CAL3_KGG1_DSM2 (255<<0)
+ #define KCONFIG_CAL4 (INTERNAL_REGISTER_GROUP_K + 3) ///< 0xDF
+ #define KCONFIG_CAL4_KGG1_DSM1 (255<<0)
+
+#define INTERNAL_REGISTER_GROUP_L (0xE0)
+ #define OTP1_BYTE1 (INTERNAL_REGISTER_GROUP_L + 0) ///< 0xE0
+ #define DELTA_VREF_3_0 (15<<4)
+ #define INDEX_ADC1_200_25_3_0 (15<<0)
+ #define OTP1_BYTE2 (INTERNAL_REGISTER_GROUP_L + 1) ///< 0xE1
+ #define FT_IT_6_3 (15<<4)
+ #define INDEX_ADC1_100_25_3_0 (15<<0)
+ #define OTP1_BYTE3 (INTERNAL_REGISTER_GROUP_L + 2) ///< 0xE2
+ #define FT_IT_10_7 (15<<4)
+ #define INDEX_ADC2_200_25_3_0 (15<<0)
+ #define OTP1_BYTE4 (INTERNAL_REGISTER_GROUP_L + 3) ///< 0xE3
+ #define FT_IT_14_11 (15<<4)
+ #define INDEX_ADC2_100_25_3_0 (15<<0)
+
+ #define OTP2_BYTE1 (INTERNAL_REGISTER_GROUP_L + 16) ///< 0xF0
+ #define DELTA_ET_1 (1<<7)
+ #define INDEX_ADC2_100_25_4 (1<<6)
+ #define DELTA_ET_0 (1<<5)
+ #define PRODUCT_TYPE (3<<3)
+ #define ITDELTACODE25_10_8 (7<<0)
+ #define OTP2_BYTE2 (INTERNAL_REGISTER_GROUP_L + 17) ///< 0xF1
+ #define ITDELTACODE25_7_0 (255<<0)
+ #define OTP2_BYTE3 (INTERNAL_REGISTER_GROUP_L + 18) ///< 0xF2
+ #define OTP_CELL_EN (31<<3)
+ #define ITDELTACODE80_10_8 (7<<0)
+ #define OTP2_BYTE4 (INTERNAL_REGISTER_GROUP_L + 19) ///< 0xF3
+ #define ITDELTACODE80_7_0 (255<<0)
+
+ #define OTP3_BYTE1 (INTERNAL_REGISTER_GROUP_L + 20) ///< 0xF4
+ #define DEVADDR_9_0 (63<<2)
+ #define ADC1DELTACODE25_200_9 (1<<1)
+ #define ADC1DELTACODE25_200_8 (1<<0)
+ #define OTP3_BYTE2 (INTERNAL_REGISTER_GROUP_L + 21) ///< 0xF5
+ #define BGRTUNE_5_0 (63<<2)
+ #define ADC1DELTACODE80_200_9_8 (3<<0)
+ #define OTP3_BYTE3 (INTERNAL_REGISTER_GROUP_L + 22) ///< 0xF6
+ #define OSCDELTACODE25 (255<<0)
+ #define OTP3_BYTE4 (INTERNAL_REGISTER_GROUP_L + 23) ///< 0xF7
+ #define OSCDELTACODE80 (255<<0)
+
+ #define OTP4_BYTE1 (INTERNAL_REGISTER_GROUP_L + 24) ///< 0xF8
+ #define ADC1DELTACODE25_200_7_0 (255<<0)
+ #define OTP4_BYTE2 (INTERNAL_REGISTER_GROUP_L + 25) ///< 0xF9
+ #define ADC1DELTACODE80_200_7_0 (255<<0)
+ #define OTP4_BYTE3 (INTERNAL_REGISTER_GROUP_L + 26) ///< 0xFA
+ #define ADC1DELTACODE25_100_7_0 (255<<0)
+ #define OTP4_BYTE4 (INTERNAL_REGISTER_GROUP_L + 27) ///< 0xFB
+ #define ADC1DELTACODE80_100_7_0 (255<<0)
+
+ #define OTP5_BYTE1 (INTERNAL_REGISTER_GROUP_L + 28) ///< 0xFC
+ #define ADC2DELTACODE25_100_6 (1<<7)
+ #define ADC2DELTACODE25_100_5_0 (63<<1)
+ #define ADC1DELTACODE25_100_8 (1<<0)
+ #define OTP5_BYTE2 (INTERNAL_REGISTER_GROUP_L + 29) ///< 0xFD
+ #define ADC2DELTACODE80_100_6_0 (127<<1)
+ #define ADC1DELTACODE80_100_8 (1<<0)
+ #define OTP5_BYTE3 (INTERNAL_REGISTER_GROUP_L + 30) ///< 0xFE
+ #define ADC2DELTACODE25_200_7 (1<<7)
+ #define ADC2DELTACODE25_200_6_0 (127<<0)
+ #define OTP5_BYTE4 (INTERNAL_REGISTER_GROUP_L + 31) ///< 0xFF
+ #define ADC2DELTACODE80_200_7_0 (255<<0)
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c
new file mode 100755
index 00000000..433ff15c
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.c
@@ -0,0 +1,782 @@
+/*
+ * Copyright (c) 2012, uPI Semiconductor Corp. All Rights Reserved.
+ */
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/param.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+#include <linux/reboot.h>
+#include <linux/notifier.h>
+#include <linux/jiffies.h>
+#include <linux/err.h>
+#include <asm/unaligned.h>
+#include <linux/wakelock.h>
+#include <linux/version.h>
+
+#include <linux/power/wmt_battery.h>
+#include <mach/wmt_env.h>
+
+#include "ug31xx_gauge.h"
+#include "uG31xx_API.h"
+
+/* Functions Declaration */
+static int ug31xx_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val);
+static int ug31xx_update_psp(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val);
+static void ug31xx_external_power_changed(struct power_supply *psy);
+
+#define UG31XX_CHECK_FILE_CNT_THRD (20)
+
+/* Global Variables */
+static struct ug31xx_gauge *ug31 = NULL;
+static char chk_backup_file_cnt = UG31XX_CHECK_FILE_CNT_THRD;
+static char *pGGB = NULL;
+static GG_DEVICE_INFO gauge_dev_info;
+static GG_CAPACITY gauge_dev_capacity;
+static struct workqueue_struct *ug31xx_gauge_wq = NULL;
+static int charger_dc_in_before_suspend = 0;
+static bool ug3105_in_suspend = false;
+static bool ug3105_charger_enabled = true;
+static unsigned char force_reset = 0;
+static unsigned char enable_debug = 0;
+
+static __attribute__((unused)) char *chg_status[] = {
+ "Unknown",
+ "Charging",
+ "Discharging",
+ "Not charging",
+ "Full"
+};
+
+static drv_status_t drv_status = DRV_NOT_READY;
+
+#include "ggb/ug31xx_ggb_data_wms8309_wm8_20130820_110949.h"
+#include "ggb/ug31xx_ggb_data_wms8309_c7_20130725_164935.h"
+#include "ggb/ug31xx_ggb_data_wms8309_c7_20130910_130553.h"
+#include "ggb/ug31xx_ggb_data_wms7320_20130718_200031.h"
+#include "ggb/ug31xx_ggb_data_cw500_20130801_103638.h"
+#include "ggb/ug31xx_ggb_data_mp718_20131004_070110.h"
+#include "ggb/ug31xx_ggb_data_t73v_20131120_001204.h"
+
+enum {
+ UG31XX_ID_3105,
+ UG31XX_ID_3102,
+};
+
+struct ggb_info {
+ char *name;
+ uint8_t *data;
+};
+
+/* Extern Function */
+static struct ggb_info ggb_arrays[] = {
+ {
+ .name = "wms8309wm8",
+ .data = FactoryGGBXFile_wms8309_wm8,
+ }, {
+ .name = "wms7320",
+ .data = FactoryGGBXFile_wms7320,
+ }, {
+ .name = "wms8309c7_3900mAh",
+ .data = FactoryGGBXFile_wms8309_c7_3900mAh,
+ }, {
+ .name = "wms8309c7_3000mAh",
+ .data = FactoryGGBXFile_wms8309_c7_3000mAh,
+ }, {
+ .name = "cw500",
+ .data = FactoryGGBXFile_cw500,
+ }, {
+ .name = "mp718",
+ .data = FactoryGGBXFile_mp718,
+ }, {
+ .name = "t73v",
+ .data = FactoryGGBXFile_t73v,
+ },
+};
+
+struct charger_param {
+ int id;
+ int i2c;
+ int et;
+ int temp_range[2];
+ struct ggb_info *ggb;
+};
+
+static struct charger_param charger_param;
+
+static bool inline export_external_termperature(void)
+{
+ return !!charger_param.et;
+}
+
+static int parse_battery_param(void)
+{
+ char env[] = "wmt.battery.param";
+ char buf[64];
+ size_t l = sizeof(buf);
+ int i;
+
+ if (wmt_getsyspara(env, buf, &l))
+ return -EINVAL;
+
+ if (!prefixcmp(buf, "ug3105:")) {
+ charger_param.id = UG31XX_ID_3105;
+ } else if (!prefixcmp(buf, "ug3102:")) {
+ charger_param.id = UG31XX_ID_3102;
+ } else {
+ return -EINVAL;
+ }
+
+ i = sscanf(buf + 7, "%d:%d:%d:%d",
+ &charger_param.i2c, &charger_param.et,
+ &charger_param.temp_range[0],
+ &charger_param.temp_range[1]);
+ if (i < 4)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(ggb_arrays); i++) {
+ if (strstr(buf, ggb_arrays[i].name)) {
+ charger_param.ggb = &ggb_arrays[i];
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(ggb_arrays))
+ charger_param.ggb = &ggb_arrays[0];
+
+ pr_info("%s i2c%d, use %s temperature, range [%d, %d], ggb %s\n",
+ charger_param.id == UG31XX_ID_3105 ? "ug3105" : "ug3102",
+ charger_param.i2c,
+ charger_param.et ? "External" : "Internal",
+ charger_param.temp_range[0],
+ charger_param.temp_range[1],
+ charger_param.ggb->name);
+
+ return 0;
+}
+
+/*
+ * WMT MCE: Use gpio3 on ug31xx as a switch to control charger.
+ */
+static void hw_charging_set(bool enable)
+{
+ if (charger_param.id == UG31XX_ID_3105) {
+ API_I2C_SingleWrite(0, 0, 0, 0x16, enable ? 0x2 : 0x0);
+ } else if (charger_param.id == UG31XX_ID_3102) {
+ u8 data = 0;
+ API_I2C_SingleRead(0, 0, 0 ,REG_CTRL1, &data);
+ if (enable)
+ data |= CTRL1_IO1DATA;
+ else
+ data &= ~CTRL1_IO1DATA;
+ API_I2C_SingleWrite(0, 0, 0, REG_CTRL1, data);
+ }
+ ug3105_charger_enabled = enable;
+}
+
+static void inline hw_charging_disable(void)
+{
+ hw_charging_set(false);
+}
+
+static void inline hw_charging_enable(void)
+{
+ hw_charging_set(true);
+}
+
+static bool is_charging_full(void)
+{
+ return (ug3105_charger_enabled == true) && charger_is_full();
+}
+
+static enum power_supply_property ug31xx_batt_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ 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_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+};
+
+static struct power_supply ug31xx_supply[] = {
+ {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = ug31xx_batt_props,
+ .num_properties = ARRAY_SIZE(ug31xx_batt_props),
+ .get_property = ug31xx_battery_get_property,
+ .external_power_changed = ug31xx_external_power_changed,
+ },
+};
+
+static int ug31xx_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ case POWER_SUPPLY_PROP_TEMP:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ if (ug31xx_update_psp(psy, psp, val))
+ return -EINVAL;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+static int ug31xx_update_psp(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+// int rtn;
+
+ if (drv_status != DRV_INIT_OK) {
+ GAUGE_err("Gauge driver not init finish\n");
+ return -EINVAL;
+ }
+
+ if (psp == POWER_SUPPLY_PROP_TEMP) {
+ if (export_external_termperature())
+ ug31->batt_temp = gauge_dev_info.ET;
+ else
+ ug31->batt_temp = gauge_dev_info.IT;
+ val->intval = ug31->batt_temp;
+ GAUGE_notice("I.Temperature=%d, E.T=%d\n", gauge_dev_info.IT, gauge_dev_info.ET);
+ }
+
+ if (psp == POWER_SUPPLY_PROP_CAPACITY) {
+ if (ug31->batt_status == POWER_SUPPLY_STATUS_FULL)
+ ug31->batt_capacity = 100;
+ else
+ ug31->batt_capacity = gauge_dev_capacity.RSOC;
+
+ val->intval = ug31->batt_capacity;
+ GAUGE_notice("Capacity=%d %%\n", val->intval);
+ }
+
+ if (psp == POWER_SUPPLY_PROP_VOLTAGE_NOW) {
+ val->intval = ug31->batt_volt = gauge_dev_info.voltage_mV;
+ GAUGE_notice("Voltage=%d mV\n", val->intval);
+ }
+
+ if (psp == POWER_SUPPLY_PROP_CURRENT_NOW) {
+ val->intval = ug31->batt_current = gauge_dev_info.AveCurrent_mA;
+ GAUGE_notice("Current=%d mA\n", val->intval);
+ }
+
+ if (psp == POWER_SUPPLY_PROP_STATUS) {
+ int status = charger_get_status();
+ if (status < 0)
+ return status;
+
+ if (status == POWER_SUPPLY_STATUS_CHARGING && ug31->batt_capacity == 100)
+ status = POWER_SUPPLY_STATUS_FULL;
+
+ ug31->batt_status = status;
+ val->intval = ug31->batt_status;
+ GAUGE_notice("Status=%s\n", chg_status[val->intval]);
+ }
+
+ if (psp == POWER_SUPPLY_PROP_CHARGE_NOW) {
+ val->intval = ug31->batt_charge_now = gauge_dev_capacity.NAC;
+ GAUGE_notice("Charge_Now=%d mAh\n", val->intval);
+ }
+
+ if (psp == POWER_SUPPLY_PROP_CHARGE_FULL) {
+ val->intval =ug31->batt_charge_full = gauge_dev_capacity.LMD;
+ GAUGE_notice("Charge_Full=%d mAh\n", val->intval);
+ }
+
+#if 0
+ mutex_lock(&ug31->info_update_lock);
+ chk_backup_file_cnt = chk_backup_file_cnt + 1;
+ UG31_LOGI("chk_backup_file_cnt %d\n", chk_backup_file_cnt);
+ if (chk_backup_file_cnt > UG31XX_CHECK_FILE_CNT_THRD) {
+ rtn = upiGG_CheckBackupFile(pGGB);
+ if(rtn == UPI_CHECK_BACKUP_FILE_MISMATCH)
+ {
+ force_reset = 1;
+ UG31_LOGI("[%s]: Force reset due to version dismatched.\n", __func__);
+ }
+ else if(rtn == UPI_CHECK_BACKUP_FILE_EXIST)
+ {
+ chk_backup_file_cnt = 0;
+ UG31_LOGI("[%s]: Backup file existed -> no need to check frequently.\n", __func__)
+ }
+ else
+ {
+ chk_backup_file_cnt = UG31XX_CHECK_FILE_CNT_THRD;
+ UG31_LOGI("[%s]: Backup file not existed -> need to check frequently.\n", __func__)
+ }
+ UG31_LOGI("[%s]: Check backup file status = %d\n", __func__, rtn);
+ }
+ mutex_unlock(&ug31->info_update_lock);
+#endif
+ return 0;
+}
+
+static int ug31xx_powersupply_init(struct i2c_client *client)
+{
+ int i, ret;
+ for (i = 0; i < ARRAY_SIZE(ug31xx_supply); i++) {
+ ret = power_supply_register(&client->dev, &ug31xx_supply[i]);
+ if (ret) {
+ GAUGE_err("Failed to register power supply\n");
+ while (i--)
+ power_supply_unregister(&ug31xx_supply[i]);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static void ug31xx_external_power_changed(struct power_supply *psy)
+{
+ if (ug3105_in_suspend == false)
+ queue_delayed_work(ug31xx_gauge_wq, &ug31->ug31_gauge_info_work, 0*HZ);
+}
+
+#define UG31XX_INITIAL_RETRY_CNT (3)
+#define UG31XX_INITIAL_LOCK_ENABLE (1)
+#define UG31XX_INITIAL_LOCK_DISABLE (0)
+#define UG31XX_INITIAL_FIX_T_COUNT (30)
+
+static int ug31_gauge_info_reset(int enable_lock)
+{
+ int retry;
+ int gg_status;
+
+ force_reset = 0;
+
+ if(enable_lock == UG31XX_INITIAL_LOCK_ENABLE)
+ {
+ mutex_lock(&ug31->info_update_lock);
+ }
+
+ retry = 0;
+ while(retry < UG31XX_INITIAL_RETRY_CNT)
+ {
+ gg_status = UG_SUCCESS;
+ if(pGGB != NULL)
+ {
+ gg_status = upiGG_UnInitial(&pGGB);
+ pGGB = NULL;
+ GAUGE_notice("Driver remove. gg_status=0x%02x\n", gg_status);
+ }
+
+ if(gg_status == UG_SUCCESS)
+ {
+ gg_status = upiGG_Initial(&pGGB,
+ (GGBX_FILE_HEADER *)charger_param.ggb->data,
+ force_reset, UG31XX_INITIAL_FIX_T_COUNT);
+
+ if(gg_status == UG_INIT_SUCCESS)
+ {
+ chk_backup_file_cnt = UG31XX_CHECK_FILE_CNT_THRD;
+ if(enable_lock == UG31XX_INITIAL_LOCK_ENABLE)
+ {
+ mutex_unlock(&ug31->info_update_lock);
+ }
+ return (0);
+ }
+ }
+
+ retry = retry + 1;
+ GAUGE_err("GGB file read and init fail count = %d (%d)\n", retry, gg_status);
+ }
+
+ GAUGE_err("Reset uG31xx fail.\n");
+ if(pGGB != NULL)
+ {
+ gg_status = upiGG_UnInitial(&pGGB);
+ pGGB = NULL;
+ GAUGE_notice("Driver remove. gg_status=0x%02x\n", gg_status);
+ }
+
+ if(enable_lock == UG31XX_INITIAL_LOCK_ENABLE)
+ {
+ mutex_unlock(&ug31->info_update_lock);
+ }
+ return (-1);
+}
+
+static void ug31_gauge_info_work_func(struct work_struct *work)
+{
+ int gg_status = 0, retry = 3;
+ struct power_supply *psy = &ug31xx_supply[PWR_SUPPLY_BATTERY];
+ int batt_status = ug31->batt_status;
+ struct ug31xx_gauge *ug31_dev;
+
+ ug31_dev = container_of(work, struct ug31xx_gauge, ug31_gauge_info_work.work);
+
+ if(enable_debug == 0)
+ {
+ upiGG_DebugSwitch(_UPI_FALSE_);
+ UG31_LOGI("Set upiGG_DebugSwitch to FALSE\n");
+ }
+ else
+ {
+ upiGG_DebugSwitch(_UPI_TRUE_);
+ UG31_LOGI("Set upiGG_DebugSwitch to TRUE\n");
+ }
+
+ UG31_LOGI("Update gauge info!!\n");
+
+ mutex_lock(&ug31->info_update_lock);
+ while (retry-- > 0) {
+ gg_status = upiGG_ReadDeviceInfo(pGGB,&gauge_dev_info);
+ if (gg_status == UG_READ_DEVICE_INFO_SUCCESS)
+ goto read_dev_info_ok;
+ }
+ GAUGE_err("Read device info fail. gg_status=%d\n", gg_status);
+ if(gg_status == UG_MEAS_FAIL_BATTERY_REMOVED)
+ {
+ gauge_dev_capacity.NAC = 0;
+ gauge_dev_capacity.LMD = 0;
+ gauge_dev_capacity.RSOC = 0;
+ }
+ goto read_dev_info_fail;
+
+read_dev_info_ok:
+ if(gauge_dev_capacity.Ready != UG_CAP_DATA_READY)
+ {
+ if(gauge_dev_info.ET > UG31XX_MAX_TEMPERATURE_BEFORE_READY)
+ {
+ gauge_dev_info.ET = UG31XX_MAX_TEMPERATURE_BEFORE_READY;
+ }
+ if(gauge_dev_info.ET < UG31XX_MIN_TEMPERATURE_BEFORE_READY)
+ {
+ gauge_dev_info.ET = UG31XX_MIN_TEMPERATURE_BEFORE_READY;
+ }
+ }
+
+ upiGG_ReadCapacity(pGGB,&gauge_dev_capacity);
+ UG31_LOGI("Gauge info updated !!\n");
+
+read_dev_info_fail:
+ mutex_unlock(&ug31->info_update_lock);
+
+ if(force_reset == 1)
+ {
+ UG31_LOGI("Reset whole uG31xx driver.\n");
+ gg_status = ug31_gauge_info_reset(UG31XX_INITIAL_LOCK_ENABLE);
+ if(gg_status != 0)
+ {
+ goto set_polling_time;
+ }
+ gauge_dev_capacity.Ready = UG_CAP_DATA_READY;
+ }
+
+ /* Disable charging by temperture */
+
+ if (charger_param.id == UG31XX_ID_3105) {
+ if (export_external_termperature()) {
+ if (gauge_dev_info.ET < charger_param.temp_range[0] ||
+ gauge_dev_info.ET > charger_param.temp_range[1])
+ hw_charging_disable();
+ else
+ hw_charging_enable();
+ }
+ } else if (charger_param.id == UG31XX_ID_3102) {
+ hw_charging_enable();
+ }
+
+ if (wmt_charger_is_dc_plugin() && power_supply_am_i_supplied(psy)) {
+ if (gauge_dev_capacity.RSOC == 100)
+ batt_status = POWER_SUPPLY_STATUS_FULL;
+ else if (is_charging_full() && ug31->batt_capacity >= 90) {
+ batt_status = POWER_SUPPLY_STATUS_FULL;
+ gauge_dev_capacity.RSOC = 100;
+ mutex_lock(&ug31->info_update_lock);
+ upiGG_ForceTaper(pGGB, 1, 1, 1);
+ mutex_unlock(&ug31->info_update_lock);
+ } else
+ batt_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else
+ batt_status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ /* Report uevent while capacity changed */
+ if (ug31->batt_capacity != gauge_dev_capacity.RSOC ||
+ ug31->batt_status != batt_status) {
+ GAUGE_notice("Capacity changed: %d -> %d\n", ug31->batt_capacity, gauge_dev_capacity.RSOC);
+ GAUGE_notice("Status changed: %d -> %d\n", ug31->batt_status, batt_status);
+ power_supply_changed(psy);
+ }
+
+set_polling_time:
+ if (gauge_dev_capacity.Ready == UG_CAP_DATA_READY) {
+ queue_delayed_work(ug31xx_gauge_wq, &ug31_dev->ug31_gauge_info_work, 5*HZ);
+ } else if(force_reset == 1) {
+ queue_delayed_work(ug31xx_gauge_wq, &ug31_dev->ug31_gauge_info_work, 0*HZ);
+ } else {
+ queue_delayed_work(ug31xx_gauge_wq, &ug31_dev->ug31_gauge_info_work, 1*HZ);
+ }
+}
+
+static int __devinit ug31xx_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ int gg_status;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ ug31 = kzalloc(sizeof(*ug31), GFP_KERNEL);
+ if (!ug31)
+ return -ENOMEM;
+
+ ug31->client = client;
+ ug31->dev = &client->dev;
+
+ i2c_set_clientdata(client, ug31);
+ ug31xx_i2c_client_set(ug31->client);
+
+ gauge_dev_capacity.RSOC = 50;
+
+ /* get GGB file */
+ GAUGE_notice("[UPI]: Force to reset uG3105 driver (%d)\n", force_reset);
+ gg_status = ug31_gauge_info_reset(UG31XX_INITIAL_LOCK_DISABLE);
+ if(gg_status != 0)
+ {
+ goto ggb_init_fail;
+ }
+
+ mutex_init(&ug31->info_update_lock);
+ wake_lock_init(&ug31->cable_wake_lock, WAKE_LOCK_SUSPEND, "cable_state_changed");
+
+ ug31xx_gauge_wq = create_singlethread_workqueue("ug31xx_gauge_work_queue");
+ INIT_DELAYED_WORK(&ug31->ug31_gauge_info_work, ug31_gauge_info_work_func);
+ queue_delayed_work(ug31xx_gauge_wq, &ug31->ug31_gauge_info_work, 0*HZ);
+
+ /* power supply registration */
+ if (ug31xx_powersupply_init(client))
+ goto pwr_supply_fail;
+
+ force_reset = 0;
+ drv_status = DRV_INIT_OK;
+
+ GAUGE_notice(" Driver %s registered done\n", client->name);
+
+ return 0;
+
+pwr_supply_fail:
+ kfree(ug31);
+ggb_init_fail:
+ if(!pGGB) {
+ upiGG_UnInitial(&pGGB);
+ }
+ force_reset = 0;
+ return gg_status;
+}
+
+static int __devexit ug31xx_i2c_remove(struct i2c_client *client)
+{
+ struct ug31xx_gauge *ug31_dev;
+ int i = 0, gg_status;
+
+ for (i = 0; i < ARRAY_SIZE(ug31xx_supply); i++) {
+ power_supply_unregister(&ug31xx_supply[i]);
+ }
+
+ cancel_delayed_work_sync(&ug31->ug31_gauge_info_work);
+ destroy_workqueue(ug31xx_gauge_wq);
+ wake_lock_destroy(&ug31->cable_wake_lock);
+
+ gg_status = upiGG_UnInitial(&pGGB);
+ GAUGE_notice("Driver remove. gg_status=0x%02x\n", gg_status);
+
+ ug31_dev = i2c_get_clientdata(client);
+ if (ug31_dev) {
+ kfree(ug31_dev);
+ }
+ return 0;
+}
+
+static int ug31xx_i2c_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+ int gg_status;
+
+ GAUGE_notice("ug31xx_i2c_suspend() start\n");
+ ug3105_in_suspend = true;
+
+ cancel_delayed_work_sync(&ug31->ug31_gauge_info_work);
+ flush_workqueue(ug31xx_gauge_wq);
+
+ mutex_lock(&ug31->info_update_lock);
+ gg_status = upiGG_PreSuspend(pGGB);
+ if (gg_status != UG_READ_DEVICE_INFO_SUCCESS) {
+ GAUGE_err("Fail in suspend. gg_status=0x%02x\n", gg_status);
+ } else
+ GAUGE_notice("Driver suspend. gg_status=0x%02x\n", gg_status);
+ mutex_unlock(&ug31->info_update_lock);
+
+ charger_dc_in_before_suspend = wmt_charger_is_dc_plugin();
+
+ if (charger_param.id == UG31XX_ID_3102) {
+ hw_charging_enable();
+ }
+
+ GAUGE_notice("ug31xx_i2c_suspend() end\n");
+ return 0;
+}
+
+static int ug31xx_i2c_resume(struct i2c_client *client)
+{
+ int gg_status;
+
+ GAUGE_notice("ug31xx_i2c_resume() start\n");
+
+ mutex_lock(&ug31->info_update_lock);
+ gg_status = upiGG_Wakeup(pGGB, charger_dc_in_before_suspend);
+ if (gg_status != UG_READ_DEVICE_INFO_SUCCESS) {
+ GAUGE_err("Fail in resume. gg_status=0x%02x\n", gg_status);
+ if(gg_status == UG_MEAS_FAIL_BATTERY_REMOVED)
+ {
+ gauge_dev_capacity.NAC = 0;
+ gauge_dev_capacity.LMD = 0;
+ gauge_dev_capacity.RSOC = 0;
+ }
+ } else {
+ GAUGE_notice("Driver resume. gg_status=0x%02x\n", gg_status);
+ }
+ mutex_unlock(&ug31->info_update_lock);
+
+ /// [AT-PM] : Check charger status ; 08/14/2013
+ mutex_lock(&ug31->info_update_lock);
+ upiGG_ForceTaper(pGGB, is_charging_full(), charger_dc_in_before_suspend, wmt_charger_is_dc_plugin());
+ mutex_unlock(&ug31->info_update_lock);
+
+ ug3105_in_suspend = false;
+
+ cancel_delayed_work(&ug31->ug31_gauge_info_work);
+ queue_delayed_work(ug31xx_gauge_wq, &ug31->ug31_gauge_info_work, 0*HZ);
+
+ GAUGE_notice("ug31xx_i2c_resume() end\n");
+ return 0;
+}
+
+void ug31xx_i2c_shutdown(struct i2c_client *client)
+{
+ int gg_status;
+
+ cancel_delayed_work(&ug31->ug31_gauge_info_work);
+ mutex_lock(&ug31->info_update_lock);
+ gg_status = upiGG_PrePowerOff(pGGB);
+ mutex_unlock(&ug31->info_update_lock);
+ GAUGE_notice("Driver shutdown. gg_status=0x%02x\n", gg_status);
+}
+
+static const struct i2c_device_id ug31xx_i2c_id[] = {
+ { UG31XX_DEV_NAME, 0 },
+ { },
+};
+
+MODULE_DEVICE_TABLE(i2c, ug31xx_i2c_id);
+
+static struct i2c_driver ug31xx_i2c_driver = {
+ .driver = {
+ .name = UG31XX_DEV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ug31xx_i2c_probe,
+ .remove = __devexit_p(ug31xx_i2c_remove),
+ .suspend = ug31xx_i2c_suspend,
+ .resume = ug31xx_i2c_resume,
+ .shutdown = ug31xx_i2c_shutdown,
+ .id_table = ug31xx_i2c_id,
+};
+
+static struct i2c_board_info ug31xx_i2c_board_info = {
+ .type = "ug31xx-gauge",
+ .flags = 0x00,
+ .addr = 0x70,
+ .platform_data = NULL,
+ .archdata = NULL,
+ .irq = -1,
+};
+
+static struct i2c_client *i2c_client;
+static struct i2c_adapter *i2c_adap;
+
+static int __init ug31xx_i2c_init(void)
+{
+ int ret;
+
+ ret = parse_battery_param();
+ if (ret)
+ return ret;
+
+ i2c_adap = i2c_get_adapter(charger_param.i2c);
+ if (!i2c_adap) {
+ pr_err("Cannot get i2c adapter %d\n", charger_param.i2c);
+ ret = -ENODEV;
+ goto err1;
+ }
+
+ i2c_client = i2c_new_device(i2c_adap, &ug31xx_i2c_board_info);
+ if (!i2c_client) {
+ pr_err("Unable to add I2C device for 0x%x\n",
+ ug31xx_i2c_board_info.addr);
+ ret = -ENODEV;
+ goto err2;
+ }
+
+ ret = i2c_add_driver(&ug31xx_i2c_driver);
+ if (ret)
+ goto err3;
+
+ return 0;
+
+err3: i2c_unregister_device(i2c_client);
+err2: i2c_put_adapter(i2c_adap);
+err1:
+ return ret;
+}
+module_init(ug31xx_i2c_init);
+
+static void __exit ug31xx_i2c_exit(void)
+{
+ i2c_put_adapter(i2c_adap);
+ i2c_del_driver(&ug31xx_i2c_driver);
+ i2c_unregister_device(i2c_client);
+}
+module_exit(ug31xx_i2c_exit);
+
+MODULE_DESCRIPTION("ug31xx gauge driver");
+MODULE_LICENSE("GPL");
+
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,12)
+MODULE_PARM (force_reset, "b");
+MODULE_PARM (enable_debug, "b");
+#else
+module_param (force_reset, byte, 0);
+module_param (enable_debug, byte, 0644);
+#endif
+MODULE_PARM_DESC(force_reset, "Set 1 to force reset driver as first power on.");
+MODULE_PARM_DESC(enable_debug, "Set 1 to enable dumping debug message.");
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.h b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.h
new file mode 100755
index 00000000..212d7e30
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_gauge.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2012, ASUSTek, Inc. All Rights Reserved.
+ */
+
+#ifndef __UPI_ug31xx_GAUGE_H
+#define __UPI_ug31xx_GAUGE_H
+
+//#define UG31XX_DYNAMIC_POLLING
+
+#define UG31XX_DEV_NAME "ug31xx-gauge"
+
+#ifdef UG31XX_DEBUG_ENABLE
+#define GAUGE_notice(format, arg...) \
+ printk(KERN_DEBUG "GAUGE: [%s] " format , __func__ , ## arg);
+#else
+#define GAUGE_notice(format, arg...)
+#endif
+
+#define GAUGE_err(format, arg...) \
+ printk(KERN_ERR "GAUGE: [%s] " format , __FUNCTION__ , ## arg);
+
+#define UPI_DEBUG_STRING (320)
+
+typedef enum {
+ DRV_NOT_READY = 0,
+ DRV_INIT_OK,
+} drv_status_t;
+
+struct ug31xx_gauge {
+ struct i2c_client *client;
+ struct device *dev;
+ struct delayed_work batt_info_update_work;
+ struct delayed_work ug31_gauge_info_work;
+ struct wake_lock cable_wake_lock;
+ struct mutex info_update_lock;
+ u32 cable_status;
+ u32 polling_time;
+ u32 batt_volt;
+ u32 batt_capacity;
+ u32 batt_charge_now;
+ u32 batt_charge_full;
+ int batt_current;
+ int batt_temp;
+ int batt_status;
+
+ /// [AT-PM] : Add for version ; 01/30/2013
+ char gauge_debug[UPI_DEBUG_STRING];
+
+ /// [AT-PM] : Alarm status ; 04/19/2013
+ u8 alarmSts;
+};
+
+enum {
+ PWR_SUPPLY_BATTERY = 0,
+ PWR_SUPPLY_AC,
+ PWR_SUPPLY_USB
+};
+
+enum {
+ NO_CABLE = 0,
+ USB_PC_CABLE = 1,
+ AC_ADAPTER_CABLE = 3
+};
+
+#endif /*__UPI_ug31xx_GAUGE_H */
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.c b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.c
new file mode 100755
index 00000000..6249df50
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2012, ASUSTek, Inc. All Rights Reserved.
+ */
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+#include <linux/interrupt.h>
+#include <linux/idr.h>
+#include <linux/proc_fs.h>
+#include <linux/uaccess.h>
+#include "ug31xx_i2c.h"
+
+static struct i2c_client *ug31xx_client = NULL;
+
+void ug31xx_i2c_client_set(struct i2c_client *client)
+{
+ ug31xx_client = client;
+ dev_info(&ug31xx_client->dev, "%s: Ug31xx i2c client saved.\n", __func__);
+}
+
+int ug31xx_read_i2c(struct i2c_client *client,
+ u8 reg, int *rt_value, int b_single)
+{
+ struct i2c_msg msg[2];
+ unsigned char data[4];
+ int err;
+
+ if (!client || !client->adapter)
+ return -ENODEV;
+
+ if (!rt_value) return -EINVAL;
+
+ data[0] = reg;
+ //err = i2c_transfer(client->adapter, msg, 1);
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0 | I2C_M_NOSTART;
+ msg[0].len = 1;
+ if (reg >= 0x80) {
+ data[1] = SECURITY_KEY;
+ msg[0].len++;
+ }
+ msg[0].buf = (unsigned char *)data;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = (I2C_M_RD);
+ msg[1].len = b_single ? 1 : 2;
+ msg[1].buf = (unsigned char *)data;
+
+ err = i2c_transfer(client->adapter, msg, sizeof(msg)/sizeof(struct i2c_msg));
+
+ if (err < 0) return err;
+
+ if (b_single)
+ *rt_value = data[0];
+ else
+ *rt_value = get_unaligned_le16(data);
+
+ return 0;
+}
+
+int ug31xx_write_i2c(struct i2c_client *client,
+ u8 reg, int rt_value, int b_single)
+{
+ struct i2c_msg msg[1];
+ unsigned char data[4];
+ int err;
+ int idx;
+ int tmp_buf=0;
+
+ if (!client || !client->adapter)
+ return -ENODEV;
+
+ idx = 0;
+ data[idx++] = reg;
+ if (reg >= 0x80) {
+ data[idx++] = SECURITY_KEY;
+ }
+ data[idx++] = rt_value & 0x0FF;
+ data[idx++] = (rt_value & 0x0FF00)>>8;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0 | I2C_M_NOSTART;
+ msg[0].len = b_single ? idx-1 : idx;
+ msg[0].buf = (unsigned char *)data;
+
+ err = i2c_transfer(client->adapter, msg, sizeof(msg)/sizeof(struct i2c_msg));
+
+ if (err >= 0)
+ err = ug31xx_read_i2c(client, reg, &tmp_buf, b_single);
+ //dev_info(&client->dev, "%s:: 0x%02X, 0x%04X, 0x%04X. %s\n",
+ // __func__, reg, rt_value, tmp_buf, err < 0 ? "FAIL" : "SUCCESS");
+
+ return err < 0 ? err : 0;
+}
+
+bool _API_I2C_Write(u16 writeAddress, u8 writeLength, u8 *PWriteData)
+{
+ int i, ret;
+ int byte_flag=0;
+
+ if (!PWriteData) {
+ dev_err(&ug31xx_client->dev, "%s: Write buffer pointer error.\n", __func__);
+ return false;
+ }
+
+ byte_flag = ONE_BYTE;
+
+ for (i=0; i<writeLength; i++) {
+ int tmp_buf = PWriteData[i];
+
+ ret = ug31xx_write_i2c(ug31xx_client, writeAddress+i, tmp_buf, byte_flag);
+ if (ret) {
+ dev_err(&ug31xx_client->dev, "%s: Write data(0x%02X) fail. %d\n",
+ __func__, i, ret);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool _API_I2C_Read(u16 readAddress, u8 readLength, u8 *pReadDataBuffer)
+{
+ int i, ret;
+ int byte_flag=0;
+
+ if (!pReadDataBuffer) {
+ dev_err(&ug31xx_client->dev, "%s: Read buffer pointer error.\n", __func__);
+ return false;
+ }
+
+ byte_flag = ONE_BYTE;
+
+ for (i=0; i<readLength; i++) {
+ int tmp_buf=0;
+
+ ret = ug31xx_read_i2c(ug31xx_client, readAddress+i, &tmp_buf, byte_flag);
+ if (ret) {
+ dev_err(&ug31xx_client->dev, "%s: read data(0x%02X) fail. %d\n",
+ __func__, i, ret);
+ return false;
+ }
+ pReadDataBuffer[i] = tmp_buf;
+ }
+
+ return true;
+}
+
diff --git a/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.h b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.h
new file mode 100755
index 00000000..630fedd6
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/ug31xx/ug31xx_i2c.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2012, ASUSTek, Inc. All Rights Reserved.
+ */
+#include <linux/i2c.h>
+
+#define SECURITY_KEY 0x5A //i2c read/write
+#define ONE_BYTE 0x1
+#define TWO_BYTE 0x0
+
+void ug31xx_i2c_client_set(struct i2c_client *client);
+int ug31xx_read_i2c(struct i2c_client *client, u8 reg, int *rt_value, int b_single);
+int ug31xx_write_i2c(struct i2c_client *client, u8 reg, int rt_value, int b_single);
+
+bool _API_I2C_Write(u16 writeAddress, u8 writeLength, u8 *PWriteData);
+bool _API_I2C_Read(u16 readAddress, u8 readLength, u8 *pReadDataBuffer);
+
+static inline bool API_I2C_Read(bool bSecurityMode, bool bHighSpeedMode,
+ bool bTenBitMode ,u16 readAddress, u8 readLength, u8 *pReadDataBuffer)
+{
+ return _API_I2C_Read(readAddress, readLength, pReadDataBuffer);
+}
+
+static inline bool API_I2C_Write(bool bSecurityMode, bool bHighSpeedMode, bool bTenBitMode,
+ u16 writeAddress, u8 writeLength, u8 *PWriteData)
+{
+ return _API_I2C_Write(writeAddress, writeLength, PWriteData);
+}
+
+static inline bool API_I2C_SingleRead(bool bSecurityMode,bool bHighSpeedMode, bool bTenBitMode ,
+ u16 readAddress, u8 *ReadData)
+{
+ return API_I2C_Read(bSecurityMode, bHighSpeedMode, bTenBitMode, readAddress, 1, ReadData);
+}
+
+static inline bool API_I2C_SingleWrite(bool bSecurityMode, bool bHighSpeedMode, bool bTenBitMode ,
+ u16 writeAddress, u8 WriteData)
+{
+ return API_I2C_Write(bSecurityMode, bHighSpeedMode, bTenBitMode, writeAddress, 1, &WriteData);
+}
+static inline bool API_I2C_Init(u32 clockRate, u16 slaveAddr)
+{
+ return true;
+}
diff --git a/drivers/power/wmt_battery/gauge/vt1603/Makefile b/drivers/power/wmt_battery/gauge/vt1603/Makefile
new file mode 100755
index 00000000..c30df937
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/vt1603/Makefile
@@ -0,0 +1,5 @@
+
+s_wmt_batt_vt1603-objs += vt1603.o batt_leds.o
+
+obj-m += s_wmt_batt_vt1603.o
+
diff --git a/drivers/power/wmt_battery/gauge/vt1603/batt_leds.c b/drivers/power/wmt_battery/gauge/vt1603/batt_leds.c
new file mode 100755
index 00000000..108f40f9
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/vt1603/batt_leds.c
@@ -0,0 +1,189 @@
+/*
+ * batt_leds.c - WonderMedia Battery LED Driver.
+ *
+ * Copyright (C) 2013 WonderMedia Technologies, 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.
+ *
+ * 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/leds.h>
+#include <linux/rtc.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/gpio.h>
+#include <mach/wmt_iomux.h>
+#include <mach/wmt_env.h>
+
+enum {
+ LED_GREEN = 0,
+ LED_RED,
+};
+
+struct batt_led_trigger {
+ char *name;
+ struct led_trigger *trigger;
+};
+
+#define RTC_NAME "rtc0"
+static struct rtc_device *rtc_dev = NULL;
+static bool battery_led_registered = false;
+
+static struct batt_led_trigger batt_led_triggers[] = {
+ [LED_GREEN] = {
+ .name = "bat-green",
+ },
+ [LED_RED] = {
+ .name = "bat-red",
+ }
+};
+
+static struct gpio_led batt_leds[] = {
+ [LED_GREEN] = {
+ .name = "bat-green",
+ .default_trigger = "bat-green",
+ .retain_state_suspended = 1,
+ .active_low = 0,
+ },
+ [LED_RED] = {
+ .name = "bat-red",
+ .default_trigger = "bat-red",
+ .retain_state_suspended = 1,
+ .active_low = 0,
+ },
+};
+
+static struct gpio_led_platform_data batt_leds_data = {
+ .leds = batt_leds,
+ .num_leds = ARRAY_SIZE(batt_leds),
+};
+
+static struct platform_device batt_leds_dev = {
+ .name = "leds-gpio",
+ .id = -1,
+ .dev = {
+ .platform_data = &batt_leds_data,
+ },
+};
+
+static int parse_battery_led_param(void)
+{
+ char env[] = "wmt.charger.led";
+ char s[64];
+ size_t l = sizeof(s);
+ int id, r, g;
+
+ if (wmt_getsyspara(env, s, &l))
+ return -EINVAL;
+
+ if (sscanf(s, "%d:%d:%d", &id, &r, &g) != 3)
+ return -EINVAL;
+
+ if (id != 1 || !gpio_is_valid(r) || !gpio_is_valid(g))
+ return -EINVAL;
+
+ batt_leds[LED_GREEN].gpio = g;
+ batt_leds[LED_RED].gpio = r;
+
+ pr_info("charger-led: charging (gpio%d), full (gpio%d)\n", r, g);
+ return 0;
+}
+
+int batt_leds_update(int status)
+{
+ struct led_trigger *r = batt_led_triggers[LED_RED].trigger;
+ struct led_trigger *g = batt_led_triggers[LED_GREEN].trigger;
+
+ if (battery_led_registered == false)
+ return 0;
+
+ switch (status) {
+ case POWER_SUPPLY_STATUS_CHARGING:
+ led_trigger_event(r, LED_FULL);
+ led_trigger_event(g, LED_OFF);
+ gpio_direction_input(batt_leds[LED_GREEN].gpio);
+ break;
+ case POWER_SUPPLY_STATUS_FULL:
+ led_trigger_event(r, LED_OFF);
+ led_trigger_event(g, LED_FULL);
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ default:
+ led_trigger_event(r, LED_OFF);
+ led_trigger_event(g, LED_OFF);
+ gpio_direction_input(batt_leds[LED_GREEN].gpio);
+ break;
+ }
+ return 0;
+}
+
+void batt_leds_suspend_prepare(void)
+{
+ struct rtc_wkalrm tmp;
+ unsigned long time, now;
+ //unsigned long add = 30; /* seconds */
+ unsigned long add = 120; /* seconds */
+
+ if (battery_led_registered == false)
+ return;
+
+ if (!rtc_dev) {
+ rtc_dev = rtc_class_open(RTC_NAME);
+ if (IS_ERR_OR_NULL(rtc_dev)) {
+ rtc_dev = NULL;
+ pr_err("Cannot get RTC %s, %ld.\n", RTC_NAME, PTR_ERR(rtc_dev));
+ return;
+ }
+ }
+
+ tmp.enabled = 1;
+ rtc_read_time(rtc_dev, &tmp.time);
+ rtc_tm_to_time(&tmp.time, &now);
+ time = now + add;
+
+ rtc_time_to_tm(time, &tmp.time);
+ rtc_set_alarm(rtc_dev, &tmp);
+
+}
+
+void batt_leds_resume_complete(void)
+{
+ if (rtc_dev) {
+ rtc_class_close(rtc_dev);
+ rtc_dev = NULL;
+ }
+}
+
+int batt_leds_setup(void)
+{
+ int i;
+
+ if (parse_battery_led_param())
+ return -EINVAL;
+
+ platform_device_register(&batt_leds_dev);
+
+ for (i = 0; i < ARRAY_SIZE(batt_led_triggers); ++i)
+ led_trigger_register_simple(batt_led_triggers[i].name,
+ &batt_led_triggers[i].trigger);
+ battery_led_registered = true;
+ return 0;
+}
+
+void batt_leds_cleanup(void)
+{
+ int i;
+
+ if (battery_led_registered == true) {
+ platform_device_unregister(&batt_leds_dev);
+ for (i = 0; i < ARRAY_SIZE(batt_led_triggers); ++i)
+ led_trigger_unregister_simple(batt_led_triggers[i].trigger);
+ battery_led_registered = false;
+ }
+}
diff --git a/drivers/power/wmt_battery/gauge/vt1603/batt_leds.h b/drivers/power/wmt_battery/gauge/vt1603/batt_leds.h
new file mode 100755
index 00000000..dbcaa657
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/vt1603/batt_leds.h
@@ -0,0 +1,12 @@
+#ifndef _BATT_LEDS_H_
+#define _BATT_LEDS_H_
+
+extern int batt_leds_setup(void);
+extern void batt_leds_cleanup(void);
+extern void batt_leds_suspend_prepare(void);
+extern void batt_leds_resume_complete(void);
+extern int batt_leds_update(int status);
+
+#endif /* #ifndef _BATT_LEDS_H_ */
+
+
diff --git a/drivers/power/wmt_battery/gauge/vt1603/vt1603.c b/drivers/power/wmt_battery/gauge/vt1603/vt1603.c
new file mode 100755
index 00000000..e75a99e8
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/vt1603/vt1603.c
@@ -0,0 +1,513 @@
+/*
+ * vt1603.c - WonderMedia VT1603 Adc Battery Driver.
+ *
+ * Copyright (C) 2013 WonderMedia Technologies, 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.
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/proc_fs.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/power/wmt_battery.h>
+#include <mach/wmt_env.h>
+
+#include "vt1603.h"
+#include "batt_leds.h"
+
+enum {
+ COMPENSATION_VOLUME = 0,
+ COMPENSATION_BRIGHTNESS,
+ COMPENSATION_WIFI,
+ COMPENSATION_VIDEO,
+ COMPENSATION_USB,
+ COMPENSATION_HDMI,
+ COMPENSATION_COUNT
+};
+
+static const char *compensation_strings[] = {
+ "volume",
+ "brightness",
+ "wifi",
+ "video",
+ "usb",
+ "hdmi"
+};
+
+struct vt1603_device_info {
+ struct vt1603 *vt1603;
+ struct device *dev;
+ struct power_supply ps_bat;
+ struct mutex mutex;
+ int compensation[COMPENSATION_COUNT];
+
+ int capacity;
+ int sleeping;
+ int debug;
+};
+
+static struct vt1603_device_info *vt1603_dev_info = NULL;
+
+static int vt1603_set_reg8(struct vt1603 *vt1603, u8 reg, u8 val)
+{
+ int ret = vt1603->reg_write(vt1603, reg, val);
+ if (ret)
+ pr_err("vt1603 battery write error, errno%d\n", ret);
+ return ret;
+}
+
+static u8 vt1603_get_reg8(struct vt1603 *vt1603, u8 reg)
+{
+ u8 val = 0;
+ int ret = 0;
+
+ ret = vt1603->reg_read(vt1603, reg, &val);
+ if (ret < 0){
+ pr_err("vt1603 battery read error, errno%d\n", ret);
+ return 0;
+ }
+
+ return val;
+}
+
+static int vt1603_read8(struct vt1603 *vt1603, u8 reg, u8 *data)
+{
+ int ret = vt1603->reg_read(vt1603, reg, data);
+ if (ret)
+ pr_err("vt1603 battery read error, errno%d\n", ret);
+ return ret;
+}
+
+static void vt1603_setbits(struct vt1603 *vt1603, u8 reg, u8 mask)
+{
+ u8 tmp = vt1603_get_reg8(vt1603, reg) | mask;
+ vt1603_set_reg8(vt1603, reg, tmp);
+}
+
+static void vt1603_clrbits(struct vt1603 *vt1603, u8 reg, u8 mask)
+{
+ u8 tmp = vt1603_get_reg8(vt1603, reg) & (~mask);
+ vt1603_set_reg8(vt1603, reg, tmp);
+}
+
+#define ADC_DATA(low, high) ((((high) & 0x0F) << 8) + (low))
+
+static int vt1603_get_bat_data(struct vt1603 *vt1603,int *data)
+{
+ int ret = 0;
+ u8 data_l, data_h;
+
+ ret |= vt1603_read8(vt1603, VT1603_DATL_REG, &data_l);
+ ret |= vt1603_read8(vt1603, VT1603_DATH_REG, &data_h);
+
+ *data = ADC_DATA(data_l, data_h);
+ return ret;
+}
+
+static void vt1603_switch_to_bat_mode(struct vt1603 *vt1603)
+{
+ vt1603_set_reg8(vt1603, VT1603_CR_REG, 0x00);
+ vt1603_set_reg8(vt1603, 0xc6, 0x00);
+ vt1603_set_reg8(vt1603, VT1603_AMCR_REG, BIT0);
+}
+
+static inline void vt1603_bat_pen_manual(struct vt1603 *vt1603)
+{
+ vt1603_setbits(vt1603, VT1603_INTCR_REG, BIT7);
+}
+
+static void vt1603_bat_power_up(struct vt1603 *vt1603)
+{
+ if (vt1603_get_reg8(vt1603, VT1603_PWC_REG) != 0x08)
+ vt1603_set_reg8(vt1603, VT1603_PWC_REG, 0x08);
+}
+
+static int vt1603_read_volt(struct vt1603 *vt1603)
+{
+ int timeout = 2000;
+ int ret = 0;
+ int value;
+
+ // wait for interrupt that adc converted completed.
+ do {
+ if (vt1603_get_reg8(vt1603, VT1603_INTS_REG) & BIT0)
+ break;
+ } while (timeout--);
+
+ if (!timeout) {
+ pr_err("wait adc end timeout ?!\n");
+ return -ETIMEDOUT;
+ }
+
+ ret = vt1603_get_bat_data(vt1603, &value);
+ if (ret < 0) {
+ pr_err("vt1603 get bat adc data Failed!\n");
+ return ret;
+ }
+
+ return value;
+}
+
+static int vt1603_manual_read_adc(struct vt1603 *vt1603)
+{
+ int i;
+ int ret = 0;
+ uint32_t sum = 0;
+
+ /* enable sar-adc power and clock */
+ vt1603_bat_power_up(vt1603);
+ /* enable pen down/up to avoid miss irq */
+ vt1603_bat_pen_manual(vt1603);
+ /* switch vt1603 to battery detect mode */
+ vt1603_switch_to_bat_mode(vt1603);
+ /* do conversion use battery manual mode */
+ vt1603_setbits(vt1603, VT1603_INTS_REG, BIT0);
+ vt1603_set_reg8(vt1603, VT1603_CR_REG, BIT4);
+
+ for (i = 0; i < 4; i++) {
+ ret = vt1603_read_volt(vt1603);
+ if (ret < 0)
+ break;
+ sum += ret;
+
+ vt1603_setbits(vt1603, VT1603_INTS_REG, BIT0);
+ vt1603_set_reg8(vt1603, VT1603_CR_REG, BIT4); //start manual ADC mode
+ }
+ vt1603_clrbits(vt1603, VT1603_INTCR_REG, BIT7);
+ vt1603_setbits(vt1603, VT1603_INTS_REG, BIT0 | BIT3);
+ vt1603_set_reg8(vt1603, VT1603_CR_REG, BIT1);
+
+ return (ret < 0) ? ret : (sum >> 2);
+}
+
+static inline int volt_reg_to_mV(int value)
+{
+ return ((value * 1047) / 1000);
+}
+
+static int vt1603_bat_read_voltage(struct vt1603_device_info *di, int *intval)
+{
+ int ret = vt1603_manual_read_adc(di->vt1603);
+ if (ret < 0)
+ return ret;
+
+ *intval = volt_reg_to_mV(ret);
+ return 0;
+}
+
+static int vt1603_bat_read_status(struct vt1603_device_info *di, int *intval)
+{
+ int status;
+
+ status = charger_get_status();
+ if (status < 0)
+ return status;
+
+ if (status == POWER_SUPPLY_STATUS_CHARGING && di->capacity == 100)
+ status = POWER_SUPPLY_STATUS_FULL;
+
+ batt_leds_update(status);
+ *intval = status;
+ return 0;
+}
+
+static int vt1603_proc_read(char *buf, char **start, off_t offset, int len,
+ int *eof, void *data)
+{
+ int l = 0, i;
+ int ret, status, dcin, voltage, full;
+ struct vt1603_device_info *di = vt1603_dev_info;
+
+ mutex_lock(&di->mutex);
+
+ ret = vt1603_bat_read_status(di, &status);
+ if (ret) {
+ pr_err("vt1603_bat_read_status failed\n");
+ goto out;
+ }
+ voltage = vt1603_manual_read_adc(di->vt1603);
+ if (voltage < 0) {
+ pr_err("vt1603_manual_read_adc failed\n");
+ goto out;
+ }
+
+ dcin = power_supply_is_system_supplied();
+ full = charger_is_full();
+
+ l += sprintf(buf + l, "status : %d\n", status);
+ l += sprintf(buf + l, "dcin : %d\n", dcin);
+ l += sprintf(buf + l, "voltage : %d\n", voltage);
+ l += sprintf(buf + l, "full : %d\n", full);
+ l += sprintf(buf + l, "sleeping : %d\n", di->sleeping);
+ l += sprintf(buf + l, "debug : %d\n", di->debug);
+
+ for (i = 0; i < COMPENSATION_COUNT; i++) {
+ l += sprintf(buf +l, "compensation %10s : %d\n",
+ compensation_strings[i], di->compensation[i]);
+ }
+
+ /* clear after read */
+ di->sleeping = 0;
+out:
+ mutex_unlock(&di->mutex);
+ return l;
+}
+
+static int vt1603_proc_write(struct file *file, const char *buffer,
+ unsigned long count, void *data)
+{
+ int bm, usage;
+ struct vt1603_device_info *di = vt1603_dev_info;
+ int capacity;
+
+ if (sscanf(buffer, "capacity=%d", &capacity)) {
+ di->capacity = capacity;
+ power_supply_changed(&di->ps_bat);
+ return count;
+ }
+
+ if (sscanf(buffer, "debug=%d", &di->debug))
+ return count;
+
+ if (sscanf(buffer, "MODULE_CHANGE:%d-%d", &bm, &usage) < 2)
+ return 0;
+
+ if (bm < 0 || bm >= COMPENSATION_COUNT) {
+ pr_err("bm %d error, [0, %d)\n", bm, COMPENSATION_COUNT);
+ return 0;
+ }
+
+ if (usage > 100 || usage < 0) {
+ pr_err("usage %d error\n", usage);
+ return 0;
+ }
+
+ mutex_lock(&di->mutex);
+ di->compensation[bm] = usage;
+ mutex_unlock(&di->mutex);
+ return count;
+}
+
+#define VT1603_PROC_NAME "battery_calibration"
+
+static void vt1603_proc_init(void)
+{
+ struct proc_dir_entry *entry;
+
+ entry = create_proc_entry(VT1603_PROC_NAME, 0666, NULL);
+ if (entry) {
+ entry->read_proc = vt1603_proc_read;
+ entry->write_proc = vt1603_proc_write;
+ }
+}
+
+static void vt1603_proc_cleanup(void)
+{
+ remove_proc_entry(VT1603_PROC_NAME, NULL);
+}
+
+#define to_vt1603_device_info(x) container_of((x), \
+ struct vt1603_device_info, ps_bat);
+
+static int vt1603_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct vt1603_device_info *di = to_vt1603_device_info(psy);
+
+ mutex_lock(&di->mutex);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = vt1603_bat_read_status(di, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = di->capacity;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = vt1603_bat_read_voltage(di, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = 250;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 1;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ mutex_unlock(&di->mutex);
+ return ret;
+}
+
+static void vt1603_external_power_changed(struct power_supply *psy)
+{
+ power_supply_changed(psy);
+}
+
+static enum power_supply_property vt1603_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+};
+
+static int __devinit vt1603_batt_probe(struct platform_device *pdev)
+{
+ struct vt1603_device_info *di;
+ int ret;
+
+ di = kzalloc(sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ dev_err(&pdev->dev, "no memery\n");
+ return -ENOMEM;
+ }
+
+ di->dev = &pdev->dev;
+ di->vt1603 = dev_get_platdata(&pdev->dev);
+ di->capacity = 50;
+
+ mutex_init(&di->mutex);
+
+ di->ps_bat.name = "battery";
+ di->ps_bat.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->ps_bat.properties = vt1603_battery_props;
+ di->ps_bat.num_properties = ARRAY_SIZE(vt1603_battery_props);
+ di->ps_bat.get_property = vt1603_battery_get_property;
+ di->ps_bat.external_power_changed = vt1603_external_power_changed;
+
+ ret = power_supply_register(di->dev, &di->ps_bat);
+ if (ret) {
+ dev_err(di->dev, "failed to register battery: %d\n", ret);
+ kfree(di);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, di);
+ vt1603_dev_info = di;
+
+ vt1603_proc_init();
+
+ pr_info("VT1603 Battery Driver Installed!\n");
+ return 0;
+}
+
+static int __devexit vt1603_batt_remove(struct platform_device *pdev)
+{
+ struct vt1603_device_info *di = platform_get_drvdata(pdev);
+ vt1603_proc_cleanup();
+ power_supply_unregister(&di->ps_bat);
+ kfree(di);
+ vt1603_dev_info = NULL;
+ pr_info("VT1603 Battery Driver Removed!\n");
+ return 0;
+}
+
+static int vt1603_batt_suspend_prepare(struct device *dev)
+{
+ struct vt1603_device_info *di = dev_get_drvdata(dev);
+ int status, ret;
+
+ ret = vt1603_bat_read_status(di, &status);
+ if (ret) {
+ pr_err("vt1603_bat_read_status failed\n");
+ return 0;
+ }
+
+ if (status == POWER_SUPPLY_STATUS_CHARGING)
+ batt_leds_suspend_prepare();
+
+ return 0;
+}
+
+static int vt1603_batt_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int vt1603_batt_resume(struct device *dev)
+{
+ struct vt1603_device_info *di = dev_get_drvdata(dev);
+ di->sleeping = 1;
+ return 0;
+}
+
+static void vt1603_batt_resume_complete(struct device *dev)
+{
+ return batt_leds_resume_complete();
+}
+
+static const struct dev_pm_ops vt1603_batt_manager_pm = {
+ .prepare = vt1603_batt_suspend_prepare,
+ .suspend = vt1603_batt_suspend,
+ .resume = vt1603_batt_resume,
+ .complete = vt1603_batt_resume_complete,
+};
+
+static struct platform_driver vt1603_batt_driver = {
+ .driver = {
+ .name = "vt1603-batt",
+ .owner = THIS_MODULE,
+ .pm = &vt1603_batt_manager_pm,
+ },
+ .probe = vt1603_batt_probe,
+ .remove = __devexit_p(vt1603_batt_remove),
+};
+
+static int parse_battery_param(void)
+{
+ char env[] = "wmt.battery.param";
+ char buf[64];
+ size_t l = sizeof(buf);
+
+ if (wmt_getsyspara(env, buf, &l))
+ return -EINVAL;
+
+ return prefixcmp(buf, "vt1603") ? -ENODEV : 0;
+}
+
+static int __init vt1603_batt_init(void)
+{
+ if (parse_battery_param())
+ return -EINVAL;
+
+ batt_leds_setup();
+ return platform_driver_register(&vt1603_batt_driver);
+}
+
+static void __exit vt1603_batt_exit(void)
+{
+ platform_driver_unregister(&vt1603_batt_driver);
+ batt_leds_cleanup();
+}
+
+module_init(vt1603_batt_init);
+module_exit(vt1603_batt_exit);
+
+MODULE_AUTHOR("WonderMedia");
+MODULE_DESCRIPTION("WonderMedia VT1603 Adc Battery Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/power/wmt_battery/gauge/vt1603/vt1603.h b/drivers/power/wmt_battery/gauge/vt1603/vt1603.h
new file mode 100755
index 00000000..24471226
--- /dev/null
+++ b/drivers/power/wmt_battery/gauge/vt1603/vt1603.h
@@ -0,0 +1,124 @@
+/*++
+ Copyright (c) 2008 WonderMedia Technologies, 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.
+
+ 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/>.
+
+ WonderMedia Technologies, Inc.
+ 10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C.
+--*/
+
+#ifndef __VT1603_BAT_H__
+#define __VT1603_BAT_H__
+#include <linux/mfd/vt1603/core.h>
+
+#define BIT0 0x00000001
+#define BIT1 0x00000002
+#define BIT2 0x00000004
+#define BIT3 0x00000008
+#define BIT4 0x00000010
+#define BIT5 0x00000020
+#define BIT6 0x00000040
+#define BIT7 0x00000080
+#define BIT8 0x00000100
+
+/* VT1603 touch panel state */
+#define TS_PENDOWN_STATE 0x00
+#define TS_PENUP_STATE 0x01
+
+/* vt1603 bus type config */
+#define VT1603_MAX_SPI_CLK (20*1000*1000)
+#define SPI_DEFAULT_CLK (12*1000*1000)
+#define IDLE_DATA_NUM 5
+#define VT1603_SPI_FIX_CS 0x00
+#define VT1603_SPI_FAKE_CS 3 //(0x7F - 1)
+#define VT1603_SPI_BUS_0 0x00
+#define VT1603_SPI_BUS_1 0x01
+#define VT1603_REG_OP_R 0x00
+#define VT1603_REG_OP_W 0x01
+
+
+#define VT1603_I2C_FIX_ADDR 0x1A
+#define VT1603_I2C_FAKE_ADDR 0xEE
+#define VT1603_I2C_WCMD 0x00
+#define VT1603_I2C_RCMD 0x01
+#define VT1603_I2C_RWCMD 0x02
+#define VT1603_I2C_BUS_0 0x00
+#define VT1603_I2C_BUS_1 0x01
+
+
+#define BA_WAKEUP_SRC_0 BIT0
+#define BA_WAKEUP_SRC_1 BIT1
+#define BA_WAKEUP_SRC_2 BIT2
+#define BA_WAKEUP_SRC_3 BIT3
+
+#define I2C_BUS 0
+#define SPI_BUS 1
+#define VT1603_FIFO_LEN 3
+
+/* VT1603 Register address */
+#define VT1603_BTHD_REG 0x78
+#define VT1603_BCLK_REG 0x88
+#define VT1603_BAEN_REG 0x04
+
+#define VT1603_PWC_REG 0xC0
+#define VT1603_CR_REG 0xC1
+#define VT1603_CCCR_REG 0xC2
+#define VT1603_CDPR_REG 0xC3
+#define VT1603_TSPC_REG 0xC4
+#define VT1603_AMCR_REG 0xC7
+#define VT1603_INTCR_REG 0xC8
+#define VT1603_INTEN_REG 0xC9
+#define VT1603_INTS_REG 0xCA
+#define VT1603_DCR_REG 0xCB
+
+#define VT1603_TODCL_REG 0xCC
+#define VT1603_TODCH_REG 0xCD
+
+#define VT1603_DATL_REG 0xCE
+#define VT1603_DATH_REG 0xCF
+
+#define VT1603_XPL_REG 0xD0
+#define VT1603_XPH_REG 0xD1
+#define VT1603_YPL_REG 0xD2
+#define VT1603_YPH_REG 0xD3
+
+#define VT1603_BATL_REG 0xD4
+#define VT1603_BATH_REG 0xD5
+
+#define VT1603_TEMPL_REG 0xD6
+#define VT1603_TEMPH_REG 0xD7
+
+#define VT1603_ERR8_REG 0xD8
+#define VT1603_ERR7_REG 0xD9
+#define VT1603_ERR6_REG 0xDA
+#define VT1603_ERR5_REG 0xDB
+#define VT1603_ERR4_REG 0xDC
+#define VT1603_ERR3_REG 0xDD
+#define VT1603_ERR2_REG 0xDE
+#define VT1603_ERR1_REG 0xDF
+
+#define VT1603_DBG8_REG 0xE0
+#define VT1603_DBG7_REG 0xE1
+#define VT1603_DBG6_REG 0xE2
+#define VT1603_DBG5_REG 0xE3
+#define VT1603_DBG4_REG 0xE4
+#define VT1603_DBG3_REG 0xE5
+#define VT1603_DBG2_REG 0xE6
+#define VT1603_DBG1_REG 0xE7
+
+/* for VT1603 GPIO1 interrupt setting */
+#define VT1603_IMASK_REG27 27
+#define VT1603_IMASK_REG28 28
+#define VT1603_IMASK_REG29 29
+#define VT1603_IPOL_REG33 33
+#define VT1603_ISEL_REG36 36
+
+#endif /* __VT1603_TS_H__ */
diff --git a/drivers/power/wmt_battery/wmt_battery.c b/drivers/power/wmt_battery/wmt_battery.c
new file mode 100755
index 00000000..23692ee9
--- /dev/null
+++ b/drivers/power/wmt_battery/wmt_battery.c
@@ -0,0 +1,66 @@
+/*
+ * mp2625_charger.c - WonderMedia Charger Driver.
+ *
+ * Copyright (C) 2013 WonderMedia Technologies, 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.
+ *
+ * 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/gpio.h>
+#include <mach/wmt_iomux.h>
+#include <mach/wmt_env.h>
+#include <linux/power/wmt_battery.h>
+
+extern void mp2625_pc_connected(void);
+extern void g2214_pc_connected(void);
+
+void wmt_do_pc_connected(void)
+{
+ mp2625_pc_connected();
+ g2214_pc_connected();
+}
+
+static struct {
+ int led_power;
+ int led_gpio_level;
+} charger_led;
+
+void led_power_enable(int enable)
+{
+ if (gpio_is_valid(charger_led.led_power)) {
+ if (enable)
+ gpio_direction_output(charger_led.led_power,
+ charger_led.led_gpio_level);
+ else
+ gpio_direction_output(charger_led.led_power,
+ !charger_led.led_gpio_level);
+ }
+}
+
+int parse_charger_led(void)
+{
+ static const char uboot_env[] = "wmt.charger.led";
+ char buf[64];
+ size_t l = sizeof(buf);
+ int id;
+
+ if (wmt_getsyspara((char *)uboot_env, buf, &l) ||
+ (sscanf(buf, "%d:%d:%d", &id,
+ &charger_led.led_power, &charger_led.led_gpio_level) != 3) ||
+ id != 0 || !gpio_is_valid(charger_led.led_power) ||
+ gpio_request(charger_led.led_power, "led power")) {
+ charger_led.led_power = -1;
+ return -EINVAL;
+ }
+
+ led_power_enable(0);
+ return 0;
+}
+
diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c
new file mode 100644
index 00000000..8c9a607e
--- /dev/null
+++ b/drivers/power/z2_battery.c
@@ -0,0 +1,323 @@
+/*
+ * 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, 0,
+ "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 = __devexit_p(z2_batt_remove),
+ .id_table = z2_batt_id,
+};
+module_i2c_driver(z2_batt_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Edwards <sweetlilmre@gmail.com>");
+MODULE_DESCRIPTION("Zipit Z2 battery driver");