Merge commit 'AU_LINUX_ANDROID_ICS.04.00.04.00.126' into msm-3.4
AU_LINUX_ANDROID_ICS.04.00.04.00.126 from msm-3.0.
First parent is from google/android-3.4.
* commit 'AU_LINUX_ANDROID_ICS.04.00.04.00.126': (8712 commits)
PRNG: Device tree entry for qrng device.
vidc:1080p: Set video core timeout value for Thumbnail mode
msm: sps: improve the debugging support in SPS driver
board-8064 msm: Overlap secure and non secure video firmware heaps.
msm: clock: Add handoff ops for 7x30 and copper XO clocks
msm_fb: display: Wait for external vsync before DTV IOMMU unmap
msm: Fix ciruclar dependency in debug UART settings
msm: gdsc: Add GDSC regulator driver for msm-copper
defconfig: Enable Mobicore Driver.
mobicore: Add mobicore driver.
mobicore: rename variable to lower case.
mobicore: rename folder.
mobicore: add makefiles
mobicore: initial import of kernel driver
ASoC: msm: Add SLIMBUS_2_RX CPU DAI
board-8064-gpio: Update FUNC for EPM SPI CS
msm_fb: display: Remove chicken bit config during video playback
mmc: msm_sdcc: enable the sanitize capability
msm-fb: display: lm2 writeback support on mpq platfroms
msm_fb: display: Disable LVDS phy & pll during panel off
...
Signed-off-by: Steve Muckle <smuckle@codeaurora.org>
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 99dc29f..a263750 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -288,6 +288,133 @@
Say Y to enable support for the battery charger control sysfs and
platform data of MAX8998/LP3974 PMICs.
+config BATTERY_MSM
+ tristate "MSM battery"
+ depends on ARCH_MSM
+ default m
+ help
+ Say Y to enable support for the battery in Qualcomm MSM.
+
+config BATTERY_MSM8X60
+ tristate "MSM8X60 battery"
+ select PMIC8XXX_BATTALARM
+ help
+ Some MSM boards have dual charging paths to charge the battery.
+ Say Y to enable support for the battery charging in
+ such devices.
+
+config PM8058_CHARGER
+ tristate "pmic8058 charger"
+ depends on BATTERY_MSM8X60
+ depends on PMIC8058
+ help
+ Say Y to enable support for battery charging from the pmic8058.
+ pmic8058 provides a linear charging circuit connected to the usb
+ cable on Qualcomm's msm8x60 surf board.
+
+config ISL9519_CHARGER
+ tristate "isl9519 charger"
+ depends on (BATTERY_MSM8X60 || PM8921_CHARGER)
+ depends on I2C
+ default n
+ help
+ The isl9519q charger chip from intersil is connected to an external
+ charger cable and is preferred way of charging the battery because
+ of its high current rating.
+ Choose Y if you are compiling for Qualcomm's msm8x60 surf/ffa board.
+
+config SMB137B_CHARGER
+ tristate "smb137b charger"
+ default n
+ depends on I2C
+ help
+ The smb137b charger chip from summit is a switching mode based
+ charging solution.
+ Choose Y if you are compiling for Qualcomm's msm8x60 fluid board.
+ To compile this driver as a module, choose M here: the module will
+ be called smb137b.
+
+config SMB349_CHARGER
+ tristate "smb349 charger"
+ depends on I2C
+ help
+ Say Y to enable support for the SMB349 switching mode based charger
+ and sysfs. The driver supports controlling charger-enable and
+ current limiting capabilities. The driver also lets the
+ SMB349 be operated as a slave device via the power supply
+ framework.
+
+config BATTERY_MSM_FAKE
+ tristate "Fake MSM battery"
+ depends on ARCH_MSM && BATTERY_MSM
+ default n
+ help
+ Say Y to bypass actual battery queries.
+
+config PM8058_FIX_USB
+ tristate "pmic8058 software workaround for usb removal"
+ depends on PMIC8058
+ depends on !PM8058_CHARGER
+ help
+ Say Y to enable the software workaround to USB Vbus line
+ staying high even when USB cable is removed. This option
+ is in lieu of a complete pm8058 charging driver.
+
+config BATTERY_QCIBAT
+ tristate "Quanta Computer Inc. Battery"
+ depends on SENSORS_WPCE775X
+ default n
+ help
+ Say Y here if you want to use the Quanta battery driver for ST15
+ platform.
+
+config BATTERY_BQ27520
+ tristate "BQ27520 battery driver"
+ depends on I2C
+ default n
+ help
+ Say Y here to enable support for batteries with BQ27520 (I2C) chips.
+
+config BATTERY_BQ27541
+ tristate "BQ27541 battery driver"
+ depends on I2C
+ default n
+ help
+ Say Y here to enable support for batteries with BQ27541 (I2C) chips.
+
+config BQ27520_TEST_ENABLE
+ bool "Enable BQ27520 Fuel Gauge Chip Test"
+ depends on BATTERY_BQ27520
+ default n
+ help
+ Say Y here to enable Test sysfs Interface for BQ27520 Drivers.
+
+config PM8921_CHARGER
+ tristate "PM8921 Charger driver"
+ depends on MFD_PM8921_CORE
+ help
+ Say Y here to enable support for pm8921 chip charger subdevice
+
+config PM8XXX_CCADC
+ tristate "PM8XXX battery current adc driver"
+ depends on MFD_PM8921_CORE
+ help
+ Say Y here to enable support for pm8921 chip bms subdevice
+
+config LTC4088_CHARGER
+ tristate "LTC4088 Charger driver"
+ depends on GPIOLIB
+ help
+ Say Y here to enable support for ltc4088 chip charger. It controls the
+ operations through GPIO pins.
+
+config PM8921_BMS
+ select PM8XXX_CCADC
+ tristate "PM8921 Battery Monitoring System driver"
+ depends on MFD_PM8921_CORE
+ help
+ Say Y here to enable support for pm8921 chip bms subdevice
+
config CHARGER_SMB347
tristate "Summit Microelectronics SMB347 Battery Charger"
depends on I2C
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index b6b2434..007d75b 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -43,4 +43,18 @@
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
+obj-$(CONFIG_BATTERY_MSM) += msm_battery.o
+obj-$(CONFIG_BATTERY_MSM8X60) += msm_charger.o
+obj-$(CONFIG_PM8058_CHARGER) += pmic8058-charger.o
+obj-$(CONFIG_ISL9519_CHARGER) += isl9519q.o
+obj-$(CONFIG_SMB349_CHARGER) += smb349.o
+obj-$(CONFIG_PM8058_FIX_USB) += pm8058_usb_fix.o
+obj-$(CONFIG_BATTERY_QCIBAT) += qci_battery.o
+obj-$(CONFIG_BATTERY_BQ27520) += bq27520_fuelgauger.o
+obj-$(CONFIG_BATTERY_BQ27541) += bq27541_fuelgauger.o
+obj-$(CONFIG_SMB137B_CHARGER) += smb137b.o
+obj-$(CONFIG_PM8XXX_CCADC) += pm8xxx-ccadc.o
+obj-$(CONFIG_PM8921_BMS) += pm8921-bms.o
+obj-$(CONFIG_PM8921_CHARGER) += pm8921-charger.o
+obj-$(CONFIG_LTC4088_CHARGER) += ltc4088-charger.o
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
diff --git a/drivers/power/bq27520_fuelgauger.c b/drivers/power/bq27520_fuelgauger.c
new file mode 100644
index 0000000..3c191cd
--- /dev/null
+++ b/drivers/power/bq27520_fuelgauger.c
@@ -0,0 +1,960 @@
+/* Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it>
+ * Copyright (C) 2008 Eurotech S.p.A. <info@eurotech.it>
+ * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc.
+ *
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/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/time.h>
+#include <linux/i2c/bq27520.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/regulator/pmic8058-regulator.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/machine.h>
+#include <linux/err.h>
+#include <linux/msm-charger.h>
+
+#define DRIVER_VERSION "1.1.0"
+/* Bq27520 standard data commands */
+#define BQ27520_REG_CNTL 0x00
+#define BQ27520_REG_AR 0x02
+#define BQ27520_REG_ARTTE 0x04
+#define BQ27520_REG_TEMP 0x06
+#define BQ27520_REG_VOLT 0x08
+#define BQ27520_REG_FLAGS 0x0A
+#define BQ27520_REG_NAC 0x0C
+#define BQ27520_REG_FAC 0x0e
+#define BQ27520_REG_RM 0x10
+#define BQ27520_REG_FCC 0x12
+#define BQ27520_REG_AI 0x14
+#define BQ27520_REG_TTE 0x16
+#define BQ27520_REG_TTF 0x18
+#define BQ27520_REG_SI 0x1a
+#define BQ27520_REG_STTE 0x1c
+#define BQ27520_REG_MLI 0x1e
+#define BQ27520_REG_MLTTE 0x20
+#define BQ27520_REG_AE 0x22
+#define BQ27520_REG_AP 0x24
+#define BQ27520_REG_TTECP 0x26
+#define BQ27520_REG_SOH 0x28
+#define BQ27520_REG_SOC 0x2c
+#define BQ27520_REG_NIC 0x2e
+#define BQ27520_REG_ICR 0x30
+#define BQ27520_REG_LOGIDX 0x32
+#define BQ27520_REG_LOGBUF 0x34
+#define BQ27520_FLAG_DSC BIT(0)
+#define BQ27520_FLAG_FC BIT(9)
+#define BQ27520_FLAG_BAT_DET BIT(3)
+#define BQ27520_CS_DLOGEN BIT(15)
+#define BQ27520_CS_SS BIT(13)
+/* Control subcommands */
+#define BQ27520_SUBCMD_CTNL_STATUS 0x0000
+#define BQ27520_SUBCMD_DEVCIE_TYPE 0x0001
+#define BQ27520_SUBCMD_FW_VER 0x0002
+#define BQ27520_SUBCMD_HW_VER 0x0003
+#define BQ27520_SUBCMD_DF_CSUM 0x0004
+#define BQ27520_SUBCMD_PREV_MACW 0x0007
+#define BQ27520_SUBCMD_CHEM_ID 0x0008
+#define BQ27520_SUBCMD_BD_OFFSET 0x0009
+#define BQ27520_SUBCMD_INT_OFFSET 0x000a
+#define BQ27520_SUBCMD_CC_VER 0x000b
+#define BQ27520_SUBCMD_OCV 0x000c
+#define BQ27520_SUBCMD_BAT_INS 0x000d
+#define BQ27520_SUBCMD_BAT_REM 0x000e
+#define BQ27520_SUBCMD_SET_HIB 0x0011
+#define BQ27520_SUBCMD_CLR_HIB 0x0012
+#define BQ27520_SUBCMD_SET_SLP 0x0013
+#define BQ27520_SUBCMD_CLR_SLP 0x0014
+#define BQ27520_SUBCMD_FCT_RES 0x0015
+#define BQ27520_SUBCMD_ENABLE_DLOG 0x0018
+#define BQ27520_SUBCMD_DISABLE_DLOG 0x0019
+#define BQ27520_SUBCMD_SEALED 0x0020
+#define BQ27520_SUBCMD_ENABLE_IT 0x0021
+#define BQ27520_SUBCMD_DISABLE_IT 0x0023
+#define BQ27520_SUBCMD_CAL_MODE 0x0040
+#define BQ27520_SUBCMD_RESET 0x0041
+
+#define ZERO_DEGREE_CELSIUS_IN_TENTH_KELVIN (-2731)
+#define BQ27520_INIT_DELAY ((HZ)*1)
+#define BQ27520_POLLING_STATUS ((HZ)*3)
+#define BQ27520_COULOMB_POLL ((HZ)*30)
+
+/* 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);
+
+struct bq27520_device_info;
+struct bq27520_access_methods {
+ int (*read)(u8 reg, int *rt_value, int b_single,
+ struct bq27520_device_info *di);
+};
+
+struct bq27520_device_info {
+ struct device *dev;
+ int id;
+ struct bq27520_access_methods *bus;
+ struct i2c_client *client;
+ const struct bq27520_platform_data *pdata;
+ struct work_struct counter;
+ /* 300ms delay is needed after bq27520 is powered up
+ * and before any successful I2C transaction
+ */
+ struct delayed_work hw_config;
+ uint32_t irq;
+};
+
+enum {
+ GET_BATTERY_STATUS,
+ GET_BATTERY_TEMPERATURE,
+ GET_BATTERY_VOLTAGE,
+ GET_BATTERY_CAPACITY,
+ NUM_OF_STATUS,
+};
+
+struct bq27520_status {
+ /* Informations owned and maintained by Bq27520 driver, updated
+ * by poller or SOC_INT interrupt, decoupling from I/Oing
+ * hardware directly
+ */
+ int status[NUM_OF_STATUS];
+ spinlock_t lock;
+ struct delayed_work poller;
+};
+
+static struct bq27520_status current_battery_status;
+static struct bq27520_device_info *bq27520_di;
+static int coulomb_counter;
+static spinlock_t lock; /* protect access to coulomb_counter */
+static struct timer_list timer; /* charge counter timer every 30 secs */
+
+static int bq27520_i2c_txsubcmd(u8 reg, unsigned short subcmd,
+ struct bq27520_device_info *di);
+
+static int bq27520_read(u8 reg, int *rt_value, int b_single,
+ struct bq27520_device_info *di)
+{
+ return di->bus->read(reg, rt_value, b_single, di);
+}
+
+/*
+ * Return the battery temperature in tenths of degree Celsius
+ * Or < 0 if something fails.
+ */
+static int bq27520_battery_temperature(struct bq27520_device_info *di)
+{
+ int ret, temp = 0;
+
+ ret = bq27520_read(BQ27520_REG_TEMP, &temp, 0, di);
+ if (ret) {
+ dev_err(di->dev, "error %d reading temperature\n", ret);
+ return ret;
+ }
+
+ return temp + ZERO_DEGREE_CELSIUS_IN_TENTH_KELVIN;
+}
+
+/*
+ * Return the battery Voltage in milivolts
+ * Or < 0 if something fails.
+ */
+static int bq27520_battery_voltage(struct bq27520_device_info *di)
+{
+ int ret, volt = 0;
+
+ ret = bq27520_read(BQ27520_REG_VOLT, &volt, 0, di);
+ if (ret) {
+ dev_err(di->dev, "error %d reading voltage\n", ret);
+ return ret;
+ }
+
+ return volt;
+}
+
+/*
+ * Return the battery Relative State-of-Charge
+ * Or < 0 if something fails.
+ */
+static int bq27520_battery_rsoc(struct bq27520_device_info *di)
+{
+ int ret, rsoc = 0;
+
+ ret = bq27520_read(BQ27520_REG_SOC, &rsoc, 0, di);
+
+ if (ret) {
+ dev_err(di->dev,
+ "error %d reading relative State-of-Charge\n", ret);
+ return ret;
+ }
+
+ return rsoc;
+}
+
+static void bq27520_cntl_cmd(struct bq27520_device_info *di,
+ int subcmd)
+{
+ bq27520_i2c_txsubcmd(BQ27520_REG_CNTL, subcmd, di);
+}
+
+/*
+ * i2c specific code
+ */
+static int bq27520_i2c_txsubcmd(u8 reg, unsigned short subcmd,
+ struct bq27520_device_info *di)
+{
+ struct i2c_msg msg;
+ unsigned char data[3];
+
+ if (!di->client)
+ return -ENODEV;
+
+ memset(data, 0, sizeof(data));
+ data[0] = reg;
+ data[1] = subcmd & 0x00FF;
+ data[2] = (subcmd & 0xFF00) >> 8;
+
+ msg.addr = di->client->addr;
+ msg.flags = 0;
+ msg.len = 3;
+ msg.buf = data;
+
+ if (i2c_transfer(di->client->adapter, &msg, 1) < 0)
+ return -EIO;
+
+ return 0;
+}
+
+static int bq27520_chip_config(struct bq27520_device_info *di)
+{
+ int flags = 0, ret = 0;
+
+ bq27520_cntl_cmd(di, BQ27520_SUBCMD_CTNL_STATUS);
+ udelay(66);
+ ret = bq27520_read(BQ27520_REG_CNTL, &flags, 0, di);
+ if (ret < 0) {
+ dev_err(di->dev, "error %d reading register %02x\n",
+ ret, BQ27520_REG_CNTL);
+ return ret;
+ }
+ udelay(66);
+
+ bq27520_cntl_cmd(di, BQ27520_SUBCMD_ENABLE_IT);
+ udelay(66);
+
+ if (di->pdata->enable_dlog && !(flags & BQ27520_CS_DLOGEN)) {
+ bq27520_cntl_cmd(di, BQ27520_SUBCMD_ENABLE_DLOG);
+ udelay(66);
+ }
+
+ return 0;
+}
+
+static void bq27520_every_30secs(unsigned long data)
+{
+ struct bq27520_device_info *di = (struct bq27520_device_info *)data;
+
+ schedule_work(&di->counter);
+ mod_timer(&timer, jiffies + BQ27520_COULOMB_POLL);
+}
+
+static void bq27520_coulomb_counter_work(struct work_struct *work)
+{
+ int value = 0, temp = 0, index = 0, ret = 0, count = 0;
+ struct bq27520_device_info *di;
+ unsigned long flags;
+
+ di = container_of(work, struct bq27520_device_info, counter);
+
+ /* retrieve 30 values from FIFO of coulomb data logging buffer
+ * and average over time
+ */
+ do {
+ ret = bq27520_read(BQ27520_REG_LOGBUF, &temp, 0, di);
+ if (ret < 0)
+ break;
+ if (temp != 0x7FFF) {
+ ++count;
+ value += temp;
+ }
+ udelay(66);
+ ret = bq27520_read(BQ27520_REG_LOGIDX, &index, 0, di);
+ if (ret < 0)
+ break;
+ udelay(66);
+ } while (index != 0 || temp != 0x7FFF);
+
+ if (ret < 0) {
+ dev_err(di->dev, "Error %d reading datalog register\n", ret);
+ return;
+ }
+
+ if (count) {
+ spin_lock_irqsave(&lock, flags);
+ coulomb_counter = value/count;
+ spin_unlock_irqrestore(&lock, flags);
+ }
+}
+
+static int bq27520_is_battery_present(void)
+{
+ return 1;
+}
+
+static int bq27520_is_battery_temp_within_range(void)
+{
+ return 1;
+}
+
+static int bq27520_is_battery_id_valid(void)
+{
+ return 1;
+}
+
+static int bq27520_status_getter(int function)
+{
+ int status = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(¤t_battery_status.lock, flags);
+ status = current_battery_status.status[function];
+ spin_unlock_irqrestore(¤t_battery_status.lock, flags);
+
+ return status;
+}
+
+static int bq27520_get_battery_mvolts(void)
+{
+ return bq27520_status_getter(GET_BATTERY_VOLTAGE);
+}
+
+static int bq27520_get_battery_temperature(void)
+{
+ return bq27520_status_getter(GET_BATTERY_TEMPERATURE);
+}
+
+static int bq27520_get_battery_status(void)
+{
+ return bq27520_status_getter(GET_BATTERY_STATUS);
+}
+
+static int bq27520_get_remaining_capacity(void)
+{
+ return bq27520_status_getter(GET_BATTERY_CAPACITY);
+}
+
+static struct msm_battery_gauge bq27520_batt_gauge = {
+ .get_battery_mvolts = bq27520_get_battery_mvolts,
+ .get_battery_temperature = bq27520_get_battery_temperature,
+ .is_battery_present = bq27520_is_battery_present,
+ .is_battery_temp_within_range = bq27520_is_battery_temp_within_range,
+ .is_battery_id_valid = bq27520_is_battery_id_valid,
+ .get_battery_status = bq27520_get_battery_status,
+ .get_batt_remaining_capacity = bq27520_get_remaining_capacity,
+};
+
+static void update_current_battery_status(int data)
+{
+ int status[4], ret = 0;
+ unsigned long flag;
+
+ memset(status, 0, sizeof status);
+ ret = bq27520_battery_rsoc(bq27520_di);
+ status[GET_BATTERY_CAPACITY] = (ret < 0) ? 0 : ret;
+
+ status[GET_BATTERY_VOLTAGE] = bq27520_battery_voltage(bq27520_di);
+ status[GET_BATTERY_TEMPERATURE] =
+ bq27520_battery_temperature(bq27520_di);
+
+ spin_lock_irqsave(¤t_battery_status.lock, flag);
+ current_battery_status.status[GET_BATTERY_STATUS] = data;
+ current_battery_status.status[GET_BATTERY_VOLTAGE] =
+ status[GET_BATTERY_VOLTAGE];
+ current_battery_status.status[GET_BATTERY_TEMPERATURE] =
+ status[GET_BATTERY_TEMPERATURE];
+ current_battery_status.status[GET_BATTERY_CAPACITY] =
+ status[GET_BATTERY_CAPACITY];
+ spin_unlock_irqrestore(¤t_battery_status.lock, flag);
+}
+
+/* only if battery charging satus changes then notify msm_charger. otherwise
+ * only refresh current_batter_status
+ */
+static int if_notify_msm_charger(int *data)
+{
+ int ret = 0, flags = 0, status = 0;
+ unsigned long flag;
+
+ ret = bq27520_read(BQ27520_REG_FLAGS, &flags, 0, bq27520_di);
+ if (ret < 0) {
+ dev_err(bq27520_di->dev, "error %d reading register %02x\n",
+ ret, BQ27520_REG_FLAGS);
+ status = POWER_SUPPLY_STATUS_UNKNOWN;
+ } else {
+ if (flags & BQ27520_FLAG_FC)
+ status = POWER_SUPPLY_STATUS_FULL;
+ else if (flags & BQ27520_FLAG_DSC)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ *data = status;
+ spin_lock_irqsave(¤t_battery_status.lock, flag);
+ ret = (status != current_battery_status.status[GET_BATTERY_STATUS]);
+ spin_unlock_irqrestore(¤t_battery_status.lock, flag);
+ return ret;
+}
+
+static void battery_status_poller(struct work_struct *work)
+{
+ int status = 0, temp = 0;
+
+ temp = if_notify_msm_charger(&status);
+ update_current_battery_status(status);
+ if (temp)
+ msm_charger_notify_event(NULL, CHG_BATT_STATUS_CHANGE);
+
+ schedule_delayed_work(¤t_battery_status.poller,
+ BQ27520_POLLING_STATUS);
+}
+
+static void bq27520_hw_config(struct work_struct *work)
+{
+ int ret = 0, flags = 0, type = 0, fw_ver = 0, status = 0;
+ struct bq27520_device_info *di;
+
+ di = container_of(work, struct bq27520_device_info, hw_config.work);
+
+ pr_debug(KERN_INFO "Enter bq27520_hw_config\n");
+ ret = bq27520_chip_config(di);
+ if (ret) {
+ dev_err(di->dev, "Failed to config Bq27520 ret = %d\n", ret);
+ return;
+ }
+ /* bq27520 is ready for access, update current_battery_status by reading
+ * from hardware
+ */
+ if_notify_msm_charger(&status);
+ update_current_battery_status(status);
+ msm_battery_gauge_register(&bq27520_batt_gauge);
+ msm_charger_notify_event(NULL, CHG_BATT_STATUS_CHANGE);
+
+ enable_irq(di->irq);
+
+ /* poll battery status every 3 seconds, if charging status changes,
+ * notify msm_charger
+ */
+ schedule_delayed_work(¤t_battery_status.poller,
+ BQ27520_POLLING_STATUS);
+
+ if (di->pdata->enable_dlog) {
+ schedule_work(&di->counter);
+ init_timer(&timer);
+ timer.function = &bq27520_every_30secs;
+ timer.data = (unsigned long)di;
+ timer.expires = jiffies + BQ27520_COULOMB_POLL;
+ add_timer(&timer);
+ }
+
+ bq27520_cntl_cmd(di, BQ27520_SUBCMD_CTNL_STATUS);
+ udelay(66);
+ bq27520_read(BQ27520_REG_CNTL, &flags, 0, di);
+ bq27520_cntl_cmd(di, BQ27520_SUBCMD_DEVCIE_TYPE);
+ udelay(66);
+ bq27520_read(BQ27520_REG_CNTL, &type, 0, di);
+ bq27520_cntl_cmd(di, BQ27520_SUBCMD_FW_VER);
+ udelay(66);
+ bq27520_read(BQ27520_REG_CNTL, &fw_ver, 0, di);
+
+ dev_info(di->dev, "DEVICE_TYPE is 0x%02X, FIRMWARE_VERSION\
+ is 0x%02X\n", type, fw_ver);
+ dev_info(di->dev, "Complete bq27520 configuration 0x%02X\n", flags);
+}
+
+static int bq27520_read_i2c(u8 reg, int *rt_value, int b_single,
+ struct bq27520_device_info *di)
+{
+ struct i2c_client *client = di->client;
+ struct i2c_msg msg[1];
+ unsigned char data[2];
+ int err;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg->addr = client->addr;
+ msg->flags = 0;
+ msg->len = 1;
+ msg->buf = data;
+
+ data[0] = reg;
+ err = i2c_transfer(client->adapter, msg, 1);
+
+ if (err >= 0) {
+ if (!b_single)
+ msg->len = 2;
+ else
+ msg->len = 1;
+
+ msg->flags = I2C_M_RD;
+ err = i2c_transfer(client->adapter, msg, 1);
+ if (err >= 0) {
+ if (!b_single)
+ *rt_value = get_unaligned_le16(data);
+ else
+ *rt_value = data[0];
+
+ return 0;
+ }
+ }
+ return err;
+}
+
+#ifdef CONFIG_BQ27520_TEST_ENABLE
+static int reg;
+static int subcmd;
+static ssize_t bq27520_read_stdcmd(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ int temp = 0;
+ struct platform_device *client;
+ struct bq27520_device_info *di;
+
+ client = to_platform_device(dev);
+ di = platform_get_drvdata(client);
+
+ if (reg <= BQ27520_REG_ICR && reg > 0x00) {
+ ret = bq27520_read(reg, &temp, 0, di);
+ if (ret)
+ ret = snprintf(buf, PAGE_SIZE, "Read Error!\n");
+ else
+ ret = snprintf(buf, PAGE_SIZE, "0x%02x\n", temp);
+ } else
+ ret = snprintf(buf, PAGE_SIZE, "Register Error!\n");
+
+ return ret;
+}
+
+static ssize_t bq27520_write_stdcmd(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ ssize_t ret = strnlen(buf, PAGE_SIZE);
+ int cmd;
+
+ sscanf(buf, "%x", &cmd);
+ reg = cmd;
+ dev_info(dev, "recv'd cmd is 0x%02X\n", reg);
+ return ret;
+}
+
+static ssize_t bq27520_read_subcmd(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret, temp = 0;
+ struct platform_device *client;
+ struct bq27520_device_info *di;
+
+ client = to_platform_device(dev);
+ di = platform_get_drvdata(client);
+
+ if (subcmd == BQ27520_SUBCMD_DEVCIE_TYPE ||
+ subcmd == BQ27520_SUBCMD_FW_VER ||
+ subcmd == BQ27520_SUBCMD_HW_VER ||
+ subcmd == BQ27520_SUBCMD_CHEM_ID) {
+
+ bq27520_cntl_cmd(di, subcmd);/* Retrieve Chip status */
+ udelay(66);
+ ret = bq27520_read(BQ27520_REG_CNTL, &temp, 0, di);
+
+ if (ret)
+ ret = snprintf(buf, PAGE_SIZE, "Read Error!\n");
+ else
+ ret = snprintf(buf, PAGE_SIZE, "0x%02x\n", temp);
+ } else
+ ret = snprintf(buf, PAGE_SIZE, "Register Error!\n");
+
+ return ret;
+}
+
+static ssize_t bq27520_write_subcmd(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ ssize_t ret = strnlen(buf, PAGE_SIZE);
+ int cmd;
+
+ sscanf(buf, "%x", &cmd);
+ subcmd = cmd;
+ return ret;
+}
+
+static DEVICE_ATTR(std_cmd, S_IRUGO|S_IWUGO, bq27520_read_stdcmd,
+ bq27520_write_stdcmd);
+static DEVICE_ATTR(sub_cmd, S_IRUGO|S_IWUGO, bq27520_read_subcmd,
+ bq27520_write_subcmd);
+static struct attribute *fs_attrs[] = {
+ &dev_attr_std_cmd.attr,
+ &dev_attr_sub_cmd.attr,
+ NULL,
+};
+static struct attribute_group fs_attr_group = {
+ .attrs = fs_attrs,
+};
+
+static struct platform_device this_device = {
+ .name = "bq27520-test",
+ .id = -1,
+ .dev.platform_data = NULL,
+};
+#endif
+
+static irqreturn_t soc_irqhandler(int irq, void *dev_id)
+{
+ int status = 0, temp = 0;
+
+ temp = if_notify_msm_charger(&status);
+ update_current_battery_status(status);
+ if (temp)
+ msm_charger_notify_event(NULL, CHG_BATT_STATUS_CHANGE);
+ return IRQ_HANDLED;
+}
+
+static struct regulator *vreg_bq27520;
+static int bq27520_power(bool enable, struct bq27520_device_info *di)
+{
+ int rc = 0, ret;
+ const struct bq27520_platform_data *platdata;
+
+ platdata = di->pdata;
+ if (enable) {
+ /* switch on Vreg_S3 */
+ rc = regulator_enable(vreg_bq27520);
+ if (rc < 0) {
+ dev_err(di->dev, "%s: vreg %s %s failed (%d)\n",
+ __func__, platdata->vreg_name, "enable", rc);
+ goto vreg_fail;
+ }
+
+ /* Battery gauge enable and switch on onchip 2.5V LDO */
+ rc = gpio_request(platdata->chip_en, "GAUGE_EN");
+ if (rc) {
+ dev_err(di->dev, "%s: fail to request gpio %d (%d)\n",
+ __func__, platdata->chip_en, rc);
+ goto vreg_fail;
+ }
+
+ gpio_direction_output(platdata->chip_en, 0);
+ gpio_set_value(platdata->chip_en, 1);
+ rc = gpio_request(platdata->soc_int, "GAUGE_SOC_INT");
+ if (rc) {
+ dev_err(di->dev, "%s: fail to request gpio %d (%d)\n",
+ __func__, platdata->soc_int, rc);
+ goto gpio_fail;
+ }
+ gpio_direction_input(platdata->soc_int);
+ di->irq = gpio_to_irq(platdata->soc_int);
+ rc = request_threaded_irq(di->irq, NULL, soc_irqhandler,
+ IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
+ "BQ27520_IRQ", di);
+ if (rc) {
+ dev_err(di->dev, "%s: fail to request irq %d (%d)\n",
+ __func__, platdata->soc_int, rc);
+ goto irqreq_fail;
+ } else {
+ disable_irq_nosync(di->irq);
+ }
+ } else {
+ free_irq(di->irq, di);
+ gpio_free(platdata->soc_int);
+ /* switch off on-chip 2.5V LDO and disable Battery gauge */
+ gpio_set_value(platdata->chip_en, 0);
+ gpio_free(platdata->chip_en);
+ /* switch off Vreg_S3 */
+ rc = regulator_disable(vreg_bq27520);
+ if (rc < 0) {
+ dev_err(di->dev, "%s: vreg %s %s failed (%d)\n",
+ __func__, platdata->vreg_name, "disable", rc);
+ goto vreg_fail;
+ }
+ }
+ return rc;
+
+irqreq_fail:
+ gpio_free(platdata->soc_int);
+gpio_fail:
+ gpio_set_value(platdata->chip_en, 0);
+ gpio_free(platdata->chip_en);
+vreg_fail:
+ ret = !enable ? regulator_enable(vreg_bq27520) :
+ regulator_disable(vreg_bq27520);
+ if (ret < 0) {
+ dev_err(di->dev, "%s: vreg %s %s failed (%d) in err path\n",
+ __func__, platdata->vreg_name,
+ !enable ? "enable" : "disable", ret);
+ }
+ return rc;
+}
+
+static int bq27520_dev_setup(bool enable, struct bq27520_device_info *di)
+{
+ int rc;
+ const struct bq27520_platform_data *platdata;
+
+ platdata = di->pdata;
+ if (enable) {
+ /* enable and set voltage Vreg_S3 */
+ vreg_bq27520 = regulator_get(NULL,
+ platdata->vreg_name);
+ if (IS_ERR(vreg_bq27520)) {
+ dev_err(di->dev, "%s: regulator get of %s\
+ failed (%ld)\n", __func__, platdata->vreg_name,
+ PTR_ERR(vreg_bq27520));
+ rc = PTR_ERR(vreg_bq27520);
+ goto vreg_get_fail;
+ }
+ rc = regulator_set_voltage(vreg_bq27520,
+ platdata->vreg_value, platdata->vreg_value);
+ if (rc) {
+ dev_err(di->dev, "%s: regulator_set_voltage(%s) failed\
+ (%d)\n", __func__, platdata->vreg_name, rc);
+ goto vreg_get_fail;
+ }
+ } else {
+ regulator_put(vreg_bq27520);
+ }
+ return 0;
+
+vreg_get_fail:
+ regulator_put(vreg_bq27520);
+ return rc;
+}
+
+static int bq27520_battery_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct bq27520_device_info *di;
+ struct bq27520_access_methods *bus;
+ const struct bq27520_platform_data *pdata;
+ int num, retval = 0;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -ENODEV;
+
+ pdata = client->dev.platform_data;
+
+ /* 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;
+
+ di = kzalloc(sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ dev_err(&client->dev, "failed to allocate device info data\n");
+ retval = -ENOMEM;
+ goto batt_failed_1;
+ }
+ di->id = num;
+ di->pdata = pdata;
+
+ bus = kzalloc(sizeof(*bus), GFP_KERNEL);
+ if (!bus) {
+ dev_err(&client->dev, "failed to allocate data\n");
+ retval = -ENOMEM;
+ goto batt_failed_2;
+ }
+
+ i2c_set_clientdata(client, di);
+ di->dev = &client->dev;
+ bus->read = &bq27520_read_i2c;
+ di->bus = bus;
+ di->client = client;
+
+#ifdef CONFIG_BQ27520_TEST_ENABLE
+ platform_set_drvdata(&this_device, di);
+ retval = platform_device_register(&this_device);
+ if (!retval) {
+ retval = sysfs_create_group(&this_device.dev.kobj,
+ &fs_attr_group);
+ if (retval)
+ goto batt_failed_3;
+ } else
+ goto batt_failed_3;
+#endif
+
+ retval = bq27520_dev_setup(true, di);
+ if (retval) {
+ dev_err(&client->dev, "failed to setup ret = %d\n", retval);
+ goto batt_failed_3;
+ }
+
+ retval = bq27520_power(true, di);
+ if (retval) {
+ dev_err(&client->dev, "failed to powerup ret = %d\n", retval);
+ goto batt_failed_3;
+ }
+
+ spin_lock_init(&lock);
+
+ bq27520_di = di;
+ if (pdata->enable_dlog)
+ INIT_WORK(&di->counter, bq27520_coulomb_counter_work);
+
+ INIT_DELAYED_WORK(¤t_battery_status.poller,
+ battery_status_poller);
+ INIT_DELAYED_WORK(&di->hw_config, bq27520_hw_config);
+ schedule_delayed_work(&di->hw_config, BQ27520_INIT_DELAY);
+
+ return 0;
+
+batt_failed_3:
+ kfree(bus);
+batt_failed_2:
+ kfree(di);
+batt_failed_1:
+ mutex_lock(&battery_mutex);
+ idr_remove(&battery_id, num);
+ mutex_unlock(&battery_mutex);
+
+ return retval;
+}
+
+static int bq27520_battery_remove(struct i2c_client *client)
+{
+ struct bq27520_device_info *di = i2c_get_clientdata(client);
+
+ if (di->pdata->enable_dlog) {
+ del_timer_sync(&timer);
+ cancel_work_sync(&di->counter);
+ bq27520_cntl_cmd(di, BQ27520_SUBCMD_DISABLE_DLOG);
+ udelay(66);
+ }
+
+ bq27520_cntl_cmd(di, BQ27520_SUBCMD_DISABLE_IT);
+ cancel_delayed_work_sync(&di->hw_config);
+ cancel_delayed_work_sync(¤t_battery_status.poller);
+
+ bq27520_dev_setup(false, di);
+ bq27520_power(false, di);
+
+ kfree(di->bus);
+
+ mutex_lock(&battery_mutex);
+ idr_remove(&battery_id, di->id);
+ mutex_unlock(&battery_mutex);
+
+ kfree(di);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int bq27520_suspend(struct device *dev)
+{
+ struct bq27520_device_info *di = dev_get_drvdata(dev);
+
+ disable_irq_nosync(di->irq);
+ if (di->pdata->enable_dlog) {
+ del_timer_sync(&timer);
+ cancel_work_sync(&di->counter);
+ }
+
+ cancel_delayed_work_sync(¤t_battery_status.poller);
+ return 0;
+}
+
+static int bq27520_resume(struct device *dev)
+{
+ struct bq27520_device_info *di = dev_get_drvdata(dev);
+
+ enable_irq(di->irq);
+ if (di->pdata->enable_dlog)
+ add_timer(&timer);
+
+ schedule_delayed_work(¤t_battery_status.poller,
+ BQ27520_POLLING_STATUS);
+ return 0;
+}
+
+static const struct dev_pm_ops bq27520_pm_ops = {
+ .suspend = bq27520_suspend,
+ .resume = bq27520_resume,
+};
+#endif
+
+static const struct i2c_device_id bq27520_id[] = {
+ { "bq27520", 1 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, BQ27520_id);
+
+static struct i2c_driver bq27520_battery_driver = {
+ .driver = {
+ .name = "bq27520-battery",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &bq27520_pm_ops,
+#endif
+ },
+ .probe = bq27520_battery_probe,
+ .remove = bq27520_battery_remove,
+ .id_table = bq27520_id,
+};
+
+static void init_battery_status(void)
+{
+ spin_lock_init(¤t_battery_status.lock);
+ current_battery_status.status[GET_BATTERY_STATUS] =
+ POWER_SUPPLY_STATUS_UNKNOWN;
+}
+
+static int __init bq27520_battery_init(void)
+{
+ int ret;
+
+ /* initialize current_battery_status, and register with msm-charger */
+ init_battery_status();
+
+ ret = i2c_add_driver(&bq27520_battery_driver);
+ if (ret)
+ printk(KERN_ERR "Unable to register driver ret = %d\n", ret);
+
+ return ret;
+}
+module_init(bq27520_battery_init);
+
+static void __exit bq27520_battery_exit(void)
+{
+ i2c_del_driver(&bq27520_battery_driver);
+ msm_battery_gauge_unregister(&bq27520_batt_gauge);
+}
+module_exit(bq27520_battery_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Qualcomm Innovation Center, Inc.");
+MODULE_DESCRIPTION("BQ27520 battery monitor driver");
diff --git a/drivers/power/bq27541_fuelgauger.c b/drivers/power/bq27541_fuelgauger.c
new file mode 100644
index 0000000..516a861
--- /dev/null
+++ b/drivers/power/bq27541_fuelgauger.c
@@ -0,0 +1,623 @@
+/* Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it>
+ * Copyright (C) 2008 Eurotech S.p.A. <info@eurotech.it>
+ * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc.
+ *
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/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/time.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/regulator/pmic8058-regulator.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/machine.h>
+#include <linux/err.h>
+#include <linux/msm-charger.h>
+#include <linux/i2c/bq27520.h> /* use the same platform data as bq27520 */
+
+#define DRIVER_VERSION "1.1.0"
+/* Bq27541 standard data commands */
+#define BQ27541_REG_CNTL 0x00
+#define BQ27541_REG_AR 0x02
+#define BQ27541_REG_ARTTE 0x04
+#define BQ27541_REG_TEMP 0x06
+#define BQ27541_REG_VOLT 0x08
+#define BQ27541_REG_FLAGS 0x0A
+#define BQ27541_REG_NAC 0x0C
+#define BQ27541_REG_FAC 0x0e
+#define BQ27541_REG_RM 0x10
+#define BQ27541_REG_FCC 0x12
+#define BQ27541_REG_AI 0x14
+#define BQ27541_REG_TTE 0x16
+#define BQ27541_REG_TTF 0x18
+#define BQ27541_REG_SI 0x1a
+#define BQ27541_REG_STTE 0x1c
+#define BQ27541_REG_MLI 0x1e
+#define BQ27541_REG_MLTTE 0x20
+#define BQ27541_REG_AE 0x22
+#define BQ27541_REG_AP 0x24
+#define BQ27541_REG_TTECP 0x26
+#define BQ27541_REG_SOH 0x28
+#define BQ27541_REG_SOC 0x2c
+#define BQ27541_REG_NIC 0x2e
+#define BQ27541_REG_ICR 0x30
+#define BQ27541_REG_LOGIDX 0x32
+#define BQ27541_REG_LOGBUF 0x34
+
+#define BQ27541_FLAG_DSC BIT(0)
+#define BQ27541_FLAG_FC BIT(9)
+
+#define BQ27541_CS_DLOGEN BIT(15)
+#define BQ27541_CS_SS BIT(13)
+
+/* Control subcommands */
+#define BQ27541_SUBCMD_CTNL_STATUS 0x0000
+#define BQ27541_SUBCMD_DEVCIE_TYPE 0x0001
+#define BQ27541_SUBCMD_FW_VER 0x0002
+#define BQ27541_SUBCMD_HW_VER 0x0003
+#define BQ27541_SUBCMD_DF_CSUM 0x0004
+#define BQ27541_SUBCMD_PREV_MACW 0x0007
+#define BQ27541_SUBCMD_CHEM_ID 0x0008
+#define BQ27541_SUBCMD_BD_OFFSET 0x0009
+#define BQ27541_SUBCMD_INT_OFFSET 0x000a
+#define BQ27541_SUBCMD_CC_VER 0x000b
+#define BQ27541_SUBCMD_OCV 0x000c
+#define BQ27541_SUBCMD_BAT_INS 0x000d
+#define BQ27541_SUBCMD_BAT_REM 0x000e
+#define BQ27541_SUBCMD_SET_HIB 0x0011
+#define BQ27541_SUBCMD_CLR_HIB 0x0012
+#define BQ27541_SUBCMD_SET_SLP 0x0013
+#define BQ27541_SUBCMD_CLR_SLP 0x0014
+#define BQ27541_SUBCMD_FCT_RES 0x0015
+#define BQ27541_SUBCMD_ENABLE_DLOG 0x0018
+#define BQ27541_SUBCMD_DISABLE_DLOG 0x0019
+#define BQ27541_SUBCMD_SEALED 0x0020
+#define BQ27541_SUBCMD_ENABLE_IT 0x0021
+#define BQ27541_SUBCMD_DISABLE_IT 0x0023
+#define BQ27541_SUBCMD_CAL_MODE 0x0040
+#define BQ27541_SUBCMD_RESET 0x0041
+#define ZERO_DEGREE_CELSIUS_IN_TENTH_KELVIN (-2731)
+#define BQ27541_INIT_DELAY ((HZ)*1)
+
+/* 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);
+
+struct bq27541_device_info;
+struct bq27541_access_methods {
+ int (*read)(u8 reg, int *rt_value, int b_single,
+ struct bq27541_device_info *di);
+};
+
+struct bq27541_device_info {
+ struct device *dev;
+ int id;
+ struct bq27541_access_methods *bus;
+ struct i2c_client *client;
+ struct work_struct counter;
+ /* 300ms delay is needed after bq27541 is powered up
+ * and before any successful I2C transaction
+ */
+ struct delayed_work hw_config;
+};
+
+static int coulomb_counter;
+static spinlock_t lock; /* protect access to coulomb_counter */
+
+static int bq27541_i2c_txsubcmd(u8 reg, unsigned short subcmd,
+ struct bq27541_device_info *di);
+
+static int bq27541_read(u8 reg, int *rt_value, int b_single,
+ struct bq27541_device_info *di)
+{
+ return di->bus->read(reg, rt_value, b_single, di);
+}
+
+/*
+ * Return the battery temperature in tenths of degree Celsius
+ * Or < 0 if something fails.
+ */
+static int bq27541_battery_temperature(struct bq27541_device_info *di)
+{
+ int ret;
+ int temp = 0;
+
+ ret = bq27541_read(BQ27541_REG_TEMP, &temp, 0, di);
+ if (ret) {
+ dev_err(di->dev, "error reading temperature\n");
+ return ret;
+ }
+
+ return temp + ZERO_DEGREE_CELSIUS_IN_TENTH_KELVIN;
+}
+
+/*
+ * Return the battery Voltage in milivolts
+ * Or < 0 if something fails.
+ */
+static int bq27541_battery_voltage(struct bq27541_device_info *di)
+{
+ int ret;
+ int volt = 0;
+
+ ret = bq27541_read(BQ27541_REG_VOLT, &volt, 0, di);
+ if (ret) {
+ dev_err(di->dev, "error reading voltage\n");
+ return ret;
+ }
+
+ return volt * 1000;
+}
+
+static void bq27541_cntl_cmd(struct bq27541_device_info *di,
+ int subcmd)
+{
+ bq27541_i2c_txsubcmd(BQ27541_REG_CNTL, subcmd, di);
+}
+
+/*
+ * i2c specific code
+ */
+static int bq27541_i2c_txsubcmd(u8 reg, unsigned short subcmd,
+ struct bq27541_device_info *di)
+{
+ struct i2c_msg msg;
+ unsigned char data[3];
+ int ret;
+
+ if (!di->client)
+ return -ENODEV;
+
+ memset(data, 0, sizeof(data));
+ data[0] = reg;
+ data[1] = subcmd & 0x00FF;
+ data[2] = (subcmd & 0xFF00) >> 8;
+
+ msg.addr = di->client->addr;
+ msg.flags = 0;
+ msg.len = 3;
+ msg.buf = data;
+
+ ret = i2c_transfer(di->client->adapter, &msg, 1);
+ if (ret < 0)
+ return -EIO;
+
+ return 0;
+}
+
+static int bq27541_chip_config(struct bq27541_device_info *di)
+{
+ int flags = 0, ret = 0;
+
+ bq27541_cntl_cmd(di, BQ27541_SUBCMD_CTNL_STATUS);
+ udelay(66);
+ ret = bq27541_read(BQ27541_REG_CNTL, &flags, 0, di);
+ if (ret < 0) {
+ dev_err(di->dev, "error reading register %02x ret = %d\n",
+ BQ27541_REG_CNTL, ret);
+ return ret;
+ }
+ udelay(66);
+
+ bq27541_cntl_cmd(di, BQ27541_SUBCMD_ENABLE_IT);
+ udelay(66);
+
+ if (!(flags & BQ27541_CS_DLOGEN)) {
+ bq27541_cntl_cmd(di, BQ27541_SUBCMD_ENABLE_DLOG);
+ udelay(66);
+ }
+
+ return 0;
+}
+
+static void bq27541_coulomb_counter_work(struct work_struct *work)
+{
+ int value = 0, temp = 0, index = 0, ret = 0;
+ struct bq27541_device_info *di;
+ unsigned long flags;
+ int count = 0;
+
+ di = container_of(work, struct bq27541_device_info, counter);
+
+ /* retrieve 30 values from FIFO of coulomb data logging buffer
+ * and average over time
+ */
+ do {
+ ret = bq27541_read(BQ27541_REG_LOGBUF, &temp, 0, di);
+ if (ret < 0)
+ break;
+ if (temp != 0x7FFF) {
+ ++count;
+ value += temp;
+ }
+ /* delay 66uS, waiting time between continuous reading
+ * results
+ */
+ udelay(66);
+ ret = bq27541_read(BQ27541_REG_LOGIDX, &index, 0, di);
+ if (ret < 0)
+ break;
+ udelay(66);
+ } while (index != 0 || temp != 0x7FFF);
+
+ if (ret < 0) {
+ dev_err(di->dev, "Error reading datalog register\n");
+ return;
+ }
+
+ if (count) {
+ spin_lock_irqsave(&lock, flags);
+ coulomb_counter = value/count;
+ spin_unlock_irqrestore(&lock, flags);
+ }
+}
+
+struct bq27541_device_info *bq27541_di;
+
+static int bq27541_get_battery_mvolts(void)
+{
+ return bq27541_battery_voltage(bq27541_di);
+}
+
+static int bq27541_get_battery_temperature(void)
+{
+ return bq27541_battery_temperature(bq27541_di);
+}
+static int bq27541_is_battery_present(void)
+{
+ return 1;
+}
+static int bq27541_is_battery_temp_within_range(void)
+{
+ return 1;
+}
+static int bq27541_is_battery_id_valid(void)
+{
+ return 1;
+}
+
+static struct msm_battery_gauge bq27541_batt_gauge = {
+ .get_battery_mvolts = bq27541_get_battery_mvolts,
+ .get_battery_temperature = bq27541_get_battery_temperature,
+ .is_battery_present = bq27541_is_battery_present,
+ .is_battery_temp_within_range = bq27541_is_battery_temp_within_range,
+ .is_battery_id_valid = bq27541_is_battery_id_valid,
+};
+static void bq27541_hw_config(struct work_struct *work)
+{
+ int ret = 0, flags = 0, type = 0, fw_ver = 0;
+ struct bq27541_device_info *di;
+
+ di = container_of(work, struct bq27541_device_info, hw_config.work);
+ ret = bq27541_chip_config(di);
+ if (ret) {
+ dev_err(di->dev, "Failed to config Bq27541\n");
+ return;
+ }
+ msm_battery_gauge_register(&bq27541_batt_gauge);
+
+ bq27541_cntl_cmd(di, BQ27541_SUBCMD_CTNL_STATUS);
+ udelay(66);
+ bq27541_read(BQ27541_REG_CNTL, &flags, 0, di);
+ bq27541_cntl_cmd(di, BQ27541_SUBCMD_DEVCIE_TYPE);
+ udelay(66);
+ bq27541_read(BQ27541_REG_CNTL, &type, 0, di);
+ bq27541_cntl_cmd(di, BQ27541_SUBCMD_FW_VER);
+ udelay(66);
+ bq27541_read(BQ27541_REG_CNTL, &fw_ver, 0, di);
+
+ dev_info(di->dev, "DEVICE_TYPE is 0x%02X, FIRMWARE_VERSION is 0x%02X\n",
+ type, fw_ver);
+ dev_info(di->dev, "Complete bq27541 configuration 0x%02X\n", flags);
+}
+
+static int bq27541_read_i2c(u8 reg, int *rt_value, int b_single,
+ struct bq27541_device_info *di)
+{
+ struct i2c_client *client = di->client;
+ struct i2c_msg msg[1];
+ unsigned char data[2];
+ int err;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg->addr = client->addr;
+ msg->flags = 0;
+ msg->len = 1;
+ msg->buf = data;
+
+ data[0] = reg;
+ err = i2c_transfer(client->adapter, msg, 1);
+
+ if (err >= 0) {
+ if (!b_single)
+ msg->len = 2;
+ else
+ msg->len = 1;
+
+ msg->flags = I2C_M_RD;
+ err = i2c_transfer(client->adapter, msg, 1);
+ if (err >= 0) {
+ if (!b_single)
+ *rt_value = get_unaligned_le16(data);
+ else
+ *rt_value = data[0];
+
+ return 0;
+ }
+ }
+ return err;
+}
+
+#ifdef CONFIG_BQ27541_TEST_ENABLE
+static int reg;
+static int subcmd;
+static ssize_t bq27541_read_stdcmd(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ int temp = 0;
+ struct platform_device *client;
+ struct bq27541_device_info *di;
+
+ client = to_platform_device(dev);
+ di = platform_get_drvdata(client);
+
+ if (reg <= BQ27541_REG_ICR && reg > 0x00) {
+ ret = bq27541_read(reg, &temp, 0, di);
+ if (ret)
+ ret = snprintf(buf, PAGE_SIZE, "Read Error!\n");
+ else
+ ret = snprintf(buf, PAGE_SIZE, "0x%02x\n", temp);
+ } else
+ ret = snprintf(buf, PAGE_SIZE, "Register Error!\n");
+
+ return ret;
+}
+
+static ssize_t bq27541_write_stdcmd(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ ssize_t ret = strnlen(buf, PAGE_SIZE);
+ int cmd;
+
+ sscanf(buf, "%x", &cmd);
+ reg = cmd;
+ return ret;
+}
+
+static ssize_t bq27541_read_subcmd(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ int temp = 0;
+ struct platform_device *client;
+ struct bq27541_device_info *di;
+
+ client = to_platform_device(dev);
+ di = platform_get_drvdata(client);
+
+ if (subcmd == BQ27541_SUBCMD_DEVCIE_TYPE ||
+ subcmd == BQ27541_SUBCMD_FW_VER ||
+ subcmd == BQ27541_SUBCMD_HW_VER ||
+ subcmd == BQ27541_SUBCMD_CHEM_ID) {
+
+ bq27541_cntl_cmd(di, subcmd); /* Retrieve Chip status */
+ udelay(66);
+ ret = bq27541_read(BQ27541_REG_CNTL, &temp, 0, di);
+
+ if (ret)
+ ret = snprintf(buf, PAGE_SIZE, "Read Error!\n");
+ else
+ ret = snprintf(buf, PAGE_SIZE, "0x%02x\n", temp);
+ } else
+ ret = snprintf(buf, PAGE_SIZE, "Register Error!\n");
+
+ return ret;
+}
+
+static ssize_t bq27541_write_subcmd(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ ssize_t ret = strnlen(buf, PAGE_SIZE);
+ int cmd;
+
+ sscanf(buf, "%x", &cmd);
+ subcmd = cmd;
+ return ret;
+}
+
+static DEVICE_ATTR(std_cmd, S_IRUGO|S_IWUGO, bq27541_read_stdcmd,
+ bq27541_write_stdcmd);
+static DEVICE_ATTR(sub_cmd, S_IRUGO|S_IWUGO, bq27541_read_subcmd,
+ bq27541_write_subcmd);
+static struct attribute *fs_attrs[] = {
+ &dev_attr_std_cmd.attr,
+ &dev_attr_sub_cmd.attr,
+ NULL,
+};
+static struct attribute_group fs_attr_group = {
+ .attrs = fs_attrs,
+};
+
+static struct platform_device this_device = {
+ .name = "bq27541-test",
+ .id = -1,
+ .dev.platform_data = NULL,
+};
+#endif
+
+static int bq27541_battery_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ char *name;
+ struct bq27541_device_info *di;
+ struct bq27541_access_methods *bus;
+ int num;
+ int retval = 0;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -ENODEV;
+
+ /* 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;
+
+ bus = kzalloc(sizeof(*bus), GFP_KERNEL);
+ if (!bus) {
+ dev_err(&client->dev, "failed to allocate access method "
+ "data\n");
+ retval = -ENOMEM;
+ goto batt_failed_3;
+ }
+
+ i2c_set_clientdata(client, di);
+ di->dev = &client->dev;
+ bus->read = &bq27541_read_i2c;
+ di->bus = bus;
+ di->client = client;
+
+#ifdef CONFIG_BQ27541_TEST_ENABLE
+ platform_set_drvdata(&this_device, di);
+ retval = platform_device_register(&this_device);
+ if (!retval) {
+ retval = sysfs_create_group(&this_device.dev.kobj,
+ &fs_attr_group);
+ if (retval)
+ goto batt_failed_4;
+ } else
+ goto batt_failed_4;
+#endif
+
+ if (retval) {
+ dev_err(&client->dev, "failed to setup bq27541\n");
+ goto batt_failed_4;
+ }
+
+ if (retval) {
+ dev_err(&client->dev, "failed to powerup bq27541\n");
+ goto batt_failed_4;
+ }
+
+ spin_lock_init(&lock);
+
+ bq27541_di = di;
+ INIT_WORK(&di->counter, bq27541_coulomb_counter_work);
+ INIT_DELAYED_WORK(&di->hw_config, bq27541_hw_config);
+ schedule_delayed_work(&di->hw_config, BQ27541_INIT_DELAY);
+ return 0;
+
+batt_failed_4:
+ kfree(bus);
+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 bq27541_battery_remove(struct i2c_client *client)
+{
+ struct bq27541_device_info *di = i2c_get_clientdata(client);
+
+ msm_battery_gauge_unregister(&bq27541_batt_gauge);
+ bq27541_cntl_cmd(di, BQ27541_SUBCMD_DISABLE_DLOG);
+ udelay(66);
+ bq27541_cntl_cmd(di, BQ27541_SUBCMD_DISABLE_IT);
+ cancel_delayed_work_sync(&di->hw_config);
+
+ kfree(di->bus);
+
+ mutex_lock(&battery_mutex);
+ idr_remove(&battery_id, di->id);
+ mutex_unlock(&battery_mutex);
+
+ kfree(di);
+ return 0;
+}
+
+static const struct i2c_device_id bq27541_id[] = {
+ { "bq27541", 1 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, BQ27541_id);
+
+static struct i2c_driver bq27541_battery_driver = {
+ .driver = {
+ .name = "bq27541-battery",
+ },
+ .probe = bq27541_battery_probe,
+ .remove = bq27541_battery_remove,
+ .id_table = bq27541_id,
+};
+
+static int __init bq27541_battery_init(void)
+{
+ int ret;
+
+ ret = i2c_add_driver(&bq27541_battery_driver);
+ if (ret)
+ printk(KERN_ERR "Unable to register BQ27541 driver\n");
+
+ return ret;
+}
+module_init(bq27541_battery_init);
+
+static void __exit bq27541_battery_exit(void)
+{
+ i2c_del_driver(&bq27541_battery_driver);
+}
+module_exit(bq27541_battery_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Qualcomm Innovation Center, Inc.");
+MODULE_DESCRIPTION("BQ27541 battery monitor driver");
diff --git a/drivers/power/isl9519q.c b/drivers/power/isl9519q.c
new file mode 100644
index 0000000..7ebbf46
--- /dev/null
+++ b/drivers/power/isl9519q.c
@@ -0,0 +1,849 @@
+/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/msm-charger.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/i2c/isl9519.h>
+#include <linux/msm_adc.h>
+#include <linux/spinlock.h>
+
+#define CHG_CURRENT_REG 0x14
+#define MAX_SYS_VOLTAGE_REG 0x15
+#define CONTROL_REG 0x3D
+#define MIN_SYS_VOLTAGE_REG 0x3E
+#define INPUT_CURRENT_REG 0x3F
+#define MANUFACTURER_ID_REG 0xFE
+#define DEVICE_ID_REG 0xFF
+
+#define TRCKL_CHG_STATUS_BIT 0x80
+
+#define ISL9519_CHG_PERIOD_SEC 150
+
+struct isl9519q_struct {
+ struct i2c_client *client;
+ struct delayed_work charge_work;
+ int present;
+ int batt_present;
+ bool charging;
+ int chgcurrent;
+ int term_current;
+ int input_current;
+ int max_system_voltage;
+ int min_system_voltage;
+ int valid_n_gpio;
+ struct dentry *dent;
+ struct msm_hardware_charger adapter_hw_chg;
+ int suspended;
+ int charge_at_resume;
+ struct power_supply dc_psy;
+ spinlock_t lock;
+ bool notify_by_pmic;
+ bool trickle;
+};
+
+static struct isl9519q_struct *the_isl_chg;
+
+static int isl9519q_read_reg(struct i2c_client *client, int reg,
+ u16 *val)
+{
+ int ret;
+ struct isl9519q_struct *isl_chg;
+
+ isl_chg = i2c_get_clientdata(client);
+ ret = i2c_smbus_read_word_data(isl_chg->client, reg);
+
+ if (ret < 0) {
+ dev_err(&isl_chg->client->dev,
+ "i2c read fail: can't read from %02x: %d\n", reg, ret);
+ return -EAGAIN;
+ } else {
+ *val = ret;
+ }
+
+ pr_debug("reg=0x%x.val=0x%x.\n", reg, *val);
+
+ return 0;
+}
+
+static int isl9519q_write_reg(struct i2c_client *client, int reg,
+ u16 val)
+{
+ int ret;
+ struct isl9519q_struct *isl_chg;
+
+ pr_debug("reg=0x%x.val=0x%x.\n", reg, val);
+
+ isl_chg = i2c_get_clientdata(client);
+ ret = i2c_smbus_write_word_data(isl_chg->client, reg, val);
+
+ if (ret < 0) {
+ dev_err(&isl_chg->client->dev,
+ "i2c write fail: can't write %02x to %02x: %d\n",
+ val, reg, ret);
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/**
+ * Read charge-current via ADC.
+ *
+ * The ISL CCMON (charge-current-monitor) pin is connected to
+ * the PMIC MPP#X pin.
+ * This not required when notify_by_pmic is used where the PMIC
+ * uses BMS to notify the ISL on charging-done / charge-resume.
+ */
+static int isl_read_adc(int channel, int *mv_reading)
+{
+ int ret;
+ void *h;
+ struct adc_chan_result adc_chan_result;
+ struct completion conv_complete_evt;
+
+ pr_debug("called for %d\n", channel);
+ ret = adc_channel_open(channel, &h);
+ if (ret) {
+ pr_err("couldnt open channel %d ret=%d\n", channel, ret);
+ goto out;
+ }
+ init_completion(&conv_complete_evt);
+ ret = adc_channel_request_conv(h, &conv_complete_evt);
+ if (ret) {
+ pr_err("couldnt request conv channel %d ret=%d\n",
+ channel, ret);
+ goto out;
+ }
+ ret = wait_for_completion_interruptible(&conv_complete_evt);
+ if (ret) {
+ pr_err("wait interrupted channel %d ret=%d\n", channel, ret);
+ goto out;
+ }
+ ret = adc_channel_read_result(h, &adc_chan_result);
+ if (ret) {
+ pr_err("couldnt read result channel %d ret=%d\n",
+ channel, ret);
+ goto out;
+ }
+ ret = adc_channel_close(h);
+ if (ret)
+ pr_err("couldnt close channel %d ret=%d\n", channel, ret);
+ if (mv_reading)
+ *mv_reading = (int)adc_chan_result.measurement;
+
+ pr_debug("done for %d\n", channel);
+ return adc_chan_result.physical;
+out:
+ *mv_reading = 0;
+ pr_debug("done with error for %d\n", channel);
+
+ return -EINVAL;
+}
+
+static bool is_trickle_charging(struct isl9519q_struct *isl_chg)
+{
+ u16 ctrl = 0;
+ int ret;
+
+ ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, &ctrl);
+
+ if (!ret) {
+ pr_debug("control_reg=0x%x.\n", ctrl);
+ } else {
+ dev_err(&isl_chg->client->dev,
+ "%s couldnt read cntrl reg\n", __func__);
+ }
+
+ if (ctrl & TRCKL_CHG_STATUS_BIT)
+ return true;
+
+ return false;
+}
+
+static void isl_adapter_check_ichg(struct isl9519q_struct *isl_chg)
+{
+ int ichg; /* isl charger current */
+ int mv_reading = 0;
+
+ ichg = isl_read_adc(CHANNEL_ADC_BATT_AMON, &mv_reading);
+
+ dev_dbg(&isl_chg->client->dev, "%s mv_reading=%d\n",
+ __func__, mv_reading);
+ dev_dbg(&isl_chg->client->dev, "%s isl_charger_current=%d\n",
+ __func__, ichg);
+
+ if (ichg >= 0 && ichg <= isl_chg->term_current)
+ msm_charger_notify_event(&isl_chg->adapter_hw_chg,
+ CHG_DONE_EVENT);
+
+ isl_chg->trickle = is_trickle_charging(isl_chg);
+ if (isl_chg->trickle)
+ msm_charger_notify_event(&isl_chg->adapter_hw_chg,
+ CHG_BATT_BEGIN_FAST_CHARGING);
+}
+
+/**
+ * isl9519q_worker
+ *
+ * Periodic task required to kick the ISL HW watchdog to keep
+ * charging.
+ *
+ * @isl9519_work: work context.
+ */
+static void isl9519q_worker(struct work_struct *isl9519_work)
+{
+ struct isl9519q_struct *isl_chg;
+
+ isl_chg = container_of(isl9519_work, struct isl9519q_struct,
+ charge_work.work);
+
+ dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
+
+ if (!isl_chg->charging) {
+ pr_debug("stop charging.\n");
+ isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0);
+ return; /* Stop periodic worker */
+ }
+
+ /* Kick the dog by writting to CHG_CURRENT_REG */
+ isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG,
+ isl_chg->chgcurrent);
+
+ if (isl_chg->notify_by_pmic)
+ isl_chg->trickle = is_trickle_charging(isl_chg);
+ else
+ isl_adapter_check_ichg(isl_chg);
+
+ schedule_delayed_work(&isl_chg->charge_work,
+ (ISL9519_CHG_PERIOD_SEC * HZ));
+}
+
+static int isl9519q_start_charging(struct isl9519q_struct *isl_chg,
+ int chg_voltage, int chg_current)
+{
+ pr_debug("\n");
+
+ if (isl_chg->charging) {
+ pr_warn("already charging.\n");
+ return 0;
+ }
+
+ if (isl_chg->suspended) {
+ pr_warn("suspended - can't start charging.\n");
+ isl_chg->charge_at_resume = 1;
+ return 0;
+ }
+
+ dev_dbg(&isl_chg->client->dev,
+ "%s starting timed work.period=%d seconds.\n",
+ __func__, (int) ISL9519_CHG_PERIOD_SEC);
+
+ /*
+ * The ISL will start charging from the worker context.
+ * This API might be called from interrupt context.
+ */
+ schedule_delayed_work(&isl_chg->charge_work, 1);
+
+ isl_chg->charging = true;
+
+ return 0;
+}
+
+static int isl9519q_stop_charging(struct isl9519q_struct *isl_chg)
+{
+ pr_debug("\n");
+
+ if (!(isl_chg->charging)) {
+ pr_warn("already not charging.\n");
+ return 0;
+ }
+
+ if (isl_chg->suspended) {
+ isl_chg->charge_at_resume = 0;
+ return 0;
+ }
+
+ dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
+
+ isl_chg->charging = false;
+ isl_chg->trickle = false;
+ /*
+ * The ISL will stop charging from the worker context.
+ * This API might be called from interrupt context.
+ */
+ schedule_delayed_work(&isl_chg->charge_work, 1);
+
+ return 0;
+}
+
+static int isl_adapter_start_charging(struct msm_hardware_charger *hw_chg,
+ int chg_voltage, int chg_current)
+{
+ int rc;
+ struct isl9519q_struct *isl_chg;
+
+ isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
+ rc = isl9519q_start_charging(isl_chg, chg_voltage, chg_current);
+
+ return rc;
+}
+
+static int isl_adapter_stop_charging(struct msm_hardware_charger *hw_chg)
+{
+ int rc;
+ struct isl9519q_struct *isl_chg;
+
+ isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
+ rc = isl9519q_stop_charging(isl_chg);
+
+ return rc;
+}
+
+static int isl9519q_charging_switched(struct msm_hardware_charger *hw_chg)
+{
+ struct isl9519q_struct *isl_chg;
+
+ isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
+ dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
+ return 0;
+}
+
+static irqreturn_t isl_valid_handler(int irq, void *dev_id)
+{
+ int val;
+ struct isl9519q_struct *isl_chg;
+ struct i2c_client *client = dev_id;
+
+ isl_chg = i2c_get_clientdata(client);
+ val = gpio_get_value_cansleep(isl_chg->valid_n_gpio);
+ if (val < 0) {
+ dev_err(&isl_chg->client->dev,
+ "%s gpio_get_value failed for %d ret=%d\n", __func__,
+ isl_chg->valid_n_gpio, val);
+ goto err;
+ }
+ dev_dbg(&isl_chg->client->dev, "%s val=%d\n", __func__, val);
+
+ if (val) {
+ if (isl_chg->present == 1) {
+ msm_charger_notify_event(&isl_chg->adapter_hw_chg,
+ CHG_REMOVED_EVENT);
+ isl_chg->present = 0;
+ }
+ } else {
+ if (isl_chg->present == 0) {
+ msm_charger_notify_event(&isl_chg->adapter_hw_chg,
+ CHG_INSERTED_EVENT);
+ isl_chg->present = 1;
+ }
+ }
+err:
+ return IRQ_HANDLED;
+}
+
+static enum power_supply_property pm_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+};
+
+static char *pm_power_supplied_to[] = {
+ "battery",
+};
+
+static int get_prop_charge_type(struct isl9519q_struct *isl_chg)
+{
+ if (!isl_chg->present)
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ if (isl_chg->trickle)
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+
+ if (isl_chg->charging)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+static int pm_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct isl9519q_struct *isl_chg = container_of(psy,
+ struct isl9519q_struct,
+ dc_psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = isl_chg->chgcurrent;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = (int)isl_chg->present;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = get_prop_charge_type(isl_chg);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int pm_power_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct isl9519q_struct *isl_chg = container_of(psy,
+ struct isl9519q_struct,
+ dc_psy);
+ unsigned long flags;
+ int rc;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (val->intval) {
+ isl_chg->present = val->intval;
+ } else {
+ isl_chg->present = 0;
+ if (isl_chg->charging)
+ goto stop_charging;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (val->intval) {
+ if (isl_chg->chgcurrent != val->intval)
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (val->intval && isl_chg->present) {
+ if (val->intval == POWER_SUPPLY_CHARGE_TYPE_FAST)
+ goto start_charging;
+ if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE)
+ goto stop_charging;
+ } else {
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ power_supply_changed(&isl_chg->dc_psy);
+ return 0;
+
+start_charging:
+ spin_lock_irqsave(&isl_chg->lock, flags);
+ rc = isl9519q_start_charging(isl_chg, 0, isl_chg->chgcurrent);
+ if (rc)
+ pr_err("Failed to start charging rc=%d\n", rc);
+ spin_unlock_irqrestore(&isl_chg->lock, flags);
+ power_supply_changed(&isl_chg->dc_psy);
+ return rc;
+
+stop_charging:
+ spin_lock_irqsave(&isl_chg->lock, flags);
+ rc = isl9519q_stop_charging(isl_chg);
+ if (rc)
+ pr_err("Failed to start charging rc=%d\n", rc);
+ spin_unlock_irqrestore(&isl_chg->lock, flags);
+ power_supply_changed(&isl_chg->dc_psy);
+ return rc;
+}
+
+#define MAX_VOLTAGE_REG_MASK 0x3FF0
+#define MIN_VOLTAGE_REG_MASK 0x3F00
+#define DEFAULT_MAX_VOLTAGE_REG_VALUE 0x1070
+#define DEFAULT_MIN_VOLTAGE_REG_VALUE 0x0D00
+
+static int __devinit isl9519q_init_adapter(struct isl9519q_struct *isl_chg)
+{
+ int ret;
+ struct i2c_client *client = isl_chg->client;
+ struct isl_platform_data *pdata = client->dev.platform_data;
+
+ isl_chg->adapter_hw_chg.type = CHG_TYPE_AC;
+ isl_chg->adapter_hw_chg.rating = 2;
+ isl_chg->adapter_hw_chg.name = "isl-adapter";
+ isl_chg->adapter_hw_chg.start_charging = isl_adapter_start_charging;
+ isl_chg->adapter_hw_chg.stop_charging = isl_adapter_stop_charging;
+ isl_chg->adapter_hw_chg.charging_switched = isl9519q_charging_switched;
+
+ ret = gpio_request(pdata->valid_n_gpio, "isl_charger_valid");
+ if (ret) {
+ dev_err(&client->dev, "%s gpio_request failed "
+ "for %d ret=%d\n",
+ __func__, pdata->valid_n_gpio, ret);
+ goto out;
+ }
+
+ ret = msm_charger_register(&isl_chg->adapter_hw_chg);
+ if (ret) {
+ dev_err(&client->dev,
+ "%s msm_charger_register failed for ret =%d\n",
+ __func__, ret);
+ goto free_gpio;
+ }
+
+ ret = request_threaded_irq(client->irq, NULL,
+ isl_valid_handler,
+ IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING,
+ "isl_charger_valid", client);
+ if (ret) {
+ dev_err(&client->dev,
+ "%s request_threaded_irq failed "
+ "for %d ret =%d\n",
+ __func__, client->irq, ret);
+ goto unregister;
+ }
+ irq_set_irq_wake(client->irq, 1);
+
+ ret = gpio_get_value_cansleep(isl_chg->valid_n_gpio);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "%s gpio_get_value failed for %d ret=%d\n",
+ __func__, pdata->valid_n_gpio, ret);
+ /* assume absent */
+ ret = 1;
+ }
+ if (!ret) {
+ msm_charger_notify_event(&isl_chg->adapter_hw_chg,
+ CHG_INSERTED_EVENT);
+ isl_chg->present = 1;
+ }
+
+ return 0;
+
+unregister:
+ msm_charger_unregister(&isl_chg->adapter_hw_chg);
+free_gpio:
+ gpio_free(pdata->valid_n_gpio);
+out:
+ return ret;
+
+}
+
+static int __devinit isl9519q_init_ext_chg(struct isl9519q_struct *isl_chg)
+{
+ int ret;
+
+ isl_chg->dc_psy.name = "dc";
+ isl_chg->dc_psy.type = POWER_SUPPLY_TYPE_MAINS;
+ isl_chg->dc_psy.supplied_to = pm_power_supplied_to;
+ isl_chg->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to);
+ isl_chg->dc_psy.properties = pm_power_props;
+ isl_chg->dc_psy.num_properties = ARRAY_SIZE(pm_power_props);
+ isl_chg->dc_psy.get_property = pm_power_get_property;
+ isl_chg->dc_psy.set_property = pm_power_set_property;
+
+ ret = power_supply_register(&isl_chg->client->dev, &isl_chg->dc_psy);
+ if (ret) {
+ pr_err("failed to register dc charger.ret=%d.\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+static int set_reg(void *data, u64 val)
+{
+ int addr = (int)data;
+ int ret;
+ u16 temp;
+
+ temp = (u16) val;
+ ret = isl9519q_write_reg(the_isl_chg->client, addr, temp);
+
+ if (ret) {
+ pr_err("isl9519q_write_reg to %x value =%d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+ return 0;
+}
+static int get_reg(void *data, u64 *val)
+{
+ int addr = (int)data;
+ int ret;
+ u16 temp;
+
+ ret = isl9519q_read_reg(the_isl_chg->client, addr, &temp);
+ if (ret) {
+ pr_err("isl9519q_read_reg to %x value =%d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+
+ *val = temp;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n");
+
+static void create_debugfs_entries(struct isl9519q_struct *isl_chg)
+{
+ isl_chg->dent = debugfs_create_dir("isl9519q", NULL);
+
+ if (IS_ERR(isl_chg->dent)) {
+ pr_err("isl9519q driver couldn't create debugfs dir\n");
+ return;
+ }
+
+ debugfs_create_file("CHG_CURRENT_REG", 0644, isl_chg->dent,
+ (void *) CHG_CURRENT_REG, ®_fops);
+ debugfs_create_file("MAX_SYS_VOLTAGE_REG", 0644, isl_chg->dent,
+ (void *) MAX_SYS_VOLTAGE_REG, ®_fops);
+ debugfs_create_file("CONTROL_REG", 0644, isl_chg->dent,
+ (void *) CONTROL_REG, ®_fops);
+ debugfs_create_file("MIN_SYS_VOLTAGE_REG", 0644, isl_chg->dent,
+ (void *) MIN_SYS_VOLTAGE_REG, ®_fops);
+ debugfs_create_file("INPUT_CURRENT_REG", 0644, isl_chg->dent,
+ (void *) INPUT_CURRENT_REG, ®_fops);
+ debugfs_create_file("MANUFACTURER_ID_REG", 0644, isl_chg->dent,
+ (void *) MANUFACTURER_ID_REG, ®_fops);
+ debugfs_create_file("DEVICE_ID_REG", 0644, isl_chg->dent,
+ (void *) DEVICE_ID_REG, ®_fops);
+}
+
+static void remove_debugfs_entries(struct isl9519q_struct *isl_chg)
+{
+ if (isl_chg->dent)
+ debugfs_remove_recursive(isl_chg->dent);
+}
+
+static int __devinit isl9519q_hwinit(struct isl9519q_struct *isl_chg)
+{
+ int ret;
+
+ ret = isl9519q_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
+ isl_chg->max_system_voltage);
+ if (ret) {
+ pr_err("Failed to set MAX_SYS_VOLTAGE rc=%d\n", ret);
+ return ret;
+ }
+
+ ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG,
+ isl_chg->min_system_voltage);
+ if (ret) {
+ pr_err("Failed to set MIN_SYS_VOLTAGE rc=%d\n", ret);
+ return ret;
+ }
+
+ if (isl_chg->input_current) {
+ ret = isl9519q_write_reg(isl_chg->client,
+ INPUT_CURRENT_REG,
+ isl_chg->input_current);
+ if (ret) {
+ pr_err("Failed to set INPUT_CURRENT rc=%d\n", ret);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int __devinit isl9519q_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct isl_platform_data *pdata;
+ struct isl9519q_struct *isl_chg;
+ int ret;
+
+ ret = 0;
+ pdata = client->dev.platform_data;
+
+ pr_debug("\n");
+
+ if (pdata == NULL) {
+ dev_err(&client->dev, "%s no platform data\n", __func__);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_WORD_DATA)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ isl_chg = kzalloc(sizeof(*isl_chg), GFP_KERNEL);
+ if (!isl_chg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ spin_lock_init(&isl_chg->lock);
+
+ INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_worker);
+ isl_chg->client = client;
+ isl_chg->chgcurrent = pdata->chgcurrent;
+ isl_chg->term_current = pdata->term_current;
+ isl_chg->input_current = pdata->input_current;
+ isl_chg->max_system_voltage = pdata->max_system_voltage;
+ isl_chg->min_system_voltage = pdata->min_system_voltage;
+ isl_chg->valid_n_gpio = pdata->valid_n_gpio;
+
+ /* h/w ignores lower 7 bits of charging current and input current */
+ isl_chg->chgcurrent &= ~0x7F;
+ isl_chg->input_current &= ~0x7F;
+
+ /**
+ * ISL is Notified by PMIC to start/stop charging, rather than
+ * handling interrupt from ISL for End-Of-Chargring, and
+ * monitoring the charge-current periodically. The valid_n_gpio
+ * is also not used, dc-present is detected by PMIC.
+ */
+ isl_chg->notify_by_pmic = (client->irq == 0);
+ i2c_set_clientdata(client, isl_chg);
+
+ if (pdata->chg_detection_config) {
+ ret = pdata->chg_detection_config();
+ if (ret) {
+ dev_err(&client->dev, "%s valid config failed ret=%d\n",
+ __func__, ret);
+ goto free_isl_chg;
+ }
+ }
+
+ isl_chg->max_system_voltage &= MAX_VOLTAGE_REG_MASK;
+ isl_chg->min_system_voltage &= MIN_VOLTAGE_REG_MASK;
+ if (isl_chg->max_system_voltage == 0)
+ isl_chg->max_system_voltage = DEFAULT_MAX_VOLTAGE_REG_VALUE;
+ if (isl_chg->min_system_voltage == 0)
+ isl_chg->min_system_voltage = DEFAULT_MIN_VOLTAGE_REG_VALUE;
+
+ ret = isl9519q_hwinit(isl_chg);
+ if (ret)
+ goto free_isl_chg;
+
+ if (isl_chg->notify_by_pmic)
+ ret = isl9519q_init_ext_chg(isl_chg);
+ else
+ ret = isl9519q_init_adapter(isl_chg);
+
+ if (ret)
+ goto free_isl_chg;
+
+ the_isl_chg = isl_chg;
+ create_debugfs_entries(isl_chg);
+
+ pr_info("OK.\n");
+
+ return 0;
+
+free_isl_chg:
+ kfree(isl_chg);
+out:
+ return ret;
+}
+
+static int __devexit isl9519q_remove(struct i2c_client *client)
+{
+ struct isl_platform_data *pdata;
+ struct isl9519q_struct *isl_chg = i2c_get_clientdata(client);
+
+ pdata = client->dev.platform_data;
+ gpio_free(pdata->valid_n_gpio);
+ free_irq(client->irq, client);
+ cancel_delayed_work_sync(&isl_chg->charge_work);
+ if (isl_chg->notify_by_pmic) {
+ power_supply_unregister(&isl_chg->dc_psy);
+ } else {
+ msm_charger_notify_event(&isl_chg->adapter_hw_chg,
+ CHG_REMOVED_EVENT);
+ msm_charger_unregister(&isl_chg->adapter_hw_chg);
+ }
+ remove_debugfs_entries(isl_chg);
+ the_isl_chg = NULL;
+ kfree(isl_chg);
+ return 0;
+}
+
+static const struct i2c_device_id isl9519q_id[] = {
+ {"isl9519q", 0},
+ {},
+};
+
+#ifdef CONFIG_PM
+static int isl9519q_suspend(struct device *dev)
+{
+ struct isl9519q_struct *isl_chg = dev_get_drvdata(dev);
+
+ dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
+ /*
+ * do not suspend while we are charging
+ * because we need to periodically update the register
+ * for charging to proceed
+ */
+ if (isl_chg->charging)
+ return -EBUSY;
+
+ isl_chg->suspended = 1;
+ return 0;
+}
+
+static int isl9519q_resume(struct device *dev)
+{
+ struct isl9519q_struct *isl_chg = dev_get_drvdata(dev);
+
+ dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
+ isl_chg->suspended = 0;
+ if (isl_chg->charge_at_resume) {
+ isl_chg->charge_at_resume = 0;
+ isl9519q_start_charging(isl_chg, 0, 0);
+ }
+ return 0;
+}
+
+static const struct dev_pm_ops isl9519q_pm_ops = {
+ .suspend = isl9519q_suspend,
+ .resume = isl9519q_resume,
+};
+#endif
+
+static struct i2c_driver isl9519q_driver = {
+ .driver = {
+ .name = "isl9519q",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &isl9519q_pm_ops,
+#endif
+ },
+ .probe = isl9519q_probe,
+ .remove = __devexit_p(isl9519q_remove),
+ .id_table = isl9519q_id,
+};
+
+static int __init isl9519q_init(void)
+{
+ return i2c_add_driver(&isl9519q_driver);
+}
+
+late_initcall_sync(isl9519q_init);
+
+static void __exit isl9519q_exit(void)
+{
+ return i2c_del_driver(&isl9519q_driver);
+}
+
+module_exit(isl9519q_exit);
+
+MODULE_AUTHOR("Abhijeet Dharmapurikar <adharmap@codeaurora.org>");
+MODULE_DESCRIPTION("Driver for ISL9519Q Charger chip");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/ltc4088-charger.c b/drivers/power/ltc4088-charger.c
new file mode 100644
index 0000000..dbc75cd
--- /dev/null
+++ b/drivers/power/ltc4088-charger.c
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/power/ltc4088-charger.h>
+
+#define MAX_CURRENT_UA(n) (n)
+#define MAX_CURRENT_MA(n) (n * MAX_CURRENT_UA(1000))
+
+/**
+ * ltc4088_max_current - A typical current values supported by the charger
+ * @LTC4088_MAX_CURRENT_100mA: 100mA current
+ * @LTC4088_MAX_CURRENT_500mA: 500mA current
+ * @LTC4088_MAX_CURRENT_1A: 1A current
+ */
+enum ltc4088_max_current {
+ LTC4088_MAX_CURRENT_100mA = 100,
+ LTC4088_MAX_CURRENT_500mA = 500,
+ LTC4088_MAX_CURRENT_1A = 1000,
+};
+
+/**
+ * struct ltc4088_chg_chip - Device information
+ * @dev: Device pointer to access the parent
+ * @lock: Enable mutual exclusion
+ * @usb_psy: USB device information
+ * @gpio_mode_select_d0: GPIO #pin for D0 charger line
+ * @gpio_mode_select_d1: GPIO #pin for D1 charger line
+ * @gpio_mode_select_d2: GPIO #pin for D2 charger line
+ * @max_current: Maximum current that is supplied at this time
+ */
+struct ltc4088_chg_chip {
+ struct device *dev;
+ struct mutex lock;
+ struct power_supply usb_psy;
+ unsigned int gpio_mode_select_d0;
+ unsigned int gpio_mode_select_d1;
+ unsigned int gpio_mode_select_d2;
+ unsigned int max_current;
+};
+
+static enum power_supply_property pm_power_props[] = {
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *pm_power_supplied_to[] = {
+ "battery",
+};
+
+static int ltc4088_set_charging(struct ltc4088_chg_chip *chip, bool enable)
+{
+ mutex_lock(&chip->lock);
+
+ if (enable) {
+ gpio_set_value_cansleep(chip->gpio_mode_select_d2, 0);
+ } else {
+ /* When disabling charger, set the max current to 0 also */
+ chip->max_current = 0;
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d2, 1);
+ }
+
+ mutex_unlock(&chip->lock);
+
+ return 0;
+}
+
+static void ltc4088_set_max_current(struct ltc4088_chg_chip *chip, int value)
+{
+ mutex_lock(&chip->lock);
+
+ /* If current is less than 100mA, we can not support that granularity */
+ if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_100mA)) {
+ chip->max_current = 0;
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
+ } else if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_500mA)) {
+ chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_100mA);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 0);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 0);
+ } else if (value < MAX_CURRENT_MA(LTC4088_MAX_CURRENT_1A)) {
+ chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_500mA);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 0);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
+ } else {
+ chip->max_current = MAX_CURRENT_MA(LTC4088_MAX_CURRENT_1A);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 0);
+ }
+
+ mutex_unlock(&chip->lock);
+}
+
+static void ltc4088_set_charging_off(struct ltc4088_chg_chip *chip)
+{
+ gpio_set_value_cansleep(chip->gpio_mode_select_d0, 1);
+ gpio_set_value_cansleep(chip->gpio_mode_select_d1, 1);
+}
+
+static int ltc4088_set_initial_state(struct ltc4088_chg_chip *chip)
+{
+ int rc;
+
+ rc = gpio_request(chip->gpio_mode_select_d0, "ltc4088_D0");
+ if (rc) {
+ pr_err("gpio request failed for GPIO %d\n",
+ chip->gpio_mode_select_d0);
+ return rc;
+ }
+
+ rc = gpio_request(chip->gpio_mode_select_d1, "ltc4088_D1");
+ if (rc) {
+ pr_err("gpio request failed for GPIO %d\n",
+ chip->gpio_mode_select_d1);
+ goto gpio_err_d0;
+ }
+
+ rc = gpio_request(chip->gpio_mode_select_d2, "ltc4088_D2");
+ if (rc) {
+ pr_err("gpio request failed for GPIO %d\n",
+ chip->gpio_mode_select_d2);
+ goto gpio_err_d1;
+ }
+
+ rc = gpio_direction_output(chip->gpio_mode_select_d0, 0);
+ if (rc) {
+ pr_err("failed to set direction for GPIO %d\n",
+ chip->gpio_mode_select_d0);
+ goto gpio_err_d2;
+ }
+
+ rc = gpio_direction_output(chip->gpio_mode_select_d1, 0);
+ if (rc) {
+ pr_err("failed to set direction for GPIO %d\n",
+ chip->gpio_mode_select_d1);
+ goto gpio_err_d2;
+ }
+
+ rc = gpio_direction_output(chip->gpio_mode_select_d2, 1);
+ if (rc) {
+ pr_err("failed to set direction for GPIO %d\n",
+ chip->gpio_mode_select_d2);
+ goto gpio_err_d2;
+ }
+
+ return 0;
+
+gpio_err_d2:
+ gpio_free(chip->gpio_mode_select_d2);
+gpio_err_d1:
+ gpio_free(chip->gpio_mode_select_d1);
+gpio_err_d0:
+ gpio_free(chip->gpio_mode_select_d0);
+ return rc;
+}
+
+static int pm_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ltc4088_chg_chip *chip;
+
+ if (psy->type == POWER_SUPPLY_TYPE_USB) {
+ chip = container_of(psy, struct ltc4088_chg_chip,
+ usb_psy);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (chip->max_current)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = chip->max_current;
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int pm_power_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct ltc4088_chg_chip *chip;
+
+ if (psy->type == POWER_SUPPLY_TYPE_USB) {
+ chip = container_of(psy, struct ltc4088_chg_chip,
+ usb_psy);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ltc4088_set_charging(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ltc4088_set_max_current(chip, val->intval);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int __devinit ltc4088_charger_probe(struct platform_device *pdev)
+{
+ int rc;
+ struct ltc4088_chg_chip *chip;
+ const struct ltc4088_charger_platform_data *pdata
+ = pdev->dev.platform_data;
+
+ if (!pdata) {
+ pr_err("missing platform data\n");
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(struct ltc4088_chg_chip),
+ GFP_KERNEL);
+ if (!chip) {
+ pr_err("Cannot allocate pm_chg_chip\n");
+ return -ENOMEM;
+ }
+
+ chip->dev = &pdev->dev;
+
+ if (pdata->gpio_mode_select_d0 < 0 ||
+ pdata->gpio_mode_select_d1 < 0 ||
+ pdata->gpio_mode_select_d2 < 0) {
+ pr_err("Invalid platform data supplied\n");
+ rc = -EINVAL;
+ goto free_chip;
+ }
+
+ mutex_init(&chip->lock);
+
+ chip->gpio_mode_select_d0 = pdata->gpio_mode_select_d0;
+ chip->gpio_mode_select_d1 = pdata->gpio_mode_select_d1;
+ chip->gpio_mode_select_d2 = pdata->gpio_mode_select_d2;
+
+ chip->usb_psy.name = "usb",
+ chip->usb_psy.type = POWER_SUPPLY_TYPE_USB,
+ chip->usb_psy.supplied_to = pm_power_supplied_to,
+ chip->usb_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to),
+ chip->usb_psy.properties = pm_power_props,
+ chip->usb_psy.num_properties = ARRAY_SIZE(pm_power_props),
+ chip->usb_psy.get_property = pm_power_get_property,
+ chip->usb_psy.set_property = pm_power_set_property,
+
+ rc = power_supply_register(chip->dev, &chip->usb_psy);
+ if (rc < 0) {
+ pr_err("power_supply_register usb failed rc = %d\n", rc);
+ goto free_chip;
+ }
+
+ platform_set_drvdata(pdev, chip);
+
+ rc = ltc4088_set_initial_state(chip);
+ if (rc < 0) {
+ pr_err("setting initial state failed rc = %d\n", rc);
+ goto unregister_usb;
+ }
+
+ return 0;
+
+unregister_usb:
+ platform_set_drvdata(pdev, NULL);
+ power_supply_unregister(&chip->usb_psy);
+free_chip:
+ kfree(chip);
+
+ return rc;
+}
+
+static int __devexit ltc4088_charger_remove(struct platform_device *pdev)
+{
+ struct ltc4088_chg_chip *chip = platform_get_drvdata(pdev);
+
+ ltc4088_set_charging_off(chip);
+
+ gpio_free(chip->gpio_mode_select_d2);
+ gpio_free(chip->gpio_mode_select_d1);
+ gpio_free(chip->gpio_mode_select_d0);
+
+ power_supply_unregister(&chip->usb_psy);
+
+ platform_set_drvdata(pdev, NULL);
+ mutex_destroy(&chip->lock);
+ kfree(chip);
+
+ return 0;
+}
+
+static struct platform_driver ltc4088_charger_driver = {
+ .probe = ltc4088_charger_probe,
+ .remove = __devexit_p(ltc4088_charger_remove),
+ .driver = {
+ .name = LTC4088_CHARGER_DEV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init ltc4088_charger_init(void)
+{
+ return platform_driver_register(<c4088_charger_driver);
+}
+
+static void __exit ltc4088_charger_exit(void)
+{
+ platform_driver_unregister(<c4088_charger_driver);
+}
+
+subsys_initcall(ltc4088_charger_init);
+module_exit(ltc4088_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("LTC4088 charger/battery driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:" LTC4088_CHARGER_DEV_NAME);
diff --git a/drivers/power/msm_battery.c b/drivers/power/msm_battery.c
new file mode 100644
index 0000000..0555399
--- /dev/null
+++ b/drivers/power/msm_battery.c
@@ -0,0 +1,1592 @@
+/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * this needs to be before <linux/kernel.h> is loaded,
+ * and <linux/sched.h> loads <linux/kernel.h>
+ */
+#define DEBUG 0
+
+#include <linux/slab.h>
+#include <linux/earlysuspend.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/sched.h>
+#include <linux/signal.h>
+#include <linux/uaccess.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+
+#include <asm/atomic.h>
+
+#include <mach/msm_rpcrouter.h>
+#include <mach/msm_battery.h>
+
+#define BATTERY_RPC_PROG 0x30000089
+#define BATTERY_RPC_VER_1_1 0x00010001
+#define BATTERY_RPC_VER_2_1 0x00020001
+#define BATTERY_RPC_VER_4_1 0x00040001
+#define BATTERY_RPC_VER_5_1 0x00050001
+
+#define BATTERY_RPC_CB_PROG (BATTERY_RPC_PROG | 0x01000000)
+
+#define CHG_RPC_PROG 0x3000001a
+#define CHG_RPC_VER_1_1 0x00010001
+#define CHG_RPC_VER_1_3 0x00010003
+#define CHG_RPC_VER_2_2 0x00020002
+#define CHG_RPC_VER_3_1 0x00030001
+#define CHG_RPC_VER_4_1 0x00040001
+
+#define BATTERY_REGISTER_PROC 2
+#define BATTERY_MODIFY_CLIENT_PROC 4
+#define BATTERY_DEREGISTER_CLIENT_PROC 5
+#define BATTERY_READ_MV_PROC 12
+#define BATTERY_ENABLE_DISABLE_FILTER_PROC 14
+
+#define VBATT_FILTER 2
+
+#define BATTERY_CB_TYPE_PROC 1
+#define BATTERY_CB_ID_ALL_ACTIV 1
+#define BATTERY_CB_ID_LOW_VOL 2
+
+#define BATTERY_LOW 3200
+#define BATTERY_HIGH 4300
+
+#define ONCRPC_CHG_GET_GENERAL_STATUS_PROC 12
+#define ONCRPC_CHARGER_API_VERSIONS_PROC 0xffffffff
+
+#define BATT_RPC_TIMEOUT 5000 /* 5 sec */
+
+#define INVALID_BATT_HANDLE -1
+
+#define RPC_TYPE_REQ 0
+#define RPC_TYPE_REPLY 1
+#define RPC_REQ_REPLY_COMMON_HEADER_SIZE (3 * sizeof(uint32_t))
+
+
+#if DEBUG
+#define DBG_LIMIT(x...) do {if (printk_ratelimit()) pr_debug(x); } while (0)
+#else
+#define DBG_LIMIT(x...) do {} while (0)
+#endif
+
+enum {
+ BATTERY_REGISTRATION_SUCCESSFUL = 0,
+ BATTERY_DEREGISTRATION_SUCCESSFUL = BATTERY_REGISTRATION_SUCCESSFUL,
+ BATTERY_MODIFICATION_SUCCESSFUL = BATTERY_REGISTRATION_SUCCESSFUL,
+ BATTERY_INTERROGATION_SUCCESSFUL = BATTERY_REGISTRATION_SUCCESSFUL,
+ BATTERY_CLIENT_TABLE_FULL = 1,
+ BATTERY_REG_PARAMS_WRONG = 2,
+ BATTERY_DEREGISTRATION_FAILED = 4,
+ BATTERY_MODIFICATION_FAILED = 8,
+ BATTERY_INTERROGATION_FAILED = 16,
+ /* Client's filter could not be set because perhaps it does not exist */
+ BATTERY_SET_FILTER_FAILED = 32,
+ /* Client's could not be found for enabling or disabling the individual
+ * client */
+ BATTERY_ENABLE_DISABLE_INDIVIDUAL_CLIENT_FAILED = 64,
+ BATTERY_LAST_ERROR = 128,
+};
+
+enum {
+ BATTERY_VOLTAGE_UP = 0,
+ BATTERY_VOLTAGE_DOWN,
+ BATTERY_VOLTAGE_ABOVE_THIS_LEVEL,
+ BATTERY_VOLTAGE_BELOW_THIS_LEVEL,
+ BATTERY_VOLTAGE_LEVEL,
+ BATTERY_ALL_ACTIVITY,
+ VBATT_CHG_EVENTS,
+ BATTERY_VOLTAGE_UNKNOWN,
+};
+
+/*
+ * This enum contains defintions of the charger hardware status
+ */
+enum chg_charger_status_type {
+ /* The charger is good */
+ CHARGER_STATUS_GOOD,
+ /* The charger is bad */
+ CHARGER_STATUS_BAD,
+ /* The charger is weak */
+ CHARGER_STATUS_WEAK,
+ /* Invalid charger status. */
+ CHARGER_STATUS_INVALID
+};
+
+/*
+ *This enum contains defintions of the charger hardware type
+ */
+enum chg_charger_hardware_type {
+ /* The charger is removed */
+ CHARGER_TYPE_NONE,
+ /* The charger is a regular wall charger */
+ CHARGER_TYPE_WALL,
+ /* The charger is a PC USB */
+ CHARGER_TYPE_USB_PC,
+ /* The charger is a wall USB charger */
+ CHARGER_TYPE_USB_WALL,
+ /* The charger is a USB carkit */
+ CHARGER_TYPE_USB_CARKIT,
+ /* Invalid charger hardware status. */
+ CHARGER_TYPE_INVALID
+};
+
+/*
+ * This enum contains defintions of the battery status
+ */
+enum chg_battery_status_type {
+ /* The battery is good */
+ BATTERY_STATUS_GOOD,
+ /* The battery is cold/hot */
+ BATTERY_STATUS_BAD_TEMP,
+ /* The battery is bad */
+ BATTERY_STATUS_BAD,
+ /* The battery is removed */
+ BATTERY_STATUS_REMOVED, /* on v2.2 only */
+ BATTERY_STATUS_INVALID_v1 = BATTERY_STATUS_REMOVED,
+ /* Invalid battery status. */
+ BATTERY_STATUS_INVALID
+};
+
+/*
+ *This enum contains defintions of the battery voltage level
+ */
+enum chg_battery_level_type {
+ /* The battery voltage is dead/very low (less than 3.2V) */
+ BATTERY_LEVEL_DEAD,
+ /* The battery voltage is weak/low (between 3.2V and 3.4V) */
+ BATTERY_LEVEL_WEAK,
+ /* The battery voltage is good/normal(between 3.4V and 4.2V) */
+ BATTERY_LEVEL_GOOD,
+ /* The battery voltage is up to full (close to 4.2V) */
+ BATTERY_LEVEL_FULL,
+ /* Invalid battery voltage level. */
+ BATTERY_LEVEL_INVALID
+};
+
+#ifndef CONFIG_BATTERY_MSM_FAKE
+struct rpc_reply_batt_chg_v1 {
+ struct rpc_reply_hdr hdr;
+ u32 more_data;
+
+ u32 charger_status;
+ u32 charger_type;
+ u32 battery_status;
+ u32 battery_level;
+ u32 battery_voltage;
+ u32 battery_temp;
+};
+
+struct rpc_reply_batt_chg_v2 {
+ struct rpc_reply_batt_chg_v1 v1;
+
+ u32 is_charger_valid;
+ u32 is_charging;
+ u32 is_battery_valid;
+ u32 ui_event;
+};
+
+union rpc_reply_batt_chg {
+ struct rpc_reply_batt_chg_v1 v1;
+ struct rpc_reply_batt_chg_v2 v2;
+};
+
+static union rpc_reply_batt_chg rep_batt_chg;
+#endif
+
+struct msm_battery_info {
+ u32 voltage_max_design;
+ u32 voltage_min_design;
+ u32 chg_api_version;
+ u32 batt_technology;
+ u32 batt_api_version;
+
+ u32 avail_chg_sources;
+ u32 current_chg_source;
+
+ u32 batt_status;
+ u32 batt_health;
+ u32 charger_valid;
+ u32 batt_valid;
+ u32 batt_capacity; /* in percentage */
+
+ u32 charger_status;
+ u32 charger_type;
+ u32 battery_status;
+ u32 battery_level;
+ u32 battery_voltage; /* in millie volts */
+ u32 battery_temp; /* in celsius */
+
+ u32(*calculate_capacity) (u32 voltage);
+
+ s32 batt_handle;
+
+ struct power_supply *msm_psy_ac;
+ struct power_supply *msm_psy_usb;
+ struct power_supply *msm_psy_batt;
+ struct power_supply *current_ps;
+
+ struct msm_rpc_client *batt_client;
+ struct msm_rpc_endpoint *chg_ep;
+
+ wait_queue_head_t wait_q;
+
+ u32 vbatt_modify_reply_avail;
+
+ struct early_suspend early_suspend;
+};
+
+static struct msm_battery_info msm_batt_info = {
+ .batt_handle = INVALID_BATT_HANDLE,
+ .charger_status = CHARGER_STATUS_BAD,
+ .charger_type = CHARGER_TYPE_INVALID,
+ .battery_status = BATTERY_STATUS_GOOD,
+ .battery_level = BATTERY_LEVEL_FULL,
+ .battery_voltage = BATTERY_HIGH,
+ .batt_capacity = 100,
+ .batt_status = POWER_SUPPLY_STATUS_DISCHARGING,
+ .batt_health = POWER_SUPPLY_HEALTH_GOOD,
+ .batt_valid = 1,
+ .battery_temp = 23,
+ .vbatt_modify_reply_avail = 0,
+};
+
+static enum power_supply_property msm_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *msm_power_supplied_to[] = {
+ "battery",
+};
+
+static int msm_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 = msm_batt_info.current_chg_source & AC_CHG
+ ? 1 : 0;
+ }
+ if (psy->type == POWER_SUPPLY_TYPE_USB) {
+ val->intval = msm_batt_info.current_chg_source & USB_CHG
+ ? 1 : 0;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static struct power_supply msm_psy_ac = {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .supplied_to = msm_power_supplied_to,
+ .num_supplicants = ARRAY_SIZE(msm_power_supplied_to),
+ .properties = msm_power_props,
+ .num_properties = ARRAY_SIZE(msm_power_props),
+ .get_property = msm_power_get_property,
+};
+
+static struct power_supply msm_psy_usb = {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .supplied_to = msm_power_supplied_to,
+ .num_supplicants = ARRAY_SIZE(msm_power_supplied_to),
+ .properties = msm_power_props,
+ .num_properties = ARRAY_SIZE(msm_power_props),
+ .get_property = msm_power_get_property,
+};
+
+static enum power_supply_property msm_batt_power_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int msm_batt_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = msm_batt_info.batt_status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = msm_batt_info.batt_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = msm_batt_info.batt_valid;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = msm_batt_info.batt_technology;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = msm_batt_info.voltage_max_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = msm_batt_info.voltage_min_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = msm_batt_info.battery_voltage;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = msm_batt_info.batt_capacity;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static struct power_supply msm_psy_batt = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = msm_batt_power_props,
+ .num_properties = ARRAY_SIZE(msm_batt_power_props),
+ .get_property = msm_batt_power_get_property,
+};
+
+#ifndef CONFIG_BATTERY_MSM_FAKE
+struct msm_batt_get_volt_ret_data {
+ u32 battery_voltage;
+};
+
+static int msm_batt_get_volt_ret_func(struct msm_rpc_client *batt_client,
+ void *buf, void *data)
+{
+ struct msm_batt_get_volt_ret_data *data_ptr, *buf_ptr;
+
+ data_ptr = (struct msm_batt_get_volt_ret_data *)data;
+ buf_ptr = (struct msm_batt_get_volt_ret_data *)buf;
+
+ data_ptr->battery_voltage = be32_to_cpu(buf_ptr->battery_voltage);
+
+ return 0;
+}
+
+static u32 msm_batt_get_vbatt_voltage(void)
+{
+ int rc;
+
+ struct msm_batt_get_volt_ret_data rep;
+
+ rc = msm_rpc_client_req(msm_batt_info.batt_client,
+ BATTERY_READ_MV_PROC,
+ NULL, NULL,
+ msm_batt_get_volt_ret_func, &rep,
+ msecs_to_jiffies(BATT_RPC_TIMEOUT));
+
+ if (rc < 0) {
+ pr_err("%s: FAIL: vbatt get volt. rc=%d\n", __func__, rc);
+ return 0;
+ }
+
+ return rep.battery_voltage;
+}
+
+#define be32_to_cpu_self(v) (v = be32_to_cpu(v))
+
+static int msm_batt_get_batt_chg_status(void)
+{
+ int rc;
+
+ struct rpc_req_batt_chg {
+ struct rpc_request_hdr hdr;
+ u32 more_data;
+ } req_batt_chg;
+ struct rpc_reply_batt_chg_v1 *v1p;
+
+ req_batt_chg.more_data = cpu_to_be32(1);
+
+ memset(&rep_batt_chg, 0, sizeof(rep_batt_chg));
+
+ v1p = &rep_batt_chg.v1;
+ rc = msm_rpc_call_reply(msm_batt_info.chg_ep,
+ ONCRPC_CHG_GET_GENERAL_STATUS_PROC,
+ &req_batt_chg, sizeof(req_batt_chg),
+ &rep_batt_chg, sizeof(rep_batt_chg),
+ msecs_to_jiffies(BATT_RPC_TIMEOUT));
+ if (rc < 0) {
+ pr_err("%s: ERROR. msm_rpc_call_reply failed! proc=%d rc=%d\n",
+ __func__, ONCRPC_CHG_GET_GENERAL_STATUS_PROC, rc);
+ return rc;
+ } else if (be32_to_cpu(v1p->more_data)) {
+ be32_to_cpu_self(v1p->charger_status);
+ be32_to_cpu_self(v1p->charger_type);
+ be32_to_cpu_self(v1p->battery_status);
+ be32_to_cpu_self(v1p->battery_level);
+ be32_to_cpu_self(v1p->battery_voltage);
+ be32_to_cpu_self(v1p->battery_temp);
+ } else {
+ pr_err("%s: No battery/charger data in RPC reply\n", __func__);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void msm_batt_update_psy_status(void)
+{
+ static u32 unnecessary_event_count;
+ u32 charger_status;
+ u32 charger_type;
+ u32 battery_status;
+ u32 battery_level;
+ u32 battery_voltage;
+ u32 battery_temp;
+ struct power_supply *supp;
+
+ if (msm_batt_get_batt_chg_status())
+ return;
+
+ charger_status = rep_batt_chg.v1.charger_status;
+ charger_type = rep_batt_chg.v1.charger_type;
+ battery_status = rep_batt_chg.v1.battery_status;
+ battery_level = rep_batt_chg.v1.battery_level;
+ battery_voltage = rep_batt_chg.v1.battery_voltage;
+ battery_temp = rep_batt_chg.v1.battery_temp;
+
+ /* Make correction for battery status */
+ if (battery_status == BATTERY_STATUS_INVALID_v1) {
+ if (msm_batt_info.chg_api_version < CHG_RPC_VER_3_1)
+ battery_status = BATTERY_STATUS_INVALID;
+ }
+
+ if (charger_status == msm_batt_info.charger_status &&
+ charger_type == msm_batt_info.charger_type &&
+ battery_status == msm_batt_info.battery_status &&
+ battery_level == msm_batt_info.battery_level &&
+ battery_voltage == msm_batt_info.battery_voltage &&
+ battery_temp == msm_batt_info.battery_temp) {
+ /* Got unnecessary event from Modem PMIC VBATT driver.
+ * Nothing changed in Battery or charger status.
+ */
+ unnecessary_event_count++;
+ if ((unnecessary_event_count % 20) == 1)
+ DBG_LIMIT("BATT: same event count = %u\n",
+ unnecessary_event_count);
+ return;
+ }
+
+ unnecessary_event_count = 0;
+
+ DBG_LIMIT("BATT: rcvd: %d, %d, %d, %d; %d, %d\n",
+ charger_status, charger_type, battery_status,
+ battery_level, battery_voltage, battery_temp);
+
+ if (battery_status == BATTERY_STATUS_INVALID &&
+ battery_level != BATTERY_LEVEL_INVALID) {
+ DBG_LIMIT("BATT: change status(%d) to (%d) for level=%d\n",
+ battery_status, BATTERY_STATUS_GOOD, battery_level);
+ battery_status = BATTERY_STATUS_GOOD;
+ }
+
+ if (msm_batt_info.charger_type != charger_type) {
+ if (charger_type == CHARGER_TYPE_USB_WALL ||
+ charger_type == CHARGER_TYPE_USB_PC ||
+ charger_type == CHARGER_TYPE_USB_CARKIT) {
+ DBG_LIMIT("BATT: USB charger plugged in\n");
+ msm_batt_info.current_chg_source = USB_CHG;
+ supp = &msm_psy_usb;
+ } else if (charger_type == CHARGER_TYPE_WALL) {
+ DBG_LIMIT("BATT: AC Wall changer plugged in\n");
+ msm_batt_info.current_chg_source = AC_CHG;
+ supp = &msm_psy_ac;
+ } else {
+ if (msm_batt_info.current_chg_source & AC_CHG)
+ DBG_LIMIT("BATT: AC Wall charger removed\n");
+ else if (msm_batt_info.current_chg_source & USB_CHG)
+ DBG_LIMIT("BATT: USB charger removed\n");
+ else
+ DBG_LIMIT("BATT: No charger present\n");
+ msm_batt_info.current_chg_source = 0;
+ supp = &msm_psy_batt;
+
+ /* Correct charger status */
+ if (charger_status != CHARGER_STATUS_INVALID) {
+ DBG_LIMIT("BATT: No charging!\n");
+ charger_status = CHARGER_STATUS_INVALID;
+ msm_batt_info.batt_status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+ }
+ } else
+ supp = NULL;
+
+ if (msm_batt_info.charger_status != charger_status) {
+ if (charger_status == CHARGER_STATUS_GOOD ||
+ charger_status == CHARGER_STATUS_WEAK) {
+ if (msm_batt_info.current_chg_source) {
+ DBG_LIMIT("BATT: Charging.\n");
+ msm_batt_info.batt_status =
+ POWER_SUPPLY_STATUS_CHARGING;
+
+ /* Correct when supp==NULL */
+ if (msm_batt_info.current_chg_source & AC_CHG)
+ supp = &msm_psy_ac;
+ else
+ supp = &msm_psy_usb;
+ }
+ } else {
+ DBG_LIMIT("BATT: No charging.\n");
+ msm_batt_info.batt_status =
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ supp = &msm_psy_batt;
+ }
+ } else {
+ /* Correct charger status */
+ if (charger_type != CHARGER_TYPE_INVALID &&
+ charger_status == CHARGER_STATUS_GOOD) {
+ DBG_LIMIT("BATT: In charging\n");
+ msm_batt_info.batt_status =
+ POWER_SUPPLY_STATUS_CHARGING;
+ }
+ }
+
+ /* Correct battery voltage and status */
+ if (!battery_voltage) {
+ if (charger_status == CHARGER_STATUS_INVALID) {
+ DBG_LIMIT("BATT: Read VBATT\n");
+ battery_voltage = msm_batt_get_vbatt_voltage();
+ } else
+ /* Use previous */
+ battery_voltage = msm_batt_info.battery_voltage;
+ }
+ if (battery_status == BATTERY_STATUS_INVALID) {
+ if (battery_voltage >= msm_batt_info.voltage_min_design &&
+ battery_voltage <= msm_batt_info.voltage_max_design) {
+ DBG_LIMIT("BATT: Battery valid\n");
+ msm_batt_info.batt_valid = 1;
+ battery_status = BATTERY_STATUS_GOOD;
+ }
+ }
+
+ if (msm_batt_info.battery_status != battery_status) {
+ if (battery_status != BATTERY_STATUS_INVALID) {
+ msm_batt_info.batt_valid = 1;
+
+ if (battery_status == BATTERY_STATUS_BAD) {
+ DBG_LIMIT("BATT: Battery bad.\n");
+ msm_batt_info.batt_health =
+ POWER_SUPPLY_HEALTH_DEAD;
+ } else if (battery_status == BATTERY_STATUS_BAD_TEMP) {
+ DBG_LIMIT("BATT: Battery overheat.\n");
+ msm_batt_info.batt_health =
+ POWER_SUPPLY_HEALTH_OVERHEAT;
+ } else {
+ DBG_LIMIT("BATT: Battery good.\n");
+ msm_batt_info.batt_health =
+ POWER_SUPPLY_HEALTH_GOOD;
+ }
+ } else {
+ msm_batt_info.batt_valid = 0;
+ DBG_LIMIT("BATT: Battery invalid.\n");
+ msm_batt_info.batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ if (msm_batt_info.batt_status != POWER_SUPPLY_STATUS_CHARGING) {
+ if (battery_status == BATTERY_STATUS_INVALID) {
+ DBG_LIMIT("BATT: Battery -> unknown\n");
+ msm_batt_info.batt_status =
+ POWER_SUPPLY_STATUS_UNKNOWN;
+ } else {
+ DBG_LIMIT("BATT: Battery -> discharging\n");
+ msm_batt_info.batt_status =
+ POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ }
+
+ if (!supp) {
+ if (msm_batt_info.current_chg_source) {
+ if (msm_batt_info.current_chg_source & AC_CHG)
+ supp = &msm_psy_ac;
+ else
+ supp = &msm_psy_usb;
+ } else
+ supp = &msm_psy_batt;
+ }
+ }
+
+ msm_batt_info.charger_status = charger_status;
+ msm_batt_info.charger_type = charger_type;
+ msm_batt_info.battery_status = battery_status;
+ msm_batt_info.battery_level = battery_level;
+ msm_batt_info.battery_temp = battery_temp;
+
+ if (msm_batt_info.battery_voltage != battery_voltage) {
+ msm_batt_info.battery_voltage = battery_voltage;
+ msm_batt_info.batt_capacity =
+ msm_batt_info.calculate_capacity(battery_voltage);
+ DBG_LIMIT("BATT: voltage = %u mV [capacity = %d%%]\n",
+ battery_voltage, msm_batt_info.batt_capacity);
+
+ if (!supp)
+ supp = msm_batt_info.current_ps;
+ }
+
+ if (supp) {
+ msm_batt_info.current_ps = supp;
+ DBG_LIMIT("BATT: Supply = %s\n", supp->name);
+ power_supply_changed(supp);
+ }
+}
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+struct batt_modify_client_req {
+
+ u32 client_handle;
+
+ /* The voltage at which callback (CB) should be called. */
+ u32 desired_batt_voltage;
+
+ /* The direction when the CB should be called. */
+ u32 voltage_direction;
+
+ /* The registered callback to be called when voltage and
+ * direction specs are met. */
+ u32 batt_cb_id;
+
+ /* The call back data */
+ u32 cb_data;
+};
+
+struct batt_modify_client_rep {
+ u32 result;
+};
+
+static int msm_batt_modify_client_arg_func(struct msm_rpc_client *batt_client,
+ void *buf, void *data)
+{
+ struct batt_modify_client_req *batt_modify_client_req =
+ (struct batt_modify_client_req *)data;
+ u32 *req = (u32 *)buf;
+ int size = 0;
+
+ *req = cpu_to_be32(batt_modify_client_req->client_handle);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_modify_client_req->desired_batt_voltage);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_modify_client_req->voltage_direction);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_modify_client_req->batt_cb_id);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_modify_client_req->cb_data);
+ size += sizeof(u32);
+
+ return size;
+}
+
+static int msm_batt_modify_client_ret_func(struct msm_rpc_client *batt_client,
+ void *buf, void *data)
+{
+ struct batt_modify_client_rep *data_ptr, *buf_ptr;
+
+ data_ptr = (struct batt_modify_client_rep *)data;
+ buf_ptr = (struct batt_modify_client_rep *)buf;
+
+ data_ptr->result = be32_to_cpu(buf_ptr->result);
+
+ return 0;
+}
+
+static int msm_batt_modify_client(u32 client_handle, u32 desired_batt_voltage,
+ u32 voltage_direction, u32 batt_cb_id, u32 cb_data)
+{
+ int rc;
+
+ struct batt_modify_client_req req;
+ struct batt_modify_client_rep rep;
+
+ req.client_handle = client_handle;
+ req.desired_batt_voltage = desired_batt_voltage;
+ req.voltage_direction = voltage_direction;
+ req.batt_cb_id = batt_cb_id;
+ req.cb_data = cb_data;
+
+ rc = msm_rpc_client_req(msm_batt_info.batt_client,
+ BATTERY_MODIFY_CLIENT_PROC,
+ msm_batt_modify_client_arg_func, &req,
+ msm_batt_modify_client_ret_func, &rep,
+ msecs_to_jiffies(BATT_RPC_TIMEOUT));
+
+ if (rc < 0) {
+ pr_err("%s: ERROR. failed to modify Vbatt client\n",
+ __func__);
+ return rc;
+ }
+
+ if (rep.result != BATTERY_MODIFICATION_SUCCESSFUL) {
+ pr_err("%s: ERROR. modify client failed. result = %u\n",
+ __func__, rep.result);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+void msm_batt_early_suspend(struct early_suspend *h)
+{
+ int rc;
+
+ pr_debug("%s: enter\n", __func__);
+
+ if (msm_batt_info.batt_handle != INVALID_BATT_HANDLE) {
+ rc = msm_batt_modify_client(msm_batt_info.batt_handle,
+ BATTERY_LOW, BATTERY_VOLTAGE_BELOW_THIS_LEVEL,
+ BATTERY_CB_ID_LOW_VOL, BATTERY_LOW);
+
+ if (rc < 0) {
+ pr_err("%s: msm_batt_modify_client. rc=%d\n",
+ __func__, rc);
+ return;
+ }
+ } else {
+ pr_err("%s: ERROR. invalid batt_handle\n", __func__);
+ return;
+ }
+
+ pr_debug("%s: exit\n", __func__);
+}
+
+void msm_batt_late_resume(struct early_suspend *h)
+{
+ int rc;
+
+ pr_debug("%s: enter\n", __func__);
+
+ if (msm_batt_info.batt_handle != INVALID_BATT_HANDLE) {
+ rc = msm_batt_modify_client(msm_batt_info.batt_handle,
+ BATTERY_LOW, BATTERY_ALL_ACTIVITY,
+ BATTERY_CB_ID_ALL_ACTIV, BATTERY_ALL_ACTIVITY);
+ if (rc < 0) {
+ pr_err("%s: msm_batt_modify_client FAIL rc=%d\n",
+ __func__, rc);
+ return;
+ }
+ } else {
+ pr_err("%s: ERROR. invalid batt_handle\n", __func__);
+ return;
+ }
+
+ msm_batt_update_psy_status();
+ pr_debug("%s: exit\n", __func__);
+}
+#endif
+
+struct msm_batt_vbatt_filter_req {
+ u32 batt_handle;
+ u32 enable_filter;
+ u32 vbatt_filter;
+};
+
+struct msm_batt_vbatt_filter_rep {
+ u32 result;
+};
+
+static int msm_batt_filter_arg_func(struct msm_rpc_client *batt_client,
+
+ void *buf, void *data)
+{
+ struct msm_batt_vbatt_filter_req *vbatt_filter_req =
+ (struct msm_batt_vbatt_filter_req *)data;
+ u32 *req = (u32 *)buf;
+ int size = 0;
+
+ *req = cpu_to_be32(vbatt_filter_req->batt_handle);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(vbatt_filter_req->enable_filter);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(vbatt_filter_req->vbatt_filter);
+ size += sizeof(u32);
+ return size;
+}
+
+static int msm_batt_filter_ret_func(struct msm_rpc_client *batt_client,
+ void *buf, void *data)
+{
+
+ struct msm_batt_vbatt_filter_rep *data_ptr, *buf_ptr;
+
+ data_ptr = (struct msm_batt_vbatt_filter_rep *)data;
+ buf_ptr = (struct msm_batt_vbatt_filter_rep *)buf;
+
+ data_ptr->result = be32_to_cpu(buf_ptr->result);
+ return 0;
+}
+
+static int msm_batt_enable_filter(u32 vbatt_filter)
+{
+ int rc;
+ struct msm_batt_vbatt_filter_req vbatt_filter_req;
+ struct msm_batt_vbatt_filter_rep vbatt_filter_rep;
+
+ vbatt_filter_req.batt_handle = msm_batt_info.batt_handle;
+ vbatt_filter_req.enable_filter = 1;
+ vbatt_filter_req.vbatt_filter = vbatt_filter;
+
+ rc = msm_rpc_client_req(msm_batt_info.batt_client,
+ BATTERY_ENABLE_DISABLE_FILTER_PROC,
+ msm_batt_filter_arg_func, &vbatt_filter_req,
+ msm_batt_filter_ret_func, &vbatt_filter_rep,
+ msecs_to_jiffies(BATT_RPC_TIMEOUT));
+
+ if (rc < 0) {
+ pr_err("%s: FAIL: enable vbatt filter. rc=%d\n",
+ __func__, rc);
+ return rc;
+ }
+
+ if (vbatt_filter_rep.result != BATTERY_DEREGISTRATION_SUCCESSFUL) {
+ pr_err("%s: FAIL: enable vbatt filter: result=%d\n",
+ __func__, vbatt_filter_rep.result);
+ return -EIO;
+ }
+
+ pr_debug("%s: enable vbatt filter: OK\n", __func__);
+ return rc;
+}
+
+struct batt_client_registration_req {
+ /* The voltage at which callback (CB) should be called. */
+ u32 desired_batt_voltage;
+
+ /* The direction when the CB should be called. */
+ u32 voltage_direction;
+
+ /* The registered callback to be called when voltage and
+ * direction specs are met. */
+ u32 batt_cb_id;
+
+ /* The call back data */
+ u32 cb_data;
+ u32 more_data;
+ u32 batt_error;
+};
+
+struct batt_client_registration_req_4_1 {
+ /* The voltage at which callback (CB) should be called. */
+ u32 desired_batt_voltage;
+
+ /* The direction when the CB should be called. */
+ u32 voltage_direction;
+
+ /* The registered callback to be called when voltage and
+ * direction specs are met. */
+ u32 batt_cb_id;
+
+ /* The call back data */
+ u32 cb_data;
+ u32 batt_error;
+};
+
+struct batt_client_registration_rep {
+ u32 batt_handle;
+};
+
+struct batt_client_registration_rep_4_1 {
+ u32 batt_handle;
+ u32 more_data;
+ u32 err;
+};
+
+static int msm_batt_register_arg_func(struct msm_rpc_client *batt_client,
+ void *buf, void *data)
+{
+ struct batt_client_registration_req *batt_reg_req =
+ (struct batt_client_registration_req *)data;
+
+ u32 *req = (u32 *)buf;
+ int size = 0;
+
+
+ if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1) {
+ *req = cpu_to_be32(batt_reg_req->desired_batt_voltage);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_reg_req->voltage_direction);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_reg_req->batt_cb_id);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_reg_req->cb_data);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_reg_req->batt_error);
+ size += sizeof(u32);
+
+ return size;
+ } else {
+ *req = cpu_to_be32(batt_reg_req->desired_batt_voltage);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_reg_req->voltage_direction);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_reg_req->batt_cb_id);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_reg_req->cb_data);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_reg_req->more_data);
+ size += sizeof(u32);
+ req++;
+
+ *req = cpu_to_be32(batt_reg_req->batt_error);
+ size += sizeof(u32);
+
+ return size;
+ }
+
+}
+
+static int msm_batt_register_ret_func(struct msm_rpc_client *batt_client,
+ void *buf, void *data)
+{
+ struct batt_client_registration_rep *data_ptr, *buf_ptr;
+ struct batt_client_registration_rep_4_1 *data_ptr_4_1, *buf_ptr_4_1;
+
+ if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1) {
+ data_ptr_4_1 = (struct batt_client_registration_rep_4_1 *)data;
+ buf_ptr_4_1 = (struct batt_client_registration_rep_4_1 *)buf;
+
+ data_ptr_4_1->batt_handle
+ = be32_to_cpu(buf_ptr_4_1->batt_handle);
+ data_ptr_4_1->more_data
+ = be32_to_cpu(buf_ptr_4_1->more_data);
+ data_ptr_4_1->err = be32_to_cpu(buf_ptr_4_1->err);
+ return 0;
+ } else {
+ data_ptr = (struct batt_client_registration_rep *)data;
+ buf_ptr = (struct batt_client_registration_rep *)buf;
+
+ data_ptr->batt_handle = be32_to_cpu(buf_ptr->batt_handle);
+ return 0;
+ }
+}
+
+static int msm_batt_register(u32 desired_batt_voltage,
+ u32 voltage_direction, u32 batt_cb_id, u32 cb_data)
+{
+ struct batt_client_registration_req batt_reg_req;
+ struct batt_client_registration_req_4_1 batt_reg_req_4_1;
+ struct batt_client_registration_rep batt_reg_rep;
+ struct batt_client_registration_rep_4_1 batt_reg_rep_4_1;
+ void *request;
+ void *reply;
+ int rc;
+
+ if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1) {
+ batt_reg_req_4_1.desired_batt_voltage = desired_batt_voltage;
+ batt_reg_req_4_1.voltage_direction = voltage_direction;
+ batt_reg_req_4_1.batt_cb_id = batt_cb_id;
+ batt_reg_req_4_1.cb_data = cb_data;
+ batt_reg_req_4_1.batt_error = 1;
+ request = &batt_reg_req_4_1;
+ } else {
+ batt_reg_req.desired_batt_voltage = desired_batt_voltage;
+ batt_reg_req.voltage_direction = voltage_direction;
+ batt_reg_req.batt_cb_id = batt_cb_id;
+ batt_reg_req.cb_data = cb_data;
+ batt_reg_req.more_data = 1;
+ batt_reg_req.batt_error = 0;
+ request = &batt_reg_req;
+ }
+
+ if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1)
+ reply = &batt_reg_rep_4_1;
+ else
+ reply = &batt_reg_rep;
+
+ rc = msm_rpc_client_req(msm_batt_info.batt_client,
+ BATTERY_REGISTER_PROC,
+ msm_batt_register_arg_func, request,
+ msm_batt_register_ret_func, reply,
+ msecs_to_jiffies(BATT_RPC_TIMEOUT));
+
+ if (rc < 0) {
+ pr_err("%s: FAIL: vbatt register. rc=%d\n", __func__, rc);
+ return rc;
+ }
+
+ if (msm_batt_info.batt_api_version == BATTERY_RPC_VER_4_1) {
+ if (batt_reg_rep_4_1.more_data != 0
+ && batt_reg_rep_4_1.err
+ != BATTERY_REGISTRATION_SUCCESSFUL) {
+ pr_err("%s: vBatt Registration Failed proc_num=%d\n"
+ , __func__, BATTERY_REGISTER_PROC);
+ return -EIO;
+ }
+ msm_batt_info.batt_handle = batt_reg_rep_4_1.batt_handle;
+ } else
+ msm_batt_info.batt_handle = batt_reg_rep.batt_handle;
+
+ return 0;
+}
+
+struct batt_client_deregister_req {
+ u32 batt_handle;
+};
+
+struct batt_client_deregister_rep {
+ u32 batt_error;
+};
+
+static int msm_batt_deregister_arg_func(struct msm_rpc_client *batt_client,
+ void *buf, void *data)
+{
+ struct batt_client_deregister_req *deregister_req =
+ (struct batt_client_deregister_req *)data;
+ u32 *req = (u32 *)buf;
+ int size = 0;
+
+ *req = cpu_to_be32(deregister_req->batt_handle);
+ size += sizeof(u32);
+
+ return size;
+}
+
+static int msm_batt_deregister_ret_func(struct msm_rpc_client *batt_client,
+ void *buf, void *data)
+{
+ struct batt_client_deregister_rep *data_ptr, *buf_ptr;
+
+ data_ptr = (struct batt_client_deregister_rep *)data;
+ buf_ptr = (struct batt_client_deregister_rep *)buf;
+
+ data_ptr->batt_error = be32_to_cpu(buf_ptr->batt_error);
+
+ return 0;
+}
+
+static int msm_batt_deregister(u32 batt_handle)
+{
+ int rc;
+ struct batt_client_deregister_req req;
+ struct batt_client_deregister_rep rep;
+
+ req.batt_handle = batt_handle;
+
+ rc = msm_rpc_client_req(msm_batt_info.batt_client,
+ BATTERY_DEREGISTER_CLIENT_PROC,
+ msm_batt_deregister_arg_func, &req,
+ msm_batt_deregister_ret_func, &rep,
+ msecs_to_jiffies(BATT_RPC_TIMEOUT));
+
+ if (rc < 0) {
+ pr_err("%s: FAIL: vbatt deregister. rc=%d\n", __func__, rc);
+ return rc;
+ }
+
+ if (rep.batt_error != BATTERY_DEREGISTRATION_SUCCESSFUL) {
+ pr_err("%s: vbatt deregistration FAIL. error=%d, handle=%d\n",
+ __func__, rep.batt_error, batt_handle);
+ return -EIO;
+ }
+
+ return 0;
+}
+#endif /* CONFIG_BATTERY_MSM_FAKE */
+
+static int msm_batt_cleanup(void)
+{
+ int rc = 0;
+
+#ifndef CONFIG_BATTERY_MSM_FAKE
+ if (msm_batt_info.batt_handle != INVALID_BATT_HANDLE) {
+
+ rc = msm_batt_deregister(msm_batt_info.batt_handle);
+ if (rc < 0)
+ pr_err("%s: FAIL: msm_batt_deregister. rc=%d\n",
+ __func__, rc);
+ }
+
+ msm_batt_info.batt_handle = INVALID_BATT_HANDLE;
+
+ if (msm_batt_info.batt_client)
+ msm_rpc_unregister_client(msm_batt_info.batt_client);
+#endif /* CONFIG_BATTERY_MSM_FAKE */
+
+ if (msm_batt_info.msm_psy_ac)
+ power_supply_unregister(msm_batt_info.msm_psy_ac);
+
+ if (msm_batt_info.msm_psy_usb)
+ power_supply_unregister(msm_batt_info.msm_psy_usb);
+ if (msm_batt_info.msm_psy_batt)
+ power_supply_unregister(msm_batt_info.msm_psy_batt);
+
+#ifndef CONFIG_BATTERY_MSM_FAKE
+ if (msm_batt_info.chg_ep) {
+ rc = msm_rpc_close(msm_batt_info.chg_ep);
+ if (rc < 0) {
+ pr_err("%s: FAIL. msm_rpc_close(chg_ep). rc=%d\n",
+ __func__, rc);
+ }
+ }
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ if (msm_batt_info.early_suspend.suspend == msm_batt_early_suspend)
+ unregister_early_suspend(&msm_batt_info.early_suspend);
+#endif
+#endif
+ return rc;
+}
+
+static u32 msm_batt_capacity(u32 current_voltage)
+{
+ u32 low_voltage = msm_batt_info.voltage_min_design;
+ u32 high_voltage = msm_batt_info.voltage_max_design;
+
+ if (current_voltage <= low_voltage)
+ return 0;
+ else if (current_voltage >= high_voltage)
+ return 100;
+ else
+ return (current_voltage - low_voltage) * 100
+ / (high_voltage - low_voltage);
+}
+
+#ifndef CONFIG_BATTERY_MSM_FAKE
+int msm_batt_get_charger_api_version(void)
+{
+ int rc ;
+ struct rpc_reply_hdr *reply;
+
+ struct rpc_req_chg_api_ver {
+ struct rpc_request_hdr hdr;
+ u32 more_data;
+ } req_chg_api_ver;
+
+ struct rpc_rep_chg_api_ver {
+ struct rpc_reply_hdr hdr;
+ u32 num_of_chg_api_versions;
+ u32 *chg_api_versions;
+ };
+
+ u32 num_of_versions;
+
+ struct rpc_rep_chg_api_ver *rep_chg_api_ver;
+
+
+ req_chg_api_ver.more_data = cpu_to_be32(1);
+
+ msm_rpc_setup_req(&req_chg_api_ver.hdr, CHG_RPC_PROG, CHG_RPC_VER_1_1,
+ ONCRPC_CHARGER_API_VERSIONS_PROC);
+
+ rc = msm_rpc_write(msm_batt_info.chg_ep, &req_chg_api_ver,
+ sizeof(req_chg_api_ver));
+ if (rc < 0) {
+ pr_err("%s: FAIL: msm_rpc_write. proc=0x%08x, rc=%d\n",
+ __func__, ONCRPC_CHARGER_API_VERSIONS_PROC, rc);
+ return rc;
+ }
+
+ for (;;) {
+ rc = msm_rpc_read(msm_batt_info.chg_ep, (void *) &reply, -1,
+ BATT_RPC_TIMEOUT);
+ if (rc < 0)
+ return rc;
+ if (rc < RPC_REQ_REPLY_COMMON_HEADER_SIZE) {
+ pr_err("%s: LENGTH ERR: msm_rpc_read. rc=%d (<%d)\n",
+ __func__, rc, RPC_REQ_REPLY_COMMON_HEADER_SIZE);
+
+ rc = -EIO;
+ break;
+ }
+ /* we should not get RPC REQ or call packets -- ignore them */
+ if (reply->type == RPC_TYPE_REQ) {
+ pr_err("%s: TYPE ERR: type=%d (!=%d)\n",
+ __func__, reply->type, RPC_TYPE_REQ);
+ kfree(reply);
+ continue;
+ }
+
+ /* If an earlier call timed out, we could get the (no
+ * longer wanted) reply for it. Ignore replies that
+ * we don't expect
+ */
+ if (reply->xid != req_chg_api_ver.hdr.xid) {
+ pr_err("%s: XID ERR: xid=%d (!=%d)\n", __func__,
+ reply->xid, req_chg_api_ver.hdr.xid);
+ kfree(reply);
+ continue;
+ }
+ if (reply->reply_stat != RPCMSG_REPLYSTAT_ACCEPTED) {
+ rc = -EPERM;
+ break;
+ }
+ if (reply->data.acc_hdr.accept_stat !=
+ RPC_ACCEPTSTAT_SUCCESS) {
+ rc = -EINVAL;
+ break;
+ }
+
+ rep_chg_api_ver = (struct rpc_rep_chg_api_ver *)reply;
+
+ num_of_versions =
+ be32_to_cpu(rep_chg_api_ver->num_of_chg_api_versions);
+
+ rep_chg_api_ver->chg_api_versions = (u32 *)
+ ((u8 *) reply + sizeof(struct rpc_reply_hdr) +
+ sizeof(rep_chg_api_ver->num_of_chg_api_versions));
+
+ rc = be32_to_cpu(
+ rep_chg_api_ver->chg_api_versions[num_of_versions - 1]);
+
+ pr_debug("%s: num_of_chg_api_versions = %u. "
+ "The chg api version = 0x%08x\n", __func__,
+ num_of_versions, rc);
+ break;
+ }
+ kfree(reply);
+ return rc;
+}
+
+static int msm_batt_cb_func(struct msm_rpc_client *client,
+ void *buffer, int in_size)
+{
+ int rc = 0;
+ struct rpc_request_hdr *req;
+ u32 procedure;
+ u32 accept_status;
+
+ req = (struct rpc_request_hdr *)buffer;
+ procedure = be32_to_cpu(req->procedure);
+
+ switch (procedure) {
+ case BATTERY_CB_TYPE_PROC:
+ accept_status = RPC_ACCEPTSTAT_SUCCESS;
+ break;
+
+ default:
+ accept_status = RPC_ACCEPTSTAT_PROC_UNAVAIL;
+ pr_err("%s: ERROR. procedure (%d) not supported\n",
+ __func__, procedure);
+ break;
+ }
+
+ msm_rpc_start_accepted_reply(msm_batt_info.batt_client,
+ be32_to_cpu(req->xid), accept_status);
+
+ rc = msm_rpc_send_accepted_reply(msm_batt_info.batt_client, 0);
+ if (rc)
+ pr_err("%s: FAIL: sending reply. rc=%d\n", __func__, rc);
+
+ if (accept_status == RPC_ACCEPTSTAT_SUCCESS)
+ msm_batt_update_psy_status();
+
+ return rc;
+}
+#endif /* CONFIG_BATTERY_MSM_FAKE */
+
+static int __devinit msm_batt_probe(struct platform_device *pdev)
+{
+ int rc;
+ struct msm_psy_batt_pdata *pdata = pdev->dev.platform_data;
+
+ if (pdev->id != -1) {
+ dev_err(&pdev->dev,
+ "%s: MSM chipsets Can only support one"
+ " battery ", __func__);
+ return -EINVAL;
+ }
+
+#ifndef CONFIG_BATTERY_MSM_FAKE
+ if (pdata->avail_chg_sources & AC_CHG) {
+#else
+ {
+#endif
+ rc = power_supply_register(&pdev->dev, &msm_psy_ac);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "%s: power_supply_register failed"
+ " rc = %d\n", __func__, rc);
+ msm_batt_cleanup();
+ return rc;
+ }
+ msm_batt_info.msm_psy_ac = &msm_psy_ac;
+ msm_batt_info.avail_chg_sources |= AC_CHG;
+ }
+
+ if (pdata->avail_chg_sources & USB_CHG) {
+ rc = power_supply_register(&pdev->dev, &msm_psy_usb);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "%s: power_supply_register failed"
+ " rc = %d\n", __func__, rc);
+ msm_batt_cleanup();
+ return rc;
+ }
+ msm_batt_info.msm_psy_usb = &msm_psy_usb;
+ msm_batt_info.avail_chg_sources |= USB_CHG;
+ }
+
+ if (!msm_batt_info.msm_psy_ac && !msm_batt_info.msm_psy_usb) {
+
+ dev_err(&pdev->dev,
+ "%s: No external Power supply(AC or USB)"
+ "is avilable\n", __func__);
+ msm_batt_cleanup();
+ return -ENODEV;
+ }
+
+ msm_batt_info.voltage_max_design = pdata->voltage_max_design;
+ msm_batt_info.voltage_min_design = pdata->voltage_min_design;
+ msm_batt_info.batt_technology = pdata->batt_technology;
+ msm_batt_info.calculate_capacity = pdata->calculate_capacity;
+
+ if (!msm_batt_info.voltage_min_design)
+ msm_batt_info.voltage_min_design = BATTERY_LOW;
+ if (!msm_batt_info.voltage_max_design)
+ msm_batt_info.voltage_max_design = BATTERY_HIGH;
+
+ if (msm_batt_info.batt_technology == POWER_SUPPLY_TECHNOLOGY_UNKNOWN)
+ msm_batt_info.batt_technology = POWER_SUPPLY_TECHNOLOGY_LION;
+
+ if (!msm_batt_info.calculate_capacity)
+ msm_batt_info.calculate_capacity = msm_batt_capacity;
+
+ rc = power_supply_register(&pdev->dev, &msm_psy_batt);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "%s: power_supply_register failed"
+ " rc=%d\n", __func__, rc);
+ msm_batt_cleanup();
+ return rc;
+ }
+ msm_batt_info.msm_psy_batt = &msm_psy_batt;
+
+#ifndef CONFIG_BATTERY_MSM_FAKE
+ rc = msm_batt_register(BATTERY_LOW, BATTERY_ALL_ACTIVITY,
+ BATTERY_CB_ID_ALL_ACTIV, BATTERY_ALL_ACTIVITY);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "%s: msm_batt_register failed rc = %d\n", __func__, rc);
+ msm_batt_cleanup();
+ return rc;
+ }
+
+ rc = msm_batt_enable_filter(VBATT_FILTER);
+
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "%s: msm_batt_enable_filter failed rc = %d\n",
+ __func__, rc);
+ msm_batt_cleanup();
+ return rc;
+ }
+
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ msm_batt_info.early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN;
+ msm_batt_info.early_suspend.suspend = msm_batt_early_suspend;
+ msm_batt_info.early_suspend.resume = msm_batt_late_resume;
+ register_early_suspend(&msm_batt_info.early_suspend);
+#endif
+ msm_batt_update_psy_status();
+
+#else
+ power_supply_changed(&msm_psy_ac);
+#endif /* CONFIG_BATTERY_MSM_FAKE */
+
+ return 0;
+}
+
+static int __devexit msm_batt_remove(struct platform_device *pdev)
+{
+ int rc;
+ rc = msm_batt_cleanup();
+
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "%s: msm_batt_cleanup failed rc=%d\n", __func__, rc);
+ return rc;
+ }
+ return 0;
+}
+
+static struct platform_driver msm_batt_driver = {
+ .probe = msm_batt_probe,
+ .remove = __devexit_p(msm_batt_remove),
+ .driver = {
+ .name = "msm-battery",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __devinit msm_batt_init_rpc(void)
+{
+ int rc;
+
+#ifdef CONFIG_BATTERY_MSM_FAKE
+ pr_info("Faking MSM battery\n");
+#else
+
+ msm_batt_info.chg_ep =
+ msm_rpc_connect_compatible(CHG_RPC_PROG, CHG_RPC_VER_4_1, 0);
+ msm_batt_info.chg_api_version = CHG_RPC_VER_4_1;
+ if (msm_batt_info.chg_ep == NULL) {
+ pr_err("%s: rpc connect CHG_RPC_PROG = NULL\n", __func__);
+ return -ENODEV;
+ }
+
+ if (IS_ERR(msm_batt_info.chg_ep)) {
+ msm_batt_info.chg_ep = msm_rpc_connect_compatible(
+ CHG_RPC_PROG, CHG_RPC_VER_3_1, 0);
+ msm_batt_info.chg_api_version = CHG_RPC_VER_3_1;
+ }
+ if (IS_ERR(msm_batt_info.chg_ep)) {
+ msm_batt_info.chg_ep = msm_rpc_connect_compatible(
+ CHG_RPC_PROG, CHG_RPC_VER_1_1, 0);
+ msm_batt_info.chg_api_version = CHG_RPC_VER_1_1;
+ }
+ if (IS_ERR(msm_batt_info.chg_ep)) {
+ msm_batt_info.chg_ep = msm_rpc_connect_compatible(
+ CHG_RPC_PROG, CHG_RPC_VER_1_3, 0);
+ msm_batt_info.chg_api_version = CHG_RPC_VER_1_3;
+ }
+ if (IS_ERR(msm_batt_info.chg_ep)) {
+ msm_batt_info.chg_ep = msm_rpc_connect_compatible(
+ CHG_RPC_PROG, CHG_RPC_VER_2_2, 0);
+ msm_batt_info.chg_api_version = CHG_RPC_VER_2_2;
+ }
+ if (IS_ERR(msm_batt_info.chg_ep)) {
+ rc = PTR_ERR(msm_batt_info.chg_ep);
+ pr_err("%s: FAIL: rpc connect for CHG_RPC_PROG. rc=%d\n",
+ __func__, rc);
+ msm_batt_info.chg_ep = NULL;
+ return rc;
+ }
+
+ /* Get the real 1.x version */
+ if (msm_batt_info.chg_api_version == CHG_RPC_VER_1_1)
+ msm_batt_info.chg_api_version =
+ msm_batt_get_charger_api_version();
+
+ /* Fall back to 1.1 for default */
+ if (msm_batt_info.chg_api_version < 0)
+ msm_batt_info.chg_api_version = CHG_RPC_VER_1_1;
+ msm_batt_info.batt_api_version = BATTERY_RPC_VER_4_1;
+
+ msm_batt_info.batt_client =
+ msm_rpc_register_client("battery", BATTERY_RPC_PROG,
+ BATTERY_RPC_VER_4_1,
+ 1, msm_batt_cb_func);
+
+ if (msm_batt_info.batt_client == NULL) {
+ pr_err("%s: FAIL: rpc_register_client. batt_client=NULL\n",
+ __func__);
+ return -ENODEV;
+ }
+ if (IS_ERR(msm_batt_info.batt_client)) {
+ msm_batt_info.batt_client =
+ msm_rpc_register_client("battery", BATTERY_RPC_PROG,
+ BATTERY_RPC_VER_1_1,
+ 1, msm_batt_cb_func);
+ msm_batt_info.batt_api_version = BATTERY_RPC_VER_1_1;
+ }
+ if (IS_ERR(msm_batt_info.batt_client)) {
+ msm_batt_info.batt_client =
+ msm_rpc_register_client("battery", BATTERY_RPC_PROG,
+ BATTERY_RPC_VER_2_1,
+ 1, msm_batt_cb_func);
+ msm_batt_info.batt_api_version = BATTERY_RPC_VER_2_1;
+ }
+ if (IS_ERR(msm_batt_info.batt_client)) {
+ msm_batt_info.batt_client =
+ msm_rpc_register_client("battery", BATTERY_RPC_PROG,
+ BATTERY_RPC_VER_5_1,
+ 1, msm_batt_cb_func);
+ msm_batt_info.batt_api_version = BATTERY_RPC_VER_5_1;
+ }
+ if (IS_ERR(msm_batt_info.batt_client)) {
+ rc = PTR_ERR(msm_batt_info.batt_client);
+ pr_err("%s: ERROR: rpc_register_client: rc = %d\n ",
+ __func__, rc);
+ msm_batt_info.batt_client = NULL;
+ return rc;
+ }
+#endif /* CONFIG_BATTERY_MSM_FAKE */
+
+ rc = platform_driver_register(&msm_batt_driver);
+
+ if (rc < 0)
+ pr_err("%s: FAIL: platform_driver_register. rc = %d\n",
+ __func__, rc);
+
+ return rc;
+}
+
+static int __init msm_batt_init(void)
+{
+ int rc;
+
+ pr_debug("%s: enter\n", __func__);
+
+ rc = msm_batt_init_rpc();
+
+ if (rc < 0) {
+ pr_err("%s: FAIL: msm_batt_init_rpc. rc=%d\n", __func__, rc);
+ msm_batt_cleanup();
+ return rc;
+ }
+
+ pr_info("%s: Charger/Battery = 0x%08x/0x%08x (RPC version)\n",
+ __func__, msm_batt_info.chg_api_version,
+ msm_batt_info.batt_api_version);
+
+ return 0;
+}
+
+static void __exit msm_batt_exit(void)
+{
+ platform_driver_unregister(&msm_batt_driver);
+}
+
+module_init(msm_batt_init);
+module_exit(msm_batt_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Kiran Kandi, Qualcomm Innovation Center, Inc.");
+MODULE_DESCRIPTION("Battery driver for Qualcomm MSM chipsets.");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:msm_battery");
diff --git a/drivers/power/msm_charger.c b/drivers/power/msm_charger.c
new file mode 100644
index 0000000..8594ec2
--- /dev/null
+++ b/drivers/power/msm_charger.c
@@ -0,0 +1,1286 @@
+/* Copyright (c) 2010-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/interrupt.h>
+#include <linux/power_supply.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/msm-charger.h>
+#include <linux/time.h>
+#include <linux/slab.h>
+#include <linux/wakelock.h>
+
+#include <asm/atomic.h>
+
+#include <mach/msm_hsusb.h>
+
+#define MSM_CHG_MAX_EVENTS 16
+#define CHARGING_TEOC_MS 9000000
+#define UPDATE_TIME_MS 60000
+#define RESUME_CHECK_PERIOD_MS 60000
+
+#define DEFAULT_BATT_MAX_V 4200
+#define DEFAULT_BATT_MIN_V 3200
+
+#define MSM_CHARGER_GAUGE_MISSING_VOLTS 3500
+#define MSM_CHARGER_GAUGE_MISSING_TEMP 35
+/**
+ * enum msm_battery_status
+ * @BATT_STATUS_ABSENT: battery not present
+ * @BATT_STATUS_ID_INVALID: battery present but the id is invalid
+ * @BATT_STATUS_DISCHARGING: battery is present and is discharging
+ * @BATT_STATUS_TRKL_CHARGING: battery is being trickle charged
+ * @BATT_STATUS_FAST_CHARGING: battery is being fast charged
+ * @BATT_STATUS_JUST_FINISHED_CHARGING: just finished charging,
+ * battery is fully charged. Do not begin charging untill the
+ * voltage falls below a threshold to avoid overcharging
+ * @BATT_STATUS_TEMPERATURE_OUT_OF_RANGE: battery present,
+ no charging, temp is hot/cold
+ */
+enum msm_battery_status {
+ BATT_STATUS_ABSENT,
+ BATT_STATUS_ID_INVALID,
+ BATT_STATUS_DISCHARGING,
+ BATT_STATUS_TRKL_CHARGING,
+ BATT_STATUS_FAST_CHARGING,
+ BATT_STATUS_JUST_FINISHED_CHARGING,
+ BATT_STATUS_TEMPERATURE_OUT_OF_RANGE,
+};
+
+struct msm_hardware_charger_priv {
+ struct list_head list;
+ struct msm_hardware_charger *hw_chg;
+ enum msm_hardware_charger_state hw_chg_state;
+ unsigned int max_source_current;
+ struct power_supply psy;
+};
+
+struct msm_charger_event {
+ enum msm_hardware_charger_event event;
+ struct msm_hardware_charger *hw_chg;
+};
+
+struct msm_charger_mux {
+ int inited;
+ struct list_head msm_hardware_chargers;
+ int count_chargers;
+ struct mutex msm_hardware_chargers_lock;
+
+ struct device *dev;
+
+ unsigned int max_voltage;
+ unsigned int min_voltage;
+
+ unsigned int safety_time;
+ struct delayed_work teoc_work;
+
+ unsigned int update_time;
+ int stop_update;
+ struct delayed_work update_heartbeat_work;
+
+ struct mutex status_lock;
+ enum msm_battery_status batt_status;
+ struct msm_hardware_charger_priv *current_chg_priv;
+ struct msm_hardware_charger_priv *current_mon_priv;
+
+ unsigned int (*get_batt_capacity_percent) (void);
+
+ struct msm_charger_event *queue;
+ int tail;
+ int head;
+ spinlock_t queue_lock;
+ int queue_count;
+ struct work_struct queue_work;
+ struct workqueue_struct *event_wq_thread;
+ struct wake_lock wl;
+};
+
+static struct msm_charger_mux msm_chg;
+
+static struct msm_battery_gauge *msm_batt_gauge;
+
+static int is_chg_capable_of_charging(struct msm_hardware_charger_priv *priv)
+{
+ if (priv->hw_chg_state == CHG_READY_STATE
+ || priv->hw_chg_state == CHG_CHARGING_STATE)
+ return 1;
+
+ return 0;
+}
+
+static int is_batt_status_capable_of_charging(void)
+{
+ if (msm_chg.batt_status == BATT_STATUS_ABSENT
+ || msm_chg.batt_status == BATT_STATUS_TEMPERATURE_OUT_OF_RANGE
+ || msm_chg.batt_status == BATT_STATUS_ID_INVALID
+ || msm_chg.batt_status == BATT_STATUS_JUST_FINISHED_CHARGING)
+ return 0;
+ return 1;
+}
+
+static int is_batt_status_charging(void)
+{
+ if (msm_chg.batt_status == BATT_STATUS_TRKL_CHARGING
+ || msm_chg.batt_status == BATT_STATUS_FAST_CHARGING)
+ return 1;
+ return 0;
+}
+
+static int is_battery_present(void)
+{
+ if (msm_batt_gauge && msm_batt_gauge->is_battery_present)
+ return msm_batt_gauge->is_battery_present();
+ else {
+ pr_err("msm-charger: no batt gauge batt=absent\n");
+ return 0;
+ }
+}
+
+static int is_battery_temp_within_range(void)
+{
+ if (msm_batt_gauge && msm_batt_gauge->is_battery_temp_within_range)
+ return msm_batt_gauge->is_battery_temp_within_range();
+ else {
+ pr_err("msm-charger no batt gauge batt=out_of_temperatur\n");
+ return 0;
+ }
+}
+
+static int is_battery_id_valid(void)
+{
+ if (msm_batt_gauge && msm_batt_gauge->is_battery_id_valid)
+ return msm_batt_gauge->is_battery_id_valid();
+ else {
+ pr_err("msm-charger no batt gauge batt=id_invalid\n");
+ return 0;
+ }
+}
+
+static int get_prop_battery_mvolts(void)
+{
+ if (msm_batt_gauge && msm_batt_gauge->get_battery_mvolts)
+ return msm_batt_gauge->get_battery_mvolts();
+ else {
+ pr_err("msm-charger no batt gauge assuming 3.5V\n");
+ return MSM_CHARGER_GAUGE_MISSING_VOLTS;
+ }
+}
+
+static int get_battery_temperature(void)
+{
+ if (msm_batt_gauge && msm_batt_gauge->get_battery_temperature)
+ return msm_batt_gauge->get_battery_temperature();
+ else {
+ pr_err("msm-charger no batt gauge assuming 35 deg G\n");
+ return MSM_CHARGER_GAUGE_MISSING_TEMP;
+ }
+}
+
+static int get_prop_batt_capacity(void)
+{
+ int capacity;
+
+ if (msm_batt_gauge && msm_batt_gauge->get_batt_remaining_capacity)
+ capacity = msm_batt_gauge->get_batt_remaining_capacity();
+ else
+ capacity = msm_chg.get_batt_capacity_percent();
+
+ if (capacity <= 10)
+ pr_err("battery capacity very low = %d\n", capacity);
+
+ return capacity;
+}
+
+static int get_prop_batt_health(void)
+{
+ int status = 0;
+
+ if (msm_chg.batt_status == BATT_STATUS_TEMPERATURE_OUT_OF_RANGE)
+ status = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else
+ status = POWER_SUPPLY_HEALTH_GOOD;
+
+ return status;
+}
+
+static int get_prop_charge_type(void)
+{
+ int status = 0;
+
+ if (msm_chg.batt_status == BATT_STATUS_TRKL_CHARGING)
+ status = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ else if (msm_chg.batt_status == BATT_STATUS_FAST_CHARGING)
+ status = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else
+ status = POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ return status;
+}
+
+static int get_prop_batt_status(void)
+{
+ int status = 0;
+
+ if (msm_batt_gauge && msm_batt_gauge->get_battery_status) {
+ status = msm_batt_gauge->get_battery_status();
+ if (status == POWER_SUPPLY_STATUS_CHARGING ||
+ status == POWER_SUPPLY_STATUS_FULL ||
+ status == POWER_SUPPLY_STATUS_DISCHARGING)
+ return status;
+ }
+
+ if (is_batt_status_charging())
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else if (msm_chg.batt_status ==
+ BATT_STATUS_JUST_FINISHED_CHARGING
+ && msm_chg.current_chg_priv != NULL)
+ status = POWER_SUPPLY_STATUS_FULL;
+ else
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ return status;
+}
+
+ /* This function should only be called within handle_event or resume */
+static void update_batt_status(void)
+{
+ if (is_battery_present()) {
+ if (is_battery_id_valid()) {
+ if (msm_chg.batt_status == BATT_STATUS_ABSENT
+ || msm_chg.batt_status
+ == BATT_STATUS_ID_INVALID) {
+ msm_chg.batt_status = BATT_STATUS_DISCHARGING;
+ }
+ } else
+ msm_chg.batt_status = BATT_STATUS_ID_INVALID;
+ } else
+ msm_chg.batt_status = BATT_STATUS_ABSENT;
+}
+
+static enum power_supply_property msm_power_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *msm_power_supplied_to[] = {
+ "battery",
+};
+
+static int msm_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct msm_hardware_charger_priv *priv;
+
+ priv = container_of(psy, struct msm_hardware_charger_priv, psy);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !(priv->hw_chg_state == CHG_ABSENT_STATE);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = (priv->hw_chg_state == CHG_READY_STATE)
+ || (priv->hw_chg_state == CHG_CHARGING_STATE);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property msm_batt_power_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_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int msm_batt_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = get_prop_batt_status();
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = get_prop_charge_type();
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = get_prop_batt_health();
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !(msm_chg.batt_status == BATT_STATUS_ABSENT);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = msm_chg.max_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = msm_chg.min_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = get_prop_battery_mvolts();
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = get_prop_batt_capacity();
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static struct power_supply msm_psy_batt = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = msm_batt_power_props,
+ .num_properties = ARRAY_SIZE(msm_batt_power_props),
+ .get_property = msm_batt_power_get_property,
+};
+
+static int usb_chg_current;
+static struct msm_hardware_charger_priv *usb_hw_chg_priv;
+static void (*notify_vbus_state_func_ptr)(int);
+static int usb_notified_of_insertion;
+
+/* this is passed to the hsusb via platform_data msm_otg_pdata */
+int msm_charger_register_vbus_sn(void (*callback)(int))
+{
+ pr_debug(KERN_INFO "%s\n", __func__);
+ notify_vbus_state_func_ptr = callback;
+ return 0;
+}
+
+/* this is passed to the hsusb via platform_data msm_otg_pdata */
+void msm_charger_unregister_vbus_sn(void (*callback)(int))
+{
+ pr_debug(KERN_INFO "%s\n", __func__);
+ notify_vbus_state_func_ptr = NULL;
+}
+
+static void notify_usb_of_the_plugin_event(struct msm_hardware_charger_priv
+ *hw_chg, int plugin)
+{
+ plugin = !!plugin;
+ if (plugin == 1 && usb_notified_of_insertion == 0) {
+ usb_notified_of_insertion = 1;
+ if (notify_vbus_state_func_ptr) {
+ dev_dbg(msm_chg.dev, "%s notifying plugin\n", __func__);
+ (*notify_vbus_state_func_ptr) (plugin);
+ } else
+ dev_dbg(msm_chg.dev, "%s unable to notify plugin\n",
+ __func__);
+ usb_hw_chg_priv = hw_chg;
+ }
+ if (plugin == 0 && usb_notified_of_insertion == 1) {
+ if (notify_vbus_state_func_ptr) {
+ dev_dbg(msm_chg.dev, "%s notifying unplugin\n",
+ __func__);
+ (*notify_vbus_state_func_ptr) (plugin);
+ } else
+ dev_dbg(msm_chg.dev, "%s unable to notify unplugin\n",
+ __func__);
+ usb_notified_of_insertion = 0;
+ usb_hw_chg_priv = NULL;
+ }
+}
+
+static unsigned int msm_chg_get_batt_capacity_percent(void)
+{
+ unsigned int current_voltage = get_prop_battery_mvolts();
+ unsigned int low_voltage = msm_chg.min_voltage;
+ unsigned int high_voltage = msm_chg.max_voltage;
+
+ if (current_voltage <= low_voltage)
+ return 0;
+ else if (current_voltage >= high_voltage)
+ return 100;
+ else
+ return (current_voltage - low_voltage) * 100
+ / (high_voltage - low_voltage);
+}
+
+#ifdef DEBUG
+static inline void debug_print(const char *func,
+ struct msm_hardware_charger_priv *hw_chg_priv)
+{
+ dev_dbg(msm_chg.dev,
+ "%s current=(%s)(s=%d)(r=%d) new=(%s)(s=%d)(r=%d) batt=%d En\n",
+ func,
+ msm_chg.current_chg_priv ? msm_chg.current_chg_priv->
+ hw_chg->name : "none",
+ msm_chg.current_chg_priv ? msm_chg.
+ current_chg_priv->hw_chg_state : -1,
+ msm_chg.current_chg_priv ? msm_chg.current_chg_priv->
+ hw_chg->rating : -1,
+ hw_chg_priv ? hw_chg_priv->hw_chg->name : "none",
+ hw_chg_priv ? hw_chg_priv->hw_chg_state : -1,
+ hw_chg_priv ? hw_chg_priv->hw_chg->rating : -1,
+ msm_chg.batt_status);
+}
+#else
+static inline void debug_print(const char *func,
+ struct msm_hardware_charger_priv *hw_chg_priv)
+{
+}
+#endif
+
+static struct msm_hardware_charger_priv *find_best_charger(void)
+{
+ struct msm_hardware_charger_priv *hw_chg_priv;
+ struct msm_hardware_charger_priv *better;
+ int rating;
+
+ better = NULL;
+ rating = 0;
+
+ list_for_each_entry(hw_chg_priv, &msm_chg.msm_hardware_chargers, list) {
+ if (is_chg_capable_of_charging(hw_chg_priv)) {
+ if (hw_chg_priv->hw_chg->rating > rating) {
+ rating = hw_chg_priv->hw_chg->rating;
+ better = hw_chg_priv;
+ }
+ }
+ }
+
+ return better;
+}
+
+static int msm_charging_switched(struct msm_hardware_charger_priv *priv)
+{
+ int ret = 0;
+
+ if (priv->hw_chg->charging_switched)
+ ret = priv->hw_chg->charging_switched(priv->hw_chg);
+ return ret;
+}
+
+static int msm_stop_charging(struct msm_hardware_charger_priv *priv)
+{
+ int ret;
+
+ ret = priv->hw_chg->stop_charging(priv->hw_chg);
+ if (!ret)
+ wake_unlock(&msm_chg.wl);
+ return ret;
+}
+
+static void msm_enable_system_current(struct msm_hardware_charger_priv *priv)
+{
+ if (priv->hw_chg->start_system_current)
+ priv->hw_chg->start_system_current(priv->hw_chg,
+ priv->max_source_current);
+}
+
+static void msm_disable_system_current(struct msm_hardware_charger_priv *priv)
+{
+ if (priv->hw_chg->stop_system_current)
+ priv->hw_chg->stop_system_current(priv->hw_chg);
+}
+
+/* the best charger has been selected -start charging from current_chg_priv */
+static int msm_start_charging(void)
+{
+ int ret;
+ struct msm_hardware_charger_priv *priv;
+
+ priv = msm_chg.current_chg_priv;
+ wake_lock(&msm_chg.wl);
+ ret = priv->hw_chg->start_charging(priv->hw_chg, msm_chg.max_voltage,
+ priv->max_source_current);
+ if (ret) {
+ wake_unlock(&msm_chg.wl);
+ dev_err(msm_chg.dev, "%s couldnt start chg error = %d\n",
+ priv->hw_chg->name, ret);
+ } else
+ priv->hw_chg_state = CHG_CHARGING_STATE;
+
+ return ret;
+}
+
+static void handle_charging_done(struct msm_hardware_charger_priv *priv)
+{
+ if (msm_chg.current_chg_priv == priv) {
+ if (msm_chg.current_chg_priv->hw_chg_state ==
+ CHG_CHARGING_STATE)
+ if (msm_stop_charging(msm_chg.current_chg_priv)) {
+ dev_err(msm_chg.dev, "%s couldnt stop chg\n",
+ msm_chg.current_chg_priv->hw_chg->name);
+ }
+ msm_chg.current_chg_priv->hw_chg_state = CHG_READY_STATE;
+
+ msm_chg.batt_status = BATT_STATUS_JUST_FINISHED_CHARGING;
+ dev_info(msm_chg.dev, "%s: stopping safety timer work\n",
+ __func__);
+ cancel_delayed_work(&msm_chg.teoc_work);
+
+ if (msm_batt_gauge && msm_batt_gauge->monitor_for_recharging)
+ msm_batt_gauge->monitor_for_recharging();
+ else
+ dev_err(msm_chg.dev,
+ "%s: no batt gauge recharge monitor\n", __func__);
+ }
+}
+
+static void teoc(struct work_struct *work)
+{
+ /* we have been charging too long - stop charging */
+ dev_info(msm_chg.dev, "%s: safety timer work expired\n", __func__);
+
+ mutex_lock(&msm_chg.status_lock);
+ if (msm_chg.current_chg_priv != NULL
+ && msm_chg.current_chg_priv->hw_chg_state == CHG_CHARGING_STATE) {
+ handle_charging_done(msm_chg.current_chg_priv);
+ }
+ mutex_unlock(&msm_chg.status_lock);
+}
+
+static void handle_battery_inserted(void)
+{
+ /* if a charger is already present start charging */
+ if (msm_chg.current_chg_priv != NULL &&
+ is_batt_status_capable_of_charging() &&
+ !is_batt_status_charging()) {
+ if (msm_start_charging()) {
+ dev_err(msm_chg.dev, "%s couldnt start chg\n",
+ msm_chg.current_chg_priv->hw_chg->name);
+ return;
+ }
+ msm_chg.batt_status = BATT_STATUS_TRKL_CHARGING;
+
+ dev_info(msm_chg.dev, "%s: starting safety timer work\n",
+ __func__);
+ queue_delayed_work(msm_chg.event_wq_thread,
+ &msm_chg.teoc_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (msm_chg.
+ safety_time)));
+ }
+}
+
+static void handle_battery_removed(void)
+{
+ /* if a charger is charging the battery stop it */
+ if (msm_chg.current_chg_priv != NULL
+ && msm_chg.current_chg_priv->hw_chg_state == CHG_CHARGING_STATE) {
+ if (msm_stop_charging(msm_chg.current_chg_priv)) {
+ dev_err(msm_chg.dev, "%s couldnt stop chg\n",
+ msm_chg.current_chg_priv->hw_chg->name);
+ }
+ msm_chg.current_chg_priv->hw_chg_state = CHG_READY_STATE;
+
+ dev_info(msm_chg.dev, "%s: stopping safety timer work\n",
+ __func__);
+ cancel_delayed_work(&msm_chg.teoc_work);
+ }
+}
+
+static void update_heartbeat(struct work_struct *work)
+{
+ int temperature;
+
+ if (msm_chg.batt_status == BATT_STATUS_ABSENT
+ || msm_chg.batt_status == BATT_STATUS_ID_INVALID) {
+ if (is_battery_present())
+ if (is_battery_id_valid()) {
+ msm_chg.batt_status = BATT_STATUS_DISCHARGING;
+ handle_battery_inserted();
+ }
+ } else {
+ if (!is_battery_present()) {
+ msm_chg.batt_status = BATT_STATUS_ABSENT;
+ handle_battery_removed();
+ }
+ /*
+ * check battery id because a good battery could be removed
+ * and replaced with a invalid battery.
+ */
+ if (!is_battery_id_valid()) {
+ msm_chg.batt_status = BATT_STATUS_ID_INVALID;
+ handle_battery_removed();
+ }
+ }
+ pr_debug("msm-charger %s batt_status= %d\n",
+ __func__, msm_chg.batt_status);
+
+ if (msm_chg.current_chg_priv
+ && msm_chg.current_chg_priv->hw_chg_state
+ == CHG_CHARGING_STATE) {
+ temperature = get_battery_temperature();
+ /* TODO implement JEITA SPEC*/
+ }
+
+ /* notify that the voltage has changed
+ * the read of the capacity will trigger a
+ * voltage read*/
+ power_supply_changed(&msm_psy_batt);
+
+ if (msm_chg.stop_update) {
+ msm_chg.stop_update = 0;
+ return;
+ }
+ queue_delayed_work(msm_chg.event_wq_thread,
+ &msm_chg.update_heartbeat_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (msm_chg.update_time)));
+}
+
+/* set the charger state to READY before calling this */
+static void handle_charger_ready(struct msm_hardware_charger_priv *hw_chg_priv)
+{
+ struct msm_hardware_charger_priv *old_chg_priv = NULL;
+
+ debug_print(__func__, hw_chg_priv);
+
+ if (msm_chg.current_chg_priv != NULL
+ && hw_chg_priv->hw_chg->rating >
+ msm_chg.current_chg_priv->hw_chg->rating) {
+ /*
+ * a better charger was found, ask the current charger
+ * to stop charging if it was charging
+ */
+ if (msm_chg.current_chg_priv->hw_chg_state ==
+ CHG_CHARGING_STATE) {
+ if (msm_stop_charging(msm_chg.current_chg_priv)) {
+ dev_err(msm_chg.dev, "%s couldnt stop chg\n",
+ msm_chg.current_chg_priv->hw_chg->name);
+ return;
+ }
+ if (msm_charging_switched(msm_chg.current_chg_priv)) {
+ dev_err(msm_chg.dev, "%s couldnt stop chg\n",
+ msm_chg.current_chg_priv->hw_chg->name);
+ return;
+ }
+ }
+ msm_chg.current_chg_priv->hw_chg_state = CHG_READY_STATE;
+ old_chg_priv = msm_chg.current_chg_priv;
+ msm_chg.current_chg_priv = NULL;
+ }
+
+ if (msm_chg.current_chg_priv == NULL) {
+ msm_chg.current_chg_priv = hw_chg_priv;
+ dev_info(msm_chg.dev,
+ "%s: best charger = %s\n", __func__,
+ msm_chg.current_chg_priv->hw_chg->name);
+
+ msm_enable_system_current(msm_chg.current_chg_priv);
+ /*
+ * since a better charger was chosen, ask the old
+ * charger to stop providing system current
+ */
+ if (old_chg_priv != NULL)
+ msm_disable_system_current(old_chg_priv);
+
+ if (!is_batt_status_capable_of_charging())
+ return;
+
+ /* start charging from the new charger */
+ if (!msm_start_charging()) {
+ /* if we simply switched chg continue with teoc timer
+ * else we update the batt state and set the teoc
+ * timer */
+ if (!is_batt_status_charging()) {
+ dev_info(msm_chg.dev,
+ "%s: starting safety timer\n", __func__);
+ queue_delayed_work(msm_chg.event_wq_thread,
+ &msm_chg.teoc_work,
+ round_jiffies_relative
+ (msecs_to_jiffies
+ (msm_chg.safety_time)));
+ msm_chg.batt_status = BATT_STATUS_TRKL_CHARGING;
+ }
+ } else {
+ /* we couldnt start charging from the new readied
+ * charger */
+ if (is_batt_status_charging())
+ msm_chg.batt_status = BATT_STATUS_DISCHARGING;
+ }
+ }
+}
+
+static void handle_charger_removed(struct msm_hardware_charger_priv
+ *hw_chg_removed, int new_state)
+{
+ struct msm_hardware_charger_priv *hw_chg_priv;
+
+ debug_print(__func__, hw_chg_removed);
+
+ if (msm_chg.current_chg_priv == hw_chg_removed) {
+ msm_disable_system_current(hw_chg_removed);
+ if (msm_chg.current_chg_priv->hw_chg_state
+ == CHG_CHARGING_STATE) {
+ if (msm_stop_charging(hw_chg_removed)) {
+ dev_err(msm_chg.dev, "%s couldnt stop chg\n",
+ msm_chg.current_chg_priv->hw_chg->name);
+ }
+ }
+ msm_chg.current_chg_priv = NULL;
+ }
+
+ hw_chg_removed->hw_chg_state = new_state;
+
+ if (msm_chg.current_chg_priv == NULL) {
+ hw_chg_priv = find_best_charger();
+ if (hw_chg_priv == NULL) {
+ dev_info(msm_chg.dev, "%s: no chargers\n", __func__);
+ /* if the battery was Just finished charging
+ * we keep that state as is so that we dont rush
+ * in to charging the battery when a charger is
+ * plugged in shortly. */
+ if (is_batt_status_charging())
+ msm_chg.batt_status = BATT_STATUS_DISCHARGING;
+ } else {
+ msm_chg.current_chg_priv = hw_chg_priv;
+ msm_enable_system_current(hw_chg_priv);
+ dev_info(msm_chg.dev,
+ "%s: best charger = %s\n", __func__,
+ msm_chg.current_chg_priv->hw_chg->name);
+
+ if (!is_batt_status_capable_of_charging())
+ return;
+
+ if (msm_start_charging()) {
+ /* we couldnt start charging for some reason */
+ msm_chg.batt_status = BATT_STATUS_DISCHARGING;
+ }
+ }
+ }
+
+ /* if we arent charging stop the safety timer */
+ if (!is_batt_status_charging()) {
+ dev_info(msm_chg.dev, "%s: stopping safety timer work\n",
+ __func__);
+ cancel_delayed_work(&msm_chg.teoc_work);
+ }
+}
+
+static void handle_event(struct msm_hardware_charger *hw_chg, int event)
+{
+ struct msm_hardware_charger_priv *priv = NULL;
+
+ /*
+ * if hw_chg is NULL then this event comes from non-charger
+ * parties like battery gauge
+ */
+ if (hw_chg)
+ priv = hw_chg->charger_private;
+
+ mutex_lock(&msm_chg.status_lock);
+
+ switch (event) {
+ case CHG_INSERTED_EVENT:
+ if (priv->hw_chg_state != CHG_ABSENT_STATE) {
+ dev_info(msm_chg.dev,
+ "%s insertion detected when cbl present",
+ hw_chg->name);
+ break;
+ }
+ update_batt_status();
+ if (hw_chg->type == CHG_TYPE_USB) {
+ priv->hw_chg_state = CHG_PRESENT_STATE;
+ notify_usb_of_the_plugin_event(priv, 1);
+ if (usb_chg_current) {
+ priv->max_source_current = usb_chg_current;
+ usb_chg_current = 0;
+ /* usb has already indicated us to charge */
+ priv->hw_chg_state = CHG_READY_STATE;
+ handle_charger_ready(priv);
+ }
+ } else {
+ priv->hw_chg_state = CHG_READY_STATE;
+ handle_charger_ready(priv);
+ }
+ break;
+ case CHG_ENUMERATED_EVENT: /* only in USB types */
+ if (priv->hw_chg_state == CHG_ABSENT_STATE) {
+ dev_info(msm_chg.dev, "%s enum withuot presence\n",
+ hw_chg->name);
+ break;
+ }
+ update_batt_status();
+ dev_dbg(msm_chg.dev, "%s enum with %dmA to draw\n",
+ hw_chg->name, priv->max_source_current);
+ if (priv->max_source_current == 0) {
+ /* usb subsystem doesnt want us to draw
+ * charging current */
+ /* act as if the charge is removed */
+ if (priv->hw_chg_state != CHG_PRESENT_STATE)
+ handle_charger_removed(priv, CHG_PRESENT_STATE);
+ } else {
+ if (priv->hw_chg_state != CHG_READY_STATE) {
+ priv->hw_chg_state = CHG_READY_STATE;
+ handle_charger_ready(priv);
+ }
+ }
+ break;
+ case CHG_REMOVED_EVENT:
+ if (priv->hw_chg_state == CHG_ABSENT_STATE) {
+ dev_info(msm_chg.dev, "%s cable already removed\n",
+ hw_chg->name);
+ break;
+ }
+ update_batt_status();
+ if (hw_chg->type == CHG_TYPE_USB) {
+ usb_chg_current = 0;
+ notify_usb_of_the_plugin_event(priv, 0);
+ }
+ handle_charger_removed(priv, CHG_ABSENT_STATE);
+ break;
+ case CHG_DONE_EVENT:
+ if (priv->hw_chg_state == CHG_CHARGING_STATE)
+ handle_charging_done(priv);
+ break;
+ case CHG_BATT_BEGIN_FAST_CHARGING:
+ /* only update if we are TRKL charging */
+ if (msm_chg.batt_status == BATT_STATUS_TRKL_CHARGING)
+ msm_chg.batt_status = BATT_STATUS_FAST_CHARGING;
+ break;
+ case CHG_BATT_NEEDS_RECHARGING:
+ msm_chg.batt_status = BATT_STATUS_DISCHARGING;
+ handle_battery_inserted();
+ priv = msm_chg.current_chg_priv;
+ break;
+ case CHG_BATT_TEMP_OUTOFRANGE:
+ /* the batt_temp out of range can trigger
+ * when the battery is absent */
+ if (!is_battery_present()
+ && msm_chg.batt_status != BATT_STATUS_ABSENT) {
+ msm_chg.batt_status = BATT_STATUS_ABSENT;
+ handle_battery_removed();
+ break;
+ }
+ if (msm_chg.batt_status == BATT_STATUS_TEMPERATURE_OUT_OF_RANGE)
+ break;
+ msm_chg.batt_status = BATT_STATUS_TEMPERATURE_OUT_OF_RANGE;
+ handle_battery_removed();
+ break;
+ case CHG_BATT_TEMP_INRANGE:
+ if (msm_chg.batt_status != BATT_STATUS_TEMPERATURE_OUT_OF_RANGE)
+ break;
+ msm_chg.batt_status = BATT_STATUS_ID_INVALID;
+ /* check id */
+ if (!is_battery_id_valid())
+ break;
+ /* assume that we are discharging from the battery
+ * and act as if the battery was inserted
+ * if a charger is present charging will be resumed */
+ msm_chg.batt_status = BATT_STATUS_DISCHARGING;
+ handle_battery_inserted();
+ break;
+ case CHG_BATT_INSERTED:
+ if (msm_chg.batt_status != BATT_STATUS_ABSENT)
+ break;
+ /* debounce */
+ if (!is_battery_present())
+ break;
+ msm_chg.batt_status = BATT_STATUS_ID_INVALID;
+ if (!is_battery_id_valid())
+ break;
+ /* assume that we are discharging from the battery */
+ msm_chg.batt_status = BATT_STATUS_DISCHARGING;
+ /* check if a charger is present */
+ handle_battery_inserted();
+ break;
+ case CHG_BATT_REMOVED:
+ if (msm_chg.batt_status == BATT_STATUS_ABSENT)
+ break;
+ /* debounce */
+ if (is_battery_present())
+ break;
+ msm_chg.batt_status = BATT_STATUS_ABSENT;
+ handle_battery_removed();
+ break;
+ case CHG_BATT_STATUS_CHANGE:
+ /* TODO battery SOC like battery-alarm/charging-full features
+ can be added here for future improvement */
+ break;
+ }
+ dev_dbg(msm_chg.dev, "%s %d done batt_status=%d\n", __func__,
+ event, msm_chg.batt_status);
+
+ /* update userspace */
+ if (msm_batt_gauge)
+ power_supply_changed(&msm_psy_batt);
+ if (priv)
+ power_supply_changed(&priv->psy);
+
+ mutex_unlock(&msm_chg.status_lock);
+}
+
+static int msm_chg_dequeue_event(struct msm_charger_event **event)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&msm_chg.queue_lock, flags);
+ if (msm_chg.queue_count == 0) {
+ spin_unlock_irqrestore(&msm_chg.queue_lock, flags);
+ return -EINVAL;
+ }
+ *event = &msm_chg.queue[msm_chg.head];
+ msm_chg.head = (msm_chg.head + 1) % MSM_CHG_MAX_EVENTS;
+ pr_debug("%s dequeueing %d\n", __func__, (*event)->event);
+ msm_chg.queue_count--;
+ spin_unlock_irqrestore(&msm_chg.queue_lock, flags);
+ return 0;
+}
+
+static int msm_chg_enqueue_event(struct msm_hardware_charger *hw_chg,
+ enum msm_hardware_charger_event event)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&msm_chg.queue_lock, flags);
+ if (msm_chg.queue_count == MSM_CHG_MAX_EVENTS) {
+ spin_unlock_irqrestore(&msm_chg.queue_lock, flags);
+ pr_err("%s: queue full cannot enqueue %d\n",
+ __func__, event);
+ return -EAGAIN;
+ }
+ pr_debug("%s queueing %d\n", __func__, event);
+ msm_chg.queue[msm_chg.tail].event = event;
+ msm_chg.queue[msm_chg.tail].hw_chg = hw_chg;
+ msm_chg.tail = (msm_chg.tail + 1)%MSM_CHG_MAX_EVENTS;
+ msm_chg.queue_count++;
+ spin_unlock_irqrestore(&msm_chg.queue_lock, flags);
+ return 0;
+}
+
+static void process_events(struct work_struct *work)
+{
+ struct msm_charger_event *event;
+ int rc;
+
+ do {
+ rc = msm_chg_dequeue_event(&event);
+ if (!rc)
+ handle_event(event->hw_chg, event->event);
+ } while (!rc);
+}
+
+/* USB calls these to tell us how much charging current we should draw */
+void msm_charger_vbus_draw(unsigned int mA)
+{
+ if (usb_hw_chg_priv) {
+ usb_hw_chg_priv->max_source_current = mA;
+ msm_charger_notify_event(usb_hw_chg_priv->hw_chg,
+ CHG_ENUMERATED_EVENT);
+ } else
+ /* remember the current, to be used when charger is ready */
+ usb_chg_current = mA;
+}
+
+static int determine_initial_batt_status(void)
+{
+ if (is_battery_present())
+ if (is_battery_id_valid())
+ if (is_battery_temp_within_range())
+ msm_chg.batt_status = BATT_STATUS_DISCHARGING;
+ else
+ msm_chg.batt_status
+ = BATT_STATUS_TEMPERATURE_OUT_OF_RANGE;
+ else
+ msm_chg.batt_status = BATT_STATUS_ID_INVALID;
+ else
+ msm_chg.batt_status = BATT_STATUS_ABSENT;
+
+ if (is_batt_status_capable_of_charging())
+ handle_battery_inserted();
+
+ /* start updaing the battery powersupply every msm_chg.update_time
+ * milliseconds */
+ queue_delayed_work(msm_chg.event_wq_thread,
+ &msm_chg.update_heartbeat_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (msm_chg.update_time)));
+
+ pr_debug("%s:OK batt_status=%d\n", __func__, msm_chg.batt_status);
+ return 0;
+}
+
+static int __devinit msm_charger_probe(struct platform_device *pdev)
+{
+ msm_chg.dev = &pdev->dev;
+ if (pdev->dev.platform_data) {
+ unsigned int milli_secs;
+
+ struct msm_charger_platform_data *pdata
+ =
+ (struct msm_charger_platform_data *)pdev->dev.platform_data;
+
+ milli_secs = pdata->safety_time * 60 * MSEC_PER_SEC;
+ if (milli_secs > jiffies_to_msecs(MAX_JIFFY_OFFSET)) {
+ dev_warn(&pdev->dev, "%s: safety time too large"
+ "%dms\n", __func__, milli_secs);
+ milli_secs = jiffies_to_msecs(MAX_JIFFY_OFFSET);
+ }
+ msm_chg.safety_time = milli_secs;
+
+ milli_secs = pdata->update_time * 60 * MSEC_PER_SEC;
+ if (milli_secs > jiffies_to_msecs(MAX_JIFFY_OFFSET)) {
+ dev_warn(&pdev->dev, "%s: safety time too large"
+ "%dms\n", __func__, milli_secs);
+ milli_secs = jiffies_to_msecs(MAX_JIFFY_OFFSET);
+ }
+ msm_chg.update_time = milli_secs;
+
+ msm_chg.max_voltage = pdata->max_voltage;
+ msm_chg.min_voltage = pdata->min_voltage;
+ msm_chg.get_batt_capacity_percent =
+ pdata->get_batt_capacity_percent;
+ }
+ if (msm_chg.safety_time == 0)
+ msm_chg.safety_time = CHARGING_TEOC_MS;
+ if (msm_chg.update_time == 0)
+ msm_chg.update_time = UPDATE_TIME_MS;
+ if (msm_chg.max_voltage == 0)
+ msm_chg.max_voltage = DEFAULT_BATT_MAX_V;
+ if (msm_chg.min_voltage == 0)
+ msm_chg.min_voltage = DEFAULT_BATT_MIN_V;
+ if (msm_chg.get_batt_capacity_percent == NULL)
+ msm_chg.get_batt_capacity_percent =
+ msm_chg_get_batt_capacity_percent;
+
+ mutex_init(&msm_chg.status_lock);
+ INIT_DELAYED_WORK(&msm_chg.teoc_work, teoc);
+ INIT_DELAYED_WORK(&msm_chg.update_heartbeat_work, update_heartbeat);
+
+ wake_lock_init(&msm_chg.wl, WAKE_LOCK_SUSPEND, "msm_charger");
+ return 0;
+}
+
+static int __devexit msm_charger_remove(struct platform_device *pdev)
+{
+ wake_lock_destroy(&msm_chg.wl);
+ mutex_destroy(&msm_chg.status_lock);
+ power_supply_unregister(&msm_psy_batt);
+ return 0;
+}
+
+int msm_charger_notify_event(struct msm_hardware_charger *hw_chg,
+ enum msm_hardware_charger_event event)
+{
+ msm_chg_enqueue_event(hw_chg, event);
+ queue_work(msm_chg.event_wq_thread, &msm_chg.queue_work);
+ return 0;
+}
+EXPORT_SYMBOL(msm_charger_notify_event);
+
+int msm_charger_register(struct msm_hardware_charger *hw_chg)
+{
+ struct msm_hardware_charger_priv *priv;
+ int rc = 0;
+
+ if (!msm_chg.inited) {
+ pr_err("%s: msm_chg is NULL,Too early to register\n", __func__);
+ return -EAGAIN;
+ }
+
+ if (hw_chg->start_charging == NULL
+ || hw_chg->stop_charging == NULL
+ || hw_chg->name == NULL
+ || hw_chg->rating == 0) {
+ pr_err("%s: invalid hw_chg\n", __func__);
+ return -EINVAL;
+ }
+
+ priv = kzalloc(sizeof *priv, GFP_KERNEL);
+ if (priv == NULL) {
+ dev_err(msm_chg.dev, "%s kzalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+
+ priv->psy.name = hw_chg->name;
+ if (hw_chg->type == CHG_TYPE_USB)
+ priv->psy.type = POWER_SUPPLY_TYPE_USB;
+ else
+ priv->psy.type = POWER_SUPPLY_TYPE_MAINS;
+
+ priv->psy.supplied_to = msm_power_supplied_to;
+ priv->psy.num_supplicants = ARRAY_SIZE(msm_power_supplied_to);
+ priv->psy.properties = msm_power_props;
+ priv->psy.num_properties = ARRAY_SIZE(msm_power_props);
+ priv->psy.get_property = msm_power_get_property;
+
+ rc = power_supply_register(NULL, &priv->psy);
+ if (rc) {
+ dev_err(msm_chg.dev, "%s power_supply_register failed\n",
+ __func__);
+ goto out;
+ }
+
+ priv->hw_chg = hw_chg;
+ priv->hw_chg_state = CHG_ABSENT_STATE;
+ INIT_LIST_HEAD(&priv->list);
+ mutex_lock(&msm_chg.msm_hardware_chargers_lock);
+ list_add_tail(&priv->list, &msm_chg.msm_hardware_chargers);
+ mutex_unlock(&msm_chg.msm_hardware_chargers_lock);
+ hw_chg->charger_private = (void *)priv;
+ return 0;
+
+out:
+ kfree(priv);
+ return rc;
+}
+EXPORT_SYMBOL(msm_charger_register);
+
+void msm_battery_gauge_register(struct msm_battery_gauge *batt_gauge)
+{
+ int rc;
+
+ if (msm_batt_gauge) {
+ msm_batt_gauge = batt_gauge;
+ pr_err("msm-charger %s multiple battery gauge called\n",
+ __func__);
+ } else {
+ rc = power_supply_register(msm_chg.dev, &msm_psy_batt);
+ if (rc < 0) {
+ dev_err(msm_chg.dev, "%s: power_supply_register failed"
+ " rc=%d\n", __func__, rc);
+ return;
+ }
+
+ msm_batt_gauge = batt_gauge;
+ determine_initial_batt_status();
+ }
+}
+EXPORT_SYMBOL(msm_battery_gauge_register);
+
+void msm_battery_gauge_unregister(struct msm_battery_gauge *batt_gauge)
+{
+ msm_batt_gauge = NULL;
+}
+EXPORT_SYMBOL(msm_battery_gauge_unregister);
+
+int msm_charger_unregister(struct msm_hardware_charger *hw_chg)
+{
+ struct msm_hardware_charger_priv *priv;
+
+ priv = (struct msm_hardware_charger_priv *)(hw_chg->charger_private);
+ mutex_lock(&msm_chg.msm_hardware_chargers_lock);
+ list_del(&priv->list);
+ mutex_unlock(&msm_chg.msm_hardware_chargers_lock);
+ power_supply_unregister(&priv->psy);
+ kfree(priv);
+ return 0;
+}
+EXPORT_SYMBOL(msm_charger_unregister);
+
+static int msm_charger_suspend(struct device *dev)
+{
+ dev_dbg(msm_chg.dev, "%s suspended\n", __func__);
+ msm_chg.stop_update = 1;
+ cancel_delayed_work(&msm_chg.update_heartbeat_work);
+ mutex_lock(&msm_chg.status_lock);
+ handle_battery_removed();
+ mutex_unlock(&msm_chg.status_lock);
+ return 0;
+}
+
+static int msm_charger_resume(struct device *dev)
+{
+ dev_dbg(msm_chg.dev, "%s resumed\n", __func__);
+ msm_chg.stop_update = 0;
+ /* start updaing the battery powersupply every msm_chg.update_time
+ * milliseconds */
+ queue_delayed_work(msm_chg.event_wq_thread,
+ &msm_chg.update_heartbeat_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (msm_chg.update_time)));
+ mutex_lock(&msm_chg.status_lock);
+ handle_battery_inserted();
+ mutex_unlock(&msm_chg.status_lock);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(msm_charger_pm_ops,
+ msm_charger_suspend, msm_charger_resume);
+
+static struct platform_driver msm_charger_driver = {
+ .probe = msm_charger_probe,
+ .remove = __devexit_p(msm_charger_remove),
+ .driver = {
+ .name = "msm-charger",
+ .owner = THIS_MODULE,
+ .pm = &msm_charger_pm_ops,
+ },
+};
+
+static int __init msm_charger_init(void)
+{
+ int rc;
+
+ INIT_LIST_HEAD(&msm_chg.msm_hardware_chargers);
+ msm_chg.count_chargers = 0;
+ mutex_init(&msm_chg.msm_hardware_chargers_lock);
+
+ msm_chg.queue = kzalloc(sizeof(struct msm_charger_event)
+ * MSM_CHG_MAX_EVENTS,
+ GFP_KERNEL);
+ if (!msm_chg.queue) {
+ rc = -ENOMEM;
+ goto out;
+ }
+ msm_chg.tail = 0;
+ msm_chg.head = 0;
+ spin_lock_init(&msm_chg.queue_lock);
+ msm_chg.queue_count = 0;
+ INIT_WORK(&msm_chg.queue_work, process_events);
+ msm_chg.event_wq_thread = create_workqueue("msm_charger_eventd");
+ if (!msm_chg.event_wq_thread) {
+ rc = -ENOMEM;
+ goto free_queue;
+ }
+ rc = platform_driver_register(&msm_charger_driver);
+ if (rc < 0) {
+ pr_err("%s: FAIL: platform_driver_register. rc = %d\n",
+ __func__, rc);
+ goto destroy_wq_thread;
+ }
+ msm_chg.inited = 1;
+ return 0;
+
+destroy_wq_thread:
+ destroy_workqueue(msm_chg.event_wq_thread);
+free_queue:
+ kfree(msm_chg.queue);
+out:
+ return rc;
+}
+
+static void __exit msm_charger_exit(void)
+{
+ flush_workqueue(msm_chg.event_wq_thread);
+ destroy_workqueue(msm_chg.event_wq_thread);
+ kfree(msm_chg.queue);
+ platform_driver_unregister(&msm_charger_driver);
+}
+
+module_init(msm_charger_init);
+module_exit(msm_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Abhijeet Dharmapurikar <adharmap@codeaurora.org>");
+MODULE_DESCRIPTION("Battery driver for Qualcomm MSM chipsets.");
+MODULE_VERSION("1.0");
diff --git a/drivers/power/pm8058_usb_fix.c b/drivers/power/pm8058_usb_fix.c
new file mode 100644
index 0000000..80b1f87
--- /dev/null
+++ b/drivers/power/pm8058_usb_fix.c
@@ -0,0 +1,357 @@
+/* Copyright (c) 2010, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/module.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/workqueue.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+
+#include <mach/msm_xo.h>
+#include <mach/msm_hsusb.h>
+
+/* Config Regs and their bits*/
+#define PM8058_CHG_TEST 0x75
+#define IGNORE_LL 2
+
+#define PM8058_CHG_TEST_2 0xEA
+#define PM8058_CHG_TEST_3 0xEB
+#define PM8058_OVP_TEST_REG 0xF6
+#define FORCE_OVP_OFF 3
+
+#define PM8058_CHG_CNTRL 0x1E
+#define CHG_TRICKLE_EN 7
+#define CHG_USB_SUSPEND 6
+#define CHG_IMON_CAL 5
+#define CHG_IMON_GAIN 4
+#define CHG_VBUS_FROM_BOOST_OVRD 2
+#define CHG_CHARGE_DIS 1
+#define CHG_VCP_EN 0
+
+#define PM8058_CHG_CNTRL_2 0xD8
+#define ATC_DIS 7 /* coincell backed */
+#define CHARGE_AUTO_DIS 6
+#define DUMB_CHG_OVRD 5 /* coincell backed */
+#define ENUM_DONE 4
+#define CHG_TEMP_MODE 3
+#define CHG_BATT_TEMP_DIS 1 /* coincell backed */
+#define CHG_FAILED_CLEAR 0
+
+#define PM8058_CHG_VMAX_SEL 0x21
+#define PM8058_CHG_VBAT_DET 0xD9
+#define PM8058_CHG_IMAX 0x1F
+#define PM8058_CHG_TRICKLE 0xDB
+#define PM8058_CHG_ITERM 0xDC
+#define PM8058_CHG_TTRKL_MAX 0xE1
+#define PM8058_CHG_TCHG_MAX 0xE4
+#define PM8058_CHG_TEMP_THRESH 0xE2
+#define PM8058_CHG_TEMP_REG 0xE3
+#define PM8058_CHG_PULSE 0x22
+
+/* IRQ STATUS and CLEAR */
+#define PM8058_CHG_STATUS_CLEAR_IRQ_1 0x31
+#define PM8058_CHG_STATUS_CLEAR_IRQ_3 0x33
+#define PM8058_CHG_STATUS_CLEAR_IRQ_10 0xB3
+#define PM8058_CHG_STATUS_CLEAR_IRQ_11 0xB4
+
+/* IRQ MASKS */
+#define PM8058_CHG_MASK_IRQ_1 0x38
+
+#define PM8058_CHG_MASK_IRQ_3 0x3A
+#define PM8058_CHG_MASK_IRQ_10 0xBA
+#define PM8058_CHG_MASK_IRQ_11 0xBB
+
+/* IRQ Real time status regs */
+#define PM8058_CHG_STATUS_RT_1 0x3F
+#define STATUS_RTCHGVAL 7
+#define STATUS_RTCHGINVAL 6
+#define STATUS_RTBATT_REPLACE 5
+#define STATUS_RTVBATDET_LOW 4
+#define STATUS_RTCHGILIM 3
+#define STATUS_RTPCTDONE 1
+#define STATUS_RTVCP 0
+#define PM8058_CHG_STATUS_RT_3 0x41
+#define PM8058_CHG_STATUS_RT_10 0xC1
+#define PM8058_CHG_STATUS_RT_11 0xC2
+
+/* VTRIM */
+#define PM8058_CHG_VTRIM 0x1D
+#define PM8058_CHG_VBATDET_TRIM 0x1E
+#define PM8058_CHG_ITRIM 0x1F
+#define PM8058_CHG_TTRIM 0x20
+
+#define AUTO_CHARGING_VMAXSEL 4200
+#define AUTO_CHARGING_FAST_TIME_MAX_MINUTES 512
+#define AUTO_CHARGING_TRICKLE_TIME_MINUTES 30
+#define AUTO_CHARGING_VEOC_ITERM 100
+#define AUTO_CHARGING_IEOC_ITERM 160
+
+#define AUTO_CHARGING_VBATDET 4150
+#define AUTO_CHARGING_VEOC_VBATDET 4100
+#define AUTO_CHARGING_VEOC_TCHG 16
+#define AUTO_CHARGING_VEOC_TCHG_FINAL_CYCLE 32
+#define AUTO_CHARGING_VEOC_BEGIN_TIME_MS 5400000
+
+#define AUTO_CHARGING_VEOC_VBAT_LOW_CHECK_TIME_MS 60000
+#define AUTO_CHARGING_RESUME_CHARGE_DETECTION_COUNTER 5
+
+#define PM8058_CHG_I_STEP_MA 50
+#define PM8058_CHG_I_MIN_MA 50
+#define PM8058_CHG_T_TCHG_SHIFT 2
+#define PM8058_CHG_I_TERM_STEP_MA 10
+#define PM8058_CHG_V_STEP_MV 25
+#define PM8058_CHG_V_MIN_MV 2400
+/*
+ * enum pmic_chg_interrupts: pmic interrupts
+ * @CHGVAL_IRQ: charger V between 3.3 and 7.9
+ * @CHGINVAL_IRQ: charger V outside 3.3 and 7.9
+ * @VBATDET_LOW_IRQ: VBAT < VBATDET
+ * @VCP_IRQ: VDD went below VBAT: BAT_FET is turned on
+ * @CHGILIM_IRQ: mA consumed>IMAXSEL: chgloop draws less mA
+ * @ATC_DONE_IRQ: Auto Trickle done
+ * @ATCFAIL_IRQ: Auto Trickle fail
+ * @AUTO_CHGDONE_IRQ: Auto chg done
+ * @AUTO_CHGFAIL_IRQ: time exceeded w/o reaching term current
+ * @CHGSTATE_IRQ: something happend causing a state change
+ * @FASTCHG_IRQ: trkl charging completed: moving to fastchg
+ * @CHG_END_IRQ: mA has dropped to termination current
+ * @BATTTEMP_IRQ: batt temp is out of range
+ * @CHGHOT_IRQ: the pass device is too hot
+ * @CHGTLIMIT_IRQ: unused
+ * @CHG_GONE_IRQ: charger was removed
+ * @VCPMAJOR_IRQ: vcp major
+ * @VBATDET_IRQ: VBAT >= VBATDET
+ * @BATFET_IRQ: BATFET closed
+ * @BATT_REPLACE_IRQ:
+ * @BATTCONNECT_IRQ:
+ */
+enum pmic_chg_interrupts {
+ CHGVAL_IRQ,
+ CHGINVAL_IRQ,
+ VBATDET_LOW_IRQ,
+ VCP_IRQ,
+ CHGILIM_IRQ,
+ ATC_DONE_IRQ,
+ ATCFAIL_IRQ,
+ AUTO_CHGDONE_IRQ,
+ AUTO_CHGFAIL_IRQ,
+ CHGSTATE_IRQ,
+ FASTCHG_IRQ,
+ CHG_END_IRQ,
+ BATTTEMP_IRQ,
+ CHGHOT_IRQ,
+ CHGTLIMIT_IRQ,
+ CHG_GONE_IRQ,
+ VCPMAJOR_IRQ,
+ VBATDET_IRQ,
+ BATFET_IRQ,
+ BATT_REPLACE_IRQ,
+ BATTCONNECT_IRQ,
+ PMIC_CHG_MAX_INTS
+};
+
+struct pm8058_charger {
+ struct pmic_charger_pdata *pdata;
+ struct pm8058_chip *pm_chip;
+ struct device *dev;
+
+ int pmic_chg_irq[PMIC_CHG_MAX_INTS];
+ DECLARE_BITMAP(enabled_irqs, PMIC_CHG_MAX_INTS);
+
+ struct delayed_work check_vbat_low_work;
+ struct delayed_work veoc_begin_work;
+ int waiting_for_topoff;
+ int waiting_for_veoc;
+ int current_charger_current;
+
+ struct msm_xo_voter *voter;
+ struct dentry *dent;
+};
+
+static struct pm8058_charger pm8058_chg;
+
+static int pm_chg_get_rt_status(int irq)
+{
+ int count = 3;
+ int ret;
+
+ while ((ret =
+ pm8058_irq_get_rt_status(pm8058_chg.pm_chip, irq)) == -EAGAIN
+ && count--) {
+ dev_info(pm8058_chg.dev, "%s trycount=%d\n", __func__, count);
+ cpu_relax();
+ }
+ if (ret == -EAGAIN)
+ return 0;
+ else
+ return ret;
+}
+
+static int is_chg_plugged_in(void)
+{
+ return pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]);
+}
+
+static irqreturn_t pm8058_chg_chgval_handler(int irq, void *dev_id)
+{
+ u8 old, temp;
+ int ret;
+
+ if (!is_chg_plugged_in()) { /*this debounces it */
+ ret = pm8058_read(pm8058_chg.pm_chip, PM8058_OVP_TEST_REG,
+ &old, 1);
+ temp = old | BIT(FORCE_OVP_OFF);
+ ret = pm8058_write(pm8058_chg.pm_chip, PM8058_OVP_TEST_REG,
+ &temp, 1);
+ temp = 0xFC;
+ ret = pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST,
+ &temp, 1);
+ pr_debug("%s forced wrote 0xFC to test ret=%d\n",
+ __func__, ret);
+ /* 20 ms sleep is for the VCHG to discharge */
+ msleep(20);
+ temp = 0xF0;
+ ret = pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST,
+ &temp, 1);
+ ret = pm8058_write(pm8058_chg.pm_chip, PM8058_OVP_TEST_REG,
+ &old, 1);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void free_irqs(void)
+{
+ int i;
+
+ for (i = 0; i < PMIC_CHG_MAX_INTS; i++)
+ if (pm8058_chg.pmic_chg_irq[i]) {
+ free_irq(pm8058_chg.pmic_chg_irq[i], NULL);
+ pm8058_chg.pmic_chg_irq[i] = 0;
+ }
+}
+
+static int __devinit request_irqs(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret;
+
+ ret = 0;
+ bitmap_fill(pm8058_chg.enabled_irqs, PMIC_CHG_MAX_INTS);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "CHGVAL");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource CHGVAL\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_any_context_irq(res->start,
+ pm8058_chg_chgval_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[CHGVAL_IRQ] = res->start;
+ }
+ }
+
+ return 0;
+
+err_out:
+ free_irqs();
+ return -EINVAL;
+}
+
+static int pm8058_usb_voltage_lower_limit(void)
+{
+ u8 temp, old;
+ int ret = 0;
+
+ temp = 0x10;
+ ret |= pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1);
+ ret |= pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEST, &old, 1);
+ old = old & ~BIT(IGNORE_LL);
+ temp = 0x90 | (0xF & old);
+ pr_debug("%s writing 0x%x to test\n", __func__, temp);
+ ret |= pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST, &temp, 1);
+
+ return ret;
+}
+
+static int __devinit pm8058_charger_probe(struct platform_device *pdev)
+{
+ struct pm8058_chip *pm_chip;
+
+ pm_chip = dev_get_drvdata(pdev->dev.parent);
+ if (pm_chip == NULL) {
+ pr_err("%s:no parent data passed in.\n", __func__);
+ return -EFAULT;
+ }
+
+ pm8058_chg.pm_chip = pm_chip;
+ pm8058_chg.pdata = pdev->dev.platform_data;
+ pm8058_chg.dev = &pdev->dev;
+
+ if (request_irqs(pdev)) {
+ pr_err("%s: couldnt register interrupts\n", __func__);
+ return -EINVAL;
+ }
+
+ if (pm8058_usb_voltage_lower_limit()) {
+ pr_err("%s: couldnt write to IGNORE_LL\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __devexit pm8058_charger_remove(struct platform_device *pdev)
+{
+ free_irqs();
+ return 0;
+}
+
+static struct platform_driver pm8058_charger_driver = {
+ .probe = pm8058_charger_probe,
+ .remove = __devexit_p(pm8058_charger_remove),
+ .driver = {
+ .name = "pm-usb-fix",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pm8058_charger_init(void)
+{
+ return platform_driver_register(&pm8058_charger_driver);
+}
+
+static void __exit pm8058_charger_exit(void)
+{
+ platform_driver_unregister(&pm8058_charger_driver);
+}
+
+late_initcall(pm8058_charger_init);
+module_exit(pm8058_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8058 BATTERY driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:pm8058_charger");
diff --git a/drivers/power/pm8921-bms.c b/drivers/power/pm8921-bms.c
new file mode 100644
index 0000000..b0439bc
--- /dev/null
+++ b/drivers/power/pm8921-bms.c
@@ -0,0 +1,2720 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/pm8xxx/pm8921-bms.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/mfd/pm8xxx/pm8xxx-adc.h>
+#include <linux/mfd/pm8xxx/ccadc.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+
+#define BMS_CONTROL 0x224
+#define BMS_S1_DELAY 0x225
+#define BMS_OUTPUT0 0x230
+#define BMS_OUTPUT1 0x231
+#define BMS_TOLERANCES 0x232
+#define BMS_TEST1 0x237
+
+#define ADC_ARB_SECP_CNTRL 0x190
+#define ADC_ARB_SECP_AMUX_CNTRL 0x191
+#define ADC_ARB_SECP_ANA_PARAM 0x192
+#define ADC_ARB_SECP_DIG_PARAM 0x193
+#define ADC_ARB_SECP_RSV 0x194
+#define ADC_ARB_SECP_DATA1 0x195
+#define ADC_ARB_SECP_DATA0 0x196
+
+#define ADC_ARB_BMS_CNTRL 0x18D
+#define AMUX_TRIM_2 0x322
+#define TEST_PROGRAM_REV 0x339
+
+enum pmic_bms_interrupts {
+ PM8921_BMS_SBI_WRITE_OK,
+ PM8921_BMS_CC_THR,
+ PM8921_BMS_VSENSE_THR,
+ PM8921_BMS_VSENSE_FOR_R,
+ PM8921_BMS_OCV_FOR_R,
+ PM8921_BMS_GOOD_OCV,
+ PM8921_BMS_VSENSE_AVG,
+ PM_BMS_MAX_INTS,
+};
+
+struct pm8921_soc_params {
+ uint16_t last_good_ocv_raw;
+ int cc;
+
+ int last_good_ocv_uv;
+};
+
+struct pm8921_rbatt_params {
+ uint16_t ocv_for_rbatt_raw;
+ uint16_t vsense_for_rbatt_raw;
+ uint16_t vbatt_for_rbatt_raw;
+
+ int ocv_for_rbatt_uv;
+ int vsense_for_rbatt_uv;
+ int vbatt_for_rbatt_uv;
+};
+
+/**
+ * struct pm8921_bms_chip -
+ * @bms_output_lock: lock to prevent concurrent bms reads
+ *
+ * @last_ocv_uv_mutex: mutex to protect simultaneous invocations of calculate
+ * state of charge, note that last_ocv_uv could be
+ * changed as soc is adjusted. This mutex protects
+ * simultaneous updates of last_ocv_uv as well. This mutex
+ * also protects changes to *_at_100 variables used in
+ * faking 100% SOC.
+ */
+struct pm8921_bms_chip {
+ struct device *dev;
+ struct dentry *dent;
+ unsigned int r_sense;
+ unsigned int i_test;
+ unsigned int v_failure;
+ unsigned int fcc;
+ struct single_row_lut *fcc_temp_lut;
+ struct single_row_lut *fcc_sf_lut;
+ struct pc_temp_ocv_lut *pc_temp_ocv_lut;
+ struct sf_lut *pc_sf_lut;
+ struct sf_lut *rbatt_sf_lut;
+ int delta_rbatt_mohm;
+ struct work_struct calib_hkadc_work;
+ struct delayed_work calib_ccadc_work;
+ unsigned int calib_delay_ms;
+ unsigned int revision;
+ unsigned int xoadc_v0625_usb_present;
+ unsigned int xoadc_v0625_usb_absent;
+ unsigned int xoadc_v0625;
+ unsigned int xoadc_v125;
+ unsigned int batt_temp_channel;
+ unsigned int vbat_channel;
+ unsigned int ref625mv_channel;
+ unsigned int ref1p25v_channel;
+ unsigned int batt_id_channel;
+ unsigned int pmic_bms_irq[PM_BMS_MAX_INTS];
+ DECLARE_BITMAP(enabled_irqs, PM_BMS_MAX_INTS);
+ struct mutex bms_output_lock;
+ struct single_row_lut *adjusted_fcc_temp_lut;
+ unsigned int charging_began;
+ unsigned int start_percent;
+ unsigned int end_percent;
+ enum battery_type batt_type;
+ uint16_t ocv_reading_at_100;
+ int cc_reading_at_100;
+ int max_voltage_uv;
+
+ int batt_temp_suspend;
+ int soc_rbatt_suspend;
+ int default_rbatt_mohm;
+ int amux_2_trim_delta;
+ uint16_t prev_last_good_ocv_raw;
+ unsigned int rconn_mohm;
+ struct mutex last_ocv_uv_mutex;
+ int last_ocv_uv;
+ int last_cc_uah; /* used for Iavg calc for UUC */
+ struct timeval t;
+ int last_uuc_uah;
+ int enable_fcc_learning;
+};
+
+static struct pm8921_bms_chip *the_chip;
+
+#define DEFAULT_RBATT_MOHMS 128
+#define DEFAULT_OCV_MICROVOLTS 3900000
+#define DEFAULT_CHARGE_CYCLES 0
+
+static int last_usb_cal_delta_uv = 1800;
+module_param(last_usb_cal_delta_uv, int, 0644);
+
+static int last_chargecycles = DEFAULT_CHARGE_CYCLES;
+static int last_charge_increase;
+module_param(last_chargecycles, int, 0644);
+module_param(last_charge_increase, int, 0644);
+
+static int last_rbatt = -EINVAL;
+static int last_soc = -EINVAL;
+static int last_real_fcc_mah = -EINVAL;
+static int last_real_fcc_batt_temp = -EINVAL;
+
+static int bms_ops_set(const char *val, const struct kernel_param *kp)
+{
+ if (*(int *)kp->arg == -EINVAL)
+ return param_set_int(val, kp);
+ else
+ return 0;
+}
+
+static struct kernel_param_ops bms_param_ops = {
+ .set = bms_ops_set,
+ .get = param_get_int,
+};
+
+module_param_cb(last_rbatt, &bms_param_ops, &last_rbatt, 0644);
+module_param_cb(last_soc, &bms_param_ops, &last_soc, 0644);
+
+/*
+ * bms_fake_battery is set in setups where a battery emulator is used instead
+ * of a real battery. This makes the bms driver report a different/fake value
+ * regardless of the calculated state of charge.
+ */
+static int bms_fake_battery = -EINVAL;
+module_param(bms_fake_battery, int, 0644);
+
+/* bms_start_XXX and bms_end_XXX are read only */
+static int bms_start_percent;
+static int bms_start_ocv_uv;
+static int bms_start_cc_uah;
+static int bms_end_percent;
+static int bms_end_ocv_uv;
+static int bms_end_cc_uah;
+
+static int bms_ro_ops_set(const char *val, const struct kernel_param *kp)
+{
+ return -EINVAL;
+}
+
+static struct kernel_param_ops bms_ro_param_ops = {
+ .set = bms_ro_ops_set,
+ .get = param_get_int,
+};
+module_param_cb(bms_start_percent, &bms_ro_param_ops, &bms_start_percent, 0644);
+module_param_cb(bms_start_ocv_uv, &bms_ro_param_ops, &bms_start_ocv_uv, 0644);
+module_param_cb(bms_start_cc_uah, &bms_ro_param_ops, &bms_start_cc_uah, 0644);
+
+module_param_cb(bms_end_percent, &bms_ro_param_ops, &bms_end_percent, 0644);
+module_param_cb(bms_end_ocv_uv, &bms_ro_param_ops, &bms_end_ocv_uv, 0644);
+module_param_cb(bms_end_cc_uah, &bms_ro_param_ops, &bms_end_cc_uah, 0644);
+
+static int interpolate_fcc(struct pm8921_bms_chip *chip, int batt_temp);
+static void readjust_fcc_table(void)
+{
+ struct single_row_lut *temp, *old;
+ int i, fcc, ratio;
+
+ if (!the_chip->fcc_temp_lut) {
+ pr_err("The static fcc lut table is NULL\n");
+ return;
+ }
+
+ temp = kzalloc(sizeof(struct single_row_lut), GFP_KERNEL);
+ if (!temp) {
+ pr_err("Cannot allocate memory for adjusted fcc table\n");
+ return;
+ }
+
+ fcc = interpolate_fcc(the_chip, last_real_fcc_batt_temp);
+
+ temp->cols = the_chip->fcc_temp_lut->cols;
+ for (i = 0; i < the_chip->fcc_temp_lut->cols; i++) {
+ temp->x[i] = the_chip->fcc_temp_lut->x[i];
+ ratio = div_u64(the_chip->fcc_temp_lut->y[i] * 1000, fcc);
+ temp->y[i] = (ratio * last_real_fcc_mah);
+ temp->y[i] /= 1000;
+ pr_debug("temp=%d, staticfcc=%d, adjfcc=%d, ratio=%d\n",
+ temp->x[i], the_chip->fcc_temp_lut->y[i],
+ temp->y[i], ratio);
+ }
+
+ old = the_chip->adjusted_fcc_temp_lut;
+ the_chip->adjusted_fcc_temp_lut = temp;
+ kfree(old);
+}
+
+static int bms_last_real_fcc_set(const char *val,
+ const struct kernel_param *kp)
+{
+ int rc = 0;
+
+ if (last_real_fcc_mah == -EINVAL)
+ rc = param_set_int(val, kp);
+ if (rc) {
+ pr_err("Failed to set last_real_fcc_mah rc=%d\n", rc);
+ return rc;
+ }
+ if (last_real_fcc_batt_temp != -EINVAL)
+ readjust_fcc_table();
+ return rc;
+}
+static struct kernel_param_ops bms_last_real_fcc_param_ops = {
+ .set = bms_last_real_fcc_set,
+ .get = param_get_int,
+};
+module_param_cb(last_real_fcc_mah, &bms_last_real_fcc_param_ops,
+ &last_real_fcc_mah, 0644);
+
+static int bms_last_real_fcc_batt_temp_set(const char *val,
+ const struct kernel_param *kp)
+{
+ int rc = 0;
+
+ if (last_real_fcc_batt_temp == -EINVAL)
+ rc = param_set_int(val, kp);
+ if (rc) {
+ pr_err("Failed to set last_real_fcc_batt_temp rc=%d\n", rc);
+ return rc;
+ }
+ if (last_real_fcc_mah != -EINVAL)
+ readjust_fcc_table();
+ return rc;
+}
+
+static struct kernel_param_ops bms_last_real_fcc_batt_temp_param_ops = {
+ .set = bms_last_real_fcc_batt_temp_set,
+ .get = param_get_int,
+};
+module_param_cb(last_real_fcc_batt_temp, &bms_last_real_fcc_batt_temp_param_ops,
+ &last_real_fcc_batt_temp, 0644);
+
+static int pm_bms_get_rt_status(struct pm8921_bms_chip *chip, int irq_id)
+{
+ return pm8xxx_read_irq_stat(chip->dev->parent,
+ chip->pmic_bms_irq[irq_id]);
+}
+
+static void pm8921_bms_enable_irq(struct pm8921_bms_chip *chip, int interrupt)
+{
+ if (!__test_and_set_bit(interrupt, chip->enabled_irqs)) {
+ dev_dbg(chip->dev, "%s %d\n", __func__,
+ chip->pmic_bms_irq[interrupt]);
+ enable_irq(chip->pmic_bms_irq[interrupt]);
+ }
+}
+
+static void pm8921_bms_disable_irq(struct pm8921_bms_chip *chip, int interrupt)
+{
+ if (__test_and_clear_bit(interrupt, chip->enabled_irqs)) {
+ pr_debug("%d\n", chip->pmic_bms_irq[interrupt]);
+ disable_irq_nosync(chip->pmic_bms_irq[interrupt]);
+ }
+}
+
+static int pm_bms_masked_write(struct pm8921_bms_chip *chip, u16 addr,
+ u8 mask, u8 val)
+{
+ int rc;
+ u8 reg;
+
+ rc = pm8xxx_readb(chip->dev->parent, addr, ®);
+ if (rc) {
+ pr_err("read failed addr = %03X, rc = %d\n", addr, rc);
+ return rc;
+ }
+ reg &= ~mask;
+ reg |= val & mask;
+ rc = pm8xxx_writeb(chip->dev->parent, addr, reg);
+ if (rc) {
+ pr_err("write failed addr = %03X, rc = %d\n", addr, rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int usb_chg_plugged_in(void)
+{
+ union power_supply_propval ret = {0,};
+ static struct power_supply *psy;
+
+ if (psy == NULL) {
+ psy = power_supply_get_by_name("usb");
+ if (psy == NULL)
+ return 0;
+ }
+
+ if (psy->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &ret))
+ return 0;
+
+ return ret.intval;
+}
+
+#define HOLD_OREG_DATA BIT(1)
+static int pm_bms_lock_output_data(struct pm8921_bms_chip *chip)
+{
+ int rc;
+
+ rc = pm_bms_masked_write(chip, BMS_CONTROL, HOLD_OREG_DATA,
+ HOLD_OREG_DATA);
+ if (rc) {
+ pr_err("couldnt lock bms output rc = %d\n", rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int pm_bms_unlock_output_data(struct pm8921_bms_chip *chip)
+{
+ int rc;
+
+ rc = pm_bms_masked_write(chip, BMS_CONTROL, HOLD_OREG_DATA, 0);
+ if (rc) {
+ pr_err("fail to unlock BMS_CONTROL rc = %d\n", rc);
+ return rc;
+ }
+ return 0;
+}
+
+#define SELECT_OUTPUT_DATA 0x1C
+#define SELECT_OUTPUT_TYPE_SHIFT 2
+#define OCV_FOR_RBATT 0x0
+#define VSENSE_FOR_RBATT 0x1
+#define VBATT_FOR_RBATT 0x2
+#define CC_MSB 0x3
+#define CC_LSB 0x4
+#define LAST_GOOD_OCV_VALUE 0x5
+#define VSENSE_AVG 0x6
+#define VBATT_AVG 0x7
+
+static int pm_bms_read_output_data(struct pm8921_bms_chip *chip, int type,
+ int16_t *result)
+{
+ int rc;
+ u8 reg;
+
+ if (!result) {
+ pr_err("result pointer null\n");
+ return -EINVAL;
+ }
+ *result = 0;
+ if (type < OCV_FOR_RBATT || type > VBATT_AVG) {
+ pr_err("invalid type %d asked to read\n", type);
+ return -EINVAL;
+ }
+
+ rc = pm_bms_masked_write(chip, BMS_CONTROL, SELECT_OUTPUT_DATA,
+ type << SELECT_OUTPUT_TYPE_SHIFT);
+ if (rc) {
+ pr_err("fail to select %d type in BMS_CONTROL rc = %d\n",
+ type, rc);
+ return rc;
+ }
+
+ rc = pm8xxx_readb(chip->dev->parent, BMS_OUTPUT0, ®);
+ if (rc) {
+ pr_err("fail to read BMS_OUTPUT0 for type %d rc = %d\n",
+ type, rc);
+ return rc;
+ }
+ *result = reg;
+ rc = pm8xxx_readb(chip->dev->parent, BMS_OUTPUT1, ®);
+ if (rc) {
+ pr_err("fail to read BMS_OUTPUT1 for type %d rc = %d\n",
+ type, rc);
+ return rc;
+ }
+ *result |= reg << 8;
+ pr_debug("type %d result %x", type, *result);
+ return 0;
+}
+
+#define V_PER_BIT_MUL_FACTOR 97656
+#define V_PER_BIT_DIV_FACTOR 1000
+#define XOADC_INTRINSIC_OFFSET 0x6000
+static int xoadc_reading_to_microvolt(unsigned int a)
+{
+ if (a <= XOADC_INTRINSIC_OFFSET)
+ return 0;
+
+ return (a - XOADC_INTRINSIC_OFFSET)
+ * V_PER_BIT_MUL_FACTOR / V_PER_BIT_DIV_FACTOR;
+}
+
+#define XOADC_CALIB_UV 625000
+#define VBATT_MUL_FACTOR 3
+static int adjust_xo_vbatt_reading(struct pm8921_bms_chip *chip,
+ int usb_chg, unsigned int uv)
+{
+ s64 numerator, denominator;
+ int local_delta;
+
+ if (uv == 0)
+ return 0;
+
+ /* dont adjust if not calibrated */
+ if (chip->xoadc_v0625 == 0 || chip->xoadc_v125 == 0) {
+ pr_debug("No cal yet return %d\n", VBATT_MUL_FACTOR * uv);
+ return VBATT_MUL_FACTOR * uv;
+ }
+
+ if (usb_chg)
+ local_delta = last_usb_cal_delta_uv;
+ else
+ local_delta = 0;
+
+ pr_debug("using delta = %d\n", local_delta);
+ numerator = ((s64)uv - chip->xoadc_v0625 - local_delta)
+ * XOADC_CALIB_UV;
+ denominator = (s64)chip->xoadc_v125 - chip->xoadc_v0625 - local_delta;
+ if (denominator == 0)
+ return uv * VBATT_MUL_FACTOR;
+ return (XOADC_CALIB_UV + local_delta + div_s64(numerator, denominator))
+ * VBATT_MUL_FACTOR;
+}
+
+#define CC_RESOLUTION_N_V1 1085069
+#define CC_RESOLUTION_D_V1 100000
+#define CC_RESOLUTION_N_V2 868056
+#define CC_RESOLUTION_D_V2 10000
+static s64 cc_to_microvolt_v1(s64 cc)
+{
+ return div_s64(cc * CC_RESOLUTION_N_V1, CC_RESOLUTION_D_V1);
+}
+
+static s64 cc_to_microvolt_v2(s64 cc)
+{
+ return div_s64(cc * CC_RESOLUTION_N_V2, CC_RESOLUTION_D_V2);
+}
+
+static s64 cc_to_microvolt(struct pm8921_bms_chip *chip, s64 cc)
+{
+ /*
+ * resolution (the value of a single bit) was changed after revision 2.0
+ * for more accurate readings
+ */
+ return (chip->revision < PM8XXX_REVISION_8921_2p0) ?
+ cc_to_microvolt_v1((s64)cc) :
+ cc_to_microvolt_v2((s64)cc);
+}
+
+#define CC_READING_TICKS 55
+#define SLEEP_CLK_HZ 32768
+#define SECONDS_PER_HOUR 3600
+/**
+ * ccmicrovolt_to_nvh -
+ * @cc_uv: coulumb counter converted to uV
+ *
+ * RETURNS: coulumb counter based charge in nVh
+ * (nano Volt Hour)
+ */
+static s64 ccmicrovolt_to_nvh(s64 cc_uv)
+{
+ return div_s64(cc_uv * CC_READING_TICKS * 1000,
+ SLEEP_CLK_HZ * SECONDS_PER_HOUR);
+}
+
+/* returns the signed value read from the hardware */
+static int read_cc(struct pm8921_bms_chip *chip, int *result)
+{
+ int rc;
+ uint16_t msw, lsw;
+
+ rc = pm_bms_read_output_data(chip, CC_LSB, &lsw);
+ if (rc) {
+ pr_err("fail to read CC_LSB rc = %d\n", rc);
+ return rc;
+ }
+ rc = pm_bms_read_output_data(chip, CC_MSB, &msw);
+ if (rc) {
+ pr_err("fail to read CC_MSB rc = %d\n", rc);
+ return rc;
+ }
+ *result = msw << 16 | lsw;
+ pr_debug("msw = %04x lsw = %04x cc = %d\n", msw, lsw, *result);
+ return 0;
+}
+
+static int adjust_xo_vbatt_reading_for_mbg(struct pm8921_bms_chip *chip,
+ int result)
+{
+ int64_t numerator;
+ int64_t denominator;
+
+ if (chip->amux_2_trim_delta == 0)
+ return result;
+
+ numerator = (s64)result * 1000000;
+ denominator = (1000000 + (410 * (s64)chip->amux_2_trim_delta));
+ return div_s64(numerator, denominator);
+}
+
+static int convert_vbatt_raw_to_uv(struct pm8921_bms_chip *chip,
+ int usb_chg,
+ uint16_t reading, int *result)
+{
+ *result = xoadc_reading_to_microvolt(reading);
+ pr_debug("raw = %04x vbatt = %u\n", reading, *result);
+ *result = adjust_xo_vbatt_reading(chip, usb_chg, *result);
+ pr_debug("after adj vbatt = %u\n", *result);
+ *result = adjust_xo_vbatt_reading_for_mbg(chip, *result);
+ return 0;
+}
+
+static int convert_vsense_to_uv(struct pm8921_bms_chip *chip,
+ int16_t reading, int *result)
+{
+ *result = pm8xxx_ccadc_reading_to_microvolt(chip->revision, reading);
+ pr_debug("raw = %04x vsense = %d\n", reading, *result);
+ *result = pm8xxx_cc_adjust_for_gain(*result);
+ pr_debug("after adj vsense = %d\n", *result);
+ return 0;
+}
+
+static int read_vsense_avg(struct pm8921_bms_chip *chip, int *result)
+{
+ int rc;
+ int16_t reading;
+
+ rc = pm_bms_read_output_data(chip, VSENSE_AVG, &reading);
+ if (rc) {
+ pr_err("fail to read VSENSE_AVG rc = %d\n", rc);
+ return rc;
+ }
+
+ convert_vsense_to_uv(chip, reading, result);
+ return 0;
+}
+
+static int linear_interpolate(int y0, int x0, int y1, int x1, int x)
+{
+ if (y0 == y1 || x == x0)
+ return y0;
+ if (x1 == x0 || x == x1)
+ return y1;
+
+ return y0 + ((y1 - y0) * (x - x0) / (x1 - x0));
+}
+
+static int interpolate_single_lut(struct single_row_lut *lut, int x)
+{
+ int i, result;
+
+ if (x < lut->x[0]) {
+ pr_debug("x %d less than known range return y = %d lut = %pS\n",
+ x, lut->y[0], lut);
+ return lut->y[0];
+ }
+ if (x > lut->x[lut->cols - 1]) {
+ pr_debug("x %d more than known range return y = %d lut = %pS\n",
+ x, lut->y[lut->cols - 1], lut);
+ return lut->y[lut->cols - 1];
+ }
+
+ for (i = 0; i < lut->cols; i++)
+ if (x <= lut->x[i])
+ break;
+ if (x == lut->x[i]) {
+ result = lut->y[i];
+ } else {
+ result = linear_interpolate(
+ lut->y[i - 1],
+ lut->x[i - 1],
+ lut->y[i],
+ lut->x[i],
+ x);
+ }
+ return result;
+}
+
+static int interpolate_fcc(struct pm8921_bms_chip *chip, int batt_temp)
+{
+ /* batt_temp is in tenths of degC - convert it to degC for lookups */
+ batt_temp = batt_temp/10;
+ return interpolate_single_lut(chip->fcc_temp_lut, batt_temp);
+}
+
+static int interpolate_fcc_adjusted(struct pm8921_bms_chip *chip, int batt_temp)
+{
+ /* batt_temp is in tenths of degC - convert it to degC for lookups */
+ batt_temp = batt_temp/10;
+ return interpolate_single_lut(chip->adjusted_fcc_temp_lut, batt_temp);
+}
+
+static int interpolate_scalingfactor_fcc(struct pm8921_bms_chip *chip,
+ int cycles)
+{
+ /*
+ * sf table could be null when no battery aging data is available, in
+ * that case return 100%
+ */
+ if (chip->fcc_sf_lut)
+ return interpolate_single_lut(chip->fcc_sf_lut, cycles);
+ else
+ return 100;
+}
+
+static int interpolate_scalingfactor(struct pm8921_bms_chip *chip,
+ struct sf_lut *sf_lut,
+ int row_entry, int pc)
+{
+ int i, scalefactorrow1, scalefactorrow2, scalefactor;
+ int rows, cols;
+ int row1 = 0;
+ int row2 = 0;
+
+ /*
+ * sf table could be null when no battery aging data is available, in
+ * that case return 100%
+ */
+ if (!sf_lut)
+ return 100;
+
+ rows = sf_lut->rows;
+ cols = sf_lut->cols;
+ if (pc > sf_lut->percent[0]) {
+ pr_debug("pc %d greater than known pc ranges for sfd\n", pc);
+ row1 = 0;
+ row2 = 0;
+ }
+ if (pc < sf_lut->percent[rows - 1]) {
+ pr_debug("pc %d less than known pc ranges for sf", pc);
+ row1 = rows - 1;
+ row2 = rows - 1;
+ }
+ for (i = 0; i < rows; i++) {
+ if (pc == sf_lut->percent[i]) {
+ row1 = i;
+ row2 = i;
+ break;
+ }
+ if (pc > sf_lut->percent[i]) {
+ row1 = i - 1;
+ row2 = i;
+ break;
+ }
+ }
+
+ if (row_entry < sf_lut->row_entries[0])
+ row_entry = sf_lut->row_entries[0];
+ if (row_entry > sf_lut->row_entries[cols - 1])
+ row_entry = sf_lut->row_entries[cols - 1];
+
+ for (i = 0; i < cols; i++)
+ if (row_entry <= sf_lut->row_entries[i])
+ break;
+ if (row_entry == sf_lut->row_entries[i]) {
+ scalefactor = linear_interpolate(
+ sf_lut->sf[row1][i],
+ sf_lut->percent[row1],
+ sf_lut->sf[row2][i],
+ sf_lut->percent[row2],
+ pc);
+ return scalefactor;
+ }
+
+ scalefactorrow1 = linear_interpolate(
+ sf_lut->sf[row1][i - 1],
+ sf_lut->row_entries[i - 1],
+ sf_lut->sf[row1][i],
+ sf_lut->row_entries[i],
+ row_entry);
+
+ scalefactorrow2 = linear_interpolate(
+ sf_lut->sf[row2][i - 1],
+ sf_lut->row_entries[i - 1],
+ sf_lut->sf[row2][i],
+ sf_lut->row_entries[i],
+ row_entry);
+
+ scalefactor = linear_interpolate(
+ scalefactorrow1,
+ sf_lut->percent[row1],
+ scalefactorrow2,
+ sf_lut->percent[row2],
+ pc);
+
+ return scalefactor;
+}
+
+static int is_between(int left, int right, int value)
+{
+ if (left >= right && left >= value && value >= right)
+ return 1;
+ if (left <= right && left <= value && value <= right)
+ return 1;
+
+ return 0;
+}
+
+static int interpolate_pc(struct pm8921_bms_chip *chip,
+ int batt_temp, int ocv)
+{
+ int i, j, pcj, pcj_minus_one, pc;
+ int rows = chip->pc_temp_ocv_lut->rows;
+ int cols = chip->pc_temp_ocv_lut->cols;
+
+ /* batt_temp is in tenths of degC - convert it to degC for lookups */
+ batt_temp = batt_temp/10;
+
+ if (batt_temp < chip->pc_temp_ocv_lut->temp[0]) {
+ pr_debug("batt_temp %d < known temp range for pc\n", batt_temp);
+ batt_temp = chip->pc_temp_ocv_lut->temp[0];
+ }
+ if (batt_temp > chip->pc_temp_ocv_lut->temp[cols - 1]) {
+ pr_debug("batt_temp %d > known temp range for pc\n", batt_temp);
+ batt_temp = chip->pc_temp_ocv_lut->temp[cols - 1];
+ }
+
+ for (j = 0; j < cols; j++)
+ if (batt_temp <= chip->pc_temp_ocv_lut->temp[j])
+ break;
+ if (batt_temp == chip->pc_temp_ocv_lut->temp[j]) {
+ /* found an exact match for temp in the table */
+ if (ocv >= chip->pc_temp_ocv_lut->ocv[0][j])
+ return chip->pc_temp_ocv_lut->percent[0];
+ if (ocv <= chip->pc_temp_ocv_lut->ocv[rows - 1][j])
+ return chip->pc_temp_ocv_lut->percent[rows - 1];
+ for (i = 0; i < rows; i++) {
+ if (ocv >= chip->pc_temp_ocv_lut->ocv[i][j]) {
+ if (ocv == chip->pc_temp_ocv_lut->ocv[i][j])
+ return
+ chip->pc_temp_ocv_lut->percent[i];
+ pc = linear_interpolate(
+ chip->pc_temp_ocv_lut->percent[i],
+ chip->pc_temp_ocv_lut->ocv[i][j],
+ chip->pc_temp_ocv_lut->percent[i - 1],
+ chip->pc_temp_ocv_lut->ocv[i - 1][j],
+ ocv);
+ return pc;
+ }
+ }
+ }
+
+ /*
+ * batt_temp is within temperature for
+ * column j-1 and j
+ */
+ if (ocv >= chip->pc_temp_ocv_lut->ocv[0][j])
+ return chip->pc_temp_ocv_lut->percent[0];
+ if (ocv <= chip->pc_temp_ocv_lut->ocv[rows - 1][j - 1])
+ return chip->pc_temp_ocv_lut->percent[rows - 1];
+
+ pcj_minus_one = 0;
+ pcj = 0;
+ for (i = 0; i < rows-1; i++) {
+ if (pcj == 0
+ && is_between(chip->pc_temp_ocv_lut->ocv[i][j],
+ chip->pc_temp_ocv_lut->ocv[i+1][j], ocv)) {
+ pcj = linear_interpolate(
+ chip->pc_temp_ocv_lut->percent[i],
+ chip->pc_temp_ocv_lut->ocv[i][j],
+ chip->pc_temp_ocv_lut->percent[i + 1],
+ chip->pc_temp_ocv_lut->ocv[i+1][j],
+ ocv);
+ }
+
+ if (pcj_minus_one == 0
+ && is_between(chip->pc_temp_ocv_lut->ocv[i][j-1],
+ chip->pc_temp_ocv_lut->ocv[i+1][j-1], ocv)) {
+
+ pcj_minus_one = linear_interpolate(
+ chip->pc_temp_ocv_lut->percent[i],
+ chip->pc_temp_ocv_lut->ocv[i][j-1],
+ chip->pc_temp_ocv_lut->percent[i + 1],
+ chip->pc_temp_ocv_lut->ocv[i+1][j-1],
+ ocv);
+ }
+
+ if (pcj && pcj_minus_one) {
+ pc = linear_interpolate(
+ pcj_minus_one,
+ chip->pc_temp_ocv_lut->temp[j-1],
+ pcj,
+ chip->pc_temp_ocv_lut->temp[j],
+ batt_temp);
+ return pc;
+ }
+ }
+
+ if (pcj)
+ return pcj;
+
+ if (pcj_minus_one)
+ return pcj_minus_one;
+
+ pr_debug("%d ocv wasn't found for temp %d in the LUT returning 100%%",
+ ocv, batt_temp);
+ return 100;
+}
+
+#define BMS_MODE_BIT BIT(6)
+#define EN_VBAT_BIT BIT(5)
+#define OVERRIDE_MODE_DELAY_MS 20
+int pm8921_bms_get_simultaneous_battery_voltage_and_current(int *ibat_ua,
+ int *vbat_uv)
+{
+ int16_t vsense_raw;
+ int16_t vbat_raw;
+ int vsense_uv;
+ int usb_chg;
+
+ if (the_chip == NULL) {
+ pr_err("Called to early\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&the_chip->bms_output_lock);
+
+ pm8xxx_writeb(the_chip->dev->parent, BMS_S1_DELAY, 0x00);
+ pm_bms_masked_write(the_chip, BMS_CONTROL,
+ BMS_MODE_BIT | EN_VBAT_BIT, BMS_MODE_BIT | EN_VBAT_BIT);
+
+ msleep(OVERRIDE_MODE_DELAY_MS);
+
+ pm_bms_lock_output_data(the_chip);
+ pm_bms_read_output_data(the_chip, VSENSE_AVG, &vsense_raw);
+ pm_bms_read_output_data(the_chip, VBATT_AVG, &vbat_raw);
+ pm_bms_unlock_output_data(the_chip);
+ pm_bms_masked_write(the_chip, BMS_CONTROL,
+ BMS_MODE_BIT | EN_VBAT_BIT, 0);
+
+ pm8xxx_writeb(the_chip->dev->parent, BMS_S1_DELAY, 0x0B);
+
+ mutex_unlock(&the_chip->bms_output_lock);
+
+ usb_chg = usb_chg_plugged_in();
+
+ convert_vbatt_raw_to_uv(the_chip, usb_chg, vbat_raw, vbat_uv);
+ convert_vsense_to_uv(the_chip, vsense_raw, &vsense_uv);
+ *ibat_ua = vsense_uv * 1000 / (int)the_chip->r_sense;
+
+ pr_debug("vsense_raw = 0x%x vbat_raw = 0x%x"
+ " ibat_ua = %d vbat_uv = %d\n",
+ (uint16_t)vsense_raw, (uint16_t)vbat_raw,
+ *ibat_ua, *vbat_uv);
+ return 0;
+}
+EXPORT_SYMBOL(pm8921_bms_get_simultaneous_battery_voltage_and_current);
+
+static int read_rbatt_params_raw(struct pm8921_bms_chip *chip,
+ struct pm8921_rbatt_params *raw)
+{
+ int usb_chg;
+
+ mutex_lock(&chip->bms_output_lock);
+ pm_bms_lock_output_data(chip);
+
+ pm_bms_read_output_data(chip,
+ OCV_FOR_RBATT, &raw->ocv_for_rbatt_raw);
+ pm_bms_read_output_data(chip,
+ VBATT_FOR_RBATT, &raw->vbatt_for_rbatt_raw);
+ pm_bms_read_output_data(chip,
+ VSENSE_FOR_RBATT, &raw->vsense_for_rbatt_raw);
+
+ pm_bms_unlock_output_data(chip);
+ mutex_unlock(&chip->bms_output_lock);
+
+ usb_chg = usb_chg_plugged_in();
+ convert_vbatt_raw_to_uv(chip, usb_chg,
+ raw->vbatt_for_rbatt_raw, &raw->vbatt_for_rbatt_uv);
+ convert_vbatt_raw_to_uv(chip, usb_chg,
+ raw->ocv_for_rbatt_raw, &raw->ocv_for_rbatt_uv);
+ convert_vsense_to_uv(chip, raw->vsense_for_rbatt_raw,
+ &raw->vsense_for_rbatt_uv);
+
+ pr_debug("vbatt_for_rbatt_raw = 0x%x, vbatt_for_rbatt= %duV\n",
+ raw->vbatt_for_rbatt_raw, raw->vbatt_for_rbatt_uv);
+ pr_debug("ocv_for_rbatt_raw = 0x%x, ocv_for_rbatt= %duV\n",
+ raw->ocv_for_rbatt_raw, raw->ocv_for_rbatt_uv);
+ pr_debug("vsense_for_rbatt_raw = 0x%x, vsense_for_rbatt= %duV\n",
+ raw->vsense_for_rbatt_raw, raw->vsense_for_rbatt_uv);
+ return 0;
+}
+
+#define MBG_TRANSIENT_ERROR_RAW 51
+static void adjust_pon_ocv_raw(struct pm8921_bms_chip *chip,
+ struct pm8921_soc_params *raw)
+{
+ /* in 8921 parts the PON ocv is taken when the MBG is not settled.
+ * decrease the pon ocv by 15mV raw value to account for it
+ * Since a 1/3rd of vbatt is supplied to the adc the raw value
+ * needs to be adjusted by 5mV worth bits
+ */
+ if (raw->last_good_ocv_raw >= MBG_TRANSIENT_ERROR_RAW)
+ raw->last_good_ocv_raw -= MBG_TRANSIENT_ERROR_RAW;
+}
+
+static int read_soc_params_raw(struct pm8921_bms_chip *chip,
+ struct pm8921_soc_params *raw)
+{
+ int usb_chg;
+
+ mutex_lock(&chip->bms_output_lock);
+ pm_bms_lock_output_data(chip);
+
+ pm_bms_read_output_data(chip,
+ LAST_GOOD_OCV_VALUE, &raw->last_good_ocv_raw);
+ read_cc(chip, &raw->cc);
+
+ pm_bms_unlock_output_data(chip);
+ mutex_unlock(&chip->bms_output_lock);
+
+ usb_chg = usb_chg_plugged_in();
+
+ if (chip->prev_last_good_ocv_raw == 0) {
+ chip->prev_last_good_ocv_raw = raw->last_good_ocv_raw;
+ adjust_pon_ocv_raw(chip, raw);
+ convert_vbatt_raw_to_uv(chip, usb_chg,
+ raw->last_good_ocv_raw, &raw->last_good_ocv_uv);
+ chip->last_ocv_uv = raw->last_good_ocv_uv;
+ } else if (chip->prev_last_good_ocv_raw != raw->last_good_ocv_raw) {
+ chip->prev_last_good_ocv_raw = raw->last_good_ocv_raw;
+ convert_vbatt_raw_to_uv(chip, usb_chg,
+ raw->last_good_ocv_raw, &raw->last_good_ocv_uv);
+ chip->last_ocv_uv = raw->last_good_ocv_uv;
+ /* forget the old cc value upon ocv */
+ chip->last_cc_uah = 0;
+ } else {
+ raw->last_good_ocv_uv = chip->last_ocv_uv;
+ }
+
+ /* fake a high OCV if we are just done charging */
+ if (chip->ocv_reading_at_100 != raw->last_good_ocv_raw) {
+ chip->ocv_reading_at_100 = 0;
+ chip->cc_reading_at_100 = 0;
+ } else {
+ /*
+ * force 100% ocv by selecting the highest voltage the
+ * battery could ever reach
+ */
+ raw->last_good_ocv_uv = chip->max_voltage_uv;
+ chip->last_ocv_uv = chip->max_voltage_uv;
+ }
+ pr_debug("0p625 = %duV\n", chip->xoadc_v0625);
+ pr_debug("1p25 = %duV\n", chip->xoadc_v125);
+ pr_debug("last_good_ocv_raw= 0x%x, last_good_ocv_uv= %duV\n",
+ raw->last_good_ocv_raw, raw->last_good_ocv_uv);
+ pr_debug("cc_raw= 0x%x\n", raw->cc);
+ return 0;
+}
+
+static int get_rbatt(struct pm8921_bms_chip *chip, int soc_rbatt, int batt_temp)
+{
+ int rbatt, scalefactor;
+
+ rbatt = (last_rbatt < 0) ? chip->default_rbatt_mohm : last_rbatt;
+ pr_debug("rbatt before scaling = %d\n", rbatt);
+ if (chip->rbatt_sf_lut == NULL) {
+ pr_debug("RBATT = %d\n", rbatt);
+ return rbatt;
+ }
+ /* Convert the batt_temp to DegC from deciDegC */
+ batt_temp = batt_temp / 10;
+ scalefactor = interpolate_scalingfactor(chip, chip->rbatt_sf_lut,
+ batt_temp, soc_rbatt);
+ pr_debug("rbatt sf = %d for batt_temp = %d, soc_rbatt = %d\n",
+ scalefactor, batt_temp, soc_rbatt);
+ rbatt = (rbatt * scalefactor) / 100;
+
+ rbatt += the_chip->rconn_mohm;
+ pr_debug("adding rconn_mohm = %d rbatt = %d\n",
+ the_chip->rconn_mohm, rbatt);
+
+ if (is_between(20, 10, soc_rbatt))
+ rbatt = rbatt
+ + ((20 - soc_rbatt) * chip->delta_rbatt_mohm) / 10;
+ else
+ if (is_between(10, 0, soc_rbatt))
+ rbatt = rbatt + chip->delta_rbatt_mohm;
+
+ pr_debug("RBATT = %d\n", rbatt);
+ return rbatt;
+}
+
+static int calculate_rbatt_resume(struct pm8921_bms_chip *chip,
+ struct pm8921_rbatt_params *raw)
+{
+ unsigned int r_batt;
+
+ if (raw->ocv_for_rbatt_uv <= 0
+ || raw->ocv_for_rbatt_uv <= raw->vbatt_for_rbatt_uv
+ || raw->vsense_for_rbatt_raw <= 0) {
+ pr_debug("rbatt readings unavailable ocv = %d, vbatt = %d,"
+ "vsen = %d\n",
+ raw->ocv_for_rbatt_uv,
+ raw->vbatt_for_rbatt_uv,
+ raw->vsense_for_rbatt_raw);
+ return -EINVAL;
+ }
+ r_batt = ((raw->ocv_for_rbatt_uv - raw->vbatt_for_rbatt_uv)
+ * chip->r_sense) / raw->vsense_for_rbatt_uv;
+ pr_debug("r_batt = %umilliOhms", r_batt);
+ return r_batt;
+}
+
+static int calculate_fcc_uah(struct pm8921_bms_chip *chip, int batt_temp,
+ int chargecycles)
+{
+ int initfcc, result, scalefactor = 0;
+
+ if (chip->adjusted_fcc_temp_lut == NULL) {
+ initfcc = interpolate_fcc(chip, batt_temp);
+
+ scalefactor = interpolate_scalingfactor_fcc(chip, chargecycles);
+
+ /* Multiply the initial FCC value by the scale factor. */
+ result = (initfcc * scalefactor * 1000) / 100;
+ pr_debug("fcc = %d uAh\n", result);
+ return result;
+ } else {
+ return 1000 * interpolate_fcc_adjusted(chip, batt_temp);
+ }
+}
+
+static int get_battery_uvolts(struct pm8921_bms_chip *chip, int *uvolts)
+{
+ int rc;
+ struct pm8xxx_adc_chan_result result;
+
+ rc = pm8xxx_adc_read(chip->vbat_channel, &result);
+ if (rc) {
+ pr_err("error reading adc channel = %d, rc = %d\n",
+ chip->vbat_channel, rc);
+ return rc;
+ }
+ pr_debug("mvolts phy = %lld meas = 0x%llx", result.physical,
+ result.measurement);
+ *uvolts = (int)result.physical;
+ return 0;
+}
+
+static int adc_based_ocv(struct pm8921_bms_chip *chip, int *ocv)
+{
+ int vbatt, rbatt, ibatt_ua, rc;
+
+ rc = get_battery_uvolts(chip, &vbatt);
+ if (rc) {
+ pr_err("failed to read vbatt from adc rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = pm8921_bms_get_battery_current(&ibatt_ua);
+ if (rc) {
+ pr_err("failed to read batt current rc = %d\n", rc);
+ return rc;
+ }
+
+ rbatt = (last_rbatt < 0) ? chip->default_rbatt_mohm : last_rbatt;
+ *ocv = vbatt + (ibatt_ua * rbatt)/1000;
+ return 0;
+}
+
+static int calculate_pc(struct pm8921_bms_chip *chip, int ocv_uv, int batt_temp,
+ int chargecycles)
+{
+ int pc, scalefactor;
+
+ pc = interpolate_pc(chip, batt_temp, ocv_uv / 1000);
+ pr_debug("pc = %u for ocv = %dmicroVolts batt_temp = %d\n",
+ pc, ocv_uv, batt_temp);
+
+ scalefactor = interpolate_scalingfactor(chip,
+ chip->pc_sf_lut, chargecycles, pc);
+ pr_debug("scalefactor = %u batt_temp = %d\n", scalefactor, batt_temp);
+
+ /* Multiply the initial FCC value by the scale factor. */
+ pc = (pc * scalefactor) / 100;
+ return pc;
+}
+
+/**
+ * calculate_cc_uah -
+ * @chip: the bms chip pointer
+ * @cc: the cc reading from bms h/w
+ * @val: return value
+ * @coulumb_counter: adjusted coulumb counter for 100%
+ *
+ * RETURNS: in val pointer coulumb counter based charger in uAh
+ * (micro Amp hour)
+ */
+static void calculate_cc_uah(struct pm8921_bms_chip *chip, int cc, int *val)
+{
+ int64_t cc_voltage_uv, cc_nvh, cc_uah;
+
+ cc_voltage_uv = cc;
+ cc_voltage_uv -= chip->cc_reading_at_100;
+ pr_debug("cc = %d. after subtracting 0x%x cc = %lld\n",
+ cc, chip->cc_reading_at_100,
+ cc_voltage_uv);
+ cc_voltage_uv = cc_to_microvolt(chip, cc_voltage_uv);
+ cc_voltage_uv = pm8xxx_cc_adjust_for_gain(cc_voltage_uv);
+ pr_debug("cc_voltage_uv = %lld microvolts\n", cc_voltage_uv);
+ cc_nvh = ccmicrovolt_to_nvh(cc_voltage_uv);
+ pr_debug("cc_nvh = %lld nano_volt_hour\n", cc_nvh);
+ cc_uah = div_s64(cc_nvh, chip->r_sense);
+ *val = cc_uah;
+}
+
+static int calculate_uuc_uah_at_given_current(struct pm8921_bms_chip *chip,
+ int batt_temp, int chargecycles,
+ int rbatt, int fcc_uah, int i_ma)
+{
+ int unusable_uv, pc_unusable, uuc;
+
+ /* calculate unusable charge with itest */
+ unusable_uv = (rbatt * i_ma) + (chip->v_failure * 1000);
+ pc_unusable = calculate_pc(chip, unusable_uv, batt_temp, chargecycles);
+ uuc = (fcc_uah * pc_unusable) / 100;
+ pr_debug("For i_ma = %d, unusable_uv = %d unusable_pc = %d uuc = %d\n",
+ i_ma, unusable_uv, pc_unusable, uuc);
+ return uuc;
+}
+
+/* soc_rbatt when uuc_reported should be equal to uuc_now */
+#define SOC_RBATT_CHG 80
+#define SOC_RBATT_DISCHG 10
+static int calculate_unusable_charge_uah(struct pm8921_bms_chip *chip,
+ int rbatt, int fcc_uah, int cc_uah,
+ int soc_rbatt, int batt_temp, int chargecycles)
+{
+ struct timeval now;
+ int delta_time_s;
+ int delta_cc_uah;
+ int iavg_ua, iavg_ma;
+ int uuc_uah_itest, uuc_uah_iavg, uuc_now, uuc_reported;
+ s64 stepsize = 0;
+ int firsttime = 0;
+
+ delta_cc_uah = cc_uah - chip->last_cc_uah;
+ do_gettimeofday(&now);
+ if (chip->t.tv_sec != 0) {
+ delta_time_s = (now.tv_sec - chip->t.tv_sec);
+ } else {
+ /* uuc calculation for the first time */
+ delta_time_s = 0;
+ firsttime = 1;
+ }
+
+ if (delta_time_s != 0)
+ iavg_ua = div_s64((s64)delta_cc_uah * 3600, delta_time_s);
+ else
+ iavg_ua = 0;
+
+ iavg_ma = iavg_ua/1000;
+
+ pr_debug("t.tv_sec = %d, now.tv_sec = %d\n", (int)chip->t.tv_sec,
+ (int)now.tv_sec);
+
+ pr_debug("delta_time_s = %d iavg_ma = %d\n", delta_time_s, iavg_ma);
+
+ if (iavg_ma == 0) {
+ pr_debug("Iavg = 0 returning last uuc = %d\n",
+ chip->last_uuc_uah);
+ uuc_reported = chip->last_uuc_uah;
+ goto out;
+ }
+
+ /* calculate unusable charge with itest */
+ uuc_uah_itest = calculate_uuc_uah_at_given_current(chip,
+ batt_temp, chargecycles,
+ rbatt, fcc_uah, chip->i_test);
+
+ pr_debug("itest = %d uuc_itest = %d\n", chip->i_test, uuc_uah_itest);
+
+ /* calculate unusable charge with iavg */
+ iavg_ma = max(0, iavg_ma);
+ uuc_uah_iavg = calculate_uuc_uah_at_given_current(chip,
+ batt_temp, chargecycles,
+ rbatt, fcc_uah, iavg_ma);
+ pr_debug("iavg = %d uuc_iavg = %d\n", iavg_ma, uuc_uah_iavg);
+
+ if (firsttime) {
+ if (cc_uah < chip->last_cc_uah)
+ chip->last_uuc_uah = uuc_uah_itest;
+ else
+ chip->last_uuc_uah = uuc_uah_iavg;
+ pr_debug("firsttime uuc_prev = %d\n", chip->last_uuc_uah);
+ }
+
+ uuc_now = min(uuc_uah_itest, uuc_uah_iavg);
+
+ uuc_reported = -EINVAL;
+ if (cc_uah < chip->last_cc_uah) {
+ /* charging */
+ if (uuc_now < chip->last_uuc_uah) {
+ stepsize = max(1, (SOC_RBATT_CHG - soc_rbatt));
+ /* uuc_reported = uuc_prev + deltauuc / stepsize */
+ uuc_reported = div_s64 (stepsize * chip->last_uuc_uah
+ + (uuc_now - chip->last_uuc_uah),
+ stepsize);
+ uuc_reported = max(0, uuc_reported);
+ }
+ } else {
+ if (uuc_now > chip->last_uuc_uah) {
+ stepsize = max(1, (soc_rbatt - SOC_RBATT_DISCHG));
+ /* uuc_reported = uuc_prev + deltauuc / stepsize */
+ uuc_reported = div_s64 (stepsize * chip->last_uuc_uah
+ + (uuc_now - chip->last_uuc_uah),
+ stepsize);
+ uuc_reported = max(0, uuc_reported);
+ }
+ }
+ if (uuc_reported == -EINVAL)
+ uuc_reported = chip->last_uuc_uah;
+
+ pr_debug("uuc_now = %d uuc_prev = %d stepsize = %d uuc_reported = %d\n",
+ uuc_now, chip->last_uuc_uah, (int)stepsize,
+ uuc_reported);
+
+out:
+ /* remember the reported uuc */
+ chip->last_uuc_uah = uuc_reported;
+
+ /* remember cc_uah */
+ chip->last_cc_uah = cc_uah;
+
+ /* remember this time */
+ chip->t = now;
+
+ return uuc_reported;
+}
+
+/* calculate remainging charge at the time of ocv */
+static int calculate_remaining_charge_uah(struct pm8921_bms_chip *chip,
+ struct pm8921_soc_params *raw,
+ int fcc_uah, int batt_temp,
+ int chargecycles)
+{
+ int ocv, pc;
+
+ ocv = raw->last_good_ocv_uv;
+ pc = calculate_pc(chip, ocv, batt_temp, chargecycles);
+ pr_debug("ocv = %d pc = %d\n", ocv, pc);
+ return (fcc_uah * pc) / 100;
+}
+
+static void calculate_soc_params(struct pm8921_bms_chip *chip,
+ struct pm8921_soc_params *raw,
+ int batt_temp, int chargecycles,
+ int *fcc_uah,
+ int *unusable_charge_uah,
+ int *remaining_charge_uah,
+ int *cc_uah,
+ int *rbatt)
+{
+ int soc_rbatt;
+
+ *fcc_uah = calculate_fcc_uah(chip, batt_temp, chargecycles);
+ pr_debug("FCC = %uuAh batt_temp = %d, cycles = %d\n",
+ *fcc_uah, batt_temp, chargecycles);
+
+
+ /* calculate remainging charge */
+ *remaining_charge_uah = calculate_remaining_charge_uah(chip, raw,
+ *fcc_uah, batt_temp, chargecycles);
+ pr_debug("RC = %uuAh\n", *remaining_charge_uah);
+
+ /* calculate cc micro_volt_hour */
+ calculate_cc_uah(chip, raw->cc, cc_uah);
+ pr_debug("cc_uah = %duAh raw->cc = %x cc = %lld after subtracting %x\n",
+ *cc_uah, raw->cc,
+ (int64_t)raw->cc - chip->cc_reading_at_100,
+ chip->cc_reading_at_100);
+
+ soc_rbatt = ((*remaining_charge_uah - *cc_uah) * 100) / *fcc_uah;
+ if (soc_rbatt < 0)
+ soc_rbatt = 0;
+ *rbatt = get_rbatt(chip, soc_rbatt, batt_temp);
+
+ *unusable_charge_uah = calculate_unusable_charge_uah(chip, *rbatt,
+ *fcc_uah, *cc_uah, soc_rbatt,
+ batt_temp,
+ chargecycles);
+ pr_debug("UUC = %uuAh\n", *unusable_charge_uah);
+}
+
+static int calculate_real_fcc_uah(struct pm8921_bms_chip *chip,
+ struct pm8921_soc_params *raw,
+ int batt_temp, int chargecycles,
+ int *ret_fcc_uah)
+{
+ int fcc_uah, unusable_charge_uah;
+ int remaining_charge_uah;
+ int cc_uah;
+ int real_fcc_uah;
+ int rbatt;
+
+ calculate_soc_params(chip, raw, batt_temp, chargecycles,
+ &fcc_uah,
+ &unusable_charge_uah,
+ &remaining_charge_uah,
+ &cc_uah,
+ &rbatt);
+
+ real_fcc_uah = remaining_charge_uah - cc_uah;
+ *ret_fcc_uah = fcc_uah;
+ pr_debug("real_fcc = %d, RC = %d CC = %d fcc = %d\n",
+ real_fcc_uah, remaining_charge_uah, cc_uah, fcc_uah);
+ return real_fcc_uah;
+}
+
+static int bound_soc(int soc)
+{
+ soc = max(0, soc);
+ soc = min(100, soc);
+ return soc;
+}
+
+static int last_soc_est = -EINVAL;
+static int adjust_soc(struct pm8921_bms_chip *chip, int soc, int batt_temp,
+ int rbatt , int fcc_uah, int uuc_uah, int cc_uah)
+{
+ int ibat_ua = 0, vbat_uv = 0;
+ int ocv_est_uv = 0, soc_est = 0, pc_est = 0, pc = 0;
+ int delta_ocv_uv = 0;
+ int n = 0;
+ int rc_new_uah = 0;
+ int pc_new = 0;
+ int soc_new = 0;
+ int m = 0;
+
+ pm8921_bms_get_simultaneous_battery_voltage_and_current(&ibat_ua,
+ &vbat_uv);
+
+ if (ibat_ua < 0)
+ goto out;
+ ocv_est_uv = vbat_uv + (ibat_ua * rbatt)/1000;
+ pc_est = calculate_pc(chip, ocv_est_uv, batt_temp, last_chargecycles);
+ soc_est = div_s64((s64)fcc_uah * pc_est - uuc_uah*100,
+ (s64)fcc_uah - uuc_uah);
+ soc_est = bound_soc(soc_est);
+
+ /*
+ * do not adjust if soc_est is between 45 and 25 OR soc_est is
+ * same as what bms calculated
+ */
+ if (is_between(45, 25, soc_est) || soc_est == soc)
+ goto out;
+
+ if (last_soc_est == -EINVAL)
+ last_soc_est = soc;
+
+ n = min(200, max(1 , soc + soc_est + last_soc_est));
+ /* remember the last soc_est in last_soc_est */
+ last_soc_est = soc_est;
+
+ pc = calculate_pc(chip, chip->last_ocv_uv,
+ batt_temp, last_chargecycles);
+ if (pc > 0) {
+ pc_new = calculate_pc(chip, chip->last_ocv_uv - (++m * 1000),
+ batt_temp, last_chargecycles);
+ while (pc_new == pc) {
+ /* start taking 10mV steps */
+ m = m + 10;
+ pc_new = calculate_pc(chip,
+ chip->last_ocv_uv - (m * 1000),
+ batt_temp, last_chargecycles);
+ }
+ } else {
+ /*
+ * pc is already at the lowest point,
+ * assume 1 millivolt translates to 1% pc
+ */
+ pc = 1;
+ pc_new = 0;
+ m = 1;
+ }
+
+ delta_ocv_uv = div_s64((soc - soc_est) * (s64)m * 1000,
+ n * (pc - pc_new));
+ chip->last_ocv_uv -= delta_ocv_uv;
+
+ if (chip->last_ocv_uv >= chip->max_voltage_uv)
+ chip->last_ocv_uv = chip->max_voltage_uv;
+
+ /* calculate the soc based on this new ocv */
+ pc_new = calculate_pc(chip, chip->last_ocv_uv,
+ batt_temp, last_chargecycles);
+ rc_new_uah = (fcc_uah * pc_new) / 100;
+ soc_new = (rc_new_uah - cc_uah - uuc_uah)*100 / (fcc_uah - uuc_uah);
+ soc_new = bound_soc(soc_new);
+
+ /*
+ * if soc_new is ZERO force it higher so that phone doesnt report soc=0
+ * soc = 0 should happen only when soc_est == 0
+ */
+ if (soc_new == 0 && soc_est != 0)
+ soc_new = 1;
+
+ soc = soc_new;
+
+out:
+ pr_debug("ibat_ua = %d, vbat_uv = %d, ocv_est_uv = %d, pc_est = %d, "
+ "soc_est = %d, n = %d, delta_ocv_uv = %d, last_ocv_uv = %d, "
+ "pc_new = %d, soc_new = %d\n",
+ ibat_ua, vbat_uv, ocv_est_uv, pc_est,
+ soc_est, n, delta_ocv_uv, chip->last_ocv_uv,
+ pc_new, soc_new);
+
+ return soc;
+}
+
+/*
+ * Remaining Usable Charge = remaining_charge (charge at ocv instance)
+ * - coloumb counter charge
+ * - unusable charge (due to battery resistance)
+ * SOC% = (remaining usable charge/ fcc - usable_charge);
+ */
+static int calculate_state_of_charge(struct pm8921_bms_chip *chip,
+ struct pm8921_soc_params *raw,
+ int batt_temp, int chargecycles)
+{
+ int remaining_usable_charge_uah, fcc_uah, unusable_charge_uah;
+ int remaining_charge_uah, soc;
+ int cc_uah;
+ int rbatt;
+
+ calculate_soc_params(chip, raw, batt_temp, chargecycles,
+ &fcc_uah,
+ &unusable_charge_uah,
+ &remaining_charge_uah,
+ &cc_uah,
+ &rbatt);
+
+ /* calculate remaining usable charge */
+ remaining_usable_charge_uah = remaining_charge_uah
+ - cc_uah
+ - unusable_charge_uah;
+
+ pr_debug("RUC = %duAh\n", remaining_usable_charge_uah);
+ if (fcc_uah - unusable_charge_uah <= 0) {
+ pr_warn("FCC = %duAh, UUC = %duAh forcing soc = 0\n",
+ fcc_uah, unusable_charge_uah);
+ soc = 0;
+ } else {
+ soc = (remaining_usable_charge_uah * 100)
+ / (fcc_uah - unusable_charge_uah);
+ }
+
+ if (soc > 100)
+ soc = 100;
+ pr_debug("SOC = %u%%\n", soc);
+
+ if (bms_fake_battery != -EINVAL) {
+ pr_debug("Returning Fake SOC = %d%%\n", bms_fake_battery);
+ return bms_fake_battery;
+ }
+
+ if (soc < 0) {
+ pr_err("bad rem_usb_chg = %d rem_chg %d,"
+ "cc_uah %d, unusb_chg %d\n",
+ remaining_usable_charge_uah,
+ remaining_charge_uah,
+ cc_uah, unusable_charge_uah);
+
+ pr_err("for bad rem_usb_chg last_ocv_uv = %d"
+ "chargecycles = %d, batt_temp = %d"
+ "fcc = %d soc =%d\n",
+ chip->last_ocv_uv, chargecycles, batt_temp,
+ fcc_uah, soc);
+ soc = 0;
+ }
+
+ soc = adjust_soc(chip, soc, batt_temp, rbatt,
+ fcc_uah, unusable_charge_uah, cc_uah);
+
+ if (last_soc == -EINVAL || soc <= last_soc) {
+ last_soc = soc;
+ } else {
+ /*
+ * soc > last_soc
+ * the device must be charging for reporting a higher soc, if
+ * not ignore this soc and continue reporting the last_soc
+ */
+ if (the_chip->start_percent != -EINVAL)
+ last_soc = soc;
+ else
+ pr_debug("soc = %d reporting last_soc = %d\n", soc,
+ last_soc);
+ }
+
+ pr_debug("Reported SOC = %u%%\n", last_soc);
+ return last_soc;
+}
+#define MIN_DELTA_625_UV 1000
+static void calib_hkadc(struct pm8921_bms_chip *chip)
+{
+ int voltage, rc;
+ struct pm8xxx_adc_chan_result result;
+ int usb_chg;
+ int this_delta;
+
+ rc = pm8xxx_adc_read(the_chip->ref1p25v_channel, &result);
+ if (rc) {
+ pr_err("ADC failed for 1.25volts rc = %d\n", rc);
+ return;
+ }
+ voltage = xoadc_reading_to_microvolt(result.adc_code);
+
+ pr_debug("result 1.25v = 0x%x, voltage = %duV adc_meas = %lld\n",
+ result.adc_code, voltage, result.measurement);
+
+ chip->xoadc_v125 = voltage;
+
+ rc = pm8xxx_adc_read(the_chip->ref625mv_channel, &result);
+ if (rc) {
+ pr_err("ADC failed for 1.25volts rc = %d\n", rc);
+ return;
+ }
+ voltage = xoadc_reading_to_microvolt(result.adc_code);
+
+ usb_chg = usb_chg_plugged_in();
+ pr_debug("result 0.625V = 0x%x, voltage = %duV adc_meas = %lld "
+ "usb_chg = %d\n",
+ result.adc_code, voltage, result.measurement,
+ usb_chg);
+
+ if (usb_chg)
+ chip->xoadc_v0625_usb_present = voltage;
+ else
+ chip->xoadc_v0625_usb_absent = voltage;
+
+ chip->xoadc_v0625 = voltage;
+ if (chip->xoadc_v0625_usb_present && chip->xoadc_v0625_usb_absent) {
+ this_delta = chip->xoadc_v0625_usb_present
+ - chip->xoadc_v0625_usb_absent;
+ pr_debug("this_delta= %duV\n", this_delta);
+ if (this_delta > MIN_DELTA_625_UV)
+ last_usb_cal_delta_uv = this_delta;
+ pr_debug("625V_present= %d, 625V_absent= %d, delta = %duV\n",
+ chip->xoadc_v0625_usb_present,
+ chip->xoadc_v0625_usb_absent,
+ last_usb_cal_delta_uv);
+ }
+}
+
+static void calibrate_hkadc_work(struct work_struct *work)
+{
+ struct pm8921_bms_chip *chip = container_of(work,
+ struct pm8921_bms_chip, calib_hkadc_work);
+
+ calib_hkadc(chip);
+}
+
+void pm8921_bms_calibrate_hkadc(void)
+{
+ schedule_work(&the_chip->calib_hkadc_work);
+}
+
+static void calibrate_ccadc_work(struct work_struct *work)
+{
+ struct pm8921_bms_chip *chip = container_of(work,
+ struct pm8921_bms_chip, calib_ccadc_work.work);
+
+ pm8xxx_calib_ccadc();
+ calib_hkadc(chip);
+ schedule_delayed_work(&chip->calib_ccadc_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (chip->calib_delay_ms)));
+}
+
+int pm8921_bms_get_vsense_avg(int *result)
+{
+ int rc = -EINVAL;
+
+ if (the_chip) {
+ mutex_lock(&the_chip->bms_output_lock);
+ pm_bms_lock_output_data(the_chip);
+ rc = read_vsense_avg(the_chip, result);
+ pm_bms_unlock_output_data(the_chip);
+ mutex_unlock(&the_chip->bms_output_lock);
+ }
+
+ pr_err("called before initialization\n");
+ return rc;
+}
+EXPORT_SYMBOL(pm8921_bms_get_vsense_avg);
+
+int pm8921_bms_get_battery_current(int *result_ua)
+{
+ int vsense;
+
+ if (!the_chip) {
+ pr_err("called before initialization\n");
+ return -EINVAL;
+ }
+ if (the_chip->r_sense == 0) {
+ pr_err("r_sense is zero\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&the_chip->bms_output_lock);
+ pm_bms_lock_output_data(the_chip);
+ read_vsense_avg(the_chip, &vsense);
+ pm_bms_unlock_output_data(the_chip);
+ mutex_unlock(&the_chip->bms_output_lock);
+ pr_debug("vsense=%duV\n", vsense);
+ /* cast for signed division */
+ *result_ua = vsense * 1000 / (int)the_chip->r_sense;
+ pr_debug("ibat=%duA\n", *result_ua);
+ return 0;
+}
+EXPORT_SYMBOL(pm8921_bms_get_battery_current);
+
+int pm8921_bms_get_percent_charge(void)
+{
+ int batt_temp, rc;
+ struct pm8xxx_adc_chan_result result;
+ struct pm8921_soc_params raw;
+ int soc;
+
+ if (!the_chip) {
+ pr_err("called before initialization\n");
+ return -EINVAL;
+ }
+
+ rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result);
+ if (rc) {
+ pr_err("error reading adc channel = %d, rc = %d\n",
+ the_chip->batt_temp_channel, rc);
+ return rc;
+ }
+ pr_debug("batt_temp phy = %lld meas = 0x%llx", result.physical,
+ result.measurement);
+ batt_temp = (int)result.physical;
+
+ mutex_lock(&the_chip->last_ocv_uv_mutex);
+ read_soc_params_raw(the_chip, &raw);
+
+ soc = calculate_state_of_charge(the_chip, &raw,
+ batt_temp, last_chargecycles);
+ mutex_unlock(&the_chip->last_ocv_uv_mutex);
+ return soc;
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_get_percent_charge);
+
+int pm8921_bms_get_rbatt(void)
+{
+ int batt_temp, rc;
+ struct pm8xxx_adc_chan_result result;
+ struct pm8921_soc_params raw;
+ int fcc_uah;
+ int unusable_charge_uah;
+ int remaining_charge_uah;
+ int cc_uah;
+ int rbatt;
+
+ if (!the_chip) {
+ pr_err("called before initialization\n");
+ return -EINVAL;
+ }
+
+ rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result);
+ if (rc) {
+ pr_err("error reading adc channel = %d, rc = %d\n",
+ the_chip->batt_temp_channel, rc);
+ return rc;
+ }
+ pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical,
+ result.measurement);
+ batt_temp = (int)result.physical;
+
+ mutex_lock(&the_chip->last_ocv_uv_mutex);
+
+ read_soc_params_raw(the_chip, &raw);
+
+ calculate_soc_params(the_chip, &raw, batt_temp, last_chargecycles,
+ &fcc_uah,
+ &unusable_charge_uah,
+ &remaining_charge_uah,
+ &cc_uah,
+ &rbatt);
+ mutex_unlock(&the_chip->last_ocv_uv_mutex);
+
+ return rbatt;
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_get_rbatt);
+
+int pm8921_bms_get_fcc(void)
+{
+ int batt_temp, rc;
+ struct pm8xxx_adc_chan_result result;
+
+ if (!the_chip) {
+ pr_err("called before initialization\n");
+ return -EINVAL;
+ }
+
+ rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result);
+ if (rc) {
+ pr_err("error reading adc channel = %d, rc = %d\n",
+ the_chip->batt_temp_channel, rc);
+ return rc;
+ }
+ pr_debug("batt_temp phy = %lld meas = 0x%llx", result.physical,
+ result.measurement);
+ batt_temp = (int)result.physical;
+ return calculate_fcc_uah(the_chip, batt_temp, last_chargecycles);
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_get_fcc);
+
+#define IBAT_TOL_MASK 0x0F
+#define OCV_TOL_MASK 0xF0
+#define IBAT_TOL_DEFAULT 0x03
+#define IBAT_TOL_NOCHG 0x0F
+#define OCV_TOL_DEFAULT 0x20
+#define OCV_TOL_NO_OCV 0x00
+void pm8921_bms_charging_began(void)
+{
+ int batt_temp, rc;
+ struct pm8xxx_adc_chan_result result;
+ struct pm8921_soc_params raw;
+
+ rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result);
+ if (rc) {
+ pr_err("error reading adc channel = %d, rc = %d\n",
+ the_chip->batt_temp_channel, rc);
+ return;
+ }
+ pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical,
+ result.measurement);
+ batt_temp = (int)result.physical;
+
+ mutex_lock(&the_chip->last_ocv_uv_mutex);
+ read_soc_params_raw(the_chip, &raw);
+
+ the_chip->start_percent = calculate_state_of_charge(the_chip, &raw,
+ batt_temp, last_chargecycles);
+ mutex_unlock(&the_chip->last_ocv_uv_mutex);
+
+ bms_start_percent = the_chip->start_percent;
+ bms_start_ocv_uv = raw.last_good_ocv_uv;
+ calculate_cc_uah(the_chip, raw.cc, &bms_start_cc_uah);
+ pm_bms_masked_write(the_chip, BMS_TOLERANCES,
+ IBAT_TOL_MASK, IBAT_TOL_DEFAULT);
+ pr_debug("start_percent = %u%%\n", the_chip->start_percent);
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_charging_began);
+
+#define DELTA_FCC_PERCENT 3
+#define MIN_START_PERCENT_FOR_LEARNING 30
+void pm8921_bms_charging_end(int is_battery_full)
+{
+ int batt_temp, rc;
+ struct pm8xxx_adc_chan_result result;
+ struct pm8921_soc_params raw;
+
+ if (the_chip == NULL)
+ return;
+
+ rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result);
+ if (rc) {
+ pr_err("error reading adc channel = %d, rc = %d\n",
+ the_chip->batt_temp_channel, rc);
+ return;
+ }
+ pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical,
+ result.measurement);
+ batt_temp = (int)result.physical;
+
+ mutex_lock(&the_chip->last_ocv_uv_mutex);
+
+ read_soc_params_raw(the_chip, &raw);
+
+ calculate_cc_uah(the_chip, raw.cc, &bms_end_cc_uah);
+
+ bms_end_ocv_uv = raw.last_good_ocv_uv;
+
+ if (is_battery_full && the_chip->enable_fcc_learning
+ && the_chip->start_percent <= MIN_START_PERCENT_FOR_LEARNING) {
+ int fcc_uah, new_fcc_uah, delta_fcc_uah;
+
+ new_fcc_uah = calculate_real_fcc_uah(the_chip, &raw,
+ batt_temp, last_chargecycles,
+ &fcc_uah);
+ delta_fcc_uah = new_fcc_uah - fcc_uah;
+ if (delta_fcc_uah < 0)
+ delta_fcc_uah = -delta_fcc_uah;
+
+ if (delta_fcc_uah * 100 > (DELTA_FCC_PERCENT * fcc_uah)) {
+ /* new_fcc_uah is outside the scope limit it */
+ if (new_fcc_uah > fcc_uah)
+ new_fcc_uah
+ = (fcc_uah +
+ (DELTA_FCC_PERCENT * fcc_uah) / 100);
+ else
+ new_fcc_uah
+ = (fcc_uah -
+ (DELTA_FCC_PERCENT * fcc_uah) / 100);
+
+ pr_debug("delta_fcc=%d > %d percent of fcc=%d"
+ "restring it to %d\n",
+ delta_fcc_uah, DELTA_FCC_PERCENT,
+ fcc_uah, new_fcc_uah);
+ }
+
+ last_real_fcc_mah = new_fcc_uah/1000;
+ last_real_fcc_batt_temp = batt_temp;
+ readjust_fcc_table();
+
+ }
+
+ if (is_battery_full) {
+ the_chip->ocv_reading_at_100 = raw.last_good_ocv_raw;
+ the_chip->cc_reading_at_100 = raw.cc;
+
+ the_chip->last_ocv_uv = the_chip->max_voltage_uv;
+ raw.last_good_ocv_uv = the_chip->max_voltage_uv;
+ /*
+ * since we are treating this as an ocv event
+ * forget the old cc value
+ */
+ the_chip->last_cc_uah = 0;
+ pr_debug("EOC ocv_reading = 0x%x cc = 0x%x\n",
+ the_chip->ocv_reading_at_100,
+ the_chip->cc_reading_at_100);
+ }
+
+ the_chip->end_percent = calculate_state_of_charge(the_chip, &raw,
+ batt_temp, last_chargecycles);
+ mutex_unlock(&the_chip->last_ocv_uv_mutex);
+
+ bms_end_percent = the_chip->end_percent;
+
+ if (the_chip->end_percent > the_chip->start_percent) {
+ last_charge_increase +=
+ the_chip->end_percent - the_chip->start_percent;
+ if (last_charge_increase > 100) {
+ last_chargecycles++;
+ last_charge_increase = last_charge_increase % 100;
+ }
+ }
+ pr_debug("end_percent = %u%% last_charge_increase = %d"
+ "last_chargecycles = %d\n",
+ the_chip->end_percent,
+ last_charge_increase,
+ last_chargecycles);
+ the_chip->start_percent = -EINVAL;
+ the_chip->end_percent = -EINVAL;
+ pm_bms_masked_write(the_chip, BMS_TOLERANCES,
+ IBAT_TOL_MASK, IBAT_TOL_NOCHG);
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_charging_end);
+
+int pm8921_bms_stop_ocv_updates(struct pm8921_bms_chip *chip)
+{
+ pr_debug("stopping ocv updates\n");
+ return pm_bms_masked_write(chip, BMS_TOLERANCES,
+ OCV_TOL_MASK, OCV_TOL_NO_OCV);
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_stop_ocv_updates);
+
+int pm8921_bms_start_ocv_updates(struct pm8921_bms_chip *chip)
+{
+ pr_debug("stopping ocv updates\n");
+ return pm_bms_masked_write(chip, BMS_TOLERANCES,
+ OCV_TOL_MASK, OCV_TOL_DEFAULT);
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_start_ocv_updates);
+
+static irqreturn_t pm8921_bms_sbi_write_ok_handler(int irq, void *data)
+{
+ pr_debug("irq = %d triggered", irq);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8921_bms_cc_thr_handler(int irq, void *data)
+{
+ pr_debug("irq = %d triggered", irq);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8921_bms_vsense_thr_handler(int irq, void *data)
+{
+ pr_debug("irq = %d triggered", irq);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8921_bms_vsense_for_r_handler(int irq, void *data)
+{
+ pr_debug("irq = %d triggered", irq);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8921_bms_ocv_for_r_handler(int irq, void *data)
+{
+ struct pm8921_bms_chip *chip = data;
+
+ pr_debug("irq = %d triggered", irq);
+ schedule_work(&chip->calib_hkadc_work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8921_bms_good_ocv_handler(int irq, void *data)
+{
+ struct pm8921_bms_chip *chip = data;
+
+ pr_debug("irq = %d triggered", irq);
+ schedule_work(&chip->calib_hkadc_work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8921_bms_vsense_avg_handler(int irq, void *data)
+{
+ pr_debug("irq = %d triggered", irq);
+ return IRQ_HANDLED;
+}
+
+struct pm_bms_irq_init_data {
+ unsigned int irq_id;
+ char *name;
+ unsigned long flags;
+ irqreturn_t (*handler)(int, void *);
+};
+
+#define BMS_IRQ(_id, _flags, _handler) \
+{ \
+ .irq_id = _id, \
+ .name = #_id, \
+ .flags = _flags, \
+ .handler = _handler, \
+}
+
+struct pm_bms_irq_init_data bms_irq_data[] = {
+ BMS_IRQ(PM8921_BMS_SBI_WRITE_OK, IRQF_TRIGGER_RISING,
+ pm8921_bms_sbi_write_ok_handler),
+ BMS_IRQ(PM8921_BMS_CC_THR, IRQF_TRIGGER_RISING,
+ pm8921_bms_cc_thr_handler),
+ BMS_IRQ(PM8921_BMS_VSENSE_THR, IRQF_TRIGGER_RISING,
+ pm8921_bms_vsense_thr_handler),
+ BMS_IRQ(PM8921_BMS_VSENSE_FOR_R, IRQF_TRIGGER_RISING,
+ pm8921_bms_vsense_for_r_handler),
+ BMS_IRQ(PM8921_BMS_OCV_FOR_R, IRQF_TRIGGER_RISING,
+ pm8921_bms_ocv_for_r_handler),
+ BMS_IRQ(PM8921_BMS_GOOD_OCV, IRQF_TRIGGER_RISING,
+ pm8921_bms_good_ocv_handler),
+ BMS_IRQ(PM8921_BMS_VSENSE_AVG, IRQF_TRIGGER_RISING,
+ pm8921_bms_vsense_avg_handler),
+};
+
+static void free_irqs(struct pm8921_bms_chip *chip)
+{
+ int i;
+
+ for (i = 0; i < PM_BMS_MAX_INTS; i++)
+ if (chip->pmic_bms_irq[i]) {
+ free_irq(chip->pmic_bms_irq[i], NULL);
+ chip->pmic_bms_irq[i] = 0;
+ }
+}
+
+static int __devinit request_irqs(struct pm8921_bms_chip *chip,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret, i;
+
+ ret = 0;
+ bitmap_fill(chip->enabled_irqs, PM_BMS_MAX_INTS);
+
+ for (i = 0; i < ARRAY_SIZE(bms_irq_data); i++) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ bms_irq_data[i].name);
+ if (res == NULL) {
+ pr_err("couldn't find %s\n", bms_irq_data[i].name);
+ goto err_out;
+ }
+ ret = request_irq(res->start, bms_irq_data[i].handler,
+ bms_irq_data[i].flags,
+ bms_irq_data[i].name, chip);
+ if (ret < 0) {
+ pr_err("couldn't request %d (%s) %d\n", res->start,
+ bms_irq_data[i].name, ret);
+ goto err_out;
+ }
+ chip->pmic_bms_irq[bms_irq_data[i].irq_id] = res->start;
+ pm8921_bms_disable_irq(chip, bms_irq_data[i].irq_id);
+ }
+ return 0;
+
+err_out:
+ free_irqs(chip);
+ return -EINVAL;
+}
+
+static int pm8921_bms_suspend(struct device *dev)
+{
+ int rc;
+ struct pm8xxx_adc_chan_result result;
+ struct pm8921_bms_chip *chip = dev_get_drvdata(dev);
+ struct pm8921_soc_params raw;
+ int fcc_uah;
+ int remaining_charge_uah;
+ int cc_uah;
+
+ chip->batt_temp_suspend = 0;
+ rc = pm8xxx_adc_read(chip->batt_temp_channel, &result);
+ if (rc) {
+ pr_err("error reading adc channel = %d, rc = %d\n",
+ chip->batt_temp_channel, rc);
+ }
+ chip->batt_temp_suspend = (int)result.physical;
+
+ mutex_lock(&chip->last_ocv_uv_mutex);
+ read_soc_params_raw(chip, &raw);
+
+ fcc_uah = calculate_fcc_uah(chip,
+ chip->batt_temp_suspend, last_chargecycles);
+ pr_debug("FCC = %uuAh batt_temp = %d, cycles = %d\n",
+ fcc_uah, chip->batt_temp_suspend, last_chargecycles);
+ /* calculate remainging charge */
+ remaining_charge_uah = calculate_remaining_charge_uah(chip, &raw,
+ fcc_uah, chip->batt_temp_suspend,
+ last_chargecycles);
+ pr_debug("RC = %uuAh\n", remaining_charge_uah);
+
+ /* calculate cc micro_volt_hour */
+ calculate_cc_uah(chip, raw.cc, &cc_uah);
+ pr_debug("cc_uah = %duAh raw->cc = %x cc = %lld after subtracting %x\n",
+ cc_uah, raw.cc,
+ (int64_t)raw.cc - chip->cc_reading_at_100,
+ chip->cc_reading_at_100);
+ chip->soc_rbatt_suspend = ((remaining_charge_uah - cc_uah) * 100)
+ / fcc_uah;
+ mutex_unlock(&chip->last_ocv_uv_mutex);
+
+ return 0;
+}
+
+#define DELTA_RBATT_PERCENT 10
+static int pm8921_bms_resume(struct device *dev)
+{
+ struct pm8921_rbatt_params raw;
+ struct pm8921_bms_chip *chip = dev_get_drvdata(dev);
+ int rbatt;
+ int expected_rbatt;
+ int scalefactor;
+ int delta_rbatt;
+
+ read_rbatt_params_raw(chip, &raw);
+ rbatt = calculate_rbatt_resume(chip, &raw);
+
+ if (rbatt < 0)
+ return 0;
+
+ expected_rbatt
+ = (last_rbatt < 0) ? chip->default_rbatt_mohm : last_rbatt;
+
+ if (chip->rbatt_sf_lut) {
+ scalefactor = interpolate_scalingfactor(chip,
+ chip->rbatt_sf_lut,
+ chip->batt_temp_suspend / 10,
+ chip->soc_rbatt_suspend);
+ rbatt = rbatt * 100 / scalefactor;
+ }
+
+ delta_rbatt = expected_rbatt - rbatt;
+ if (delta_rbatt)
+ delta_rbatt = -delta_rbatt;
+ /*
+ * only update last_rbatt if rbatt is within some
+ * percent of expected_rbatt
+ */
+ if (delta_rbatt * 100 <= DELTA_RBATT_PERCENT * expected_rbatt)
+ last_rbatt = rbatt;
+
+ return 0;
+}
+
+static const struct dev_pm_ops pm8921_pm_ops = {
+ .suspend = pm8921_bms_suspend,
+ .resume = pm8921_bms_resume,
+};
+#define EN_BMS_BIT BIT(7)
+#define EN_PON_HS_BIT BIT(0)
+static int __devinit pm8921_bms_hw_init(struct pm8921_bms_chip *chip)
+{
+ int rc;
+
+ rc = pm_bms_masked_write(chip, BMS_CONTROL,
+ EN_BMS_BIT | EN_PON_HS_BIT, EN_BMS_BIT | EN_PON_HS_BIT);
+ if (rc) {
+ pr_err("failed to enable pon and bms addr = %d %d",
+ BMS_CONTROL, rc);
+ }
+
+ /* The charger will call start charge later if usb is present */
+ pm_bms_masked_write(chip, BMS_TOLERANCES,
+ IBAT_TOL_MASK, IBAT_TOL_NOCHG);
+ return 0;
+}
+
+static void check_initial_ocv(struct pm8921_bms_chip *chip)
+{
+ int ocv_uv, rc;
+ int16_t ocv_raw;
+ int usb_chg;
+
+ /*
+ * Check if a ocv is available in bms hw,
+ * if not compute it here at boot time and save it
+ * in the last_ocv_uv.
+ */
+ ocv_uv = 0;
+ pm_bms_read_output_data(chip, LAST_GOOD_OCV_VALUE, &ocv_raw);
+ usb_chg = usb_chg_plugged_in();
+ rc = convert_vbatt_raw_to_uv(chip, usb_chg, ocv_raw, &ocv_uv);
+ if (rc || ocv_uv == 0) {
+ rc = adc_based_ocv(chip, &ocv_uv);
+ if (rc) {
+ pr_err("failed to read adc based ocv_uv rc = %d\n", rc);
+ ocv_uv = DEFAULT_OCV_MICROVOLTS;
+ }
+ }
+ chip->last_ocv_uv = ocv_uv;
+ pr_debug("ocv_uv = %d last_ocv_uv = %d\n", ocv_uv, chip->last_ocv_uv);
+}
+
+static int64_t read_battery_id(struct pm8921_bms_chip *chip)
+{
+ int rc;
+ struct pm8xxx_adc_chan_result result;
+
+ rc = pm8xxx_adc_read(chip->batt_id_channel, &result);
+ if (rc) {
+ pr_err("error reading batt id channel = %d, rc = %d\n",
+ chip->vbat_channel, rc);
+ return rc;
+ }
+ pr_debug("batt_id phy = %lld meas = 0x%llx\n", result.physical,
+ result.measurement);
+ return result.adc_code;
+}
+
+#define PALLADIUM_ID_MIN 0x7F40
+#define PALLADIUM_ID_MAX 0x7F5A
+#define DESAY_5200_ID_MIN 0x7F7F
+#define DESAY_5200_ID_MAX 0x802F
+static int set_battery_data(struct pm8921_bms_chip *chip)
+{
+ int64_t battery_id;
+
+ if (chip->batt_type == BATT_DESAY)
+ goto desay;
+ else if (chip->batt_type == BATT_PALLADIUM)
+ goto palladium;
+
+ battery_id = read_battery_id(chip);
+ if (battery_id < 0) {
+ pr_err("cannot read battery id err = %lld\n", battery_id);
+ return battery_id;
+ }
+
+ if (is_between(PALLADIUM_ID_MIN, PALLADIUM_ID_MAX, battery_id)) {
+ goto palladium;
+ } else if (is_between(DESAY_5200_ID_MIN, DESAY_5200_ID_MAX,
+ battery_id)) {
+ goto desay;
+ } else {
+ pr_warn("invalid battid, palladium 1500 assumed batt_id %llx\n",
+ battery_id);
+ goto palladium;
+ }
+
+palladium:
+ chip->fcc = palladium_1500_data.fcc;
+ chip->fcc_temp_lut = palladium_1500_data.fcc_temp_lut;
+ chip->fcc_sf_lut = palladium_1500_data.fcc_sf_lut;
+ chip->pc_temp_ocv_lut = palladium_1500_data.pc_temp_ocv_lut;
+ chip->pc_sf_lut = palladium_1500_data.pc_sf_lut;
+ chip->rbatt_sf_lut = palladium_1500_data.rbatt_sf_lut;
+ chip->default_rbatt_mohm
+ = palladium_1500_data.default_rbatt_mohm;
+ chip->delta_rbatt_mohm = palladium_1500_data.delta_rbatt_mohm;
+ return 0;
+desay:
+ chip->fcc = desay_5200_data.fcc;
+ chip->fcc_temp_lut = desay_5200_data.fcc_temp_lut;
+ chip->pc_temp_ocv_lut = desay_5200_data.pc_temp_ocv_lut;
+ chip->pc_sf_lut = desay_5200_data.pc_sf_lut;
+ chip->rbatt_sf_lut = desay_5200_data.rbatt_sf_lut;
+ chip->default_rbatt_mohm = desay_5200_data.default_rbatt_mohm;
+ chip->delta_rbatt_mohm = desay_5200_data.delta_rbatt_mohm;
+ return 0;
+}
+
+enum bms_request_operation {
+ CALC_RBATT,
+ CALC_FCC,
+ CALC_PC,
+ CALC_SOC,
+ CALIB_HKADC,
+ CALIB_CCADC,
+ GET_VBAT_VSENSE_SIMULTANEOUS,
+ STOP_OCV,
+ START_OCV,
+};
+
+static int test_batt_temp = 5;
+static int test_chargecycle = 150;
+static int test_ocv = 3900000;
+enum {
+ TEST_BATT_TEMP,
+ TEST_CHARGE_CYCLE,
+ TEST_OCV,
+};
+static int get_test_param(void *data, u64 * val)
+{
+ switch ((int)data) {
+ case TEST_BATT_TEMP:
+ *val = test_batt_temp;
+ break;
+ case TEST_CHARGE_CYCLE:
+ *val = test_chargecycle;
+ break;
+ case TEST_OCV:
+ *val = test_ocv;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+static int set_test_param(void *data, u64 val)
+{
+ switch ((int)data) {
+ case TEST_BATT_TEMP:
+ test_batt_temp = (int)val;
+ break;
+ case TEST_CHARGE_CYCLE:
+ test_chargecycle = (int)val;
+ break;
+ case TEST_OCV:
+ test_ocv = (int)val;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(temp_fops, get_test_param, set_test_param, "%llu\n");
+
+static int get_calc(void *data, u64 * val)
+{
+ int param = (int)data;
+ int ret = 0;
+ int ibat_ua, vbat_uv;
+ struct pm8921_soc_params raw;
+ struct pm8921_rbatt_params rraw;
+
+ read_soc_params_raw(the_chip, &raw);
+ read_rbatt_params_raw(the_chip, &rraw);
+
+ *val = 0;
+
+ /* global irq number passed in via data */
+ switch (param) {
+ case CALC_RBATT:
+ *val = calculate_rbatt_resume(the_chip, &rraw);
+ break;
+ case CALC_FCC:
+ *val = calculate_fcc_uah(the_chip, test_batt_temp,
+ test_chargecycle);
+ break;
+ case CALC_PC:
+ *val = calculate_pc(the_chip, test_ocv, test_batt_temp,
+ test_chargecycle);
+ break;
+ case CALC_SOC:
+ *val = calculate_state_of_charge(the_chip, &raw,
+ test_batt_temp, test_chargecycle);
+ break;
+ case CALIB_HKADC:
+ /* reading this will trigger calibration */
+ *val = 0;
+ calib_hkadc(the_chip);
+ break;
+ case CALIB_CCADC:
+ /* reading this will trigger calibration */
+ *val = 0;
+ pm8xxx_calib_ccadc();
+ break;
+ case GET_VBAT_VSENSE_SIMULTANEOUS:
+ /* reading this will call simultaneous vbat and vsense */
+ *val =
+ pm8921_bms_get_simultaneous_battery_voltage_and_current(
+ &ibat_ua,
+ &vbat_uv);
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+
+static int set_calc(void *data, u64 val)
+{
+ int param = (int)data;
+ int ret = 0;
+
+ switch (param) {
+ case STOP_OCV:
+ pm8921_bms_stop_ocv_updates(the_chip);
+ break;
+ case START_OCV:
+ pm8921_bms_start_ocv_updates(the_chip);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+DEFINE_SIMPLE_ATTRIBUTE(calc_fops, get_calc, set_calc, "%llu\n");
+
+static int get_reading(void *data, u64 * val)
+{
+ int param = (int)data;
+ int ret = 0;
+ struct pm8921_soc_params raw;
+ struct pm8921_rbatt_params rraw;
+
+ read_soc_params_raw(the_chip, &raw);
+ read_rbatt_params_raw(the_chip, &rraw);
+
+ *val = 0;
+
+ switch (param) {
+ case CC_MSB:
+ case CC_LSB:
+ *val = raw.cc;
+ break;
+ case LAST_GOOD_OCV_VALUE:
+ *val = raw.last_good_ocv_uv;
+ break;
+ case VBATT_FOR_RBATT:
+ *val = rraw.vbatt_for_rbatt_uv;
+ break;
+ case VSENSE_FOR_RBATT:
+ *val = rraw.vsense_for_rbatt_uv;
+ break;
+ case OCV_FOR_RBATT:
+ *val = rraw.ocv_for_rbatt_uv;
+ break;
+ case VSENSE_AVG:
+ read_vsense_avg(the_chip, (uint *)val);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reading_fops, get_reading, NULL, "%lld\n");
+
+static int get_rt_status(void *data, u64 * val)
+{
+ int i = (int)data;
+ int ret;
+
+ /* global irq number passed in via data */
+ ret = pm_bms_get_rt_status(the_chip, i);
+ *val = ret;
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(rt_fops, get_rt_status, NULL, "%llu\n");
+
+static int get_reg(void *data, u64 * val)
+{
+ int addr = (int)data;
+ int ret;
+ u8 temp;
+
+ ret = pm8xxx_readb(the_chip->dev->parent, addr, &temp);
+ if (ret) {
+ pr_err("pm8xxx_readb to %x value = %d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+ *val = temp;
+ return 0;
+}
+
+static int set_reg(void *data, u64 val)
+{
+ int addr = (int)data;
+ int ret;
+ u8 temp;
+
+ temp = (u8) val;
+ ret = pm8xxx_writeb(the_chip->dev->parent, addr, temp);
+ if (ret) {
+ pr_err("pm8xxx_writeb to %x value = %d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n");
+
+static void create_debugfs_entries(struct pm8921_bms_chip *chip)
+{
+ int i;
+
+ chip->dent = debugfs_create_dir("pm8921-bms", NULL);
+
+ if (IS_ERR(chip->dent)) {
+ pr_err("pmic bms couldnt create debugfs dir\n");
+ return;
+ }
+
+ debugfs_create_file("BMS_CONTROL", 0644, chip->dent,
+ (void *)BMS_CONTROL, ®_fops);
+ debugfs_create_file("BMS_OUTPUT0", 0644, chip->dent,
+ (void *)BMS_OUTPUT0, ®_fops);
+ debugfs_create_file("BMS_OUTPUT1", 0644, chip->dent,
+ (void *)BMS_OUTPUT1, ®_fops);
+ debugfs_create_file("BMS_TEST1", 0644, chip->dent,
+ (void *)BMS_TEST1, ®_fops);
+
+ debugfs_create_file("test_batt_temp", 0644, chip->dent,
+ (void *)TEST_BATT_TEMP, &temp_fops);
+ debugfs_create_file("test_chargecycle", 0644, chip->dent,
+ (void *)TEST_CHARGE_CYCLE, &temp_fops);
+ debugfs_create_file("test_ocv", 0644, chip->dent,
+ (void *)TEST_OCV, &temp_fops);
+
+ debugfs_create_file("read_cc", 0644, chip->dent,
+ (void *)CC_MSB, &reading_fops);
+ debugfs_create_file("read_last_good_ocv", 0644, chip->dent,
+ (void *)LAST_GOOD_OCV_VALUE, &reading_fops);
+ debugfs_create_file("read_vbatt_for_rbatt", 0644, chip->dent,
+ (void *)VBATT_FOR_RBATT, &reading_fops);
+ debugfs_create_file("read_vsense_for_rbatt", 0644, chip->dent,
+ (void *)VSENSE_FOR_RBATT, &reading_fops);
+ debugfs_create_file("read_ocv_for_rbatt", 0644, chip->dent,
+ (void *)OCV_FOR_RBATT, &reading_fops);
+ debugfs_create_file("read_vsense_avg", 0644, chip->dent,
+ (void *)VSENSE_AVG, &reading_fops);
+
+ debugfs_create_file("show_rbatt", 0644, chip->dent,
+ (void *)CALC_RBATT, &calc_fops);
+ debugfs_create_file("show_fcc", 0644, chip->dent,
+ (void *)CALC_FCC, &calc_fops);
+ debugfs_create_file("show_pc", 0644, chip->dent,
+ (void *)CALC_PC, &calc_fops);
+ debugfs_create_file("show_soc", 0644, chip->dent,
+ (void *)CALC_SOC, &calc_fops);
+ debugfs_create_file("calib_hkadc", 0644, chip->dent,
+ (void *)CALIB_HKADC, &calc_fops);
+ debugfs_create_file("calib_ccadc", 0644, chip->dent,
+ (void *)CALIB_CCADC, &calc_fops);
+ debugfs_create_file("stop_ocv", 0644, chip->dent,
+ (void *)STOP_OCV, &calc_fops);
+ debugfs_create_file("start_ocv", 0644, chip->dent,
+ (void *)START_OCV, &calc_fops);
+
+ debugfs_create_file("simultaneous", 0644, chip->dent,
+ (void *)GET_VBAT_VSENSE_SIMULTANEOUS, &calc_fops);
+
+ for (i = 0; i < ARRAY_SIZE(bms_irq_data); i++) {
+ if (chip->pmic_bms_irq[bms_irq_data[i].irq_id])
+ debugfs_create_file(bms_irq_data[i].name, 0444,
+ chip->dent,
+ (void *)bms_irq_data[i].irq_id,
+ &rt_fops);
+ }
+}
+
+#define REG_SBI_CONFIG 0x04F
+#define PAGE3_ENABLE_MASK 0x6
+#define PROGRAM_REV_MASK 0x0F
+#define PROGRAM_REV 0x9
+static int read_ocv_trim(struct pm8921_bms_chip *chip)
+{
+ int rc;
+ u8 reg, sbi_config;
+
+ rc = pm8xxx_readb(chip->dev->parent, REG_SBI_CONFIG, &sbi_config);
+ if (rc) {
+ pr_err("error = %d reading sbi config reg\n", rc);
+ return rc;
+ }
+
+ reg = sbi_config | PAGE3_ENABLE_MASK;
+ rc = pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, reg);
+ if (rc) {
+ pr_err("error = %d writing sbi config reg\n", rc);
+ return rc;
+ }
+
+ rc = pm8xxx_readb(chip->dev->parent, TEST_PROGRAM_REV, ®);
+ if (rc)
+ pr_err("Error %d reading %d addr %d\n",
+ rc, reg, TEST_PROGRAM_REV);
+ pr_err("program rev reg is 0x%x\n", reg);
+ reg &= PROGRAM_REV_MASK;
+
+ /* If the revision is equal or higher do not adjust trim delta */
+ if (reg >= PROGRAM_REV) {
+ chip->amux_2_trim_delta = 0;
+ goto restore_sbi_config;
+ }
+
+ rc = pm8xxx_readb(chip->dev->parent, AMUX_TRIM_2, ®);
+ if (rc) {
+ pr_err("error = %d reading trim reg\n", rc);
+ return rc;
+ }
+
+ pr_err("trim reg is 0x%x\n", reg);
+ chip->amux_2_trim_delta = abs(0x49 - reg);
+ pr_err("trim delta is %d\n", chip->amux_2_trim_delta);
+
+restore_sbi_config:
+ rc = pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, sbi_config);
+ if (rc) {
+ pr_err("error = %d writing sbi config reg\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int __devinit pm8921_bms_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ int vbatt;
+ struct pm8921_bms_chip *chip;
+ const struct pm8921_bms_platform_data *pdata
+ = pdev->dev.platform_data;
+
+ if (!pdata) {
+ pr_err("missing platform data\n");
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(struct pm8921_bms_chip), GFP_KERNEL);
+ if (!chip) {
+ pr_err("Cannot allocate pm_bms_chip\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&chip->bms_output_lock);
+ mutex_init(&chip->last_ocv_uv_mutex);
+ chip->dev = &pdev->dev;
+ chip->r_sense = pdata->r_sense;
+ chip->i_test = pdata->i_test;
+ chip->v_failure = pdata->v_failure;
+ chip->calib_delay_ms = pdata->calib_delay_ms;
+ chip->max_voltage_uv = pdata->max_voltage_uv;
+ chip->batt_type = pdata->battery_type;
+ chip->rconn_mohm = pdata->rconn_mohm;
+ chip->start_percent = -EINVAL;
+ chip->end_percent = -EINVAL;
+ rc = set_battery_data(chip);
+ if (rc) {
+ pr_err("%s bad battery data %d\n", __func__, rc);
+ goto free_chip;
+ }
+
+ if (chip->pc_temp_ocv_lut == NULL) {
+ pr_err("temp ocv lut table is NULL\n");
+ rc = -EINVAL;
+ goto free_chip;
+ }
+
+ /* set defaults in the battery data */
+ if (chip->default_rbatt_mohm <= 0)
+ chip->default_rbatt_mohm = DEFAULT_RBATT_MOHMS;
+
+ chip->batt_temp_channel = pdata->bms_cdata.batt_temp_channel;
+ chip->vbat_channel = pdata->bms_cdata.vbat_channel;
+ chip->ref625mv_channel = pdata->bms_cdata.ref625mv_channel;
+ chip->ref1p25v_channel = pdata->bms_cdata.ref1p25v_channel;
+ chip->batt_id_channel = pdata->bms_cdata.batt_id_channel;
+ chip->revision = pm8xxx_get_revision(chip->dev->parent);
+ chip->enable_fcc_learning = pdata->enable_fcc_learning;
+ INIT_WORK(&chip->calib_hkadc_work, calibrate_hkadc_work);
+
+ rc = request_irqs(chip, pdev);
+ if (rc) {
+ pr_err("couldn't register interrupts rc = %d\n", rc);
+ goto free_chip;
+ }
+
+ rc = pm8921_bms_hw_init(chip);
+ if (rc) {
+ pr_err("couldn't init hardware rc = %d\n", rc);
+ goto free_irqs;
+ }
+
+ platform_set_drvdata(pdev, chip);
+ the_chip = chip;
+ create_debugfs_entries(chip);
+
+ rc = read_ocv_trim(chip);
+ if (rc) {
+ pr_err("couldn't adjust ocv_trim rc= %d\n", rc);
+ goto free_irqs;
+ }
+ check_initial_ocv(chip);
+
+ INIT_DELAYED_WORK(&chip->calib_ccadc_work, calibrate_ccadc_work);
+ /* begin calibration only on chips > 2.0 */
+ if (chip->revision >= PM8XXX_REVISION_8921_2p0)
+ schedule_delayed_work(&chip->calib_ccadc_work, 0);
+
+ /* initial hkadc calibration */
+ schedule_work(&chip->calib_hkadc_work);
+ /* enable the vbatt reading interrupts for scheduling hkadc calib */
+ pm8921_bms_enable_irq(chip, PM8921_BMS_GOOD_OCV);
+ pm8921_bms_enable_irq(chip, PM8921_BMS_OCV_FOR_R);
+
+ get_battery_uvolts(chip, &vbatt);
+ pr_info("OK battery_capacity_at_boot=%d volt = %d ocv = %d\n",
+ pm8921_bms_get_percent_charge(),
+ vbatt, chip->last_ocv_uv);
+ return 0;
+
+free_irqs:
+ free_irqs(chip);
+free_chip:
+ kfree(chip);
+ return rc;
+}
+
+static int __devexit pm8921_bms_remove(struct platform_device *pdev)
+{
+ struct pm8921_bms_chip *chip = platform_get_drvdata(pdev);
+
+ free_irqs(chip);
+ kfree(chip->adjusted_fcc_temp_lut);
+ platform_set_drvdata(pdev, NULL);
+ the_chip = NULL;
+ kfree(chip);
+ return 0;
+}
+
+static struct platform_driver pm8921_bms_driver = {
+ .probe = pm8921_bms_probe,
+ .remove = __devexit_p(pm8921_bms_remove),
+ .driver = {
+ .name = PM8921_BMS_DEV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &pm8921_pm_ops
+ },
+};
+
+static int __init pm8921_bms_init(void)
+{
+ return platform_driver_register(&pm8921_bms_driver);
+}
+
+static void __exit pm8921_bms_exit(void)
+{
+ platform_driver_unregister(&pm8921_bms_driver);
+}
+
+late_initcall(pm8921_bms_init);
+module_exit(pm8921_bms_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8921 bms driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:" PM8921_BMS_DEV_NAME);
diff --git a/drivers/power/pm8921-charger.c b/drivers/power/pm8921-charger.c
new file mode 100644
index 0000000..c983389
--- /dev/null
+++ b/drivers/power/pm8921-charger.c
@@ -0,0 +1,3973 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/mfd/pm8xxx/pm8921-charger.h>
+#include <linux/mfd/pm8xxx/pm8921-bms.h>
+#include <linux/mfd/pm8xxx/pm8xxx-adc.h>
+#include <linux/mfd/pm8xxx/ccadc.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/workqueue.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+
+#include <mach/msm_xo.h>
+#include <mach/msm_hsusb.h>
+
+#define CHG_BUCK_CLOCK_CTRL 0x14
+
+#define PBL_ACCESS1 0x04
+#define PBL_ACCESS2 0x05
+#define SYS_CONFIG_1 0x06
+#define SYS_CONFIG_2 0x07
+#define CHG_CNTRL 0x204
+#define CHG_IBAT_MAX 0x205
+#define CHG_TEST 0x206
+#define CHG_BUCK_CTRL_TEST1 0x207
+#define CHG_BUCK_CTRL_TEST2 0x208
+#define CHG_BUCK_CTRL_TEST3 0x209
+#define COMPARATOR_OVERRIDE 0x20A
+#define PSI_TXRX_SAMPLE_DATA_0 0x20B
+#define PSI_TXRX_SAMPLE_DATA_1 0x20C
+#define PSI_TXRX_SAMPLE_DATA_2 0x20D
+#define PSI_TXRX_SAMPLE_DATA_3 0x20E
+#define PSI_CONFIG_STATUS 0x20F
+#define CHG_IBAT_SAFE 0x210
+#define CHG_ITRICKLE 0x211
+#define CHG_CNTRL_2 0x212
+#define CHG_VBAT_DET 0x213
+#define CHG_VTRICKLE 0x214
+#define CHG_ITERM 0x215
+#define CHG_CNTRL_3 0x216
+#define CHG_VIN_MIN 0x217
+#define CHG_TWDOG 0x218
+#define CHG_TTRKL_MAX 0x219
+#define CHG_TEMP_THRESH 0x21A
+#define CHG_TCHG_MAX 0x21B
+#define USB_OVP_CONTROL 0x21C
+#define DC_OVP_CONTROL 0x21D
+#define USB_OVP_TEST 0x21E
+#define DC_OVP_TEST 0x21F
+#define CHG_VDD_MAX 0x220
+#define CHG_VDD_SAFE 0x221
+#define CHG_VBAT_BOOT_THRESH 0x222
+#define USB_OVP_TRIM 0x355
+#define BUCK_CONTROL_TRIM1 0x356
+#define BUCK_CONTROL_TRIM2 0x357
+#define BUCK_CONTROL_TRIM3 0x358
+#define BUCK_CONTROL_TRIM4 0x359
+#define CHG_DEFAULTS_TRIM 0x35A
+#define CHG_ITRIM 0x35B
+#define CHG_TTRIM 0x35C
+#define CHG_COMP_OVR 0x20A
+#define IUSB_FINE_RES 0x2B6
+
+/* check EOC every 10 seconds */
+#define EOC_CHECK_PERIOD_MS 10000
+/* check for USB unplug every 200 msecs */
+#define UNPLUG_CHECK_WAIT_PERIOD_MS 200
+
+enum chg_fsm_state {
+ FSM_STATE_OFF_0 = 0,
+ FSM_STATE_BATFETDET_START_12 = 12,
+ FSM_STATE_BATFETDET_END_16 = 16,
+ FSM_STATE_ON_CHG_HIGHI_1 = 1,
+ FSM_STATE_ATC_2A = 2,
+ FSM_STATE_ATC_2B = 18,
+ FSM_STATE_ON_BAT_3 = 3,
+ FSM_STATE_ATC_FAIL_4 = 4 ,
+ FSM_STATE_DELAY_5 = 5,
+ FSM_STATE_ON_CHG_AND_BAT_6 = 6,
+ FSM_STATE_FAST_CHG_7 = 7,
+ FSM_STATE_TRKL_CHG_8 = 8,
+ FSM_STATE_CHG_FAIL_9 = 9,
+ FSM_STATE_EOC_10 = 10,
+ FSM_STATE_ON_CHG_VREGOK_11 = 11,
+ FSM_STATE_ATC_PAUSE_13 = 13,
+ FSM_STATE_FAST_CHG_PAUSE_14 = 14,
+ FSM_STATE_TRKL_CHG_PAUSE_15 = 15,
+ FSM_STATE_START_BOOT = 20,
+ FSM_STATE_FLCB_VREGOK = 21,
+ FSM_STATE_FLCB = 22,
+};
+
+struct fsm_state_to_batt_status {
+ enum chg_fsm_state fsm_state;
+ int batt_state;
+};
+
+static struct fsm_state_to_batt_status map[] = {
+ {FSM_STATE_OFF_0, POWER_SUPPLY_STATUS_UNKNOWN},
+ {FSM_STATE_BATFETDET_START_12, POWER_SUPPLY_STATUS_UNKNOWN},
+ {FSM_STATE_BATFETDET_END_16, POWER_SUPPLY_STATUS_UNKNOWN},
+ /*
+ * for CHG_HIGHI_1 report NOT_CHARGING if battery missing,
+ * too hot/cold, charger too hot
+ */
+ {FSM_STATE_ON_CHG_HIGHI_1, POWER_SUPPLY_STATUS_FULL},
+ {FSM_STATE_ATC_2A, POWER_SUPPLY_STATUS_CHARGING},
+ {FSM_STATE_ATC_2B, POWER_SUPPLY_STATUS_CHARGING},
+ {FSM_STATE_ON_BAT_3, POWER_SUPPLY_STATUS_DISCHARGING},
+ {FSM_STATE_ATC_FAIL_4, POWER_SUPPLY_STATUS_DISCHARGING},
+ {FSM_STATE_DELAY_5, POWER_SUPPLY_STATUS_UNKNOWN },
+ {FSM_STATE_ON_CHG_AND_BAT_6, POWER_SUPPLY_STATUS_CHARGING},
+ {FSM_STATE_FAST_CHG_7, POWER_SUPPLY_STATUS_CHARGING},
+ {FSM_STATE_TRKL_CHG_8, POWER_SUPPLY_STATUS_CHARGING},
+ {FSM_STATE_CHG_FAIL_9, POWER_SUPPLY_STATUS_DISCHARGING},
+ {FSM_STATE_EOC_10, POWER_SUPPLY_STATUS_FULL},
+ {FSM_STATE_ON_CHG_VREGOK_11, POWER_SUPPLY_STATUS_NOT_CHARGING},
+ {FSM_STATE_ATC_PAUSE_13, POWER_SUPPLY_STATUS_NOT_CHARGING},
+ {FSM_STATE_FAST_CHG_PAUSE_14, POWER_SUPPLY_STATUS_NOT_CHARGING},
+ {FSM_STATE_TRKL_CHG_PAUSE_15, POWER_SUPPLY_STATUS_NOT_CHARGING},
+ {FSM_STATE_START_BOOT, POWER_SUPPLY_STATUS_NOT_CHARGING},
+ {FSM_STATE_FLCB_VREGOK, POWER_SUPPLY_STATUS_NOT_CHARGING},
+ {FSM_STATE_FLCB, POWER_SUPPLY_STATUS_NOT_CHARGING},
+};
+
+enum chg_regulation_loop {
+ VDD_LOOP = BIT(3),
+ BAT_CURRENT_LOOP = BIT(2),
+ INPUT_CURRENT_LOOP = BIT(1),
+ INPUT_VOLTAGE_LOOP = BIT(0),
+ CHG_ALL_LOOPS = VDD_LOOP | BAT_CURRENT_LOOP
+ | INPUT_CURRENT_LOOP | INPUT_VOLTAGE_LOOP,
+};
+
+enum pmic_chg_interrupts {
+ USBIN_VALID_IRQ = 0,
+ USBIN_OV_IRQ,
+ BATT_INSERTED_IRQ,
+ VBATDET_LOW_IRQ,
+ USBIN_UV_IRQ,
+ VBAT_OV_IRQ,
+ CHGWDOG_IRQ,
+ VCP_IRQ,
+ ATCDONE_IRQ,
+ ATCFAIL_IRQ,
+ CHGDONE_IRQ,
+ CHGFAIL_IRQ,
+ CHGSTATE_IRQ,
+ LOOP_CHANGE_IRQ,
+ FASTCHG_IRQ,
+ TRKLCHG_IRQ,
+ BATT_REMOVED_IRQ,
+ BATTTEMP_HOT_IRQ,
+ CHGHOT_IRQ,
+ BATTTEMP_COLD_IRQ,
+ CHG_GONE_IRQ,
+ BAT_TEMP_OK_IRQ,
+ COARSE_DET_LOW_IRQ,
+ VDD_LOOP_IRQ,
+ VREG_OV_IRQ,
+ VBATDET_IRQ,
+ BATFET_IRQ,
+ PSI_IRQ,
+ DCIN_VALID_IRQ,
+ DCIN_OV_IRQ,
+ DCIN_UV_IRQ,
+ PM_CHG_MAX_INTS,
+};
+
+struct bms_notify {
+ int is_battery_full;
+ int is_charging;
+ struct work_struct work;
+};
+
+/**
+ * struct pm8921_chg_chip -device information
+ * @dev: device pointer to access the parent
+ * @usb_present: present status of usb
+ * @dc_present: present status of dc
+ * @usb_charger_current: usb current to charge the battery with used when
+ * the usb path is enabled or charging is resumed
+ * @safety_time: max time for which charging will happen
+ * @update_time: how frequently the userland needs to be updated
+ * @max_voltage_mv: the max volts the batt should be charged up to
+ * @min_voltage_mv: the min battery voltage before turning the FETon
+ * @cool_temp_dc: the cool temp threshold in deciCelcius
+ * @warm_temp_dc: the warm temp threshold in deciCelcius
+ * @resume_voltage_delta: the voltage delta from vdd max at which the
+ * battery should resume charging
+ * @term_current: The charging based term current
+ *
+ */
+struct pm8921_chg_chip {
+ struct device *dev;
+ unsigned int usb_present;
+ unsigned int dc_present;
+ unsigned int usb_charger_current;
+ unsigned int max_bat_chg_current;
+ unsigned int pmic_chg_irq[PM_CHG_MAX_INTS];
+ unsigned int safety_time;
+ unsigned int ttrkl_time;
+ unsigned int update_time;
+ unsigned int max_voltage_mv;
+ unsigned int min_voltage_mv;
+ int cool_temp_dc;
+ int warm_temp_dc;
+ unsigned int temp_check_period;
+ unsigned int cool_bat_chg_current;
+ unsigned int warm_bat_chg_current;
+ unsigned int cool_bat_voltage;
+ unsigned int warm_bat_voltage;
+ unsigned int is_bat_cool;
+ unsigned int is_bat_warm;
+ unsigned int resume_voltage_delta;
+ unsigned int term_current;
+ unsigned int vbat_channel;
+ unsigned int batt_temp_channel;
+ unsigned int batt_id_channel;
+ struct power_supply usb_psy;
+ struct power_supply dc_psy;
+ struct power_supply *ext_psy;
+ struct power_supply batt_psy;
+ struct dentry *dent;
+ struct bms_notify bms_notify;
+ bool keep_btm_on_suspend;
+ bool ext_charging;
+ bool ext_charge_done;
+ bool iusb_fine_res;
+ DECLARE_BITMAP(enabled_irqs, PM_CHG_MAX_INTS);
+ struct work_struct battery_id_valid_work;
+ int64_t batt_id_min;
+ int64_t batt_id_max;
+ int trkl_voltage;
+ int weak_voltage;
+ int trkl_current;
+ int weak_current;
+ int vin_min;
+ unsigned int *thermal_mitigation;
+ int thermal_levels;
+ struct delayed_work update_heartbeat_work;
+ struct delayed_work eoc_work;
+ struct delayed_work unplug_check_work;
+ struct delayed_work vin_collapse_check_work;
+ struct wake_lock eoc_wake_lock;
+ enum pm8921_chg_cold_thr cold_thr;
+ enum pm8921_chg_hot_thr hot_thr;
+ int rconn_mohm;
+ enum pm8921_chg_led_src_config led_src_config;
+};
+
+/* user space parameter to limit usb current */
+static unsigned int usb_max_current;
+/*
+ * usb_target_ma is used for wall charger
+ * adaptive input current limiting only. Use
+ * pm_iusbmax_get() to get current maximum usb current setting.
+ */
+static int usb_target_ma;
+static int charging_disabled;
+static int thermal_mitigation;
+
+static struct pm8921_chg_chip *the_chip;
+
+static struct pm8xxx_adc_arb_btm_param btm_config;
+
+static int pm_chg_masked_write(struct pm8921_chg_chip *chip, u16 addr,
+ u8 mask, u8 val)
+{
+ int rc;
+ u8 reg;
+
+ rc = pm8xxx_readb(chip->dev->parent, addr, ®);
+ if (rc) {
+ pr_err("pm8xxx_readb failed: addr=%03X, rc=%d\n", addr, rc);
+ return rc;
+ }
+ reg &= ~mask;
+ reg |= val & mask;
+ rc = pm8xxx_writeb(chip->dev->parent, addr, reg);
+ if (rc) {
+ pr_err("pm8xxx_writeb failed: addr=%03X, rc=%d\n", addr, rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int pm_chg_get_rt_status(struct pm8921_chg_chip *chip, int irq_id)
+{
+ return pm8xxx_read_irq_stat(chip->dev->parent,
+ chip->pmic_chg_irq[irq_id]);
+}
+
+/* Treat OverVoltage/UnderVoltage as source missing */
+static int is_usb_chg_plugged_in(struct pm8921_chg_chip *chip)
+{
+ return pm_chg_get_rt_status(chip, USBIN_VALID_IRQ);
+}
+
+/* Treat OverVoltage/UnderVoltage as source missing */
+static int is_dc_chg_plugged_in(struct pm8921_chg_chip *chip)
+{
+ return pm_chg_get_rt_status(chip, DCIN_VALID_IRQ);
+}
+
+#define CAPTURE_FSM_STATE_CMD 0xC2
+#define READ_BANK_7 0x70
+#define READ_BANK_4 0x40
+static int pm_chg_get_fsm_state(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int err, ret = 0;
+
+ temp = CAPTURE_FSM_STATE_CMD;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return err;
+ }
+
+ temp = READ_BANK_7;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return err;
+ }
+
+ err = pm8xxx_readb(chip->dev->parent, CHG_TEST, &temp);
+ if (err) {
+ pr_err("pm8xxx_readb fail: addr=%03X, rc=%d\n", CHG_TEST, err);
+ return err;
+ }
+ /* get the lower 4 bits */
+ ret = temp & 0xF;
+
+ temp = READ_BANK_4;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return err;
+ }
+
+ err = pm8xxx_readb(chip->dev->parent, CHG_TEST, &temp);
+ if (err) {
+ pr_err("pm8xxx_readb fail: addr=%03X, rc=%d\n", CHG_TEST, err);
+ return err;
+ }
+ /* get the upper 1 bit */
+ ret |= (temp & 0x1) << 4;
+ return ret;
+}
+
+#define READ_BANK_6 0x60
+static int pm_chg_get_regulation_loop(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int err;
+
+ temp = READ_BANK_6;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return err;
+ }
+
+ err = pm8xxx_readb(chip->dev->parent, CHG_TEST, &temp);
+ if (err) {
+ pr_err("pm8xxx_readb fail: addr=%03X, rc=%d\n", CHG_TEST, err);
+ return err;
+ }
+
+ /* return the lower 4 bits */
+ return temp & CHG_ALL_LOOPS;
+}
+
+#define CHG_USB_SUSPEND_BIT BIT(2)
+static int pm_chg_usb_suspend_enable(struct pm8921_chg_chip *chip, int enable)
+{
+ return pm_chg_masked_write(chip, CHG_CNTRL_3, CHG_USB_SUSPEND_BIT,
+ enable ? CHG_USB_SUSPEND_BIT : 0);
+}
+
+#define CHG_EN_BIT BIT(7)
+static int pm_chg_auto_enable(struct pm8921_chg_chip *chip, int enable)
+{
+ return pm_chg_masked_write(chip, CHG_CNTRL_3, CHG_EN_BIT,
+ enable ? CHG_EN_BIT : 0);
+}
+
+#define CHG_FAILED_CLEAR BIT(0)
+#define ATC_FAILED_CLEAR BIT(1)
+static int pm_chg_failed_clear(struct pm8921_chg_chip *chip, int clear)
+{
+ int rc;
+
+ rc = pm_chg_masked_write(chip, CHG_CNTRL_3, ATC_FAILED_CLEAR,
+ clear ? ATC_FAILED_CLEAR : 0);
+ rc |= pm_chg_masked_write(chip, CHG_CNTRL_3, CHG_FAILED_CLEAR,
+ clear ? CHG_FAILED_CLEAR : 0);
+ return rc;
+}
+
+#define CHG_CHARGE_DIS_BIT BIT(1)
+static int pm_chg_charge_dis(struct pm8921_chg_chip *chip, int disable)
+{
+ return pm_chg_masked_write(chip, CHG_CNTRL, CHG_CHARGE_DIS_BIT,
+ disable ? CHG_CHARGE_DIS_BIT : 0);
+}
+
+static int pm_is_chg_charge_dis(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+
+ pm8xxx_readb(chip->dev->parent, CHG_CNTRL, &temp);
+ return temp & CHG_CHARGE_DIS_BIT;
+}
+#define PM8921_CHG_V_MIN_MV 3240
+#define PM8921_CHG_V_STEP_MV 20
+#define PM8921_CHG_V_STEP_10MV_OFFSET_BIT BIT(7)
+#define PM8921_CHG_VDDMAX_MAX 4500
+#define PM8921_CHG_VDDMAX_MIN 3400
+#define PM8921_CHG_V_MASK 0x7F
+static int __pm_chg_vddmax_set(struct pm8921_chg_chip *chip, int voltage)
+{
+ int remainder;
+ u8 temp = 0;
+
+ if (voltage < PM8921_CHG_VDDMAX_MIN
+ || voltage > PM8921_CHG_VDDMAX_MAX) {
+ pr_err("bad mV=%d asked to set\n", voltage);
+ return -EINVAL;
+ }
+
+ temp = (voltage - PM8921_CHG_V_MIN_MV) / PM8921_CHG_V_STEP_MV;
+
+ remainder = voltage % 20;
+ if (remainder >= 10) {
+ temp |= PM8921_CHG_V_STEP_10MV_OFFSET_BIT;
+ }
+
+ pr_debug("voltage=%d setting %02x\n", voltage, temp);
+ return pm8xxx_writeb(chip->dev->parent, CHG_VDD_MAX, temp);
+}
+
+static int pm_chg_vddmax_get(struct pm8921_chg_chip *chip, int *voltage)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_readb(chip->dev->parent, CHG_VDD_MAX, &temp);
+ if (rc) {
+ pr_err("rc = %d while reading vdd max\n", rc);
+ *voltage = 0;
+ return rc;
+ }
+ *voltage = (int)(temp & PM8921_CHG_V_MASK) * PM8921_CHG_V_STEP_MV
+ + PM8921_CHG_V_MIN_MV;
+ if (temp & PM8921_CHG_V_STEP_10MV_OFFSET_BIT)
+ *voltage = *voltage + 10;
+ return 0;
+}
+
+static int pm_chg_vddmax_set(struct pm8921_chg_chip *chip, int voltage)
+{
+ int current_mv, ret, steps, i;
+ bool increase;
+
+ ret = 0;
+
+ if (voltage < PM8921_CHG_VDDMAX_MIN
+ || voltage > PM8921_CHG_VDDMAX_MAX) {
+ pr_err("bad mV=%d asked to set\n", voltage);
+ return -EINVAL;
+ }
+
+ ret = pm_chg_vddmax_get(chip, ¤t_mv);
+ if (ret) {
+ pr_err("Failed to read vddmax rc=%d\n", ret);
+ return -EINVAL;
+ }
+ if (current_mv == voltage)
+ return 0;
+
+ /* Only change in increments when USB is present */
+ if (is_usb_chg_plugged_in(chip)) {
+ if (current_mv < voltage) {
+ steps = (voltage - current_mv) / PM8921_CHG_V_STEP_MV;
+ increase = true;
+ } else {
+ steps = (current_mv - voltage) / PM8921_CHG_V_STEP_MV;
+ increase = false;
+ }
+ for (i = 0; i < steps; i++) {
+ if (increase)
+ current_mv += PM8921_CHG_V_STEP_MV;
+ else
+ current_mv -= PM8921_CHG_V_STEP_MV;
+ ret |= __pm_chg_vddmax_set(chip, current_mv);
+ }
+ }
+ ret |= __pm_chg_vddmax_set(chip, voltage);
+ return ret;
+}
+
+#define PM8921_CHG_VDDSAFE_MIN 3400
+#define PM8921_CHG_VDDSAFE_MAX 4500
+static int pm_chg_vddsafe_set(struct pm8921_chg_chip *chip, int voltage)
+{
+ u8 temp;
+
+ if (voltage < PM8921_CHG_VDDSAFE_MIN
+ || voltage > PM8921_CHG_VDDSAFE_MAX) {
+ pr_err("bad mV=%d asked to set\n", voltage);
+ return -EINVAL;
+ }
+ temp = (voltage - PM8921_CHG_V_MIN_MV) / PM8921_CHG_V_STEP_MV;
+ pr_debug("voltage=%d setting %02x\n", voltage, temp);
+ return pm_chg_masked_write(chip, CHG_VDD_SAFE, PM8921_CHG_V_MASK, temp);
+}
+
+#define PM8921_CHG_VBATDET_MIN 3240
+#define PM8921_CHG_VBATDET_MAX 5780
+static int pm_chg_vbatdet_set(struct pm8921_chg_chip *chip, int voltage)
+{
+ u8 temp;
+
+ if (voltage < PM8921_CHG_VBATDET_MIN
+ || voltage > PM8921_CHG_VBATDET_MAX) {
+ pr_err("bad mV=%d asked to set\n", voltage);
+ return -EINVAL;
+ }
+ temp = (voltage - PM8921_CHG_V_MIN_MV) / PM8921_CHG_V_STEP_MV;
+ pr_debug("voltage=%d setting %02x\n", voltage, temp);
+ return pm_chg_masked_write(chip, CHG_VBAT_DET, PM8921_CHG_V_MASK, temp);
+}
+
+#define PM8921_CHG_VINMIN_MIN_MV 3800
+#define PM8921_CHG_VINMIN_STEP_MV 100
+#define PM8921_CHG_VINMIN_USABLE_MAX 6500
+#define PM8921_CHG_VINMIN_USABLE_MIN 4300
+#define PM8921_CHG_VINMIN_MASK 0x1F
+static int pm_chg_vinmin_set(struct pm8921_chg_chip *chip, int voltage)
+{
+ u8 temp;
+
+ if (voltage < PM8921_CHG_VINMIN_USABLE_MIN
+ || voltage > PM8921_CHG_VINMIN_USABLE_MAX) {
+ pr_err("bad mV=%d asked to set\n", voltage);
+ return -EINVAL;
+ }
+ temp = (voltage - PM8921_CHG_VINMIN_MIN_MV) / PM8921_CHG_VINMIN_STEP_MV;
+ pr_debug("voltage=%d setting %02x\n", voltage, temp);
+ return pm_chg_masked_write(chip, CHG_VIN_MIN, PM8921_CHG_VINMIN_MASK,
+ temp);
+}
+
+static int pm_chg_vinmin_get(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int rc, voltage_mv;
+
+ rc = pm8xxx_readb(chip->dev->parent, CHG_VIN_MIN, &temp);
+ temp &= PM8921_CHG_VINMIN_MASK;
+
+ voltage_mv = PM8921_CHG_VINMIN_MIN_MV +
+ (int)temp * PM8921_CHG_VINMIN_STEP_MV;
+
+ return voltage_mv;
+}
+
+#define PM8921_CHG_IBATMAX_MIN 325
+#define PM8921_CHG_IBATMAX_MAX 2000
+#define PM8921_CHG_I_MIN_MA 225
+#define PM8921_CHG_I_STEP_MA 50
+#define PM8921_CHG_I_MASK 0x3F
+static int pm_chg_ibatmax_set(struct pm8921_chg_chip *chip, int chg_current)
+{
+ u8 temp;
+
+ if (chg_current < PM8921_CHG_IBATMAX_MIN
+ || chg_current > PM8921_CHG_IBATMAX_MAX) {
+ pr_err("bad mA=%d asked to set\n", chg_current);
+ return -EINVAL;
+ }
+ temp = (chg_current - PM8921_CHG_I_MIN_MA) / PM8921_CHG_I_STEP_MA;
+ return pm_chg_masked_write(chip, CHG_IBAT_MAX, PM8921_CHG_I_MASK, temp);
+}
+
+#define PM8921_CHG_IBATSAFE_MIN 225
+#define PM8921_CHG_IBATSAFE_MAX 3375
+static int pm_chg_ibatsafe_set(struct pm8921_chg_chip *chip, int chg_current)
+{
+ u8 temp;
+
+ if (chg_current < PM8921_CHG_IBATSAFE_MIN
+ || chg_current > PM8921_CHG_IBATSAFE_MAX) {
+ pr_err("bad mA=%d asked to set\n", chg_current);
+ return -EINVAL;
+ }
+ temp = (chg_current - PM8921_CHG_I_MIN_MA) / PM8921_CHG_I_STEP_MA;
+ return pm_chg_masked_write(chip, CHG_IBAT_SAFE,
+ PM8921_CHG_I_MASK, temp);
+}
+
+#define PM8921_CHG_ITERM_MIN_MA 50
+#define PM8921_CHG_ITERM_MAX_MA 200
+#define PM8921_CHG_ITERM_STEP_MA 10
+#define PM8921_CHG_ITERM_MASK 0xF
+static int pm_chg_iterm_set(struct pm8921_chg_chip *chip, int chg_current)
+{
+ u8 temp;
+
+ if (chg_current < PM8921_CHG_ITERM_MIN_MA
+ || chg_current > PM8921_CHG_ITERM_MAX_MA) {
+ pr_err("bad mA=%d asked to set\n", chg_current);
+ return -EINVAL;
+ }
+
+ temp = (chg_current - PM8921_CHG_ITERM_MIN_MA)
+ / PM8921_CHG_ITERM_STEP_MA;
+ return pm_chg_masked_write(chip, CHG_ITERM, PM8921_CHG_ITERM_MASK,
+ temp);
+}
+
+static int pm_chg_iterm_get(struct pm8921_chg_chip *chip, int *chg_current)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_readb(chip->dev->parent, CHG_ITERM, &temp);
+ if (rc) {
+ pr_err("err=%d reading CHG_ITEM\n", rc);
+ *chg_current = 0;
+ return rc;
+ }
+ temp &= PM8921_CHG_ITERM_MASK;
+ *chg_current = (int)temp * PM8921_CHG_ITERM_STEP_MA
+ + PM8921_CHG_ITERM_MIN_MA;
+ return 0;
+}
+
+struct usb_ma_limit_entry {
+ int usb_ma;
+ u8 value;
+};
+
+static struct usb_ma_limit_entry usb_ma_table[] = {
+ {100, 0x0},
+ {200, 0x1},
+ {500, 0x2},
+ {600, 0x3},
+ {700, 0x4},
+ {800, 0x5},
+ {850, 0x6},
+ {900, 0x8},
+ {950, 0x7},
+ {1000, 0x9},
+ {1100, 0xA},
+ {1200, 0xB},
+ {1300, 0xC},
+ {1400, 0xD},
+ {1500, 0xE},
+ {1600, 0xF},
+};
+
+#define PM8921_CHG_IUSB_MASK 0x1C
+#define PM8921_CHG_IUSB_SHIFT 2
+#define PM8921_CHG_IUSB_MAX 7
+#define PM8921_CHG_IUSB_MIN 0
+#define PM8917_IUSB_FINE_RES BIT(0)
+static int pm_chg_iusbmax_set(struct pm8921_chg_chip *chip, int reg_val)
+{
+ u8 temp, fineres;
+ int rc;
+
+ fineres = PM8917_IUSB_FINE_RES & usb_ma_table[reg_val].value;
+ reg_val = usb_ma_table[reg_val].value >> 1;
+
+ if (reg_val < PM8921_CHG_IUSB_MIN || reg_val > PM8921_CHG_IUSB_MAX) {
+ pr_err("bad mA=%d asked to set\n", reg_val);
+ return -EINVAL;
+ }
+ temp = reg_val << PM8921_CHG_IUSB_SHIFT;
+
+ /* IUSB_FINE_RES */
+ if (chip->iusb_fine_res) {
+ /* Clear IUSB_FINE_RES bit to avoid overshoot */
+ rc = pm_chg_masked_write(chip, IUSB_FINE_RES,
+ PM8917_IUSB_FINE_RES, 0);
+
+ rc |= pm_chg_masked_write(chip, PBL_ACCESS2,
+ PM8921_CHG_IUSB_MASK, temp);
+
+ if (rc) {
+ pr_err("Failed to write PBL_ACCESS2 rc=%d\n", rc);
+ return rc;
+ }
+
+ if (fineres) {
+ rc = pm_chg_masked_write(chip, IUSB_FINE_RES,
+ PM8917_IUSB_FINE_RES, fineres);
+ if (rc)
+ pr_err("Failed to write ISUB_FINE_RES rc=%d\n",
+ rc);
+ }
+ } else {
+ rc = pm_chg_masked_write(chip, PBL_ACCESS2,
+ PM8921_CHG_IUSB_MASK, temp);
+ if (rc)
+ pr_err("Failed to write PBL_ACCESS2 rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+static int pm_chg_iusbmax_get(struct pm8921_chg_chip *chip, int *mA)
+{
+ u8 temp, fineres;
+ int rc, i;
+
+ fineres = 0;
+ *mA = 0;
+ rc = pm8xxx_readb(chip->dev->parent, PBL_ACCESS2, &temp);
+ if (rc) {
+ pr_err("err=%d reading PBL_ACCESS2\n", rc);
+ return rc;
+ }
+
+ if (chip->iusb_fine_res) {
+ rc = pm8xxx_readb(chip->dev->parent, IUSB_FINE_RES, &fineres);
+ if (rc) {
+ pr_err("err=%d reading IUSB_FINE_RES\n", rc);
+ return rc;
+ }
+ }
+ temp &= PM8921_CHG_IUSB_MASK;
+ temp = temp >> PM8921_CHG_IUSB_SHIFT;
+
+ temp = (temp << 1) | (fineres & PM8917_IUSB_FINE_RES);
+ for (i = ARRAY_SIZE(usb_ma_table) - 1; i >= 0; i--) {
+ if (usb_ma_table[i].value == temp)
+ break;
+ }
+
+ *mA = usb_ma_table[i].usb_ma;
+
+ return rc;
+}
+
+#define PM8921_CHG_WD_MASK 0x1F
+static int pm_chg_disable_wd(struct pm8921_chg_chip *chip)
+{
+ /* writing 0 to the wd timer disables it */
+ return pm_chg_masked_write(chip, CHG_TWDOG, PM8921_CHG_WD_MASK, 0);
+}
+
+#define PM8921_CHG_TCHG_MASK 0x7F
+#define PM8921_CHG_TCHG_MIN 4
+#define PM8921_CHG_TCHG_MAX 512
+#define PM8921_CHG_TCHG_STEP 4
+static int pm_chg_tchg_max_set(struct pm8921_chg_chip *chip, int minutes)
+{
+ u8 temp;
+
+ if (minutes < PM8921_CHG_TCHG_MIN || minutes > PM8921_CHG_TCHG_MAX) {
+ pr_err("bad max minutes =%d asked to set\n", minutes);
+ return -EINVAL;
+ }
+
+ temp = (minutes - 1)/PM8921_CHG_TCHG_STEP;
+ return pm_chg_masked_write(chip, CHG_TCHG_MAX, PM8921_CHG_TCHG_MASK,
+ temp);
+}
+
+#define PM8921_CHG_TTRKL_MASK 0x1F
+#define PM8921_CHG_TTRKL_MIN 1
+#define PM8921_CHG_TTRKL_MAX 64
+static int pm_chg_ttrkl_max_set(struct pm8921_chg_chip *chip, int minutes)
+{
+ u8 temp;
+
+ if (minutes < PM8921_CHG_TTRKL_MIN || minutes > PM8921_CHG_TTRKL_MAX) {
+ pr_err("bad max minutes =%d asked to set\n", minutes);
+ return -EINVAL;
+ }
+
+ temp = minutes - 1;
+ return pm_chg_masked_write(chip, CHG_TTRKL_MAX, PM8921_CHG_TTRKL_MASK,
+ temp);
+}
+
+#define PM8921_CHG_VTRKL_MIN_MV 2050
+#define PM8921_CHG_VTRKL_MAX_MV 2800
+#define PM8921_CHG_VTRKL_STEP_MV 50
+#define PM8921_CHG_VTRKL_SHIFT 4
+#define PM8921_CHG_VTRKL_MASK 0xF0
+static int pm_chg_vtrkl_low_set(struct pm8921_chg_chip *chip, int millivolts)
+{
+ u8 temp;
+
+ if (millivolts < PM8921_CHG_VTRKL_MIN_MV
+ || millivolts > PM8921_CHG_VTRKL_MAX_MV) {
+ pr_err("bad voltage = %dmV asked to set\n", millivolts);
+ return -EINVAL;
+ }
+
+ temp = (millivolts - PM8921_CHG_VTRKL_MIN_MV)/PM8921_CHG_VTRKL_STEP_MV;
+ temp = temp << PM8921_CHG_VTRKL_SHIFT;
+ return pm_chg_masked_write(chip, CHG_VTRICKLE, PM8921_CHG_VTRKL_MASK,
+ temp);
+}
+
+#define PM8921_CHG_VWEAK_MIN_MV 2100
+#define PM8921_CHG_VWEAK_MAX_MV 3600
+#define PM8921_CHG_VWEAK_STEP_MV 100
+#define PM8921_CHG_VWEAK_MASK 0x0F
+static int pm_chg_vweak_set(struct pm8921_chg_chip *chip, int millivolts)
+{
+ u8 temp;
+
+ if (millivolts < PM8921_CHG_VWEAK_MIN_MV
+ || millivolts > PM8921_CHG_VWEAK_MAX_MV) {
+ pr_err("bad voltage = %dmV asked to set\n", millivolts);
+ return -EINVAL;
+ }
+
+ temp = (millivolts - PM8921_CHG_VWEAK_MIN_MV)/PM8921_CHG_VWEAK_STEP_MV;
+ return pm_chg_masked_write(chip, CHG_VTRICKLE, PM8921_CHG_VWEAK_MASK,
+ temp);
+}
+
+#define PM8921_CHG_ITRKL_MIN_MA 50
+#define PM8921_CHG_ITRKL_MAX_MA 200
+#define PM8921_CHG_ITRKL_MASK 0x0F
+#define PM8921_CHG_ITRKL_STEP_MA 10
+static int pm_chg_itrkl_set(struct pm8921_chg_chip *chip, int milliamps)
+{
+ u8 temp;
+
+ if (milliamps < PM8921_CHG_ITRKL_MIN_MA
+ || milliamps > PM8921_CHG_ITRKL_MAX_MA) {
+ pr_err("bad current = %dmA asked to set\n", milliamps);
+ return -EINVAL;
+ }
+
+ temp = (milliamps - PM8921_CHG_ITRKL_MIN_MA)/PM8921_CHG_ITRKL_STEP_MA;
+
+ return pm_chg_masked_write(chip, CHG_ITRICKLE, PM8921_CHG_ITRKL_MASK,
+ temp);
+}
+
+#define PM8921_CHG_IWEAK_MIN_MA 325
+#define PM8921_CHG_IWEAK_MAX_MA 525
+#define PM8921_CHG_IWEAK_SHIFT 7
+#define PM8921_CHG_IWEAK_MASK 0x80
+static int pm_chg_iweak_set(struct pm8921_chg_chip *chip, int milliamps)
+{
+ u8 temp;
+
+ if (milliamps < PM8921_CHG_IWEAK_MIN_MA
+ || milliamps > PM8921_CHG_IWEAK_MAX_MA) {
+ pr_err("bad current = %dmA asked to set\n", milliamps);
+ return -EINVAL;
+ }
+
+ if (milliamps < PM8921_CHG_IWEAK_MAX_MA)
+ temp = 0;
+ else
+ temp = 1;
+
+ temp = temp << PM8921_CHG_IWEAK_SHIFT;
+ return pm_chg_masked_write(chip, CHG_ITRICKLE, PM8921_CHG_IWEAK_MASK,
+ temp);
+}
+
+#define PM8921_CHG_BATT_TEMP_THR_COLD BIT(1)
+#define PM8921_CHG_BATT_TEMP_THR_COLD_SHIFT 1
+static int pm_chg_batt_cold_temp_config(struct pm8921_chg_chip *chip,
+ enum pm8921_chg_cold_thr cold_thr)
+{
+ u8 temp;
+
+ temp = cold_thr << PM8921_CHG_BATT_TEMP_THR_COLD_SHIFT;
+ temp = temp & PM8921_CHG_BATT_TEMP_THR_COLD;
+ return pm_chg_masked_write(chip, CHG_CNTRL_2,
+ PM8921_CHG_BATT_TEMP_THR_COLD,
+ temp);
+}
+
+#define PM8921_CHG_BATT_TEMP_THR_HOT BIT(0)
+#define PM8921_CHG_BATT_TEMP_THR_HOT_SHIFT 0
+static int pm_chg_batt_hot_temp_config(struct pm8921_chg_chip *chip,
+ enum pm8921_chg_hot_thr hot_thr)
+{
+ u8 temp;
+
+ temp = hot_thr << PM8921_CHG_BATT_TEMP_THR_HOT_SHIFT;
+ temp = temp & PM8921_CHG_BATT_TEMP_THR_HOT;
+ return pm_chg_masked_write(chip, CHG_CNTRL_2,
+ PM8921_CHG_BATT_TEMP_THR_HOT,
+ temp);
+}
+
+#define PM8921_CHG_LED_SRC_CONFIG_SHIFT 4
+#define PM8921_CHG_LED_SRC_CONFIG_MASK 0x30
+static int pm_chg_led_src_config(struct pm8921_chg_chip *chip,
+ enum pm8921_chg_led_src_config led_src_config)
+{
+ u8 temp;
+
+ if (led_src_config < LED_SRC_GND ||
+ led_src_config > LED_SRC_BYPASS)
+ return -EINVAL;
+
+ if (led_src_config == LED_SRC_BYPASS)
+ return 0;
+
+ temp = led_src_config << PM8921_CHG_LED_SRC_CONFIG_SHIFT;
+
+ return pm_chg_masked_write(chip, CHG_CNTRL_3,
+ PM8921_CHG_LED_SRC_CONFIG_MASK, temp);
+}
+
+static void disable_input_voltage_regulation(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x70);
+ if (rc) {
+ pr_err("Failed to write 0x70 to CTRL_TEST3 rc = %d\n", rc);
+ return;
+ }
+ rc = pm8xxx_readb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, &temp);
+ if (rc) {
+ pr_err("Failed to read CTRL_TEST3 rc = %d\n", rc);
+ return;
+ }
+ /* set the input voltage disable bit and the write bit */
+ temp |= 0x81;
+ rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, temp);
+ if (rc) {
+ pr_err("Failed to write 0x%x to CTRL_TEST3 rc=%d\n", temp, rc);
+ return;
+ }
+}
+
+static void enable_input_voltage_regulation(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x70);
+ if (rc) {
+ pr_err("Failed to write 0x70 to CTRL_TEST3 rc = %d\n", rc);
+ return;
+ }
+ rc = pm8xxx_readb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, &temp);
+ if (rc) {
+ pr_err("Failed to read CTRL_TEST3 rc = %d\n", rc);
+ return;
+ }
+ /* unset the input voltage disable bit */
+ temp &= 0xFE;
+ /* set the write bit */
+ temp |= 0x80;
+ rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, temp);
+ if (rc) {
+ pr_err("Failed to write 0x%x to CTRL_TEST3 rc=%d\n", temp, rc);
+ return;
+ }
+}
+
+static int64_t read_battery_id(struct pm8921_chg_chip *chip)
+{
+ int rc;
+ struct pm8xxx_adc_chan_result result;
+
+ rc = pm8xxx_adc_read(chip->batt_id_channel, &result);
+ if (rc) {
+ pr_err("error reading batt id channel = %d, rc = %d\n",
+ chip->vbat_channel, rc);
+ return rc;
+ }
+ pr_debug("batt_id phy = %lld meas = 0x%llx\n", result.physical,
+ result.measurement);
+ return result.physical;
+}
+
+static int is_battery_valid(struct pm8921_chg_chip *chip)
+{
+ int64_t rc;
+
+ if (chip->batt_id_min == 0 && chip->batt_id_max == 0)
+ return 1;
+
+ rc = read_battery_id(chip);
+ if (rc < 0) {
+ pr_err("error reading batt id channel = %d, rc = %lld\n",
+ chip->vbat_channel, rc);
+ /* assume battery id is valid when adc error happens */
+ return 1;
+ }
+
+ if (rc < chip->batt_id_min || rc > chip->batt_id_max) {
+ pr_err("batt_id phy =%lld is not valid\n", rc);
+ return 0;
+ }
+ return 1;
+}
+
+static void check_battery_valid(struct pm8921_chg_chip *chip)
+{
+ if (is_battery_valid(chip) == 0) {
+ pr_err("batt_id not valid, disbling charging\n");
+ pm_chg_auto_enable(chip, 0);
+ } else {
+ pm_chg_auto_enable(chip, !charging_disabled);
+ }
+}
+
+static void battery_id_valid(struct work_struct *work)
+{
+ struct pm8921_chg_chip *chip = container_of(work,
+ struct pm8921_chg_chip, battery_id_valid_work);
+
+ check_battery_valid(chip);
+}
+
+static void pm8921_chg_enable_irq(struct pm8921_chg_chip *chip, int interrupt)
+{
+ if (!__test_and_set_bit(interrupt, chip->enabled_irqs)) {
+ dev_dbg(chip->dev, "%d\n", chip->pmic_chg_irq[interrupt]);
+ enable_irq(chip->pmic_chg_irq[interrupt]);
+ }
+}
+
+static void pm8921_chg_disable_irq(struct pm8921_chg_chip *chip, int interrupt)
+{
+ if (__test_and_clear_bit(interrupt, chip->enabled_irqs)) {
+ dev_dbg(chip->dev, "%d\n", chip->pmic_chg_irq[interrupt]);
+ disable_irq_nosync(chip->pmic_chg_irq[interrupt]);
+ }
+}
+
+static int pm8921_chg_is_enabled(struct pm8921_chg_chip *chip, int interrupt)
+{
+ return test_bit(interrupt, chip->enabled_irqs);
+}
+
+static bool is_ext_charging(struct pm8921_chg_chip *chip)
+{
+ union power_supply_propval ret = {0,};
+
+ if (!chip->ext_psy)
+ return false;
+ if (chip->ext_psy->get_property(chip->ext_psy,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &ret))
+ return false;
+ if (ret.intval > POWER_SUPPLY_CHARGE_TYPE_NONE)
+ return ret.intval;
+
+ return false;
+}
+
+static bool is_ext_trickle_charging(struct pm8921_chg_chip *chip)
+{
+ union power_supply_propval ret = {0,};
+
+ if (!chip->ext_psy)
+ return false;
+ if (chip->ext_psy->get_property(chip->ext_psy,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &ret))
+ return false;
+ if (ret.intval == POWER_SUPPLY_CHARGE_TYPE_TRICKLE)
+ return true;
+
+ return false;
+}
+
+static int is_battery_charging(int fsm_state)
+{
+ if (is_ext_charging(the_chip))
+ return 1;
+
+ switch (fsm_state) {
+ case FSM_STATE_ATC_2A:
+ case FSM_STATE_ATC_2B:
+ case FSM_STATE_ON_CHG_AND_BAT_6:
+ case FSM_STATE_FAST_CHG_7:
+ case FSM_STATE_TRKL_CHG_8:
+ return 1;
+ }
+ return 0;
+}
+
+static void bms_notify(struct work_struct *work)
+{
+ struct bms_notify *n = container_of(work, struct bms_notify, work);
+
+ if (n->is_charging) {
+ pm8921_bms_charging_began();
+ } else {
+ pm8921_bms_charging_end(n->is_battery_full);
+ n->is_battery_full = 0;
+ }
+}
+
+static void bms_notify_check(struct pm8921_chg_chip *chip)
+{
+ int fsm_state, new_is_charging;
+
+ fsm_state = pm_chg_get_fsm_state(chip);
+ new_is_charging = is_battery_charging(fsm_state);
+
+ if (chip->bms_notify.is_charging ^ new_is_charging) {
+ chip->bms_notify.is_charging = new_is_charging;
+ schedule_work(&(chip->bms_notify.work));
+ }
+}
+
+static enum power_supply_property pm_power_props_usb[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static enum power_supply_property pm_power_props_mains[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *pm_power_supplied_to[] = {
+ "battery",
+};
+
+#define USB_WALL_THRESHOLD_MA 500
+static int pm_power_get_property_mains(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ /* Check if called before init */
+ if (!the_chip)
+ return -EINVAL;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (charging_disabled)
+ return 0;
+
+ /* check external charger first before the dc path */
+ if (is_ext_charging(the_chip)) {
+ val->intval = 1;
+ return 0;
+ }
+
+ if (pm_is_chg_charge_dis(the_chip)) {
+ val->intval = 0;
+ return 0;
+ }
+
+ if (the_chip->dc_present) {
+ val->intval = 1;
+ return 0;
+ }
+
+ /* USB with max current greater than 500 mA connected */
+ if (usb_target_ma > USB_WALL_THRESHOLD_MA)
+ val->intval = is_usb_chg_plugged_in(the_chip);
+ return 0;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int pm_power_get_property_usb(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int current_max;
+
+ /* Check if called before init */
+ if (!the_chip)
+ return -EINVAL;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (pm_is_chg_charge_dis(the_chip)) {
+ val->intval = 0;
+ } else {
+ pm_chg_iusbmax_get(the_chip, ¤t_max);
+ val->intval = current_max;
+ }
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (charging_disabled)
+ return 0;
+
+ /*
+ * if drawing any current from usb is disabled behave
+ * as if no usb cable is connected
+ */
+ if (pm_is_chg_charge_dis(the_chip))
+ return 0;
+
+ /* USB charging */
+ if (usb_target_ma < USB_WALL_THRESHOLD_MA)
+ val->intval = is_usb_chg_plugged_in(the_chip);
+ else
+ return 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property msm_batt_power_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_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+};
+
+static int get_prop_battery_uvolts(struct pm8921_chg_chip *chip)
+{
+ int rc;
+ struct pm8xxx_adc_chan_result result;
+
+ rc = pm8xxx_adc_read(chip->vbat_channel, &result);
+ if (rc) {
+ pr_err("error reading adc channel = %d, rc = %d\n",
+ chip->vbat_channel, rc);
+ return rc;
+ }
+ pr_debug("mvolts phy = %lld meas = 0x%llx\n", result.physical,
+ result.measurement);
+ return (int)result.physical;
+}
+
+static unsigned int voltage_based_capacity(struct pm8921_chg_chip *chip)
+{
+ unsigned int current_voltage_uv = get_prop_battery_uvolts(chip);
+ unsigned int current_voltage_mv = current_voltage_uv / 1000;
+ unsigned int low_voltage = chip->min_voltage_mv;
+ unsigned int high_voltage = chip->max_voltage_mv;
+
+ if (current_voltage_mv <= low_voltage)
+ return 0;
+ else if (current_voltage_mv >= high_voltage)
+ return 100;
+ else
+ return (current_voltage_mv - low_voltage) * 100
+ / (high_voltage - low_voltage);
+}
+
+static int get_prop_batt_present(struct pm8921_chg_chip *chip)
+{
+ return pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ);
+}
+
+static int get_prop_batt_capacity(struct pm8921_chg_chip *chip)
+{
+ int percent_soc;
+
+ if (!get_prop_batt_present(chip))
+ percent_soc = voltage_based_capacity(chip);
+ else
+ percent_soc = pm8921_bms_get_percent_charge();
+
+ if (percent_soc == -ENXIO)
+ percent_soc = voltage_based_capacity(chip);
+
+ if (percent_soc <= 10)
+ pr_warn("low battery charge = %d%%\n", percent_soc);
+
+ return percent_soc;
+}
+
+static int get_prop_batt_current(struct pm8921_chg_chip *chip)
+{
+ int result_ua, rc;
+
+ rc = pm8921_bms_get_battery_current(&result_ua);
+ if (rc == -ENXIO) {
+ rc = pm8xxx_ccadc_get_battery_current(&result_ua);
+ }
+
+ if (rc) {
+ pr_err("unable to get batt current rc = %d\n", rc);
+ return rc;
+ } else {
+ return result_ua;
+ }
+}
+
+static int get_prop_batt_fcc(struct pm8921_chg_chip *chip)
+{
+ int rc;
+
+ rc = pm8921_bms_get_fcc();
+ if (rc < 0)
+ pr_err("unable to get batt fcc rc = %d\n", rc);
+ return rc;
+}
+
+static int get_prop_batt_health(struct pm8921_chg_chip *chip)
+{
+ int temp;
+
+ temp = pm_chg_get_rt_status(chip, BATTTEMP_HOT_IRQ);
+ if (temp)
+ return POWER_SUPPLY_HEALTH_OVERHEAT;
+
+ temp = pm_chg_get_rt_status(chip, BATTTEMP_COLD_IRQ);
+ if (temp)
+ return POWER_SUPPLY_HEALTH_COLD;
+
+ return POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static int get_prop_charge_type(struct pm8921_chg_chip *chip)
+{
+ int temp;
+
+ if (!get_prop_batt_present(chip))
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ if (is_ext_trickle_charging(chip))
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+
+ if (is_ext_charging(chip))
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+
+ temp = pm_chg_get_rt_status(chip, TRKLCHG_IRQ);
+ if (temp)
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+
+ temp = pm_chg_get_rt_status(chip, FASTCHG_IRQ);
+ if (temp)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+static int get_prop_batt_status(struct pm8921_chg_chip *chip)
+{
+ int batt_state = POWER_SUPPLY_STATUS_DISCHARGING;
+ int fsm_state = pm_chg_get_fsm_state(chip);
+ int i;
+
+ if (chip->ext_psy) {
+ if (chip->ext_charge_done)
+ return POWER_SUPPLY_STATUS_FULL;
+ if (chip->ext_charging)
+ return POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(map); i++)
+ if (map[i].fsm_state == fsm_state)
+ batt_state = map[i].batt_state;
+
+ if (fsm_state == FSM_STATE_ON_CHG_HIGHI_1) {
+ if (!pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ)
+ || !pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ)
+ || pm_chg_get_rt_status(chip, CHGHOT_IRQ)
+ || pm_chg_get_rt_status(chip, VBATDET_LOW_IRQ))
+
+ batt_state = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+ return batt_state;
+}
+
+#define MAX_TOLERABLE_BATT_TEMP_DDC 680
+static int get_prop_batt_temp(struct pm8921_chg_chip *chip)
+{
+ int rc;
+ struct pm8xxx_adc_chan_result result;
+
+ rc = pm8xxx_adc_read(chip->batt_temp_channel, &result);
+ if (rc) {
+ pr_err("error reading adc channel = %d, rc = %d\n",
+ chip->vbat_channel, rc);
+ return rc;
+ }
+ pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical,
+ result.measurement);
+ if (result.physical > MAX_TOLERABLE_BATT_TEMP_DDC)
+ pr_err("BATT_TEMP= %d > 68degC, device will be shutdown\n",
+ (int) result.physical);
+
+ return (int)result.physical;
+}
+
+static int pm_batt_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pm8921_chg_chip *chip = container_of(psy, struct pm8921_chg_chip,
+ batt_psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = get_prop_batt_status(chip);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = get_prop_charge_type(chip);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = get_prop_batt_health(chip);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = get_prop_batt_present(chip);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = chip->max_voltage_mv * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = chip->min_voltage_mv * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = get_prop_battery_uvolts(chip);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = get_prop_batt_capacity(chip);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = get_prop_batt_current(chip);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = get_prop_batt_temp(chip);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ val->intval = get_prop_batt_fcc(chip) * 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void (*notify_vbus_state_func_ptr)(int);
+static int usb_chg_current;
+static DEFINE_SPINLOCK(vbus_lock);
+
+int pm8921_charger_register_vbus_sn(void (*callback)(int))
+{
+ pr_debug("%p\n", callback);
+ notify_vbus_state_func_ptr = callback;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pm8921_charger_register_vbus_sn);
+
+/* this is passed to the hsusb via platform_data msm_otg_pdata */
+void pm8921_charger_unregister_vbus_sn(void (*callback)(int))
+{
+ pr_debug("%p\n", callback);
+ notify_vbus_state_func_ptr = NULL;
+}
+EXPORT_SYMBOL_GPL(pm8921_charger_unregister_vbus_sn);
+
+static void notify_usb_of_the_plugin_event(int plugin)
+{
+ plugin = !!plugin;
+ if (notify_vbus_state_func_ptr) {
+ pr_debug("notifying plugin\n");
+ (*notify_vbus_state_func_ptr) (plugin);
+ } else {
+ pr_debug("unable to notify plugin\n");
+ }
+}
+
+/* assumes vbus_lock is held */
+static void __pm8921_charger_vbus_draw(unsigned int mA)
+{
+ int i, rc;
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return;
+ }
+
+ if (mA >= 0 && mA <= 2) {
+ usb_chg_current = 0;
+ rc = pm_chg_iusbmax_set(the_chip, 0);
+ if (rc) {
+ pr_err("unable to set iusb to %d rc = %d\n", 0, rc);
+ }
+ rc = pm_chg_usb_suspend_enable(the_chip, 1);
+ if (rc)
+ pr_err("fail to set suspend bit rc=%d\n", rc);
+ } else {
+ rc = pm_chg_usb_suspend_enable(the_chip, 0);
+ if (rc)
+ pr_err("fail to reset suspend bit rc=%d\n", rc);
+ for (i = ARRAY_SIZE(usb_ma_table) - 1; i >= 0; i--) {
+ if (usb_ma_table[i].usb_ma <= mA)
+ break;
+ }
+
+ /* Check if IUSB_FINE_RES is available */
+ if ((usb_ma_table[i].value & PM8917_IUSB_FINE_RES)
+ && !the_chip->iusb_fine_res)
+ i--;
+ if (i < 0)
+ i = 0;
+ rc = pm_chg_iusbmax_set(the_chip, i);
+ if (rc) {
+ pr_err("unable to set iusb to %d rc = %d\n", i, rc);
+ }
+ }
+}
+
+/* USB calls these to tell us how much max usb current the system can draw */
+void pm8921_charger_vbus_draw(unsigned int mA)
+{
+ unsigned long flags;
+
+ pr_debug("Enter charge=%d\n", mA);
+
+ if (!the_chip) {
+ pr_err("chip not yet initalized\n");
+ return;
+ }
+
+ /*
+ * Reject VBUS requests if USB connection is the only available
+ * power source. This makes sure that if booting without
+ * battery the iusb_max value is not decreased avoiding potential
+ * brown_outs.
+ *
+ * This would also apply when the battery has been
+ * removed from the running system.
+ */
+ if (!get_prop_batt_present(the_chip)
+ && !is_dc_chg_plugged_in(the_chip)) {
+ pr_err("rejected: no other power source connected\n");
+ return;
+ }
+
+ if (usb_max_current && mA > usb_max_current) {
+ pr_warn("restricting usb current to %d instead of %d\n",
+ usb_max_current, mA);
+ mA = usb_max_current;
+ }
+ if (usb_target_ma == 0 && mA > USB_WALL_THRESHOLD_MA)
+ usb_target_ma = mA;
+
+ spin_lock_irqsave(&vbus_lock, flags);
+ if (the_chip) {
+ if (mA > USB_WALL_THRESHOLD_MA)
+ __pm8921_charger_vbus_draw(USB_WALL_THRESHOLD_MA);
+ else
+ __pm8921_charger_vbus_draw(mA);
+ } else {
+ /*
+ * called before pmic initialized,
+ * save this value and use it at probe
+ */
+ if (mA > USB_WALL_THRESHOLD_MA)
+ usb_chg_current = USB_WALL_THRESHOLD_MA;
+ else
+ usb_chg_current = mA;
+ }
+ spin_unlock_irqrestore(&vbus_lock, flags);
+}
+EXPORT_SYMBOL_GPL(pm8921_charger_vbus_draw);
+
+int pm8921_charger_enable(bool enable)
+{
+ int rc;
+
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ enable = !!enable;
+ rc = pm_chg_auto_enable(the_chip, enable);
+ if (rc)
+ pr_err("Failed rc=%d\n", rc);
+ return rc;
+}
+EXPORT_SYMBOL(pm8921_charger_enable);
+
+int pm8921_is_usb_chg_plugged_in(void)
+{
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ return is_usb_chg_plugged_in(the_chip);
+}
+EXPORT_SYMBOL(pm8921_is_usb_chg_plugged_in);
+
+int pm8921_is_dc_chg_plugged_in(void)
+{
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ return is_dc_chg_plugged_in(the_chip);
+}
+EXPORT_SYMBOL(pm8921_is_dc_chg_plugged_in);
+
+int pm8921_is_battery_present(void)
+{
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ return get_prop_batt_present(the_chip);
+}
+EXPORT_SYMBOL(pm8921_is_battery_present);
+
+/*
+ * Disabling the charge current limit causes current
+ * current limits to have no monitoring. An adequate charger
+ * capable of supplying high current while sustaining VIN_MIN
+ * is required if the limiting is disabled.
+ */
+int pm8921_disable_input_current_limit(bool disable)
+{
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ if (disable) {
+ pr_warn("Disabling input current limit!\n");
+
+ return pm8xxx_writeb(the_chip->dev->parent,
+ CHG_BUCK_CTRL_TEST3, 0xF2);
+ }
+ return 0;
+}
+EXPORT_SYMBOL(pm8921_disable_input_current_limit);
+
+int pm8921_set_max_battery_charge_current(int ma)
+{
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ return pm_chg_ibatmax_set(the_chip, ma);
+}
+EXPORT_SYMBOL(pm8921_set_max_battery_charge_current);
+
+int pm8921_disable_source_current(bool disable)
+{
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ if (disable)
+ pr_warn("current drawn from chg=0, battery provides current\n");
+ return pm_chg_charge_dis(the_chip, disable);
+}
+EXPORT_SYMBOL(pm8921_disable_source_current);
+
+int pm8921_regulate_input_voltage(int voltage)
+{
+ int rc;
+
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ rc = pm_chg_vinmin_set(the_chip, voltage);
+
+ if (rc == 0)
+ the_chip->vin_min = voltage;
+
+ return rc;
+}
+
+#define USB_OV_THRESHOLD_MASK 0x60
+#define USB_OV_THRESHOLD_SHIFT 5
+int pm8921_usb_ovp_set_threshold(enum pm8921_usb_ov_threshold ov)
+{
+ u8 temp;
+
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+
+ if (ov > PM_USB_OV_7V) {
+ pr_err("limiting to over voltage threshold to 7volts\n");
+ ov = PM_USB_OV_7V;
+ }
+
+ temp = USB_OV_THRESHOLD_MASK & (ov << USB_OV_THRESHOLD_SHIFT);
+
+ return pm_chg_masked_write(the_chip, USB_OVP_CONTROL,
+ USB_OV_THRESHOLD_MASK, temp);
+}
+EXPORT_SYMBOL(pm8921_usb_ovp_set_threshold);
+
+#define USB_DEBOUNCE_TIME_MASK 0x06
+#define USB_DEBOUNCE_TIME_SHIFT 1
+int pm8921_usb_ovp_set_hystersis(enum pm8921_usb_debounce_time ms)
+{
+ u8 temp;
+
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+
+ if (ms > PM_USB_DEBOUNCE_80P5MS) {
+ pr_err("limiting debounce to 80.5ms\n");
+ ms = PM_USB_DEBOUNCE_80P5MS;
+ }
+
+ temp = USB_DEBOUNCE_TIME_MASK & (ms << USB_DEBOUNCE_TIME_SHIFT);
+
+ return pm_chg_masked_write(the_chip, USB_OVP_CONTROL,
+ USB_DEBOUNCE_TIME_MASK, temp);
+}
+EXPORT_SYMBOL(pm8921_usb_ovp_set_hystersis);
+
+#define USB_OVP_DISABLE_MASK 0x80
+int pm8921_usb_ovp_disable(int disable)
+{
+ u8 temp = 0;
+
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+
+ if (disable)
+ temp = USB_OVP_DISABLE_MASK;
+
+ return pm_chg_masked_write(the_chip, USB_OVP_CONTROL,
+ USB_OVP_DISABLE_MASK, temp);
+}
+
+bool pm8921_is_battery_charging(int *source)
+{
+ int fsm_state, is_charging, dc_present, usb_present;
+
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ fsm_state = pm_chg_get_fsm_state(the_chip);
+ is_charging = is_battery_charging(fsm_state);
+ if (is_charging == 0) {
+ *source = PM8921_CHG_SRC_NONE;
+ return is_charging;
+ }
+
+ if (source == NULL)
+ return is_charging;
+
+ /* the battery is charging, the source is requested, find it */
+ dc_present = is_dc_chg_plugged_in(the_chip);
+ usb_present = is_usb_chg_plugged_in(the_chip);
+
+ if (dc_present && !usb_present)
+ *source = PM8921_CHG_SRC_DC;
+
+ if (usb_present && !dc_present)
+ *source = PM8921_CHG_SRC_USB;
+
+ if (usb_present && dc_present)
+ /*
+ * The system always chooses dc for charging since it has
+ * higher priority.
+ */
+ *source = PM8921_CHG_SRC_DC;
+
+ return is_charging;
+}
+EXPORT_SYMBOL(pm8921_is_battery_charging);
+
+int pm8921_set_usb_power_supply_type(enum power_supply_type type)
+{
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+
+ if (type < POWER_SUPPLY_TYPE_USB)
+ return -EINVAL;
+
+ the_chip->usb_psy.type = type;
+ power_supply_changed(&the_chip->usb_psy);
+ power_supply_changed(&the_chip->dc_psy);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(pm8921_set_usb_power_supply_type);
+
+int pm8921_batt_temperature(void)
+{
+ if (!the_chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+ return get_prop_batt_temp(the_chip);
+}
+
+static void handle_usb_insertion_removal(struct pm8921_chg_chip *chip)
+{
+ int usb_present;
+
+ pm_chg_failed_clear(chip, 1);
+ usb_present = is_usb_chg_plugged_in(chip);
+ if (chip->usb_present ^ usb_present) {
+ notify_usb_of_the_plugin_event(usb_present);
+ chip->usb_present = usb_present;
+ power_supply_changed(&chip->usb_psy);
+ power_supply_changed(&chip->batt_psy);
+ pm8921_bms_calibrate_hkadc();
+ }
+ if (usb_present) {
+ schedule_delayed_work(&chip->unplug_check_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (UNPLUG_CHECK_WAIT_PERIOD_MS)));
+ pm8921_chg_enable_irq(chip, CHG_GONE_IRQ);
+ } else {
+ /* USB unplugged reset target current */
+ usb_target_ma = 0;
+ pm8921_chg_disable_irq(chip, CHG_GONE_IRQ);
+ }
+ enable_input_voltage_regulation(chip);
+ bms_notify_check(chip);
+}
+
+static void handle_stop_ext_chg(struct pm8921_chg_chip *chip)
+{
+ if (!chip->ext_psy) {
+ pr_debug("external charger not registered.\n");
+ return;
+ }
+
+ if (!chip->ext_charging) {
+ pr_debug("already not charging.\n");
+ return;
+ }
+
+ power_supply_set_charge_type(chip->ext_psy,
+ POWER_SUPPLY_CHARGE_TYPE_NONE);
+ pm8921_disable_source_current(false); /* release BATFET */
+ power_supply_changed(&chip->dc_psy);
+ chip->ext_charging = false;
+ chip->ext_charge_done = false;
+ bms_notify_check(chip);
+ /* Update battery charging LEDs and user space battery info */
+ power_supply_changed(&chip->batt_psy);
+}
+
+static void handle_start_ext_chg(struct pm8921_chg_chip *chip)
+{
+ int dc_present;
+ int batt_present;
+ int batt_temp_ok;
+ int vbat_ov;
+ unsigned long delay =
+ round_jiffies_relative(msecs_to_jiffies(EOC_CHECK_PERIOD_MS));
+
+ if (!chip->ext_psy) {
+ pr_debug("external charger not registered.\n");
+ return;
+ }
+
+ if (chip->ext_charging) {
+ pr_debug("already charging.\n");
+ return;
+ }
+
+ dc_present = is_dc_chg_plugged_in(the_chip);
+ batt_present = pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ);
+ batt_temp_ok = pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ);
+
+ if (!dc_present) {
+ pr_warn("%s. dc not present.\n", __func__);
+ return;
+ }
+ if (!batt_present) {
+ pr_warn("%s. battery not present.\n", __func__);
+ return;
+ }
+ if (!batt_temp_ok) {
+ pr_warn("%s. battery temperature not ok.\n", __func__);
+ return;
+ }
+ pm8921_disable_source_current(true); /* Force BATFET=ON */
+ vbat_ov = pm_chg_get_rt_status(chip, VBAT_OV_IRQ);
+ if (vbat_ov) {
+ pr_warn("%s. battery over voltage.\n", __func__);
+ return;
+ }
+
+ power_supply_set_online(chip->ext_psy, dc_present);
+ power_supply_set_charge_type(chip->ext_psy,
+ POWER_SUPPLY_CHARGE_TYPE_FAST);
+ power_supply_changed(&chip->dc_psy);
+ chip->ext_charging = true;
+ chip->ext_charge_done = false;
+ bms_notify_check(chip);
+ /* Start BMS */
+ schedule_delayed_work(&chip->eoc_work, delay);
+ wake_lock(&chip->eoc_wake_lock);
+ /* Update battery charging LEDs and user space battery info */
+ power_supply_changed(&chip->batt_psy);
+}
+
+static void turn_off_usb_ovp_fet(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, 0x30);
+ if (rc) {
+ pr_err("Failed to write 0x30 to USB_OVP_TEST rc = %d\n", rc);
+ return;
+ }
+ rc = pm8xxx_readb(chip->dev->parent, USB_OVP_TEST, &temp);
+ if (rc) {
+ pr_err("Failed to read from USB_OVP_TEST rc = %d\n", rc);
+ return;
+ }
+ /* set ovp fet disable bit and the write bit */
+ temp |= 0x81;
+ rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, temp);
+ if (rc) {
+ pr_err("Failed to write 0x%x USB_OVP_TEST rc=%d\n", temp, rc);
+ return;
+ }
+}
+
+static void turn_on_usb_ovp_fet(struct pm8921_chg_chip *chip)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, 0x30);
+ if (rc) {
+ pr_err("Failed to write 0x30 to USB_OVP_TEST rc = %d\n", rc);
+ return;
+ }
+ rc = pm8xxx_readb(chip->dev->parent, USB_OVP_TEST, &temp);
+ if (rc) {
+ pr_err("Failed to read from USB_OVP_TEST rc = %d\n", rc);
+ return;
+ }
+ /* unset ovp fet disable bit and set the write bit */
+ temp &= 0xFE;
+ temp |= 0x80;
+ rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, temp);
+ if (rc) {
+ pr_err("Failed to write 0x%x to USB_OVP_TEST rc = %d\n",
+ temp, rc);
+ return;
+ }
+}
+
+static int param_open_ovp_counter = 10;
+module_param(param_open_ovp_counter, int, 0644);
+
+#define WRITE_BANK_4 0xC0
+#define USB_OVP_DEBOUNCE_TIME 0x06
+static void unplug_ovp_fet_open(struct pm8921_chg_chip *chip)
+{
+ int chg_gone = 0, usb_chg_plugged_in = 0;
+ int count = 0;
+
+ while (count++ < param_open_ovp_counter) {
+ pm_chg_masked_write(chip, USB_OVP_CONTROL,
+ USB_OVP_DEBOUNCE_TIME, 0x0);
+ usleep(10);
+ usb_chg_plugged_in = is_usb_chg_plugged_in(chip);
+ chg_gone = pm_chg_get_rt_status(chip, CHG_GONE_IRQ);
+ pr_debug("OVP FET count = %d chg_gone=%d, usb_valid = %d\n",
+ count, chg_gone, usb_chg_plugged_in);
+
+ /* note usb_chg_plugged_in=0 => chg_gone=1 */
+ if (chg_gone == 1 && usb_chg_plugged_in == 1) {
+ pr_debug("since chg_gone = 1 dis ovp_fet for 20msec\n");
+ turn_off_usb_ovp_fet(chip);
+
+ msleep(20);
+
+ turn_on_usb_ovp_fet(chip);
+ } else {
+ break;
+ }
+ }
+ pm_chg_masked_write(chip, USB_OVP_CONTROL,
+ USB_OVP_DEBOUNCE_TIME, 0x2);
+ pr_debug("Exit count=%d chg_gone=%d, usb_valid=%d\n",
+ count, chg_gone, usb_chg_plugged_in);
+ return;
+}
+
+static int find_usb_ma_value(int value)
+{
+ int i;
+
+ for (i = ARRAY_SIZE(usb_ma_table) - 1; i >= 0; i--) {
+ if (value >= usb_ma_table[i].usb_ma)
+ break;
+ }
+
+ return i;
+}
+
+static void decrease_usb_ma_value(int *value)
+{
+ int i;
+
+ if (value) {
+ i = find_usb_ma_value(*value);
+ if (i > 0)
+ i--;
+ *value = usb_ma_table[i].usb_ma;
+ }
+}
+
+static void increase_usb_ma_value(int *value)
+{
+ int i;
+
+ if (value) {
+ i = find_usb_ma_value(*value);
+
+ if (i < (ARRAY_SIZE(usb_ma_table) - 1))
+ i++;
+ /* Get next correct entry if IUSB_FINE_RES is not available */
+ while (!the_chip->iusb_fine_res
+ && (usb_ma_table[i].value & PM8917_IUSB_FINE_RES)
+ && i < (ARRAY_SIZE(usb_ma_table) - 1))
+ i++;
+
+ *value = usb_ma_table[i].usb_ma;
+ }
+}
+
+static void vin_collapse_check_worker(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct pm8921_chg_chip *chip = container_of(dwork,
+ struct pm8921_chg_chip, vin_collapse_check_work);
+
+ /* AICL only for wall-chargers */
+ if (is_usb_chg_plugged_in(chip) &&
+ usb_target_ma > USB_WALL_THRESHOLD_MA) {
+ /* decrease usb_target_ma */
+ decrease_usb_ma_value(&usb_target_ma);
+ /* reset here, increase in unplug_check_worker */
+ __pm8921_charger_vbus_draw(USB_WALL_THRESHOLD_MA);
+ pr_debug("usb_now=%d, usb_target = %d\n",
+ USB_WALL_THRESHOLD_MA, usb_target_ma);
+ } else {
+ handle_usb_insertion_removal(chip);
+ }
+}
+
+#define VIN_MIN_COLLAPSE_CHECK_MS 50
+static irqreturn_t usbin_valid_irq_handler(int irq, void *data)
+{
+ if (usb_target_ma)
+ schedule_delayed_work(&the_chip->vin_collapse_check_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (VIN_MIN_COLLAPSE_CHECK_MS)));
+ else
+ handle_usb_insertion_removal(data);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t usbin_ov_irq_handler(int irq, void *data)
+{
+ pr_err("USB OverVoltage\n");
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t batt_inserted_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+ int status;
+
+ status = pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ);
+ schedule_work(&chip->battery_id_valid_work);
+ handle_start_ext_chg(chip);
+ pr_debug("battery present=%d", status);
+ power_supply_changed(&chip->batt_psy);
+ return IRQ_HANDLED;
+}
+
+/*
+ * this interrupt used to restart charging a battery.
+ *
+ * Note: When DC-inserted the VBAT can't go low.
+ * VPH_PWR is provided by the ext-charger.
+ * After End-Of-Charging from DC, charging can be resumed only
+ * if DC is removed and then inserted after the battery was in use.
+ * Therefore the handle_start_ext_chg() is not called.
+ */
+static irqreturn_t vbatdet_low_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+ int high_transition;
+
+ high_transition = pm_chg_get_rt_status(chip, VBATDET_LOW_IRQ);
+
+ if (high_transition) {
+ /* enable auto charging */
+ pm_chg_auto_enable(chip, !charging_disabled);
+ pr_info("batt fell below resume voltage %s\n",
+ charging_disabled ? "" : "charger enabled");
+ }
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ power_supply_changed(&chip->dc_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t usbin_uv_irq_handler(int irq, void *data)
+{
+ pr_err("USB UnderVoltage\n");
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t vbat_ov_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chgwdog_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t vcp_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t atcdone_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t atcfail_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chgdone_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ pr_debug("state_changed_to=%d\n", pm_chg_get_fsm_state(data));
+
+ handle_stop_ext_chg(chip);
+
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ power_supply_changed(&chip->dc_psy);
+
+ bms_notify_check(chip);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chgfail_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+ int ret;
+
+ ret = pm_chg_failed_clear(chip, 1);
+ if (ret)
+ pr_err("Failed to write CHG_FAILED_CLEAR bit\n");
+
+ pr_err("batt_present = %d, batt_temp_ok = %d, state_changed_to=%d\n",
+ get_prop_batt_present(chip),
+ pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ),
+ pm_chg_get_fsm_state(data));
+
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ power_supply_changed(&chip->dc_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chgstate_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ pr_debug("state_changed_to=%d\n", pm_chg_get_fsm_state(data));
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ power_supply_changed(&chip->dc_psy);
+
+ bms_notify_check(chip);
+
+ return IRQ_HANDLED;
+}
+
+static int param_vin_disable_counter = 5;
+module_param(param_vin_disable_counter, int, 0644);
+
+static void attempt_reverse_boost_fix(struct pm8921_chg_chip *chip,
+ int count, int usb_ma)
+{
+ __pm8921_charger_vbus_draw(500);
+ pr_debug("count = %d iusb=500mA\n", count);
+ disable_input_voltage_regulation(chip);
+ pr_debug("count = %d disable_input_regulation\n", count);
+
+ msleep(20);
+
+ pr_debug("count = %d end sleep 20ms chg_gone=%d, usb_valid = %d\n",
+ count,
+ pm_chg_get_rt_status(chip, CHG_GONE_IRQ),
+ is_usb_chg_plugged_in(chip));
+ pr_debug("count = %d restoring input regulation and usb_ma = %d\n",
+ count, usb_ma);
+ enable_input_voltage_regulation(chip);
+ __pm8921_charger_vbus_draw(usb_ma);
+}
+
+#define VIN_ACTIVE_BIT BIT(0)
+#define UNPLUG_WRKARND_RESTORE_WAIT_PERIOD_US 200
+#define VIN_MIN_INCREASE_MV 100
+static void unplug_check_worker(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct pm8921_chg_chip *chip = container_of(dwork,
+ struct pm8921_chg_chip, unplug_check_work);
+ u8 reg_loop;
+ int ibat, usb_chg_plugged_in, usb_ma;
+ int chg_gone = 0;
+
+ reg_loop = 0;
+ usb_chg_plugged_in = is_usb_chg_plugged_in(chip);
+ if (!usb_chg_plugged_in) {
+ pr_debug("Stopping Unplug Check Worker since USB is removed"
+ "reg_loop = %d, fsm = %d ibat = %d\n",
+ pm_chg_get_regulation_loop(chip),
+ pm_chg_get_fsm_state(chip),
+ get_prop_batt_current(chip)
+ );
+ return;
+ }
+
+ pm_chg_iusbmax_get(chip, &usb_ma);
+ if (usb_ma == 500 && !usb_target_ma) {
+ pr_debug("Stopping Unplug Check Worker since USB == 500mA\n");
+ disable_input_voltage_regulation(chip);
+ return;
+ }
+
+ if (usb_ma <= 100) {
+ pr_debug(
+ "Unenumerated yet or suspended usb_ma = %d skipping\n",
+ usb_ma);
+ goto check_again_later;
+ }
+ if (pm8921_chg_is_enabled(chip, CHG_GONE_IRQ))
+ pr_debug("chg gone irq is enabled\n");
+
+ reg_loop = pm_chg_get_regulation_loop(chip);
+ pr_debug("reg_loop=0x%x usb_ma = %d\n", reg_loop, usb_ma);
+
+ if ((reg_loop & VIN_ACTIVE_BIT) && (usb_ma > USB_WALL_THRESHOLD_MA)) {
+ decrease_usb_ma_value(&usb_ma);
+ usb_target_ma = usb_ma;
+ /* end AICL here */
+ __pm8921_charger_vbus_draw(usb_ma);
+ pr_debug("usb_now=%d, usb_target = %d\n",
+ usb_ma, usb_target_ma);
+ }
+
+ reg_loop = pm_chg_get_regulation_loop(chip);
+ pr_debug("reg_loop=0x%x usb_ma = %d\n", reg_loop, usb_ma);
+
+ if (reg_loop & VIN_ACTIVE_BIT) {
+ ibat = get_prop_batt_current(chip);
+
+ pr_debug("ibat = %d fsm = %d reg_loop = 0x%x\n",
+ ibat, pm_chg_get_fsm_state(chip), reg_loop);
+ if (ibat > 0) {
+ int count = 0;
+
+ while (count++ < param_vin_disable_counter
+ && usb_chg_plugged_in == 1) {
+ attempt_reverse_boost_fix(chip, count, usb_ma);
+ usb_chg_plugged_in
+ = is_usb_chg_plugged_in(chip);
+ }
+ }
+ }
+
+ usb_chg_plugged_in = is_usb_chg_plugged_in(chip);
+ chg_gone = pm_chg_get_rt_status(chip, CHG_GONE_IRQ);
+
+ if (chg_gone == 1 && usb_chg_plugged_in == 1) {
+ /* run the worker directly */
+ pr_debug(" ver5 step: chg_gone=%d, usb_valid = %d\n",
+ chg_gone, usb_chg_plugged_in);
+ unplug_ovp_fet_open(chip);
+ }
+
+ if (!(reg_loop & VIN_ACTIVE_BIT)) {
+ /* only increase iusb_max if vin loop not active */
+ if (usb_ma < usb_target_ma) {
+ increase_usb_ma_value(&usb_ma);
+ __pm8921_charger_vbus_draw(usb_ma);
+ pr_debug("usb_now=%d, usb_target = %d\n",
+ usb_ma, usb_target_ma);
+ } else {
+ usb_target_ma = usb_ma;
+ }
+ }
+check_again_later:
+ /* schedule to check again later */
+ schedule_delayed_work(&chip->unplug_check_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (UNPLUG_CHECK_WAIT_PERIOD_MS)));
+}
+
+static irqreturn_t loop_change_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ pr_debug("fsm_state=%d reg_loop=0x%x\n",
+ pm_chg_get_fsm_state(data),
+ pm_chg_get_regulation_loop(data));
+ schedule_work(&chip->unplug_check_work.work);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fastchg_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+ int high_transition;
+
+ high_transition = pm_chg_get_rt_status(chip, FASTCHG_IRQ);
+ if (high_transition && !delayed_work_pending(&chip->eoc_work)) {
+ wake_lock(&chip->eoc_wake_lock);
+ schedule_delayed_work(&chip->eoc_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (EOC_CHECK_PERIOD_MS)));
+ }
+ power_supply_changed(&chip->batt_psy);
+ bms_notify_check(chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t trklchg_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ power_supply_changed(&chip->batt_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t batt_removed_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+ int status;
+
+ status = pm_chg_get_rt_status(chip, BATT_REMOVED_IRQ);
+ pr_debug("battery present=%d state=%d", !status,
+ pm_chg_get_fsm_state(data));
+ handle_stop_ext_chg(chip);
+ power_supply_changed(&chip->batt_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t batttemp_hot_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ handle_stop_ext_chg(chip);
+ power_supply_changed(&chip->batt_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chghot_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ pr_debug("Chg hot fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ handle_stop_ext_chg(chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t batttemp_cold_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ pr_debug("Batt cold fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ handle_stop_ext_chg(chip);
+
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chg_gone_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+ int chg_gone, usb_chg_plugged_in;
+
+ usb_chg_plugged_in = is_usb_chg_plugged_in(chip);
+ chg_gone = pm_chg_get_rt_status(chip, CHG_GONE_IRQ);
+
+ pr_debug("chg_gone=%d, usb_valid = %d\n", chg_gone, usb_chg_plugged_in);
+ pr_debug("Chg gone fsm_state=%d\n", pm_chg_get_fsm_state(data));
+
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ return IRQ_HANDLED;
+}
+/*
+ *
+ * bat_temp_ok_irq_handler - is edge triggered, hence it will
+ * fire for two cases:
+ *
+ * If the interrupt line switches to high temperature is okay
+ * and thus charging begins.
+ * If bat_temp_ok is low this means the temperature is now
+ * too hot or cold, so charging is stopped.
+ *
+ */
+static irqreturn_t bat_temp_ok_irq_handler(int irq, void *data)
+{
+ int bat_temp_ok;
+ struct pm8921_chg_chip *chip = data;
+
+ bat_temp_ok = pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ);
+
+ pr_debug("batt_temp_ok = %d fsm_state%d\n",
+ bat_temp_ok, pm_chg_get_fsm_state(data));
+
+ if (bat_temp_ok)
+ handle_start_ext_chg(chip);
+ else
+ handle_stop_ext_chg(chip);
+
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ bms_notify_check(chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t coarse_det_low_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t vdd_loop_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t vreg_ov_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t vbatdet_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t batfet_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ pr_debug("vreg ov\n");
+ power_supply_changed(&chip->batt_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dcin_valid_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+ int dc_present;
+
+ dc_present = pm_chg_get_rt_status(chip, DCIN_VALID_IRQ);
+ if (chip->ext_psy)
+ power_supply_set_online(chip->ext_psy, dc_present);
+ chip->dc_present = dc_present;
+ if (dc_present)
+ handle_start_ext_chg(chip);
+ else
+ handle_stop_ext_chg(chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dcin_ov_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ handle_stop_ext_chg(chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dcin_uv_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ handle_stop_ext_chg(chip);
+
+ return IRQ_HANDLED;
+}
+
+static int __pm_batt_external_power_changed_work(struct device *dev, void *data)
+{
+ struct power_supply *psy = &the_chip->batt_psy;
+ struct power_supply *epsy = dev_get_drvdata(dev);
+ int i, dcin_irq;
+
+ /* Only search for external supply if none is registered */
+ if (!the_chip->ext_psy) {
+ dcin_irq = the_chip->pmic_chg_irq[DCIN_VALID_IRQ];
+ for (i = 0; i < epsy->num_supplicants; i++) {
+ if (!strncmp(epsy->supplied_to[i], psy->name, 7)) {
+ if (!strncmp(epsy->name, "dc", 2)) {
+ the_chip->ext_psy = epsy;
+ dcin_valid_irq_handler(dcin_irq,
+ the_chip);
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static void pm_batt_external_power_changed(struct power_supply *psy)
+{
+ /* Only look for an external supply if it hasn't been registered */
+ if (!the_chip->ext_psy)
+ class_for_each_device(power_supply_class, NULL, psy,
+ __pm_batt_external_power_changed_work);
+}
+
+/**
+ * update_heartbeat - internal function to update userspace
+ * per update_time minutes
+ *
+ */
+static void update_heartbeat(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct pm8921_chg_chip *chip = container_of(dwork,
+ struct pm8921_chg_chip, update_heartbeat_work);
+
+ pm_chg_failed_clear(chip, 1);
+ power_supply_changed(&chip->batt_psy);
+ schedule_delayed_work(&chip->update_heartbeat_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (chip->update_time)));
+}
+#define VDD_LOOP_ACTIVE_BIT BIT(3)
+#define VDD_MAX_INCREASE_MV 400
+static int vdd_max_increase_mv = VDD_MAX_INCREASE_MV;
+module_param(vdd_max_increase_mv, int, 0644);
+
+static int ichg_threshold_ua = -400000;
+module_param(ichg_threshold_ua, int, 0644);
+static void adjust_vdd_max_for_fastchg(struct pm8921_chg_chip *chip)
+{
+ int ichg_meas_ua, vbat_uv;
+ int ichg_meas_ma;
+ int adj_vdd_max_mv, programmed_vdd_max;
+ int vbat_batt_terminal_uv;
+ int vbat_batt_terminal_mv;
+ int reg_loop;
+ int delta_mv = 0;
+
+ if (chip->rconn_mohm == 0) {
+ pr_debug("Exiting as rconn_mohm is 0\n");
+ return;
+ }
+ /* adjust vdd_max only in normal temperature zone */
+ if (chip->is_bat_cool || chip->is_bat_warm) {
+ pr_debug("Exiting is_bat_cool = %d is_batt_warm = %d\n",
+ chip->is_bat_cool, chip->is_bat_warm);
+ return;
+ }
+
+ reg_loop = pm_chg_get_regulation_loop(chip);
+ if (!(reg_loop & VDD_LOOP_ACTIVE_BIT)) {
+ pr_debug("Exiting Vdd loop is not active reg loop=0x%x\n",
+ reg_loop);
+ return;
+ }
+
+ pm8921_bms_get_simultaneous_battery_voltage_and_current(&ichg_meas_ua,
+ &vbat_uv);
+ if (ichg_meas_ua >= 0) {
+ pr_debug("Exiting ichg_meas_ua = %d > 0\n", ichg_meas_ua);
+ return;
+ }
+ if (ichg_meas_ua <= ichg_threshold_ua) {
+ pr_debug("Exiting ichg_meas_ua = %d < ichg_threshold_ua = %d\n",
+ ichg_meas_ua, ichg_threshold_ua);
+ return;
+ }
+ ichg_meas_ma = ichg_meas_ua / 1000;
+
+ /* rconn_mohm is in milliOhms */
+ vbat_batt_terminal_uv = vbat_uv + ichg_meas_ma * the_chip->rconn_mohm;
+ vbat_batt_terminal_mv = vbat_batt_terminal_uv/1000;
+ pm_chg_vddmax_get(the_chip, &programmed_vdd_max);
+
+ delta_mv = chip->max_voltage_mv - vbat_batt_terminal_mv;
+
+ adj_vdd_max_mv = programmed_vdd_max + delta_mv;
+ pr_debug("vdd_max needs to be changed by %d mv from %d to %d\n",
+ delta_mv,
+ programmed_vdd_max,
+ adj_vdd_max_mv);
+
+ if (adj_vdd_max_mv < chip->max_voltage_mv) {
+ pr_debug("adj vdd_max lower than default max voltage\n");
+ return;
+ }
+
+ if (adj_vdd_max_mv > (chip->max_voltage_mv + vdd_max_increase_mv))
+ adj_vdd_max_mv = chip->max_voltage_mv + vdd_max_increase_mv;
+
+ pr_debug("adjusting vdd_max_mv to %d to make "
+ "vbat_batt_termial_uv = %d to %d\n",
+ adj_vdd_max_mv, vbat_batt_terminal_uv, chip->max_voltage_mv);
+ pm_chg_vddmax_set(chip, adj_vdd_max_mv);
+}
+
+enum {
+ CHG_IN_PROGRESS,
+ CHG_NOT_IN_PROGRESS,
+ CHG_FINISHED,
+};
+
+#define VBAT_TOLERANCE_MV 70
+#define CHG_DISABLE_MSLEEP 100
+static int is_charging_finished(struct pm8921_chg_chip *chip)
+{
+ int vbat_meas_uv, vbat_meas_mv, vbat_programmed, vbatdet_low;
+ int ichg_meas_ma, iterm_programmed;
+ int regulation_loop, fast_chg, vcp;
+ int rc;
+ static int last_vbat_programmed = -EINVAL;
+
+ if (!is_ext_charging(chip)) {
+ /* return if the battery is not being fastcharged */
+ fast_chg = pm_chg_get_rt_status(chip, FASTCHG_IRQ);
+ pr_debug("fast_chg = %d\n", fast_chg);
+ if (fast_chg == 0)
+ return CHG_NOT_IN_PROGRESS;
+
+ vcp = pm_chg_get_rt_status(chip, VCP_IRQ);
+ pr_debug("vcp = %d\n", vcp);
+ if (vcp == 1)
+ return CHG_IN_PROGRESS;
+
+ vbatdet_low = pm_chg_get_rt_status(chip, VBATDET_LOW_IRQ);
+ pr_debug("vbatdet_low = %d\n", vbatdet_low);
+ if (vbatdet_low == 1)
+ return CHG_IN_PROGRESS;
+
+ /* reset count if battery is hot/cold */
+ rc = pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ);
+ pr_debug("batt_temp_ok = %d\n", rc);
+ if (rc == 0)
+ return CHG_IN_PROGRESS;
+
+ /* reset count if battery voltage is less than vddmax */
+ vbat_meas_uv = get_prop_battery_uvolts(chip);
+ if (vbat_meas_uv < 0)
+ return CHG_IN_PROGRESS;
+ vbat_meas_mv = vbat_meas_uv / 1000;
+
+ rc = pm_chg_vddmax_get(chip, &vbat_programmed);
+ if (rc) {
+ pr_err("couldnt read vddmax rc = %d\n", rc);
+ return CHG_IN_PROGRESS;
+ }
+ pr_debug("vddmax = %d vbat_meas_mv=%d\n",
+ vbat_programmed, vbat_meas_mv);
+
+ if (last_vbat_programmed == -EINVAL)
+ last_vbat_programmed = vbat_programmed;
+ if (last_vbat_programmed != vbat_programmed) {
+ /* vddmax changed, reset and check again */
+ pr_debug("vddmax = %d last_vdd_max=%d\n",
+ vbat_programmed, last_vbat_programmed);
+ last_vbat_programmed = vbat_programmed;
+ return CHG_IN_PROGRESS;
+ }
+
+ regulation_loop = pm_chg_get_regulation_loop(chip);
+ if (regulation_loop < 0) {
+ pr_err("couldnt read the regulation loop err=%d\n",
+ regulation_loop);
+ return CHG_IN_PROGRESS;
+ }
+ pr_debug("regulation_loop=%d\n", regulation_loop);
+
+ if (regulation_loop != 0 && regulation_loop != VDD_LOOP)
+ return CHG_IN_PROGRESS;
+ } /* !is_ext_charging */
+
+ /* reset count if battery chg current is more than iterm */
+ rc = pm_chg_iterm_get(chip, &iterm_programmed);
+ if (rc) {
+ pr_err("couldnt read iterm rc = %d\n", rc);
+ return CHG_IN_PROGRESS;
+ }
+
+ ichg_meas_ma = (get_prop_batt_current(chip)) / 1000;
+ pr_debug("iterm_programmed = %d ichg_meas_ma=%d\n",
+ iterm_programmed, ichg_meas_ma);
+ /*
+ * ichg_meas_ma < 0 means battery is drawing current
+ * ichg_meas_ma > 0 means battery is providing current
+ */
+ if (ichg_meas_ma > 0)
+ return CHG_IN_PROGRESS;
+
+ if (ichg_meas_ma * -1 > iterm_programmed)
+ return CHG_IN_PROGRESS;
+
+ return CHG_FINISHED;
+}
+
+/**
+ * eoc_worker - internal function to check if battery EOC
+ * has happened
+ *
+ * If all conditions favouring, if the charge current is
+ * less than the term current for three consecutive times
+ * an EOC has happened.
+ * The wakelock is released if there is no need to reshedule
+ * - this happens when the battery is removed or EOC has
+ * happened
+ */
+#define CONSECUTIVE_COUNT 3
+static void eoc_worker(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct pm8921_chg_chip *chip = container_of(dwork,
+ struct pm8921_chg_chip, eoc_work);
+ static int count;
+ int end;
+
+ pm_chg_failed_clear(chip, 1);
+ end = is_charging_finished(chip);
+
+ if (end == CHG_NOT_IN_PROGRESS) {
+ count = 0;
+ wake_unlock(&chip->eoc_wake_lock);
+ return;
+ }
+
+ if (end == CHG_FINISHED) {
+ count++;
+ } else {
+ count = 0;
+ }
+
+ if (count == CONSECUTIVE_COUNT) {
+ count = 0;
+ pr_info("End of Charging\n");
+
+ pm_chg_auto_enable(chip, 0);
+
+ if (is_ext_charging(chip))
+ chip->ext_charge_done = true;
+
+ if (chip->is_bat_warm || chip->is_bat_cool)
+ chip->bms_notify.is_battery_full = 0;
+ else
+ chip->bms_notify.is_battery_full = 1;
+ /* declare end of charging by invoking chgdone interrupt */
+ chgdone_irq_handler(chip->pmic_chg_irq[CHGDONE_IRQ], chip);
+ wake_unlock(&chip->eoc_wake_lock);
+ } else {
+ adjust_vdd_max_for_fastchg(chip);
+ pr_debug("EOC count = %d\n", count);
+ schedule_delayed_work(&chip->eoc_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (EOC_CHECK_PERIOD_MS)));
+ }
+}
+
+static void btm_configure_work(struct work_struct *work)
+{
+ int rc;
+
+ rc = pm8xxx_adc_btm_configure(&btm_config);
+ if (rc)
+ pr_err("failed to configure btm rc=%d", rc);
+}
+
+DECLARE_WORK(btm_config_work, btm_configure_work);
+
+static void set_appropriate_battery_current(struct pm8921_chg_chip *chip)
+{
+ unsigned int chg_current = chip->max_bat_chg_current;
+
+ if (chip->is_bat_cool)
+ chg_current = min(chg_current, chip->cool_bat_chg_current);
+
+ if (chip->is_bat_warm)
+ chg_current = min(chg_current, chip->warm_bat_chg_current);
+
+ if (thermal_mitigation != 0 && chip->thermal_mitigation)
+ chg_current = min(chg_current,
+ chip->thermal_mitigation[thermal_mitigation]);
+
+ pm_chg_ibatmax_set(the_chip, chg_current);
+}
+
+#define TEMP_HYSTERISIS_DEGC 2
+static void battery_cool(bool enter)
+{
+ pr_debug("enter = %d\n", enter);
+ if (enter == the_chip->is_bat_cool)
+ return;
+ the_chip->is_bat_cool = enter;
+ if (enter) {
+ btm_config.low_thr_temp =
+ the_chip->cool_temp_dc + TEMP_HYSTERISIS_DEGC;
+ set_appropriate_battery_current(the_chip);
+ pm_chg_vddmax_set(the_chip, the_chip->cool_bat_voltage);
+ pm_chg_vbatdet_set(the_chip,
+ the_chip->cool_bat_voltage
+ - the_chip->resume_voltage_delta);
+ } else {
+ btm_config.low_thr_temp = the_chip->cool_temp_dc;
+ set_appropriate_battery_current(the_chip);
+ pm_chg_vddmax_set(the_chip, the_chip->max_voltage_mv);
+ pm_chg_vbatdet_set(the_chip,
+ the_chip->max_voltage_mv
+ - the_chip->resume_voltage_delta);
+ }
+ schedule_work(&btm_config_work);
+}
+
+static void battery_warm(bool enter)
+{
+ pr_debug("enter = %d\n", enter);
+ if (enter == the_chip->is_bat_warm)
+ return;
+ the_chip->is_bat_warm = enter;
+ if (enter) {
+ btm_config.high_thr_temp =
+ the_chip->warm_temp_dc - TEMP_HYSTERISIS_DEGC;
+ set_appropriate_battery_current(the_chip);
+ pm_chg_vddmax_set(the_chip, the_chip->warm_bat_voltage);
+ pm_chg_vbatdet_set(the_chip,
+ the_chip->warm_bat_voltage
+ - the_chip->resume_voltage_delta);
+ } else {
+ btm_config.high_thr_temp = the_chip->warm_temp_dc;
+ set_appropriate_battery_current(the_chip);
+ pm_chg_vddmax_set(the_chip, the_chip->max_voltage_mv);
+ pm_chg_vbatdet_set(the_chip,
+ the_chip->max_voltage_mv
+ - the_chip->resume_voltage_delta);
+ }
+ schedule_work(&btm_config_work);
+}
+
+static int configure_btm(struct pm8921_chg_chip *chip)
+{
+ int rc;
+
+ if (chip->warm_temp_dc != INT_MIN)
+ btm_config.btm_warm_fn = battery_warm;
+ else
+ btm_config.btm_warm_fn = NULL;
+
+ if (chip->cool_temp_dc != INT_MIN)
+ btm_config.btm_cool_fn = battery_cool;
+ else
+ btm_config.btm_cool_fn = NULL;
+
+ btm_config.low_thr_temp = chip->cool_temp_dc;
+ btm_config.high_thr_temp = chip->warm_temp_dc;
+ btm_config.interval = chip->temp_check_period;
+ rc = pm8xxx_adc_btm_configure(&btm_config);
+ if (rc)
+ pr_err("failed to configure btm rc = %d\n", rc);
+ rc = pm8xxx_adc_btm_start();
+ if (rc)
+ pr_err("failed to start btm rc = %d\n", rc);
+
+ return rc;
+}
+
+/**
+ * set_disable_status_param -
+ *
+ * Internal function to disable battery charging and also disable drawing
+ * any current from the source. The device is forced to run on a battery
+ * after this.
+ */
+static int set_disable_status_param(const char *val, struct kernel_param *kp)
+{
+ int ret;
+ struct pm8921_chg_chip *chip = the_chip;
+
+ ret = param_set_int(val, kp);
+ if (ret) {
+ pr_err("error setting value %d\n", ret);
+ return ret;
+ }
+ pr_info("factory set disable param to %d\n", charging_disabled);
+ if (chip) {
+ pm_chg_auto_enable(chip, !charging_disabled);
+ pm_chg_charge_dis(chip, charging_disabled);
+ }
+ return 0;
+}
+module_param_call(disabled, set_disable_status_param, param_get_uint,
+ &charging_disabled, 0644);
+
+static int rconn_mohm;
+static int set_rconn_mohm(const char *val, struct kernel_param *kp)
+{
+ int ret;
+ struct pm8921_chg_chip *chip = the_chip;
+
+ ret = param_set_int(val, kp);
+ if (ret) {
+ pr_err("error setting value %d\n", ret);
+ return ret;
+ }
+ if (chip)
+ chip->rconn_mohm = rconn_mohm;
+ return 0;
+}
+module_param_call(rconn_mohm, set_rconn_mohm, param_get_uint,
+ &rconn_mohm, 0644);
+/**
+ * set_thermal_mitigation_level -
+ *
+ * Internal function to control battery charging current to reduce
+ * temperature
+ */
+static int set_therm_mitigation_level(const char *val, struct kernel_param *kp)
+{
+ int ret;
+ struct pm8921_chg_chip *chip = the_chip;
+
+ ret = param_set_int(val, kp);
+ if (ret) {
+ pr_err("error setting value %d\n", ret);
+ return ret;
+ }
+
+ if (!chip) {
+ pr_err("called before init\n");
+ return -EINVAL;
+ }
+
+ if (!chip->thermal_mitigation) {
+ pr_err("no thermal mitigation\n");
+ return -EINVAL;
+ }
+
+ if (thermal_mitigation < 0
+ || thermal_mitigation >= chip->thermal_levels) {
+ pr_err("out of bound level selected\n");
+ return -EINVAL;
+ }
+
+ set_appropriate_battery_current(chip);
+ return ret;
+}
+module_param_call(thermal_mitigation, set_therm_mitigation_level,
+ param_get_uint,
+ &thermal_mitigation, 0644);
+
+static int set_usb_max_current(const char *val, struct kernel_param *kp)
+{
+ int ret, mA;
+ struct pm8921_chg_chip *chip = the_chip;
+
+ ret = param_set_int(val, kp);
+ if (ret) {
+ pr_err("error setting value %d\n", ret);
+ return ret;
+ }
+ if (chip) {
+ pr_warn("setting current max to %d\n", usb_max_current);
+ pm_chg_iusbmax_get(chip, &mA);
+ if (mA > usb_max_current)
+ pm8921_charger_vbus_draw(usb_max_current);
+ return 0;
+ }
+ return -EINVAL;
+}
+module_param_call(usb_max_current, set_usb_max_current,
+ param_get_uint, &usb_max_current, 0644);
+
+static void free_irqs(struct pm8921_chg_chip *chip)
+{
+ int i;
+
+ for (i = 0; i < PM_CHG_MAX_INTS; i++)
+ if (chip->pmic_chg_irq[i]) {
+ free_irq(chip->pmic_chg_irq[i], chip);
+ chip->pmic_chg_irq[i] = 0;
+ }
+}
+
+/* determines the initial present states */
+static void __devinit determine_initial_state(struct pm8921_chg_chip *chip)
+{
+ unsigned long flags;
+ int fsm_state;
+
+ chip->dc_present = !!is_dc_chg_plugged_in(chip);
+ chip->usb_present = !!is_usb_chg_plugged_in(chip);
+
+ notify_usb_of_the_plugin_event(chip->usb_present);
+ if (chip->usb_present) {
+ schedule_delayed_work(&chip->unplug_check_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (UNPLUG_CHECK_WAIT_PERIOD_MS)));
+ pm8921_chg_enable_irq(chip, CHG_GONE_IRQ);
+ }
+
+ pm8921_chg_enable_irq(chip, DCIN_VALID_IRQ);
+ pm8921_chg_enable_irq(chip, USBIN_VALID_IRQ);
+ pm8921_chg_enable_irq(chip, BATT_REMOVED_IRQ);
+ pm8921_chg_enable_irq(chip, BATT_INSERTED_IRQ);
+ pm8921_chg_enable_irq(chip, USBIN_OV_IRQ);
+ pm8921_chg_enable_irq(chip, USBIN_UV_IRQ);
+ pm8921_chg_enable_irq(chip, DCIN_OV_IRQ);
+ pm8921_chg_enable_irq(chip, DCIN_UV_IRQ);
+ pm8921_chg_enable_irq(chip, CHGFAIL_IRQ);
+ pm8921_chg_enable_irq(chip, FASTCHG_IRQ);
+ pm8921_chg_enable_irq(chip, VBATDET_LOW_IRQ);
+ pm8921_chg_enable_irq(chip, BAT_TEMP_OK_IRQ);
+
+ spin_lock_irqsave(&vbus_lock, flags);
+ if (usb_chg_current) {
+ /* reissue a vbus draw call */
+ __pm8921_charger_vbus_draw(usb_chg_current);
+ fastchg_irq_handler(chip->pmic_chg_irq[FASTCHG_IRQ], chip);
+ }
+ spin_unlock_irqrestore(&vbus_lock, flags);
+
+ fsm_state = pm_chg_get_fsm_state(chip);
+ if (is_battery_charging(fsm_state)) {
+ chip->bms_notify.is_charging = 1;
+ pm8921_bms_charging_began();
+ }
+
+ check_battery_valid(chip);
+
+ pr_debug("usb = %d, dc = %d batt = %d state=%d\n",
+ chip->usb_present,
+ chip->dc_present,
+ get_prop_batt_present(chip),
+ fsm_state);
+}
+
+struct pm_chg_irq_init_data {
+ unsigned int irq_id;
+ char *name;
+ unsigned long flags;
+ irqreturn_t (*handler)(int, void *);
+};
+
+#define CHG_IRQ(_id, _flags, _handler) \
+{ \
+ .irq_id = _id, \
+ .name = #_id, \
+ .flags = _flags, \
+ .handler = _handler, \
+}
+struct pm_chg_irq_init_data chg_irq_data[] = {
+ CHG_IRQ(USBIN_VALID_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ usbin_valid_irq_handler),
+ CHG_IRQ(USBIN_OV_IRQ, IRQF_TRIGGER_RISING, usbin_ov_irq_handler),
+ CHG_IRQ(BATT_INSERTED_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ batt_inserted_irq_handler),
+ CHG_IRQ(VBATDET_LOW_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ vbatdet_low_irq_handler),
+ CHG_IRQ(USBIN_UV_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ usbin_uv_irq_handler),
+ CHG_IRQ(VBAT_OV_IRQ, IRQF_TRIGGER_RISING, vbat_ov_irq_handler),
+ CHG_IRQ(CHGWDOG_IRQ, IRQF_TRIGGER_RISING, chgwdog_irq_handler),
+ CHG_IRQ(VCP_IRQ, IRQF_TRIGGER_RISING, vcp_irq_handler),
+ CHG_IRQ(ATCDONE_IRQ, IRQF_TRIGGER_RISING, atcdone_irq_handler),
+ CHG_IRQ(ATCFAIL_IRQ, IRQF_TRIGGER_RISING, atcfail_irq_handler),
+ CHG_IRQ(CHGDONE_IRQ, IRQF_TRIGGER_RISING, chgdone_irq_handler),
+ CHG_IRQ(CHGFAIL_IRQ, IRQF_TRIGGER_RISING, chgfail_irq_handler),
+ CHG_IRQ(CHGSTATE_IRQ, IRQF_TRIGGER_RISING, chgstate_irq_handler),
+ CHG_IRQ(LOOP_CHANGE_IRQ, IRQF_TRIGGER_RISING, loop_change_irq_handler),
+ CHG_IRQ(FASTCHG_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ fastchg_irq_handler),
+ CHG_IRQ(TRKLCHG_IRQ, IRQF_TRIGGER_RISING, trklchg_irq_handler),
+ CHG_IRQ(BATT_REMOVED_IRQ, IRQF_TRIGGER_RISING,
+ batt_removed_irq_handler),
+ CHG_IRQ(BATTTEMP_HOT_IRQ, IRQF_TRIGGER_RISING,
+ batttemp_hot_irq_handler),
+ CHG_IRQ(CHGHOT_IRQ, IRQF_TRIGGER_RISING, chghot_irq_handler),
+ CHG_IRQ(BATTTEMP_COLD_IRQ, IRQF_TRIGGER_RISING,
+ batttemp_cold_irq_handler),
+ CHG_IRQ(CHG_GONE_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ chg_gone_irq_handler),
+ CHG_IRQ(BAT_TEMP_OK_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ bat_temp_ok_irq_handler),
+ CHG_IRQ(COARSE_DET_LOW_IRQ, IRQF_TRIGGER_RISING,
+ coarse_det_low_irq_handler),
+ CHG_IRQ(VDD_LOOP_IRQ, IRQF_TRIGGER_RISING, vdd_loop_irq_handler),
+ CHG_IRQ(VREG_OV_IRQ, IRQF_TRIGGER_RISING, vreg_ov_irq_handler),
+ CHG_IRQ(VBATDET_IRQ, IRQF_TRIGGER_RISING, vbatdet_irq_handler),
+ CHG_IRQ(BATFET_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ batfet_irq_handler),
+ CHG_IRQ(DCIN_VALID_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dcin_valid_irq_handler),
+ CHG_IRQ(DCIN_OV_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dcin_ov_irq_handler),
+ CHG_IRQ(DCIN_UV_IRQ, IRQF_TRIGGER_RISING, dcin_uv_irq_handler),
+};
+
+static int __devinit request_irqs(struct pm8921_chg_chip *chip,
+ struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret, i;
+
+ ret = 0;
+ bitmap_fill(chip->enabled_irqs, PM_CHG_MAX_INTS);
+
+ for (i = 0; i < ARRAY_SIZE(chg_irq_data); i++) {
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ chg_irq_data[i].name);
+ if (res == NULL) {
+ pr_err("couldn't find %s\n", chg_irq_data[i].name);
+ goto err_out;
+ }
+ chip->pmic_chg_irq[chg_irq_data[i].irq_id] = res->start;
+ ret = request_irq(res->start, chg_irq_data[i].handler,
+ chg_irq_data[i].flags,
+ chg_irq_data[i].name, chip);
+ if (ret < 0) {
+ pr_err("couldn't request %d (%s) %d\n", res->start,
+ chg_irq_data[i].name, ret);
+ chip->pmic_chg_irq[chg_irq_data[i].irq_id] = 0;
+ goto err_out;
+ }
+ pm8921_chg_disable_irq(chip, chg_irq_data[i].irq_id);
+ }
+ return 0;
+
+err_out:
+ free_irqs(chip);
+ return -EINVAL;
+}
+
+static void pm8921_chg_force_19p2mhz_clk(struct pm8921_chg_chip *chip)
+{
+ int err;
+ u8 temp;
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD3;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD5;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ udelay(183);
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD0;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+ udelay(32);
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD3;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+}
+
+static void pm8921_chg_set_hw_clk_switching(struct pm8921_chg_chip *chip)
+{
+ int err;
+ u8 temp;
+
+ temp = 0xD1;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+
+ temp = 0xD0;
+ err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp);
+ if (err) {
+ pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST);
+ return;
+ }
+}
+
+#define ENUM_TIMER_STOP_BIT BIT(1)
+#define BOOT_DONE_BIT BIT(6)
+#define CHG_BATFET_ON_BIT BIT(3)
+#define CHG_VCP_EN BIT(0)
+#define CHG_BAT_TEMP_DIS_BIT BIT(2)
+#define SAFE_CURRENT_MA 1500
+#define VREF_BATT_THERM_FORCE_ON BIT(7)
+static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip)
+{
+ int rc;
+ int vdd_safe;
+
+ rc = pm_chg_masked_write(chip, SYS_CONFIG_2,
+ BOOT_DONE_BIT, BOOT_DONE_BIT);
+ if (rc) {
+ pr_err("Failed to set BOOT_DONE_BIT rc=%d\n", rc);
+ return rc;
+ }
+
+ vdd_safe = chip->max_voltage_mv + VDD_MAX_INCREASE_MV;
+
+ if (vdd_safe > PM8921_CHG_VDDSAFE_MAX)
+ vdd_safe = PM8921_CHG_VDDSAFE_MAX;
+
+ rc = pm_chg_vddsafe_set(chip, vdd_safe);
+
+ if (rc) {
+ pr_err("Failed to set safe voltage to %d rc=%d\n",
+ chip->max_voltage_mv, rc);
+ return rc;
+ }
+ rc = pm_chg_vbatdet_set(chip,
+ chip->max_voltage_mv
+ - chip->resume_voltage_delta);
+ if (rc) {
+ pr_err("Failed to set vbatdet comprator voltage to %d rc=%d\n",
+ chip->max_voltage_mv - chip->resume_voltage_delta, rc);
+ return rc;
+ }
+
+ rc = pm_chg_vddmax_set(chip, chip->max_voltage_mv);
+ if (rc) {
+ pr_err("Failed to set max voltage to %d rc=%d\n",
+ chip->max_voltage_mv, rc);
+ return rc;
+ }
+ rc = pm_chg_ibatsafe_set(chip, SAFE_CURRENT_MA);
+ if (rc) {
+ pr_err("Failed to set max voltage to %d rc=%d\n",
+ SAFE_CURRENT_MA, rc);
+ return rc;
+ }
+
+ rc = pm_chg_ibatmax_set(chip, chip->max_bat_chg_current);
+ if (rc) {
+ pr_err("Failed to set max current to 400 rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = pm_chg_iterm_set(chip, chip->term_current);
+ if (rc) {
+ pr_err("Failed to set term current to %d rc=%d\n",
+ chip->term_current, rc);
+ return rc;
+ }
+
+ /* Disable the ENUM TIMER */
+ rc = pm_chg_masked_write(chip, PBL_ACCESS2, ENUM_TIMER_STOP_BIT,
+ ENUM_TIMER_STOP_BIT);
+ if (rc) {
+ pr_err("Failed to set enum timer stop rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->safety_time != 0) {
+ rc = pm_chg_tchg_max_set(chip, chip->safety_time);
+ if (rc) {
+ pr_err("Failed to set max time to %d minutes rc=%d\n",
+ chip->safety_time, rc);
+ return rc;
+ }
+ }
+
+ if (chip->ttrkl_time != 0) {
+ rc = pm_chg_ttrkl_max_set(chip, chip->ttrkl_time);
+ if (rc) {
+ pr_err("Failed to set trkl time to %d minutes rc=%d\n",
+ chip->safety_time, rc);
+ return rc;
+ }
+ }
+
+ if (chip->vin_min != 0) {
+ rc = pm_chg_vinmin_set(chip, chip->vin_min);
+ if (rc) {
+ pr_err("Failed to set vin min to %d mV rc=%d\n",
+ chip->vin_min, rc);
+ return rc;
+ }
+ } else {
+ chip->vin_min = pm_chg_vinmin_get(chip);
+ }
+
+ rc = pm_chg_disable_wd(chip);
+ if (rc) {
+ pr_err("Failed to disable wd rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = pm_chg_masked_write(chip, CHG_CNTRL_2,
+ CHG_BAT_TEMP_DIS_BIT, 0);
+ if (rc) {
+ pr_err("Failed to enable temp control chg rc=%d\n", rc);
+ return rc;
+ }
+ /* switch to a 3.2Mhz for the buck */
+ rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CLOCK_CTRL, 0x15);
+ if (rc) {
+ pr_err("Failed to switch buck clk rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->trkl_voltage != 0) {
+ rc = pm_chg_vtrkl_low_set(chip, chip->trkl_voltage);
+ if (rc) {
+ pr_err("Failed to set trkl voltage to %dmv rc=%d\n",
+ chip->trkl_voltage, rc);
+ return rc;
+ }
+ }
+
+ if (chip->weak_voltage != 0) {
+ rc = pm_chg_vweak_set(chip, chip->weak_voltage);
+ if (rc) {
+ pr_err("Failed to set weak voltage to %dmv rc=%d\n",
+ chip->weak_voltage, rc);
+ return rc;
+ }
+ }
+
+ if (chip->trkl_current != 0) {
+ rc = pm_chg_itrkl_set(chip, chip->trkl_current);
+ if (rc) {
+ pr_err("Failed to set trkl current to %dmA rc=%d\n",
+ chip->trkl_voltage, rc);
+ return rc;
+ }
+ }
+
+ if (chip->weak_current != 0) {
+ rc = pm_chg_iweak_set(chip, chip->weak_current);
+ if (rc) {
+ pr_err("Failed to set weak current to %dmA rc=%d\n",
+ chip->weak_current, rc);
+ return rc;
+ }
+ }
+
+ rc = pm_chg_batt_cold_temp_config(chip, chip->cold_thr);
+ if (rc) {
+ pr_err("Failed to set cold config %d rc=%d\n",
+ chip->cold_thr, rc);
+ }
+
+ rc = pm_chg_batt_hot_temp_config(chip, chip->hot_thr);
+ if (rc) {
+ pr_err("Failed to set hot config %d rc=%d\n",
+ chip->hot_thr, rc);
+ }
+
+ rc = pm_chg_led_src_config(chip, chip->led_src_config);
+ if (rc) {
+ pr_err("Failed to set charger LED src config %d rc=%d\n",
+ chip->led_src_config, rc);
+ }
+
+ /* Workarounds for die 1.1 and 1.0 */
+ if (pm8xxx_get_revision(chip->dev->parent) < PM8XXX_REVISION_8921_2p0) {
+ pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST2, 0xF1);
+ pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xCE);
+ pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xD8);
+
+ /* software workaround for correct battery_id detection */
+ pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_0, 0xFF);
+ pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_1, 0xFF);
+ pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_2, 0xFF);
+ pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_3, 0xFF);
+ pm8xxx_writeb(chip->dev->parent, PSI_CONFIG_STATUS, 0x0D);
+ udelay(100);
+ pm8xxx_writeb(chip->dev->parent, PSI_CONFIG_STATUS, 0x0C);
+ }
+
+ /* Workarounds for die 3.0 */
+ if (pm8xxx_get_revision(chip->dev->parent) == PM8XXX_REVISION_8921_3p0)
+ pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xAC);
+
+ /* Enable isub_fine resolution AICL for PM8917 */
+ if (pm8xxx_get_version(chip->dev->parent) == PM8XXX_VERSION_8917)
+ chip->iusb_fine_res = true;
+
+ pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xD9);
+
+ /* Disable EOC FSM processing */
+ pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x91);
+
+ pm8921_chg_force_19p2mhz_clk(chip);
+
+ rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON,
+ VREF_BATT_THERM_FORCE_ON);
+ if (rc)
+ pr_err("Failed to Force Vref therm rc=%d\n", rc);
+
+ rc = pm_chg_charge_dis(chip, charging_disabled);
+ if (rc) {
+ pr_err("Failed to disable CHG_CHARGE_DIS bit rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = pm_chg_auto_enable(chip, !charging_disabled);
+ if (rc) {
+ pr_err("Failed to enable charging rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int get_rt_status(void *data, u64 * val)
+{
+ int i = (int)data;
+ int ret;
+
+ /* global irq number is passed in via data */
+ ret = pm_chg_get_rt_status(the_chip, i);
+ *val = ret;
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(rt_fops, get_rt_status, NULL, "%llu\n");
+
+static int get_fsm_status(void *data, u64 * val)
+{
+ u8 temp;
+
+ temp = pm_chg_get_fsm_state(the_chip);
+ *val = temp;
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(fsm_fops, get_fsm_status, NULL, "%llu\n");
+
+static int get_reg_loop(void *data, u64 * val)
+{
+ u8 temp;
+
+ if (!the_chip) {
+ pr_err("%s called before init\n", __func__);
+ return -EINVAL;
+ }
+ temp = pm_chg_get_regulation_loop(the_chip);
+ *val = temp;
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reg_loop_fops, get_reg_loop, NULL, "0x%02llx\n");
+
+static int get_reg(void *data, u64 * val)
+{
+ int addr = (int)data;
+ int ret;
+ u8 temp;
+
+ ret = pm8xxx_readb(the_chip->dev->parent, addr, &temp);
+ if (ret) {
+ pr_err("pm8xxx_readb to %x value =%d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+ *val = temp;
+ return 0;
+}
+
+static int set_reg(void *data, u64 val)
+{
+ int addr = (int)data;
+ int ret;
+ u8 temp;
+
+ temp = (u8) val;
+ ret = pm8xxx_writeb(the_chip->dev->parent, addr, temp);
+ if (ret) {
+ pr_err("pm8xxx_writeb to %x value =%d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n");
+
+enum {
+ BAT_WARM_ZONE,
+ BAT_COOL_ZONE,
+};
+static int get_warm_cool(void *data, u64 * val)
+{
+ if (!the_chip) {
+ pr_err("%s called before init\n", __func__);
+ return -EINVAL;
+ }
+ if ((int)data == BAT_WARM_ZONE)
+ *val = the_chip->is_bat_warm;
+ if ((int)data == BAT_COOL_ZONE)
+ *val = the_chip->is_bat_cool;
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(warm_cool_fops, get_warm_cool, NULL, "0x%lld\n");
+
+static void create_debugfs_entries(struct pm8921_chg_chip *chip)
+{
+ int i;
+
+ chip->dent = debugfs_create_dir("pm8921_chg", NULL);
+
+ if (IS_ERR(chip->dent)) {
+ pr_err("pmic charger couldnt create debugfs dir\n");
+ return;
+ }
+
+ debugfs_create_file("CHG_CNTRL", 0644, chip->dent,
+ (void *)CHG_CNTRL, ®_fops);
+ debugfs_create_file("CHG_CNTRL_2", 0644, chip->dent,
+ (void *)CHG_CNTRL_2, ®_fops);
+ debugfs_create_file("CHG_CNTRL_3", 0644, chip->dent,
+ (void *)CHG_CNTRL_3, ®_fops);
+ debugfs_create_file("PBL_ACCESS1", 0644, chip->dent,
+ (void *)PBL_ACCESS1, ®_fops);
+ debugfs_create_file("PBL_ACCESS2", 0644, chip->dent,
+ (void *)PBL_ACCESS2, ®_fops);
+ debugfs_create_file("SYS_CONFIG_1", 0644, chip->dent,
+ (void *)SYS_CONFIG_1, ®_fops);
+ debugfs_create_file("SYS_CONFIG_2", 0644, chip->dent,
+ (void *)SYS_CONFIG_2, ®_fops);
+ debugfs_create_file("CHG_VDD_MAX", 0644, chip->dent,
+ (void *)CHG_VDD_MAX, ®_fops);
+ debugfs_create_file("CHG_VDD_SAFE", 0644, chip->dent,
+ (void *)CHG_VDD_SAFE, ®_fops);
+ debugfs_create_file("CHG_VBAT_DET", 0644, chip->dent,
+ (void *)CHG_VBAT_DET, ®_fops);
+ debugfs_create_file("CHG_IBAT_MAX", 0644, chip->dent,
+ (void *)CHG_IBAT_MAX, ®_fops);
+ debugfs_create_file("CHG_IBAT_SAFE", 0644, chip->dent,
+ (void *)CHG_IBAT_SAFE, ®_fops);
+ debugfs_create_file("CHG_VIN_MIN", 0644, chip->dent,
+ (void *)CHG_VIN_MIN, ®_fops);
+ debugfs_create_file("CHG_VTRICKLE", 0644, chip->dent,
+ (void *)CHG_VTRICKLE, ®_fops);
+ debugfs_create_file("CHG_ITRICKLE", 0644, chip->dent,
+ (void *)CHG_ITRICKLE, ®_fops);
+ debugfs_create_file("CHG_ITERM", 0644, chip->dent,
+ (void *)CHG_ITERM, ®_fops);
+ debugfs_create_file("CHG_TCHG_MAX", 0644, chip->dent,
+ (void *)CHG_TCHG_MAX, ®_fops);
+ debugfs_create_file("CHG_TWDOG", 0644, chip->dent,
+ (void *)CHG_TWDOG, ®_fops);
+ debugfs_create_file("CHG_TEMP_THRESH", 0644, chip->dent,
+ (void *)CHG_TEMP_THRESH, ®_fops);
+ debugfs_create_file("CHG_COMP_OVR", 0644, chip->dent,
+ (void *)CHG_COMP_OVR, ®_fops);
+ debugfs_create_file("CHG_BUCK_CTRL_TEST1", 0644, chip->dent,
+ (void *)CHG_BUCK_CTRL_TEST1, ®_fops);
+ debugfs_create_file("CHG_BUCK_CTRL_TEST2", 0644, chip->dent,
+ (void *)CHG_BUCK_CTRL_TEST2, ®_fops);
+ debugfs_create_file("CHG_BUCK_CTRL_TEST3", 0644, chip->dent,
+ (void *)CHG_BUCK_CTRL_TEST3, ®_fops);
+ debugfs_create_file("CHG_TEST", 0644, chip->dent,
+ (void *)CHG_TEST, ®_fops);
+
+ debugfs_create_file("FSM_STATE", 0644, chip->dent, NULL,
+ &fsm_fops);
+
+ debugfs_create_file("REGULATION_LOOP_CONTROL", 0644, chip->dent, NULL,
+ ®_loop_fops);
+
+ debugfs_create_file("BAT_WARM_ZONE", 0644, chip->dent,
+ (void *)BAT_WARM_ZONE, &warm_cool_fops);
+ debugfs_create_file("BAT_COOL_ZONE", 0644, chip->dent,
+ (void *)BAT_COOL_ZONE, &warm_cool_fops);
+
+ for (i = 0; i < ARRAY_SIZE(chg_irq_data); i++) {
+ if (chip->pmic_chg_irq[chg_irq_data[i].irq_id])
+ debugfs_create_file(chg_irq_data[i].name, 0444,
+ chip->dent,
+ (void *)chg_irq_data[i].irq_id,
+ &rt_fops);
+ }
+}
+
+static int pm8921_charger_suspend_noirq(struct device *dev)
+{
+ int rc;
+ struct pm8921_chg_chip *chip = dev_get_drvdata(dev);
+
+ rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON, 0);
+ if (rc)
+ pr_err("Failed to Force Vref therm off rc=%d\n", rc);
+ pm8921_chg_set_hw_clk_switching(chip);
+ return 0;
+}
+
+static int pm8921_charger_resume_noirq(struct device *dev)
+{
+ int rc;
+ struct pm8921_chg_chip *chip = dev_get_drvdata(dev);
+
+ pm8921_chg_force_19p2mhz_clk(chip);
+
+ rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON,
+ VREF_BATT_THERM_FORCE_ON);
+ if (rc)
+ pr_err("Failed to Force Vref therm on rc=%d\n", rc);
+ return 0;
+}
+
+static int pm8921_charger_resume(struct device *dev)
+{
+ int rc;
+ struct pm8921_chg_chip *chip = dev_get_drvdata(dev);
+
+ if (!(chip->cool_temp_dc == INT_MIN && chip->warm_temp_dc == INT_MIN)
+ && !(chip->keep_btm_on_suspend)) {
+ rc = pm8xxx_adc_btm_configure(&btm_config);
+ if (rc)
+ pr_err("couldn't reconfigure btm rc=%d\n", rc);
+
+ rc = pm8xxx_adc_btm_start();
+ if (rc)
+ pr_err("couldn't restart btm rc=%d\n", rc);
+ }
+ if (pm8921_chg_is_enabled(chip, LOOP_CHANGE_IRQ)) {
+ disable_irq_wake(chip->pmic_chg_irq[LOOP_CHANGE_IRQ]);
+ pm8921_chg_disable_irq(chip, LOOP_CHANGE_IRQ);
+ }
+ return 0;
+}
+
+static int pm8921_charger_suspend(struct device *dev)
+{
+ int rc;
+ struct pm8921_chg_chip *chip = dev_get_drvdata(dev);
+
+ if (!(chip->cool_temp_dc == INT_MIN && chip->warm_temp_dc == INT_MIN)
+ && !(chip->keep_btm_on_suspend)) {
+ rc = pm8xxx_adc_btm_end();
+ if (rc)
+ pr_err("Failed to disable BTM on suspend rc=%d\n", rc);
+ }
+
+ if (is_usb_chg_plugged_in(chip)) {
+ pm8921_chg_enable_irq(chip, LOOP_CHANGE_IRQ);
+ enable_irq_wake(chip->pmic_chg_irq[LOOP_CHANGE_IRQ]);
+ }
+
+ return 0;
+}
+static int __devinit pm8921_charger_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ struct pm8921_chg_chip *chip;
+ const struct pm8921_charger_platform_data *pdata
+ = pdev->dev.platform_data;
+
+ if (!pdata) {
+ pr_err("missing platform data\n");
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(struct pm8921_chg_chip),
+ GFP_KERNEL);
+ if (!chip) {
+ pr_err("Cannot allocate pm_chg_chip\n");
+ return -ENOMEM;
+ }
+
+ chip->dev = &pdev->dev;
+ chip->safety_time = pdata->safety_time;
+ chip->ttrkl_time = pdata->ttrkl_time;
+ chip->update_time = pdata->update_time;
+ chip->max_voltage_mv = pdata->max_voltage;
+ chip->min_voltage_mv = pdata->min_voltage;
+ chip->resume_voltage_delta = pdata->resume_voltage_delta;
+ chip->term_current = pdata->term_current;
+ chip->vbat_channel = pdata->charger_cdata.vbat_channel;
+ chip->batt_temp_channel = pdata->charger_cdata.batt_temp_channel;
+ chip->batt_id_channel = pdata->charger_cdata.batt_id_channel;
+ chip->batt_id_min = pdata->batt_id_min;
+ chip->batt_id_max = pdata->batt_id_max;
+ if (pdata->cool_temp != INT_MIN)
+ chip->cool_temp_dc = pdata->cool_temp * 10;
+ else
+ chip->cool_temp_dc = INT_MIN;
+
+ if (pdata->warm_temp != INT_MIN)
+ chip->warm_temp_dc = pdata->warm_temp * 10;
+ else
+ chip->warm_temp_dc = INT_MIN;
+
+ chip->temp_check_period = pdata->temp_check_period;
+ chip->max_bat_chg_current = pdata->max_bat_chg_current;
+ chip->cool_bat_chg_current = pdata->cool_bat_chg_current;
+ chip->warm_bat_chg_current = pdata->warm_bat_chg_current;
+ chip->cool_bat_voltage = pdata->cool_bat_voltage;
+ chip->warm_bat_voltage = pdata->warm_bat_voltage;
+ chip->keep_btm_on_suspend = pdata->keep_btm_on_suspend;
+ chip->trkl_voltage = pdata->trkl_voltage;
+ chip->weak_voltage = pdata->weak_voltage;
+ chip->trkl_current = pdata->trkl_current;
+ chip->weak_current = pdata->weak_current;
+ chip->vin_min = pdata->vin_min;
+ chip->thermal_mitigation = pdata->thermal_mitigation;
+ chip->thermal_levels = pdata->thermal_levels;
+
+ chip->cold_thr = pdata->cold_thr;
+ chip->hot_thr = pdata->hot_thr;
+ chip->rconn_mohm = pdata->rconn_mohm;
+ chip->led_src_config = pdata->led_src_config;
+
+ rc = pm8921_chg_hw_init(chip);
+ if (rc) {
+ pr_err("couldn't init hardware rc=%d\n", rc);
+ goto free_chip;
+ }
+
+ chip->usb_psy.name = "usb",
+ chip->usb_psy.type = POWER_SUPPLY_TYPE_USB,
+ chip->usb_psy.supplied_to = pm_power_supplied_to,
+ chip->usb_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to),
+ chip->usb_psy.properties = pm_power_props_usb,
+ chip->usb_psy.num_properties = ARRAY_SIZE(pm_power_props_usb),
+ chip->usb_psy.get_property = pm_power_get_property_usb,
+
+ chip->dc_psy.name = "pm8921-dc",
+ chip->dc_psy.type = POWER_SUPPLY_TYPE_MAINS,
+ chip->dc_psy.supplied_to = pm_power_supplied_to,
+ chip->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to),
+ chip->dc_psy.properties = pm_power_props_mains,
+ chip->dc_psy.num_properties = ARRAY_SIZE(pm_power_props_mains),
+ chip->dc_psy.get_property = pm_power_get_property_mains,
+
+ chip->batt_psy.name = "battery",
+ chip->batt_psy.type = POWER_SUPPLY_TYPE_BATTERY,
+ chip->batt_psy.properties = msm_batt_power_props,
+ chip->batt_psy.num_properties = ARRAY_SIZE(msm_batt_power_props),
+ chip->batt_psy.get_property = pm_batt_power_get_property,
+ chip->batt_psy.external_power_changed = pm_batt_external_power_changed,
+ rc = power_supply_register(chip->dev, &chip->usb_psy);
+ if (rc < 0) {
+ pr_err("power_supply_register usb failed rc = %d\n", rc);
+ goto free_chip;
+ }
+
+ rc = power_supply_register(chip->dev, &chip->dc_psy);
+ if (rc < 0) {
+ pr_err("power_supply_register usb failed rc = %d\n", rc);
+ goto unregister_usb;
+ }
+
+ rc = power_supply_register(chip->dev, &chip->batt_psy);
+ if (rc < 0) {
+ pr_err("power_supply_register batt failed rc = %d\n", rc);
+ goto unregister_dc;
+ }
+
+ platform_set_drvdata(pdev, chip);
+ the_chip = chip;
+
+ wake_lock_init(&chip->eoc_wake_lock, WAKE_LOCK_SUSPEND, "pm8921_eoc");
+ INIT_DELAYED_WORK(&chip->eoc_work, eoc_worker);
+ INIT_DELAYED_WORK(&chip->vin_collapse_check_work,
+ vin_collapse_check_worker);
+ INIT_DELAYED_WORK(&chip->unplug_check_work, unplug_check_worker);
+
+ rc = request_irqs(chip, pdev);
+ if (rc) {
+ pr_err("couldn't register interrupts rc=%d\n", rc);
+ goto unregister_batt;
+ }
+
+ enable_irq_wake(chip->pmic_chg_irq[USBIN_VALID_IRQ]);
+ enable_irq_wake(chip->pmic_chg_irq[USBIN_OV_IRQ]);
+ enable_irq_wake(chip->pmic_chg_irq[USBIN_UV_IRQ]);
+ enable_irq_wake(chip->pmic_chg_irq[BAT_TEMP_OK_IRQ]);
+ enable_irq_wake(chip->pmic_chg_irq[VBATDET_LOW_IRQ]);
+ enable_irq_wake(chip->pmic_chg_irq[FASTCHG_IRQ]);
+ /*
+ * if both the cool_temp_dc and warm_temp_dc are invalid device doesnt
+ * care for jeita compliance
+ */
+ if (!(chip->cool_temp_dc == INT_MIN && chip->warm_temp_dc == INT_MIN)) {
+ rc = configure_btm(chip);
+ if (rc) {
+ pr_err("couldn't register with btm rc=%d\n", rc);
+ goto free_irq;
+ }
+ }
+
+ create_debugfs_entries(chip);
+
+ INIT_WORK(&chip->bms_notify.work, bms_notify);
+ INIT_WORK(&chip->battery_id_valid_work, battery_id_valid);
+
+ /* determine what state the charger is in */
+ determine_initial_state(chip);
+
+ if (chip->update_time) {
+ INIT_DELAYED_WORK(&chip->update_heartbeat_work,
+ update_heartbeat);
+ schedule_delayed_work(&chip->update_heartbeat_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (chip->update_time)));
+ }
+ return 0;
+
+free_irq:
+ free_irqs(chip);
+unregister_batt:
+ power_supply_unregister(&chip->batt_psy);
+unregister_dc:
+ power_supply_unregister(&chip->dc_psy);
+unregister_usb:
+ power_supply_unregister(&chip->usb_psy);
+free_chip:
+ kfree(chip);
+ return rc;
+}
+
+static int __devexit pm8921_charger_remove(struct platform_device *pdev)
+{
+ struct pm8921_chg_chip *chip = platform_get_drvdata(pdev);
+
+ free_irqs(chip);
+ platform_set_drvdata(pdev, NULL);
+ the_chip = NULL;
+ kfree(chip);
+ return 0;
+}
+static const struct dev_pm_ops pm8921_pm_ops = {
+ .suspend = pm8921_charger_suspend,
+ .suspend_noirq = pm8921_charger_suspend_noirq,
+ .resume_noirq = pm8921_charger_resume_noirq,
+ .resume = pm8921_charger_resume,
+};
+static struct platform_driver pm8921_charger_driver = {
+ .probe = pm8921_charger_probe,
+ .remove = __devexit_p(pm8921_charger_remove),
+ .driver = {
+ .name = PM8921_CHARGER_DEV_NAME,
+ .owner = THIS_MODULE,
+ .pm = &pm8921_pm_ops,
+ },
+};
+
+static int __init pm8921_charger_init(void)
+{
+ return platform_driver_register(&pm8921_charger_driver);
+}
+
+static void __exit pm8921_charger_exit(void)
+{
+ platform_driver_unregister(&pm8921_charger_driver);
+}
+
+late_initcall(pm8921_charger_init);
+module_exit(pm8921_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8921 charger/battery driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:" PM8921_CHARGER_DEV_NAME);
diff --git a/drivers/power/pm8xxx-ccadc.c b/drivers/power/pm8xxx-ccadc.c
new file mode 100644
index 0000000..ce72a5b
--- /dev/null
+++ b/drivers/power/pm8xxx-ccadc.c
@@ -0,0 +1,734 @@
+/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/mfd/pm8xxx/ccadc.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#define CCADC_ANA_PARAM 0x240
+#define CCADC_DIG_PARAM 0x241
+#define CCADC_RSV 0x242
+#define CCADC_DATA0 0x244
+#define CCADC_DATA1 0x245
+#define CCADC_OFFSET_TRIM1 0x34A
+#define CCADC_OFFSET_TRIM0 0x34B
+#define CCADC_FULLSCALE_TRIM1 0x34C
+#define CCADC_FULLSCALE_TRIM0 0x34D
+
+/* note : TRIM1 is the msb and TRIM0 is the lsb */
+#define ADC_ARB_SECP_CNTRL 0x190
+#define ADC_ARB_SECP_AMUX_CNTRL 0x191
+#define ADC_ARB_SECP_ANA_PARAM 0x192
+#define ADC_ARB_SECP_DIG_PARAM 0x193
+#define ADC_ARB_SECP_RSV 0x194
+#define ADC_ARB_SECP_DATA1 0x195
+#define ADC_ARB_SECP_DATA0 0x196
+
+#define ADC_ARB_BMS_CNTRL 0x18D
+
+#define START_CONV_BIT BIT(7)
+#define EOC_CONV_BIT BIT(6)
+#define SEL_CCADC_BIT BIT(1)
+#define EN_ARB_BIT BIT(0)
+
+#define CCADC_CALIB_DIG_PARAM 0xE3
+#define CCADC_CALIB_RSV_GND 0x40
+#define CCADC_CALIB_RSV_25MV 0x80
+#define CCADC_CALIB_ANA_PARAM 0x1B
+#define SAMPLE_COUNT 16
+#define ADC_WAIT_COUNT 10
+
+#define CCADC_MAX_25MV 30000
+#define CCADC_MIN_25MV 20000
+#define CCADC_MAX_0UV -4000
+#define CCADC_MIN_0UV -7000
+
+#define CCADC_INTRINSIC_OFFSET 0xC000
+
+struct pm8xxx_ccadc_chip {
+ struct device *dev;
+ struct dentry *dent;
+ u16 ccadc_offset;
+ int ccadc_gain_uv;
+ unsigned int revision;
+ int eoc_irq;
+ int r_sense;
+};
+
+static struct pm8xxx_ccadc_chip *the_chip;
+
+#ifdef DEBUG
+static s64 microvolt_to_ccadc_reading_v1(s64 uv)
+{
+ return div_s64(uv * CCADC_READING_RESOLUTION_D_V1,
+ CCADC_READING_RESOLUTION_N_V1);
+}
+
+static s64 microvolt_to_ccadc_reading_v2(s64 uv)
+{
+ return div_s64(uv * CCADC_READING_RESOLUTION_D_V2,
+ CCADC_READING_RESOLUTION_N_V2);
+}
+
+static s64 microvolt_to_ccadc_reading(struct pm8xxx_ccadc_chip *chip, s64 cc)
+{
+ /*
+ * resolution (the value of a single bit) was changed after revision 2.0
+ * for more accurate readings
+ */
+ return (the_chip->revision < PM8XXX_REVISION_8921_2p0) ?
+ microvolt_to_ccadc_reading_v1((s64)cc) :
+ microvolt_to_ccadc_reading_v2((s64)cc);
+}
+#endif
+
+static int cc_adjust_for_offset(u16 raw)
+{
+ /* this has the intrinsic offset */
+ return (int)raw - the_chip->ccadc_offset;
+}
+
+#define GAIN_REFERENCE_UV 25000
+/*
+ * gain compensation for ccadc readings - common for vsense based and
+ * couloumb counter based readings
+ */
+s64 pm8xxx_cc_adjust_for_gain(s64 uv)
+{
+ if (the_chip == NULL || the_chip->ccadc_gain_uv == 0)
+ return uv;
+
+ return div_s64(uv * GAIN_REFERENCE_UV, the_chip->ccadc_gain_uv);
+}
+EXPORT_SYMBOL(pm8xxx_cc_adjust_for_gain);
+
+static int pm_ccadc_masked_write(struct pm8xxx_ccadc_chip *chip, u16 addr,
+ u8 mask, u8 val)
+{
+ int rc;
+ u8 reg;
+
+ rc = pm8xxx_readb(chip->dev->parent, addr, ®);
+ if (rc) {
+ pr_err("read failed addr = %03X, rc = %d\n", addr, rc);
+ return rc;
+ }
+ reg &= ~mask;
+ reg |= val & mask;
+ rc = pm8xxx_writeb(chip->dev->parent, addr, reg);
+ if (rc) {
+ pr_err("write failed addr = %03X, rc = %d\n", addr, rc);
+ return rc;
+ }
+ return 0;
+}
+
+#define REG_SBI_CONFIG 0x04F
+#define PAGE3_ENABLE_MASK 0x6
+static int calib_ccadc_enable_trim_access(struct pm8xxx_ccadc_chip *chip,
+ u8 *sbi_config)
+{
+ u8 reg;
+ int rc;
+
+ rc = pm8xxx_readb(chip->dev->parent, REG_SBI_CONFIG, sbi_config);
+ if (rc) {
+ pr_err("error = %d reading sbi config reg\n", rc);
+ return rc;
+ }
+
+ reg = *sbi_config | PAGE3_ENABLE_MASK;
+ return pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, reg);
+}
+
+static int calib_ccadc_restore_trim_access(struct pm8xxx_ccadc_chip *chip,
+ u8 sbi_config)
+{
+ return pm8xxx_writeb(chip->dev->parent, REG_SBI_CONFIG, sbi_config);
+}
+
+static int calib_ccadc_enable_arbiter(struct pm8xxx_ccadc_chip *chip)
+{
+ int rc;
+
+ /* enable Arbiter, must be sent twice */
+ rc = pm_ccadc_masked_write(chip, ADC_ARB_SECP_CNTRL,
+ SEL_CCADC_BIT | EN_ARB_BIT, SEL_CCADC_BIT | EN_ARB_BIT);
+ if (rc < 0) {
+ pr_err("error = %d enabling arbiter for offset\n", rc);
+ return rc;
+ }
+ rc = pm_ccadc_masked_write(chip, ADC_ARB_SECP_CNTRL,
+ SEL_CCADC_BIT | EN_ARB_BIT, SEL_CCADC_BIT | EN_ARB_BIT);
+ if (rc < 0) {
+ pr_err("error = %d writing ADC_ARB_SECP_CNTRL\n", rc);
+ return rc;
+ }
+ return 0;
+}
+
+static int calib_start_conv(struct pm8xxx_ccadc_chip *chip,
+ u16 *result)
+{
+ int rc, i;
+ u8 data_msb, data_lsb, reg;
+
+ /* Start conversion */
+ rc = pm_ccadc_masked_write(chip, ADC_ARB_SECP_CNTRL,
+ START_CONV_BIT, START_CONV_BIT);
+ if (rc < 0) {
+ pr_err("error = %d starting offset meas\n", rc);
+ return rc;
+ }
+
+ /* Wait for End of conversion */
+ for (i = 0; i < ADC_WAIT_COUNT; i++) {
+ rc = pm8xxx_readb(chip->dev->parent,
+ ADC_ARB_SECP_CNTRL, ®);
+ if (rc < 0) {
+ pr_err("error = %d read eoc for offset\n", rc);
+ return rc;
+ }
+ if ((reg & (START_CONV_BIT | EOC_CONV_BIT)) != EOC_CONV_BIT)
+ msleep(20);
+ else
+ break;
+ }
+ if (i == ADC_WAIT_COUNT) {
+ pr_err("waited too long for offset eoc returning -EBUSY\n");
+ return -EBUSY;
+ }
+
+ rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_SECP_DATA0, &data_lsb);
+ if (rc < 0) {
+ pr_err("error = %d reading offset lsb\n", rc);
+ return rc;
+ }
+
+ rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_SECP_DATA1, &data_msb);
+ if (rc < 0) {
+ pr_err("error = %d reading offset msb\n", rc);
+ return rc;
+ }
+
+ *result = (data_msb << 8) | data_lsb;
+ return 0;
+}
+
+static int calib_ccadc_read_trim(struct pm8xxx_ccadc_chip *chip,
+ int addr, u8 *data_msb, u8 *data_lsb)
+{
+ int rc;
+ u8 sbi_config;
+
+ calib_ccadc_enable_trim_access(chip, &sbi_config);
+ rc = pm8xxx_readb(chip->dev->parent, addr, data_msb);
+ if (rc < 0) {
+ pr_err("error = %d read msb\n", rc);
+ return rc;
+ }
+ rc = pm8xxx_readb(chip->dev->parent, addr + 1, data_lsb);
+ if (rc < 0) {
+ pr_err("error = %d read lsb\n", rc);
+ return rc;
+ }
+ calib_ccadc_restore_trim_access(chip, sbi_config);
+ return 0;
+}
+
+static void calib_ccadc_read_offset_and_gain(struct pm8xxx_ccadc_chip *chip,
+ int *gain, u16 *offset)
+{
+ u8 data_msb;
+ u8 data_lsb;
+ int rc;
+
+ rc = calib_ccadc_read_trim(chip, CCADC_FULLSCALE_TRIM1,
+ &data_msb, &data_lsb);
+ *gain = (data_msb << 8) | data_lsb;
+
+ rc = calib_ccadc_read_trim(chip, CCADC_OFFSET_TRIM1,
+ &data_msb, &data_lsb);
+ *offset = (data_msb << 8) | data_lsb;
+
+ pr_debug("raw gain trim = 0x%x offset trim =0x%x\n", *gain, *offset);
+ *gain = pm8xxx_ccadc_reading_to_microvolt(chip->revision,
+ (s64)*gain - *offset);
+ pr_debug("gain uv = %duV offset=0x%x\n", *gain, *offset);
+}
+
+#define CCADC_PROGRAM_TRIM_COUNT 2
+#define ADC_ARB_BMS_CNTRL_CCADC_SHIFT 4
+#define ADC_ARB_BMS_CNTRL_CONV_MASK 0x03
+#define BMS_CONV_IN_PROGRESS 0x2
+
+static int calib_ccadc_program_trim(struct pm8xxx_ccadc_chip *chip,
+ int addr, u8 data_msb, u8 data_lsb,
+ int wait)
+{
+ int i, rc, loop;
+ u8 cntrl, sbi_config;
+ bool in_progress = 0;
+
+ loop = wait ? CCADC_PROGRAM_TRIM_COUNT : 0;
+
+ calib_ccadc_enable_trim_access(chip, &sbi_config);
+
+ for (i = 0; i < loop; i++) {
+ rc = pm8xxx_readb(chip->dev->parent, ADC_ARB_BMS_CNTRL, &cntrl);
+ if (rc < 0) {
+ pr_err("error = %d reading ADC_ARB_BMS_CNTRL\n", rc);
+ return rc;
+ }
+
+ /* break if a ccadc conversion is not happening */
+ in_progress = (((cntrl >> ADC_ARB_BMS_CNTRL_CCADC_SHIFT)
+ & ADC_ARB_BMS_CNTRL_CONV_MASK) == BMS_CONV_IN_PROGRESS);
+
+ if (!in_progress)
+ break;
+ }
+
+ if (in_progress) {
+ pr_debug("conv in progress cannot write trim,returing EBUSY\n");
+ return -EBUSY;
+ }
+
+ rc = pm8xxx_writeb(chip->dev->parent, addr, data_msb);
+ if (rc < 0) {
+ pr_err("error = %d write msb = 0x%x\n", rc, data_msb);
+ return rc;
+ }
+ rc = pm8xxx_writeb(chip->dev->parent, addr + 1, data_lsb);
+ if (rc < 0) {
+ pr_err("error = %d write lsb = 0x%x\n", rc, data_lsb);
+ return rc;
+ }
+ calib_ccadc_restore_trim_access(chip, sbi_config);
+ return 0;
+}
+
+void pm8xxx_calib_ccadc(void)
+{
+ u8 data_msb, data_lsb, sec_cntrl;
+ int result_offset, result_gain;
+ u16 result;
+ int i, rc;
+
+ rc = pm8xxx_readb(the_chip->dev->parent,
+ ADC_ARB_SECP_CNTRL, &sec_cntrl);
+ if (rc < 0) {
+ pr_err("error = %d reading ADC_ARB_SECP_CNTRL\n", rc);
+ return;
+ }
+
+ rc = calib_ccadc_enable_arbiter(the_chip);
+ if (rc < 0) {
+ pr_err("error = %d enabling arbiter for offset\n", rc);
+ goto bail;
+ }
+
+ /*
+ * Set decimation ratio to 4k, lower ratio may be used in order to speed
+ * up, pending verification through bench
+ */
+ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_DIG_PARAM,
+ CCADC_CALIB_DIG_PARAM);
+ if (rc < 0) {
+ pr_err("error = %d writing ADC_ARB_SECP_DIG_PARAM\n", rc);
+ goto bail;
+ }
+
+ result_offset = 0;
+ for (i = 0; i < SAMPLE_COUNT; i++) {
+ /* Short analog inputs to CCADC internally to ground */
+ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_RSV,
+ CCADC_CALIB_RSV_GND);
+ if (rc < 0) {
+ pr_err("error = %d selecting gnd voltage\n", rc);
+ goto bail;
+ }
+
+ /* Enable CCADC */
+ rc = pm8xxx_writeb(the_chip->dev->parent,
+ ADC_ARB_SECP_ANA_PARAM, CCADC_CALIB_ANA_PARAM);
+ if (rc < 0) {
+ pr_err("error = %d enabling ccadc\n", rc);
+ goto bail;
+ }
+
+ rc = calib_start_conv(the_chip, &result);
+ if (rc < 0) {
+ pr_err("error = %d for zero volt measurement\n", rc);
+ goto bail;
+ }
+
+ result_offset += result;
+ }
+
+ result_offset = result_offset / SAMPLE_COUNT;
+
+
+ pr_debug("offset result_offset = 0x%x, voltage = %llduV\n",
+ result_offset,
+ pm8xxx_ccadc_reading_to_microvolt(the_chip->revision,
+ ((s64)result_offset - CCADC_INTRINSIC_OFFSET)));
+
+ the_chip->ccadc_offset = result_offset;
+ data_msb = the_chip->ccadc_offset >> 8;
+ data_lsb = the_chip->ccadc_offset;
+
+ rc = calib_ccadc_program_trim(the_chip, CCADC_OFFSET_TRIM1,
+ data_msb, data_lsb, 1);
+ if (rc) {
+ pr_debug("error = %d programming offset trim 0x%02x 0x%02x\n",
+ rc, data_msb, data_lsb);
+ /* enable the interrupt and write it when it fires */
+ enable_irq(the_chip->eoc_irq);
+ }
+
+ rc = calib_ccadc_enable_arbiter(the_chip);
+ if (rc < 0) {
+ pr_err("error = %d enabling arbiter for gain\n", rc);
+ goto bail;
+ }
+
+ /*
+ * Set decimation ratio to 4k, lower ratio may be used in order to speed
+ * up, pending verification through bench
+ */
+ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_DIG_PARAM,
+ CCADC_CALIB_DIG_PARAM);
+ if (rc < 0) {
+ pr_err("error = %d enabling decimation ration for gain\n", rc);
+ goto bail;
+ }
+
+ result_gain = 0;
+ for (i = 0; i < SAMPLE_COUNT; i++) {
+ rc = pm8xxx_writeb(the_chip->dev->parent,
+ ADC_ARB_SECP_RSV, CCADC_CALIB_RSV_25MV);
+ if (rc < 0) {
+ pr_err("error = %d selecting 25mV for gain\n", rc);
+ goto bail;
+ }
+
+ /* Enable CCADC */
+ rc = pm8xxx_writeb(the_chip->dev->parent,
+ ADC_ARB_SECP_ANA_PARAM, CCADC_CALIB_ANA_PARAM);
+ if (rc < 0) {
+ pr_err("error = %d enabling ccadc\n", rc);
+ goto bail;
+ }
+
+ rc = calib_start_conv(the_chip, &result);
+ if (rc < 0) {
+ pr_err("error = %d for adc reading 25mV\n", rc);
+ goto bail;
+ }
+
+ result_gain += result;
+ }
+ result_gain = result_gain / SAMPLE_COUNT;
+
+ /*
+ * result_offset includes INTRINSIC OFFSET
+ * the_chip->ccadc_gain_uv will be the actual voltage
+ * measured for 25000UV
+ */
+ the_chip->ccadc_gain_uv = pm8xxx_ccadc_reading_to_microvolt(
+ the_chip->revision,
+ ((s64)result_gain - result_offset));
+
+ pr_debug("gain result_gain = 0x%x, voltage = %d microVolts\n",
+ result_gain, the_chip->ccadc_gain_uv);
+
+ data_msb = result_gain >> 8;
+ data_lsb = result_gain;
+ rc = calib_ccadc_program_trim(the_chip, CCADC_FULLSCALE_TRIM1,
+ data_msb, data_lsb, 0);
+ if (rc)
+ pr_debug("error = %d programming gain trim\n", rc);
+bail:
+ pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_CNTRL, sec_cntrl);
+}
+EXPORT_SYMBOL(pm8xxx_calib_ccadc);
+
+static irqreturn_t pm8921_bms_ccadc_eoc_handler(int irq, void *data)
+{
+ u8 data_msb, data_lsb;
+ struct pm8xxx_ccadc_chip *chip = data;
+ int rc;
+
+ pr_debug("irq = %d triggered\n", irq);
+ data_msb = chip->ccadc_offset >> 8;
+ data_lsb = chip->ccadc_offset;
+
+ rc = calib_ccadc_program_trim(chip, CCADC_OFFSET_TRIM1,
+ data_msb, data_lsb, 0);
+ disable_irq_nosync(chip->eoc_irq);
+
+ return IRQ_HANDLED;
+}
+
+#define CCADC_IBAT_DIG_PARAM 0xA3
+#define CCADC_IBAT_RSV 0x10
+#define CCADC_IBAT_ANA_PARAM 0x1A
+static int ccadc_get_rsense_voltage(int *voltage_uv)
+{
+ u16 raw;
+ int result;
+ int rc = 0;
+
+ rc = calib_ccadc_enable_arbiter(the_chip);
+ if (rc < 0) {
+ pr_err("error = %d enabling arbiter for offset\n", rc);
+ return rc;
+ }
+
+ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_DIG_PARAM,
+ CCADC_IBAT_DIG_PARAM);
+ if (rc < 0) {
+ pr_err("error = %d writing ADC_ARB_SECP_DIG_PARAM\n", rc);
+ return rc;
+ }
+
+ rc = pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_RSV,
+ CCADC_IBAT_RSV);
+ if (rc < 0) {
+ pr_err("error = %d selecting rsense\n", rc);
+ return rc;
+ }
+
+ rc = pm8xxx_writeb(the_chip->dev->parent,
+ ADC_ARB_SECP_ANA_PARAM, CCADC_IBAT_ANA_PARAM);
+ if (rc < 0) {
+ pr_err("error = %d enabling ccadc\n", rc);
+ return rc;
+ }
+
+ rc = calib_start_conv(the_chip, &raw);
+ if (rc < 0) {
+ pr_err("error = %d for zero volt measurement\n", rc);
+ return rc;
+ }
+
+ pr_debug("Vsense raw = 0x%x\n", raw);
+ result = cc_adjust_for_offset(raw);
+ pr_debug("Vsense after offset raw = 0x%x offset=0x%x\n",
+ result,
+ the_chip->ccadc_offset);
+ *voltage_uv = pm8xxx_ccadc_reading_to_microvolt(the_chip->revision,
+ ((s64)result));
+ pr_debug("Vsense before gain of %d = %d uV\n", the_chip->ccadc_gain_uv,
+ *voltage_uv);
+ *voltage_uv = pm8xxx_cc_adjust_for_gain(*voltage_uv);
+
+ pr_debug("Vsense = %d uV\n", *voltage_uv);
+ return 0;
+}
+
+int pm8xxx_ccadc_get_battery_current(int *bat_current_ua)
+{
+ int voltage_uv = 0, rc;
+
+ rc = ccadc_get_rsense_voltage(&voltage_uv);
+ if (rc) {
+ pr_err("cant get voltage across rsense rc = %d\n", rc);
+ return rc;
+ }
+
+ *bat_current_ua = voltage_uv * 1000/the_chip->r_sense;
+ /*
+ * ccadc reads +ve current when the battery is charging
+ * We need to return -ve if the battery is charging
+ */
+ *bat_current_ua = -1 * (*bat_current_ua);
+ pr_debug("bat current = %d ma\n", *bat_current_ua);
+ return 0;
+}
+EXPORT_SYMBOL(pm8xxx_ccadc_get_battery_current);
+
+static int get_reg(void *data, u64 * val)
+{
+ int addr = (int)data;
+ int ret;
+ u8 temp;
+
+ ret = pm8xxx_readb(the_chip->dev->parent, addr, &temp);
+ if (ret) {
+ pr_err("pm8xxx_readb to %x value = %d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+ *val = temp;
+ return 0;
+}
+
+static int set_reg(void *data, u64 val)
+{
+ int addr = (int)data;
+ int ret;
+ u8 temp;
+
+ temp = (u8) val;
+ ret = pm8xxx_writeb(the_chip->dev->parent, addr, temp);
+ if (ret) {
+ pr_err("pm8xxx_writeb to %x value = %d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n");
+
+static int get_calc(void *data, u64 * val)
+{
+ int ibat, rc;
+
+ rc = pm8xxx_ccadc_get_battery_current(&ibat);
+ *val = ibat;
+ return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(calc_fops, get_calc, NULL, "%lld\n");
+
+static void create_debugfs_entries(struct pm8xxx_ccadc_chip *chip)
+{
+ chip->dent = debugfs_create_dir("pm8xxx-ccadc", NULL);
+
+ if (IS_ERR(chip->dent)) {
+ pr_err("ccadc couldnt create debugfs dir\n");
+ return;
+ }
+
+ debugfs_create_file("CCADC_ANA_PARAM", 0644, chip->dent,
+ (void *)CCADC_ANA_PARAM, ®_fops);
+ debugfs_create_file("CCADC_DIG_PARAM", 0644, chip->dent,
+ (void *)CCADC_DIG_PARAM, ®_fops);
+ debugfs_create_file("CCADC_RSV", 0644, chip->dent,
+ (void *)CCADC_RSV, ®_fops);
+ debugfs_create_file("CCADC_DATA0", 0644, chip->dent,
+ (void *)CCADC_DATA0, ®_fops);
+ debugfs_create_file("CCADC_DATA1", 0644, chip->dent,
+ (void *)CCADC_DATA1, ®_fops);
+ debugfs_create_file("CCADC_OFFSET_TRIM1", 0644, chip->dent,
+ (void *)CCADC_OFFSET_TRIM1, ®_fops);
+ debugfs_create_file("CCADC_OFFSET_TRIM0", 0644, chip->dent,
+ (void *)CCADC_OFFSET_TRIM0, ®_fops);
+ debugfs_create_file("CCADC_FULLSCALE_TRIM1", 0644, chip->dent,
+ (void *)CCADC_FULLSCALE_TRIM1, ®_fops);
+ debugfs_create_file("CCADC_FULLSCALE_TRIM0", 0644, chip->dent,
+ (void *)CCADC_FULLSCALE_TRIM0, ®_fops);
+
+ debugfs_create_file("show_ibatt", 0644, chip->dent,
+ (void *)0, &calc_fops);
+}
+
+static int __devinit pm8xxx_ccadc_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ struct pm8xxx_ccadc_chip *chip;
+ struct resource *res;
+ const struct pm8xxx_ccadc_platform_data *pdata
+ = pdev->dev.platform_data;
+
+ if (!pdata) {
+ pr_err("missing platform data\n");
+ return -EINVAL;
+ }
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "PM8921_BMS_CCADC_EOC");
+ if (!res) {
+ pr_err("failed to get irq\n");
+ return -EINVAL;
+ }
+
+ chip = kzalloc(sizeof(struct pm8xxx_ccadc_chip), GFP_KERNEL);
+ if (!chip) {
+ pr_err("Cannot allocate pm_bms_chip\n");
+ return -ENOMEM;
+ }
+ chip->dev = &pdev->dev;
+ chip->revision = pm8xxx_get_revision(chip->dev->parent);
+ chip->eoc_irq = res->start;
+ chip->r_sense = pdata->r_sense;
+
+ calib_ccadc_read_offset_and_gain(chip,
+ &chip->ccadc_gain_uv,
+ &chip->ccadc_offset);
+ rc = request_irq(chip->eoc_irq,
+ pm8921_bms_ccadc_eoc_handler, IRQF_TRIGGER_RISING,
+ "bms_eoc_ccadc", chip);
+ if (rc) {
+ pr_err("failed to request %d irq rc= %d\n", chip->eoc_irq, rc);
+ goto free_chip;
+ }
+ disable_irq_nosync(chip->eoc_irq);
+
+ platform_set_drvdata(pdev, chip);
+ the_chip = chip;
+
+ create_debugfs_entries(chip);
+
+ return 0;
+
+free_chip:
+ kfree(chip);
+ return rc;
+}
+
+static int __devexit pm8xxx_ccadc_remove(struct platform_device *pdev)
+{
+ struct pm8xxx_ccadc_chip *chip = platform_get_drvdata(pdev);
+
+ debugfs_remove_recursive(chip->dent);
+ the_chip = NULL;
+ kfree(chip);
+ return 0;
+}
+
+static struct platform_driver pm8xxx_ccadc_driver = {
+ .probe = pm8xxx_ccadc_probe,
+ .remove = __devexit_p(pm8xxx_ccadc_remove),
+ .driver = {
+ .name = PM8XXX_CCADC_DEV_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pm8xxx_ccadc_init(void)
+{
+ return platform_driver_register(&pm8xxx_ccadc_driver);
+}
+
+static void __exit pm8xxx_ccadc_exit(void)
+{
+ platform_driver_unregister(&pm8xxx_ccadc_driver);
+}
+
+module_init(pm8xxx_ccadc_init);
+module_exit(pm8xxx_ccadc_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8XXX ccadc driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:" PM8XXX_CCADC_DEV_NAME);
diff --git a/drivers/power/pmic8058-charger.c b/drivers/power/pmic8058-charger.c
new file mode 100644
index 0000000..70b5d59
--- /dev/null
+++ b/drivers/power/pmic8058-charger.c
@@ -0,0 +1,2047 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/workqueue.h>
+#include <linux/msm-charger.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/msm_adc.h>
+#include <linux/notifier.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/pmic8058-charger.h>
+#include <linux/mfd/pm8xxx/batt-alarm.h>
+
+#include <mach/msm_xo.h>
+#include <mach/msm_hsusb.h>
+
+/* Config Regs and their bits*/
+#define PM8058_CHG_TEST 0x75
+#define IGNORE_LL 2
+#define PM8058_CHG_TEST_2 0xEA
+#define PM8058_CHG_TEST_3 0xEB
+#define PM8058_OVP_TEST_REG 0xF6
+#define FORCE_OVP_OFF 3
+
+#define PM8058_CHG_CNTRL 0x1E
+#define CHG_TRICKLE_EN 7
+#define CHG_USB_SUSPEND 6
+#define CHG_IMON_CAL 5
+#define CHG_IMON_GAIN 4
+#define CHG_CHARGE_BAT 3
+#define CHG_VBUS_FROM_BOOST_OVRD 2
+#define CHG_CHARGE_DIS 1
+#define CHG_VCP_EN 0
+
+#define PM8058_CHG_CNTRL_2 0xD8
+#define ATC_DIS 7 /* coincell backed */
+#define CHARGE_AUTO_DIS 6
+#define DUMB_CHG_OVRD 5 /* coincell backed */
+#define ENUM_DONE 4
+#define CHG_TEMP_MODE 3
+#define CHG_BATT_TEMP_DIS 1 /* coincell backed */
+#define CHG_FAILED_CLEAR 0
+
+#define PM8058_CHG_VMAX_SEL 0x21
+#define PM8058_CHG_VBAT_DET 0xD9
+#define PM8058_CHG_IMAX 0x1F
+#define PM8058_CHG_TRICKLE 0xDB
+#define PM8058_CHG_ITERM 0xDC
+#define PM8058_CHG_TTRKL_MAX 0xE1
+#define PM8058_CHG_TCHG_MAX 0xE4
+#define PM8058_CHG_TEMP_THRESH 0xE2
+#define PM8058_CHG_TEMP_REG 0xE3
+#define PM8058_CHG_PULSE 0x22
+
+/* IRQ STATUS and CLEAR */
+#define PM8058_CHG_STATUS_CLEAR_IRQ_1 0x31
+#define PM8058_CHG_STATUS_CLEAR_IRQ_3 0x33
+#define PM8058_CHG_STATUS_CLEAR_IRQ_10 0xB3
+#define PM8058_CHG_STATUS_CLEAR_IRQ_11 0xB4
+
+/* IRQ MASKS */
+#define PM8058_CHG_MASK_IRQ_1 0x38
+
+#define PM8058_CHG_MASK_IRQ_3 0x3A
+#define PM8058_CHG_MASK_IRQ_10 0xBA
+#define PM8058_CHG_MASK_IRQ_11 0xBB
+
+/* IRQ Real time status regs */
+#define PM8058_CHG_STATUS_RT_1 0x3F
+#define STATUS_RTCHGVAL 7
+#define STATUS_RTCHGINVAL 6
+#define STATUS_RTBATT_REPLACE 5
+#define STATUS_RTVBATDET_LOW 4
+#define STATUS_RTCHGILIM 3
+#define STATUS_RTPCTDONE 1
+#define STATUS_RTVCP 0
+#define PM8058_CHG_STATUS_RT_3 0x41
+#define PM8058_CHG_STATUS_RT_10 0xC1
+#define PM8058_CHG_STATUS_RT_11 0xC2
+
+/* VTRIM */
+#define PM8058_CHG_VTRIM 0x1D
+#define PM8058_CHG_VBATDET_TRIM 0x1E
+#define PM8058_CHG_ITRIM 0x1F
+#define PM8058_CHG_TTRIM 0x20
+
+#define AUTO_CHARGING_VMAXSEL 4200
+#define AUTO_CHARGING_FAST_TIME_MAX_MINUTES 512
+#define AUTO_CHARGING_TRICKLE_TIME_MINUTES 30
+#define AUTO_CHARGING_VEOC_ITERM 100
+#define AUTO_CHARGING_IEOC_ITERM 160
+#define AUTO_CHARGING_RESUME_MV 4100
+
+#define AUTO_CHARGING_VBATDET 4150
+#define AUTO_CHARGING_VBATDET_DEBOUNCE_TIME_MS 3000
+#define AUTO_CHARGING_VEOC_VBATDET 4100
+#define AUTO_CHARGING_VEOC_TCHG 16
+#define AUTO_CHARGING_VEOC_TCHG_FINAL_CYCLE 32
+#define AUTO_CHARGING_VEOC_BEGIN_TIME_MS 5400000
+
+#define AUTO_CHARGING_VEOC_VBAT_LOW_CHECK_TIME_MS 60000
+#define AUTO_CHARGING_RESUME_CHARGE_DETECTION_COUNTER 5
+
+#define AUTO_CHARGING_DONE_CHECK_TIME_MS 1000
+
+#define PM8058_CHG_I_STEP_MA 50
+#define PM8058_CHG_I_MIN_MA 50
+#define PM8058_CHG_T_TCHG_SHIFT 2
+#define PM8058_CHG_I_TERM_STEP_MA 10
+#define PM8058_CHG_V_STEP_MV 25
+#define PM8058_CHG_V_MIN_MV 2400
+/*
+ * enum pmic_chg_interrupts: pmic interrupts
+ * @CHGVAL_IRQ: charger V between 3.3 and 7.9
+ * @CHGINVAL_IRQ: charger V outside 3.3 and 7.9
+ * @VBATDET_LOW_IRQ: VBAT < VBATDET
+ * @VCP_IRQ: VDD went below VBAT: BAT_FET is turned on
+ * @CHGILIM_IRQ: mA consumed>IMAXSEL: chgloop draws less mA
+ * @ATC_DONE_IRQ: Auto Trickle done
+ * @ATCFAIL_IRQ: Auto Trickle fail
+ * @AUTO_CHGDONE_IRQ: Auto chg done
+ * @AUTO_CHGFAIL_IRQ: time exceeded w/o reaching term current
+ * @CHGSTATE_IRQ: something happend causing a state change
+ * @FASTCHG_IRQ: trkl charging completed: moving to fastchg
+ * @CHG_END_IRQ: mA has dropped to termination current
+ * @BATTTEMP_IRQ: batt temp is out of range
+ * @CHGHOT_IRQ: the pass device is too hot
+ * @CHGTLIMIT_IRQ: unused
+ * @CHG_GONE_IRQ: charger was removed
+ * @VCPMAJOR_IRQ: vcp major
+ * @VBATDET_IRQ: VBAT >= VBATDET
+ * @BATFET_IRQ: BATFET closed
+ * @BATT_REPLACE_IRQ:
+ * @BATTCONNECT_IRQ:
+ */
+enum pmic_chg_interrupts {
+ CHGVAL_IRQ,
+ CHGINVAL_IRQ,
+ VBATDET_LOW_IRQ,
+ VCP_IRQ,
+ CHGILIM_IRQ,
+ ATC_DONE_IRQ,
+ ATCFAIL_IRQ,
+ AUTO_CHGDONE_IRQ,
+ AUTO_CHGFAIL_IRQ,
+ CHGSTATE_IRQ,
+ FASTCHG_IRQ,
+ CHG_END_IRQ,
+ BATTTEMP_IRQ,
+ CHGHOT_IRQ,
+ CHGTLIMIT_IRQ,
+ CHG_GONE_IRQ,
+ VCPMAJOR_IRQ,
+ VBATDET_IRQ,
+ BATFET_IRQ,
+ BATT_REPLACE_IRQ,
+ BATTCONNECT_IRQ,
+ PMIC_CHG_MAX_INTS
+};
+
+struct pm8058_charger {
+ struct pmic_charger_pdata *pdata;
+ struct device *dev;
+
+ int pmic_chg_irq[PMIC_CHG_MAX_INTS];
+ DECLARE_BITMAP(enabled_irqs, PMIC_CHG_MAX_INTS);
+
+ struct delayed_work chg_done_check_work;
+ struct delayed_work check_vbat_low_work;
+ struct delayed_work veoc_begin_work;
+ struct delayed_work charging_check_work;
+ int waiting_for_topoff;
+ int waiting_for_veoc;
+ int vbatdet;
+ struct msm_hardware_charger hw_chg;
+ int current_charger_current;
+ int disabled;
+
+ struct msm_xo_voter *voter;
+ struct dentry *dent;
+
+ int inited;
+ int present;
+};
+
+static struct pm8058_charger pm8058_chg;
+static struct msm_hardware_charger usb_hw_chg;
+static struct pmic8058_charger_data chg_data;
+
+static int msm_battery_gauge_alarm_notify(struct notifier_block *nb,
+ unsigned long status, void *unused);
+
+static struct notifier_block alarm_notifier = {
+ .notifier_call = msm_battery_gauge_alarm_notify,
+};
+
+static int resume_mv = AUTO_CHARGING_RESUME_MV;
+static DEFINE_MUTEX(batt_alarm_lock);
+static int resume_mv_set(const char *val, struct kernel_param *kp);
+module_param_call(resume_mv, resume_mv_set, param_get_int,
+ &resume_mv, S_IRUGO | S_IWUSR);
+
+static int resume_mv_set(const char *val, struct kernel_param *kp)
+{
+ int rc;
+
+ mutex_lock(&batt_alarm_lock);
+
+ rc = param_set_int(val, kp);
+ if (rc)
+ goto out;
+
+ rc = pm8xxx_batt_alarm_threshold_set(
+ PM8XXX_BATT_ALARM_LOWER_COMPARATOR, resume_mv);
+ if (!rc)
+ rc = pm8xxx_batt_alarm_threshold_set(
+ PM8XXX_BATT_ALARM_UPPER_COMPARATOR, 4300);
+
+out:
+ mutex_unlock(&batt_alarm_lock);
+ return rc;
+}
+
+static void pm8058_chg_enable_irq(int interrupt)
+{
+ if (!__test_and_set_bit(interrupt, pm8058_chg.enabled_irqs)) {
+ dev_dbg(pm8058_chg.dev, "%s %d\n", __func__,
+ pm8058_chg.pmic_chg_irq[interrupt]);
+ enable_irq(pm8058_chg.pmic_chg_irq[interrupt]);
+ }
+}
+
+static void pm8058_chg_disable_irq(int interrupt)
+{
+ if (__test_and_clear_bit(interrupt, pm8058_chg.enabled_irqs)) {
+ dev_dbg(pm8058_chg.dev, "%s %d\n", __func__,
+ pm8058_chg.pmic_chg_irq[interrupt]);
+ disable_irq_nosync(pm8058_chg.pmic_chg_irq[interrupt]);
+ }
+}
+
+static int pm_chg_get_rt_status(int irq)
+{
+ int ret;
+
+ ret = pm8xxx_read_irq_stat(pm8058_chg.dev->parent, irq);
+ if (ret == -EAGAIN)
+ return 0;
+ else
+ return ret;
+}
+
+static int is_chg_plugged_in(void)
+{
+ return pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]);
+}
+
+#ifdef DEBUG
+static void __dump_chg_regs(void)
+{
+ u8 temp;
+ int temp2;
+
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_CNTRL = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_CNTRL_2 = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_VMAX_SEL, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_VMAX_SEL = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_VBAT_DET, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_VBAT_DET = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_IMAX, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_IMAX = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TRICKLE, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TRICKLE = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_ITERM, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_ITERM = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TTRKL_MAX, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TTRKL_MAX = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TCHG_MAX, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TCHG_MAX = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEMP_THRESH, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TEMP_THRESH = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEMP_REG, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TEMP_REG = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_PULSE, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_PULSE = 0x%x\n", temp);
+
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_STATUS_CLEAR_IRQ_1,
+ &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_1 = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_STATUS_CLEAR_IRQ_3,
+ &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_3 = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_STATUS_CLEAR_IRQ_10,
+ &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_10 = 0x%x\n",
+ temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_STATUS_CLEAR_IRQ_11,
+ &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_11 = 0x%x\n",
+ temp);
+
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_MASK_IRQ_1, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_1 = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_MASK_IRQ_3, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_3 = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_MASK_IRQ_10, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_10 = 0x%x\n", temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_MASK_IRQ_11, &temp);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_11 = 0x%x\n", temp);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]);
+ dev_dbg(pm8058_chg.dev, "CHGVAL_IRQ = %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGINVAL_IRQ]);
+ dev_dbg(pm8058_chg.dev, "CHGINVAL_IRQ = %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VBATDET_LOW_IRQ]);
+ dev_dbg(pm8058_chg.dev, "VBATDET_LOW_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VCP_IRQ]);
+ dev_dbg(pm8058_chg.dev, "VCP_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGILIM_IRQ]);
+ dev_dbg(pm8058_chg.dev, "CHGILIM_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[ATC_DONE_IRQ]);
+ dev_dbg(pm8058_chg.dev, "ATC_DONE_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[ATCFAIL_IRQ]);
+ dev_dbg(pm8058_chg.dev, "ATCFAIL_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[AUTO_CHGDONE_IRQ]);
+ dev_dbg(pm8058_chg.dev, "AUTO_CHGDONE_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[AUTO_CHGFAIL_IRQ]);
+ dev_dbg(pm8058_chg.dev, "AUTO_CHGFAIL_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGSTATE_IRQ]);
+ dev_dbg(pm8058_chg.dev, "CHGSTATE_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[FASTCHG_IRQ]);
+ dev_dbg(pm8058_chg.dev, "FASTCHG_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHG_END_IRQ]);
+ dev_dbg(pm8058_chg.dev, "CHG_END_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ]);
+ dev_dbg(pm8058_chg.dev, "BATTTEMP_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGHOT_IRQ]);
+ dev_dbg(pm8058_chg.dev, "CHGHOT_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHGTLIMIT_IRQ]);
+ dev_dbg(pm8058_chg.dev, "CHGTLIMIT_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[CHG_GONE_IRQ]);
+ dev_dbg(pm8058_chg.dev, "CHG_GONE_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VCPMAJOR_IRQ]);
+ dev_dbg(pm8058_chg.dev, "VCPMAJOR_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VBATDET_IRQ]);
+ dev_dbg(pm8058_chg.dev, "VBATDET_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATFET_IRQ]);
+ dev_dbg(pm8058_chg.dev, "BATFET_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ]);
+ dev_dbg(pm8058_chg.dev, "BATT_REPLACE_IRQ= %d\n", temp2);
+
+ temp2 = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ]);
+ dev_dbg(pm8058_chg.dev, "BATTCONNECT_IRQ= %d\n", temp2);
+}
+#else
+static inline void __dump_chg_regs(void)
+{
+}
+#endif
+
+/* SSBI register access helper functions */
+static int pm_chg_suspend(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(CHG_USB_SUSPEND);
+ else
+ temp &= ~BIT(CHG_USB_SUSPEND);
+
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, temp);
+}
+
+static int pm_chg_auto_disable(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(CHARGE_AUTO_DIS);
+ else
+ temp &= ~BIT(CHARGE_AUTO_DIS);
+
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, temp);
+}
+
+static int pm_chg_batt_temp_disable(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(CHG_BATT_TEMP_DIS);
+ else
+ temp &= ~BIT(CHG_BATT_TEMP_DIS);
+
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, temp);
+}
+
+static int pm_chg_vbatdet_set(int voltage)
+{
+ u8 temp;
+ int diff;
+
+ diff = (voltage - PM8058_CHG_V_MIN_MV);
+ if (diff < 0) {
+ dev_warn(pm8058_chg.dev, "%s bad mV=%d asked to set\n",
+ __func__, voltage);
+ return -EINVAL;
+ }
+
+ temp = diff / PM8058_CHG_V_STEP_MV;
+ dev_dbg(pm8058_chg.dev, "%s voltage=%d setting %02x\n", __func__,
+ voltage, temp);
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_VBAT_DET, temp);
+}
+
+static int pm_chg_imaxsel_set(int chg_current)
+{
+ u8 temp;
+ int diff;
+
+ diff = chg_current - PM8058_CHG_I_MIN_MA;
+ if (diff < 0) {
+ dev_warn(pm8058_chg.dev, "%s bad mA=%d asked to set\n",
+ __func__, chg_current);
+ return -EINVAL;
+ }
+ temp = diff / PM8058_CHG_I_STEP_MA;
+ /* make sure we arent writing more than 5 bits of data */
+ if (temp > 31) {
+ dev_warn(pm8058_chg.dev, "%s max mA=1500 requested mA=%d\n",
+ __func__, chg_current);
+ temp = 31;
+ }
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_IMAX, temp);
+}
+
+#define PM8058_CHG_VMAX_MIN 3300
+#define PM8058_CHG_VMAX_MAX 5500
+static int pm_chg_vmaxsel_set(int voltage)
+{
+ u8 temp;
+
+ if (voltage < PM8058_CHG_VMAX_MIN || voltage > PM8058_CHG_VMAX_MAX) {
+ dev_warn(pm8058_chg.dev, "%s bad mV=%d asked to set\n",
+ __func__, voltage);
+ return -EINVAL;
+ }
+ temp = (voltage - PM8058_CHG_V_MIN_MV) / PM8058_CHG_V_STEP_MV;
+ dev_dbg(pm8058_chg.dev, "%s mV=%d setting %02x\n", __func__, voltage,
+ temp);
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_VMAX_SEL, temp);
+}
+
+static int pm_chg_failed_clear(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(CHG_FAILED_CLEAR);
+ else
+ temp &= ~BIT(CHG_FAILED_CLEAR);
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, temp);
+}
+
+static int pm_chg_iterm_set(int chg_current)
+{
+ u8 temp;
+
+ temp = (chg_current / PM8058_CHG_I_TERM_STEP_MA) - 1;
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_ITERM, temp);
+}
+
+static int pm_chg_tchg_set(int minutes)
+{
+ u8 temp;
+
+ temp = (minutes >> PM8058_CHG_T_TCHG_SHIFT) - 1;
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TCHG_MAX, temp);
+}
+
+static int pm_chg_ttrkl_set(int minutes)
+{
+ u8 temp;
+
+ temp = minutes - 1;
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TTRKL_MAX,
+ temp);
+}
+
+static int pm_chg_enum_done_enable(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, &temp);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(ENUM_DONE);
+ else
+ temp &= ~BIT(ENUM_DONE);
+
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL_2, temp);
+}
+
+static uint32_t get_fsm_state(void)
+{
+ u8 temp;
+
+ temp = 0x00;
+ pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_3, temp);
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEST_3, &temp);
+ return (uint32_t)temp;
+}
+
+static int get_fsm_status(void *data, u64 * val)
+{
+ *val = get_fsm_state();
+ return 0;
+}
+
+enum pmic8058_chg_state pmic8058_get_fsm_state(void)
+{
+ if (!pm8058_chg.inited) {
+ pr_err("%s: called when not inited\n", __func__);
+ return -EINVAL;
+ }
+
+ return get_fsm_state();
+}
+
+static int pm_chg_disable(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(CHG_CHARGE_DIS);
+ else
+ temp &= ~BIT(CHG_CHARGE_DIS);
+
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, temp);
+}
+
+static void pm8058_start_system_current(struct msm_hardware_charger *hw_chg,
+ int max_current)
+{
+ int ret = 0;
+
+ if (pm8058_chg.disabled)
+ return;
+
+ ret = pm_chg_imaxsel_set(max_current);
+ ret |= pm_chg_enum_done_enable(1);
+ ret |= pm_chg_disable(0);
+ if (ret)
+ pr_err("%s: failed to turn on system power err=%d",
+ __func__, ret);
+}
+
+static void pm8058_stop_system_current(struct msm_hardware_charger *hw_chg)
+{
+ int ret = 0;
+
+ ret = pm_chg_enum_done_enable(0);
+ ret |= pm_chg_disable(1);
+ if (ret)
+ pr_err("%s: failed to turn off system power err=%d",
+ __func__, ret);
+}
+
+static int __pm8058_start_charging(int chg_current, int termination_current,
+ int time)
+{
+ int ret = 0;
+
+ if (pm8058_chg.disabled)
+ goto out;
+
+ dev_info(pm8058_chg.dev, "%s %dmA %dmin\n",
+ __func__, chg_current, time);
+
+ ret = pm_chg_auto_disable(1);
+ if (ret)
+ goto out;
+
+ ret = pm_chg_suspend(0);
+ if (ret)
+ goto out;
+
+ ret = pm_chg_imaxsel_set(chg_current);
+ if (ret)
+ goto out;
+
+ ret = pm_chg_failed_clear(1);
+ if (ret)
+ goto out;
+
+ ret = pm_chg_iterm_set(termination_current);
+ if (ret)
+ goto out;
+
+ ret = pm_chg_tchg_set(time);
+ if (ret)
+ goto out;
+
+ ret = pm_chg_ttrkl_set(AUTO_CHARGING_TRICKLE_TIME_MINUTES);
+ if (ret)
+ goto out;
+
+ ret = pm_chg_batt_temp_disable(0);
+ if (ret)
+ goto out;
+
+ if (pm8058_chg.voter == NULL)
+ pm8058_chg.voter = msm_xo_get(MSM_XO_TCXO_D1, "pm8058_charger");
+ msm_xo_mode_vote(pm8058_chg.voter, MSM_XO_MODE_ON);
+
+ ret = pm_chg_enum_done_enable(1);
+ if (ret)
+ goto out;
+
+ wmb();
+
+ ret = pm_chg_auto_disable(0);
+ if (ret)
+ goto out;
+
+ /* wait for the enable to update interrupt status*/
+ msleep(20);
+
+ pm8058_chg_enable_irq(AUTO_CHGFAIL_IRQ);
+ pm8058_chg_enable_irq(CHGHOT_IRQ);
+ pm8058_chg_enable_irq(AUTO_CHGDONE_IRQ);
+ pm8058_chg_enable_irq(CHG_END_IRQ);
+ pm8058_chg_enable_irq(CHGSTATE_IRQ);
+
+out:
+ return ret;
+}
+
+static void chg_done_cleanup(void)
+{
+ dev_info(pm8058_chg.dev, "%s notify pm8058 charging completion"
+ "\n", __func__);
+
+ pm8058_chg_disable_irq(AUTO_CHGDONE_IRQ);
+ cancel_delayed_work_sync(&pm8058_chg.veoc_begin_work);
+ cancel_delayed_work_sync(&pm8058_chg.check_vbat_low_work);
+
+ pm8058_chg_disable_irq(CHG_END_IRQ);
+
+ pm8058_chg_disable_irq(VBATDET_LOW_IRQ);
+ pm8058_chg_disable_irq(VBATDET_IRQ);
+ pm8058_chg.waiting_for_veoc = 0;
+ pm8058_chg.waiting_for_topoff = 0;
+
+ pm_chg_auto_disable(1);
+
+ msm_charger_notify_event(&usb_hw_chg, CHG_DONE_EVENT);
+}
+
+static void chg_done_check_work(struct work_struct *work)
+{
+ chg_done_cleanup();
+}
+
+static void charging_check_work(struct work_struct *work)
+{
+ uint32_t fsm_state = get_fsm_state();
+ int rc;
+
+ switch (fsm_state) {
+ /* We're charging, so disarm alarm */
+ case PMIC8058_CHG_STATE_ATC:
+ case PMIC8058_CHG_STATE_FAST_CHG:
+ case PMIC8058_CHG_STATE_TRKL_CHG:
+ rc = pm8xxx_batt_alarm_disable(
+ PM8XXX_BATT_ALARM_UPPER_COMPARATOR);
+ if (!rc)
+ rc = pm8xxx_batt_alarm_disable(
+ PM8XXX_BATT_ALARM_LOWER_COMPARATOR);
+ if (rc)
+ dev_err(pm8058_chg.dev,
+ "%s: unable to set alarm state\n", __func__);
+ break;
+ default:
+ /* Still not charging, so update driver state */
+ chg_done_cleanup();
+ break;
+ };
+}
+
+static int pm8058_start_charging(struct msm_hardware_charger *hw_chg,
+ int chg_voltage, int chg_current)
+{
+ int vbat_higher_than_vbatdet;
+ int ret = 0;
+
+ cancel_delayed_work_sync(&pm8058_chg.charging_check_work);
+
+ /*
+ * adjust the max current for PC USB connection - set the higher limit
+ * to 450 and make sure we never cross it
+ */
+ if (chg_current == 500)
+ chg_current = 450;
+
+ if (hw_chg->type == CHG_TYPE_AC && chg_data.max_source_current)
+ chg_current = chg_data.max_source_current;
+
+ pm8058_chg.current_charger_current = chg_current;
+ pm8058_chg_enable_irq(FASTCHG_IRQ);
+
+ ret = pm_chg_vmaxsel_set(chg_voltage);
+ if (ret)
+ goto out;
+
+ /* set vbat to CC to CV threshold */
+ ret = pm_chg_vbatdet_set(AUTO_CHARGING_VBATDET);
+ if (ret)
+ goto out;
+
+ pm8058_chg.vbatdet = AUTO_CHARGING_VBATDET;
+ /*
+ * get the state of vbat and if it is higher than
+ * AUTO_CHARGING_VBATDET we start the veoc start timer
+ * else wait for the voltage to go to AUTO_CHARGING_VBATDET
+ * and then start the 90 min timer
+ */
+ vbat_higher_than_vbatdet =
+ pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VBATDET_IRQ]);
+ if (vbat_higher_than_vbatdet) {
+ /*
+ * we are in constant voltage phase of charging
+ * IEOC should happen withing 90 mins of this instant
+ * else we enable VEOC
+ */
+ dev_info(pm8058_chg.dev, "%s begin veoc timer\n", __func__);
+ schedule_delayed_work(&pm8058_chg.veoc_begin_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (AUTO_CHARGING_VEOC_BEGIN_TIME_MS)));
+ } else
+ pm8058_chg_enable_irq(VBATDET_IRQ);
+
+ ret = __pm8058_start_charging(chg_current, AUTO_CHARGING_IEOC_ITERM,
+ AUTO_CHARGING_FAST_TIME_MAX_MINUTES);
+ pm8058_chg.current_charger_current = chg_current;
+
+ /*
+ * We want to check the FSM state to verify we're charging. We must
+ * wait before doing this to allow the VBATDET to settle. The worst
+ * case for this is two seconds. The batt alarm does not have this
+ * delay.
+ */
+ schedule_delayed_work(&pm8058_chg.charging_check_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (AUTO_CHARGING_VBATDET_DEBOUNCE_TIME_MS)));
+
+out:
+ return ret;
+}
+
+static void veoc_begin_work(struct work_struct *work)
+{
+ /* we have been doing CV for 90mins with no signs of IEOC
+ * start checking for VEOC in addition with 16min charges*/
+ dev_info(pm8058_chg.dev, "%s begin veoc detection\n", __func__);
+ pm8058_chg.waiting_for_veoc = 1;
+ /*
+ * disable VBATDET irq we dont need it unless we are at the end of
+ * charge cycle
+ */
+ pm8058_chg_disable_irq(VBATDET_IRQ);
+ __pm8058_start_charging(pm8058_chg.current_charger_current,
+ AUTO_CHARGING_VEOC_ITERM,
+ AUTO_CHARGING_VEOC_TCHG);
+}
+
+static void vbat_low_work(struct work_struct *work)
+{
+ /*
+ * It has been one minute and the battery still holds voltage
+ * start the final topoff - charging is almost done
+ */
+ dev_info(pm8058_chg.dev, "%s vbatt maintains for a minute"
+ "starting topoff\n", __func__);
+ pm8058_chg.waiting_for_veoc = 0;
+ pm8058_chg.waiting_for_topoff = 1;
+ pm8058_chg_disable_irq(VBATDET_LOW_IRQ);
+ pm8058_chg_disable_irq(VBATDET_IRQ);
+ __pm8058_start_charging(pm8058_chg.current_charger_current,
+ AUTO_CHARGING_VEOC_ITERM,
+ AUTO_CHARGING_VEOC_TCHG_FINAL_CYCLE);
+}
+
+
+static irqreturn_t pm8058_chg_chgval_handler(int irq, void *dev_id)
+{
+ u8 old, temp;
+ int ret;
+
+ if (is_chg_plugged_in()) { /* this debounces it */
+ if (!pm8058_chg.present) {
+ msm_charger_notify_event(&usb_hw_chg,
+ CHG_INSERTED_EVENT);
+ pm8058_chg.present = 1;
+ }
+ } else {
+ if (pm8058_chg.present) {
+ ret = pm8xxx_readb(pm8058_chg.dev->parent,
+ PM8058_OVP_TEST_REG,
+ &old);
+ temp = old | BIT(FORCE_OVP_OFF);
+ ret = pm8xxx_writeb(pm8058_chg.dev->parent,
+ PM8058_OVP_TEST_REG,
+ temp);
+ temp = 0xFC;
+ ret = pm8xxx_writeb(pm8058_chg.dev->parent,
+ PM8058_CHG_TEST, temp);
+ /* 10 ms sleep is for the VCHG to discharge */
+ msleep(10);
+ temp = 0xF0;
+ ret = pm8xxx_writeb(pm8058_chg.dev->parent,
+ PM8058_CHG_TEST,
+ temp);
+ ret = pm8xxx_writeb(pm8058_chg.dev->parent,
+ PM8058_OVP_TEST_REG,
+ old);
+
+ pm_chg_enum_done_enable(0);
+ pm_chg_auto_disable(1);
+ msm_charger_notify_event(&usb_hw_chg,
+ CHG_REMOVED_EVENT);
+ pm8058_chg.present = 0;
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8058_chg_chginval_handler(int irq, void *dev_id)
+{
+ u8 old, temp;
+ int ret;
+
+ if (pm8058_chg.present) {
+ pm8058_chg_disable_irq(CHGINVAL_IRQ);
+
+ pm_chg_enum_done_enable(0);
+ pm_chg_auto_disable(1);
+ ret = pm8xxx_readb(pm8058_chg.dev->parent,
+ PM8058_OVP_TEST_REG, &old);
+ temp = old | BIT(FORCE_OVP_OFF);
+ ret = pm8xxx_writeb(pm8058_chg.dev->parent,
+ PM8058_OVP_TEST_REG, temp);
+ temp = 0xFC;
+ ret = pm8xxx_writeb(pm8058_chg.dev->parent,
+ PM8058_CHG_TEST, temp);
+ /* 10 ms sleep is for the VCHG to discharge */
+ msleep(10);
+ temp = 0xF0;
+ ret = pm8xxx_writeb(pm8058_chg.dev->parent,
+ PM8058_CHG_TEST, temp);
+ ret = pm8xxx_writeb(pm8058_chg.dev->parent,
+ PM8058_OVP_TEST_REG, old);
+
+ if (!is_chg_plugged_in()) {
+ msm_charger_notify_event(&usb_hw_chg,
+ CHG_REMOVED_EVENT);
+ pm8058_chg.present = 0;
+ } else {
+ /* was a fake */
+ pm8058_chg_enable_irq(CHGINVAL_IRQ);
+ }
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8058_chg_auto_chgdone_handler(int irq, void *dev_id)
+{
+ dev_info(pm8058_chg.dev, "%s waiting a sec to confirm\n",
+ __func__);
+ pm8058_chg_disable_irq(AUTO_CHGDONE_IRQ);
+ pm8058_chg_disable_irq(VBATDET_IRQ);
+ if (!delayed_work_pending(&pm8058_chg.chg_done_check_work)) {
+ schedule_delayed_work(&pm8058_chg.chg_done_check_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (AUTO_CHARGING_DONE_CHECK_TIME_MS)));
+ }
+ return IRQ_HANDLED;
+}
+
+/* can only happen with the pmic charger when it has been charing
+ * for either 16 mins wating for VEOC or 32 mins for topoff
+ * without a IEOC indication */
+static irqreturn_t pm8058_chg_auto_chgfail_handler(int irq, void *dev_id)
+{
+ pm8058_chg_disable_irq(AUTO_CHGFAIL_IRQ);
+
+ if (pm8058_chg.waiting_for_topoff == 1) {
+ dev_info(pm8058_chg.dev, "%s topoff done, charging done\n",
+ __func__);
+ pm8058_chg.waiting_for_topoff = 0;
+ /* notify we are done charging */
+ msm_charger_notify_event(&usb_hw_chg, CHG_DONE_EVENT);
+ } else {
+ /* start one minute timer and monitor VBATDET_LOW */
+ dev_info(pm8058_chg.dev, "%s monitoring vbat_low for a"
+ "minute\n", __func__);
+ schedule_delayed_work(&pm8058_chg.check_vbat_low_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (AUTO_CHARGING_VEOC_VBAT_LOW_CHECK_TIME_MS)));
+
+ /* note we are waiting on veoc */
+ pm8058_chg.waiting_for_veoc = 1;
+
+ pm_chg_vbatdet_set(AUTO_CHARGING_VEOC_VBATDET);
+ pm8058_chg.vbatdet = AUTO_CHARGING_VEOC_VBATDET;
+ pm8058_chg_enable_irq(VBATDET_LOW_IRQ);
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8058_chg_chgstate_handler(int irq, void *dev_id)
+{
+ u8 temp;
+
+ temp = 0x00;
+ if (!pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_3, temp)) {
+ pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEST_3, &temp);
+ dev_dbg(pm8058_chg.dev, "%s state=%d\n", __func__, temp);
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8058_chg_fastchg_handler(int irq, void *dev_id)
+{
+ pm8058_chg_disable_irq(FASTCHG_IRQ);
+
+ /* we have begun the fast charging state */
+ dev_info(pm8058_chg.dev, "%s begin fast charging"
+ , __func__);
+ msm_charger_notify_event(&usb_hw_chg, CHG_BATT_BEGIN_FAST_CHARGING);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8058_chg_batttemp_handler(int irq, void *dev_id)
+{
+ int ret;
+
+ /* we could get temperature
+ * interrupt when the battery is plugged out
+ */
+ ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ]);
+ if (ret) {
+ msm_charger_notify_event(&usb_hw_chg, CHG_BATT_REMOVED);
+ } else {
+ /* read status to determine we are inrange or outofrange */
+ ret =
+ pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ]);
+ if (ret)
+ msm_charger_notify_event(&usb_hw_chg,
+ CHG_BATT_TEMP_OUTOFRANGE);
+ else
+ msm_charger_notify_event(&usb_hw_chg,
+ CHG_BATT_TEMP_INRANGE);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8058_chg_vbatdet_handler(int irq, void *dev_id)
+{
+ int ret;
+
+ /* settling time */
+ msleep(20);
+ ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[VBATDET_IRQ]);
+
+ if (ret) {
+ if (pm8058_chg.vbatdet == AUTO_CHARGING_VBATDET
+ && !delayed_work_pending(&pm8058_chg.veoc_begin_work)) {
+ /*
+ * we are in constant voltage phase of charging
+ * IEOC should happen withing 90 mins of this instant
+ * else we enable VEOC
+ */
+ dev_info(pm8058_chg.dev, "%s entered constant voltage"
+ "begin veoc timer\n", __func__);
+ schedule_delayed_work(&pm8058_chg.veoc_begin_work,
+ round_jiffies_relative
+ (msecs_to_jiffies
+ (AUTO_CHARGING_VEOC_BEGIN_TIME_MS)));
+ }
+ } else {
+ if (pm8058_chg.vbatdet == AUTO_CHARGING_VEOC_VBATDET) {
+ cancel_delayed_work_sync(
+ &pm8058_chg.check_vbat_low_work);
+
+ if (pm8058_chg.waiting_for_topoff
+ || pm8058_chg.waiting_for_veoc) {
+ /*
+ * the battery dropped its voltage below 4100
+ * around a minute charge the battery for 16
+ * mins and check vbat again for a minute
+ */
+ dev_info(pm8058_chg.dev, "%s batt dropped vlt"
+ "within a minute\n", __func__);
+ pm8058_chg.waiting_for_topoff = 0;
+ pm8058_chg.waiting_for_veoc = 1;
+ pm8058_chg_disable_irq(VBATDET_IRQ);
+ __pm8058_start_charging(pm8058_chg.
+ current_charger_current,
+ AUTO_CHARGING_VEOC_ITERM,
+ AUTO_CHARGING_VEOC_TCHG);
+ }
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8058_chg_batt_replace_handler(int irq, void *dev_id)
+{
+ int ret;
+
+ pm8058_chg_disable_irq(BATT_REPLACE_IRQ);
+ ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ]);
+ if (ret) {
+ msm_charger_notify_event(&usb_hw_chg, CHG_BATT_INSERTED);
+ /*
+ * battery is present enable batt removal
+ * and batt temperatture interrupt
+ */
+ pm8058_chg_enable_irq(BATTCONNECT_IRQ);
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8058_chg_battconnect_handler(int irq, void *dev_id)
+{
+ int ret;
+
+ ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ]);
+ if (ret) {
+ msm_charger_notify_event(&usb_hw_chg, CHG_BATT_REMOVED);
+ } else {
+ msm_charger_notify_event(&usb_hw_chg, CHG_BATT_INSERTED);
+ pm8058_chg_enable_irq(BATTTEMP_IRQ);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int get_rt_status(void *data, u64 * val)
+{
+ int i = (int)data;
+ int ret;
+
+ ret = pm_chg_get_rt_status(i);
+ *val = ret;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(rt_fops, get_rt_status, NULL, "%llu\n");
+DEFINE_SIMPLE_ATTRIBUTE(fsm_fops, get_fsm_status, NULL, "%llu\n");
+
+static void free_irqs(void)
+{
+ int i;
+
+ for (i = 0; i < PMIC_CHG_MAX_INTS; i++)
+ if (pm8058_chg.pmic_chg_irq[i]) {
+ free_irq(pm8058_chg.pmic_chg_irq[i], NULL);
+ pm8058_chg.pmic_chg_irq[i] = 0;
+ }
+}
+
+static int __devinit request_irqs(struct platform_device *pdev)
+{
+ struct resource *res;
+ int ret;
+
+ ret = 0;
+ bitmap_fill(pm8058_chg.enabled_irqs, PMIC_CHG_MAX_INTS);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "CHGVAL");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource CHGVAL\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_threaded_irq(res->start, NULL,
+ pm8058_chg_chgval_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[CHGVAL_IRQ] = res->start;
+ pm8058_chg_disable_irq(CHGVAL_IRQ);
+ enable_irq_wake(pm8058_chg.pmic_chg_irq[CHGVAL_IRQ]);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "CHGINVAL");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource CHGINVAL\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_threaded_irq(res->start, NULL,
+ pm8058_chg_chginval_handler,
+ IRQF_TRIGGER_RISING, res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[CHGINVAL_IRQ] = res->start;
+ pm8058_chg_disable_irq(CHGINVAL_IRQ);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "AUTO_CHGDONE");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource AUTO_CHGDONE\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_irq(res->start,
+ pm8058_chg_auto_chgdone_handler,
+ IRQF_TRIGGER_RISING,
+ res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[AUTO_CHGDONE_IRQ] = res->start;
+ pm8058_chg_disable_irq(AUTO_CHGDONE_IRQ);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "AUTO_CHGFAIL");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource AUTO_CHGFAIL\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_irq(res->start,
+ pm8058_chg_auto_chgfail_handler,
+ IRQF_TRIGGER_RISING, res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[AUTO_CHGFAIL_IRQ] = res->start;
+ pm8058_chg_disable_irq(AUTO_CHGFAIL_IRQ);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "CHGSTATE");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource CHGSTATE\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_irq(res->start,
+ pm8058_chg_chgstate_handler,
+ IRQF_TRIGGER_RISING, res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[CHGSTATE_IRQ] = res->start;
+ pm8058_chg_disable_irq(CHGSTATE_IRQ);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "FASTCHG");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource FASTCHG\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_irq(res->start,
+ pm8058_chg_fastchg_handler,
+ IRQF_TRIGGER_RISING, res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[FASTCHG_IRQ] = res->start;
+ pm8058_chg_disable_irq(FASTCHG_IRQ);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "BATTTEMP");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource CHG_END\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_irq(res->start,
+ pm8058_chg_batttemp_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ] = res->start;
+ pm8058_chg_disable_irq(BATTTEMP_IRQ);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+ "BATT_REPLACE");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource BATT_REPLACE\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_irq(res->start,
+ pm8058_chg_batt_replace_handler,
+ IRQF_TRIGGER_RISING, res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ] = res->start;
+ pm8058_chg_disable_irq(BATT_REPLACE_IRQ);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "BATTCONNECT");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource BATTCONNECT\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_irq(res->start,
+ pm8058_chg_battconnect_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ] = res->start;
+ pm8058_chg_disable_irq(BATTCONNECT_IRQ);
+ }
+ }
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "VBATDET");
+ if (res == NULL) {
+ dev_err(pm8058_chg.dev,
+ "%s:couldnt find resource VBATDET\n", __func__);
+ goto err_out;
+ } else {
+ ret = request_threaded_irq(res->start, NULL,
+ pm8058_chg_vbatdet_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ res->name, NULL);
+ if (ret < 0) {
+ dev_err(pm8058_chg.dev, "%s:couldnt request %d %d\n",
+ __func__, res->start, ret);
+ goto err_out;
+ } else {
+ pm8058_chg.pmic_chg_irq[VBATDET_IRQ] = res->start;
+ pm8058_chg_disable_irq(VBATDET_IRQ);
+ }
+ }
+
+ return 0;
+
+err_out:
+ free_irqs();
+ return -EINVAL;
+}
+
+static int pm8058_get_charge_batt(void)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp);
+ if (rc)
+ return rc;
+
+ temp &= BIT(CHG_CHARGE_BAT);
+ if (temp)
+ temp = 1;
+ return temp;
+}
+EXPORT_SYMBOL(pm8058_get_charge_batt);
+
+static int pm8058_set_charge_batt(int on)
+{
+ u8 temp;
+ int rc;
+
+ rc = pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, &temp);
+ if (rc)
+ return rc;
+ if (on)
+ temp |= BIT(CHG_CHARGE_BAT);
+ else
+ temp &= ~BIT(CHG_CHARGE_BAT);
+
+ return pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_CNTRL, temp);
+
+}
+EXPORT_SYMBOL(pm8058_set_charge_batt);
+
+static int get_charge_batt(void *data, u64 * val)
+{
+ int ret;
+
+ ret = pm8058_get_charge_batt();
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+}
+
+static int set_charge_batt(void *data, u64 val)
+{
+ return pm8058_set_charge_batt(val);
+}
+DEFINE_SIMPLE_ATTRIBUTE(fet_fops, get_charge_batt, set_charge_batt, "%llu\n");
+
+static void pm8058_chg_determine_initial_state(void)
+{
+ if (is_chg_plugged_in()) {
+ pm8058_chg.present = 1;
+ msm_charger_notify_event(&usb_hw_chg, CHG_INSERTED_EVENT);
+ dev_info(pm8058_chg.dev, "%s charger present\n", __func__);
+ } else {
+ pm8058_chg.present = 0;
+ dev_info(pm8058_chg.dev, "%s charger absent\n", __func__);
+ }
+ pm8058_chg_enable_irq(CHGVAL_IRQ);
+}
+
+static int pm8058_stop_charging(struct msm_hardware_charger *hw_chg)
+{
+ int ret;
+
+ dev_info(pm8058_chg.dev, "%s stopping charging\n", __func__);
+
+ /* disable the irqs enabled while charging */
+ pm8058_chg_disable_irq(AUTO_CHGFAIL_IRQ);
+ pm8058_chg_disable_irq(CHGHOT_IRQ);
+ pm8058_chg_disable_irq(AUTO_CHGDONE_IRQ);
+ pm8058_chg_disable_irq(FASTCHG_IRQ);
+ pm8058_chg_disable_irq(CHG_END_IRQ);
+ pm8058_chg_disable_irq(VBATDET_IRQ);
+ pm8058_chg_disable_irq(VBATDET_LOW_IRQ);
+
+ cancel_delayed_work_sync(&pm8058_chg.veoc_begin_work);
+ cancel_delayed_work_sync(&pm8058_chg.check_vbat_low_work);
+ cancel_delayed_work_sync(&pm8058_chg.chg_done_check_work);
+ cancel_delayed_work_sync(&pm8058_chg.charging_check_work);
+
+ ret = pm_chg_get_rt_status(pm8058_chg.pmic_chg_irq[FASTCHG_IRQ]);
+ if (ret == 1)
+ pm_chg_suspend(1);
+ else
+ dev_err(pm8058_chg.dev,
+ "%s called when not fast-charging\n", __func__);
+
+ pm_chg_failed_clear(1);
+
+ pm8058_chg.waiting_for_veoc = 0;
+ pm8058_chg.waiting_for_topoff = 0;
+
+ if (pm8058_chg.voter)
+ msm_xo_mode_vote(pm8058_chg.voter, MSM_XO_MODE_OFF);
+
+ return 0;
+}
+
+static int get_status(void *data, u64 * val)
+{
+ *val = pm8058_chg.current_charger_current;
+ return 0;
+}
+
+static int set_status(void *data, u64 val)
+{
+
+ pm8058_chg.current_charger_current = val;
+ if (pm8058_chg.current_charger_current)
+ pm8058_start_charging(NULL,
+ AUTO_CHARGING_VMAXSEL,
+ pm8058_chg.current_charger_current);
+ else
+ pm8058_stop_charging(NULL);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(chg_fops, get_status, set_status, "%llu\n");
+
+static int set_disable_status_param(const char *val, struct kernel_param *kp)
+{
+ int ret;
+
+ ret = param_set_int(val, kp);
+ if (ret)
+ return ret;
+
+ if (pm8058_chg.inited && pm8058_chg.disabled) {
+ /*
+ * stop_charging is called during usb suspend
+ * act as the usb is removed by disabling auto and enum
+ */
+ pm_chg_enum_done_enable(0);
+ pm_chg_auto_disable(1);
+ pm8058_stop_charging(NULL);
+ }
+ return 0;
+}
+module_param_call(disabled, set_disable_status_param, param_get_uint,
+ &(pm8058_chg.disabled), 0644);
+
+static int pm8058_charging_switched(struct msm_hardware_charger *hw_chg)
+{
+ u8 temp;
+
+ temp = 0xA3;
+ pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_2, temp);
+ temp = 0x84;
+ pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_2, temp);
+ msleep(2);
+ temp = 0x80;
+ pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST_2, temp);
+ return 0;
+}
+
+static int get_reg(void *data, u64 * val)
+{
+ int i = (int)data;
+ int ret;
+ u8 temp;
+
+ ret = pm8xxx_readb(pm8058_chg.dev->parent, i, &temp);
+ if (ret)
+ return -EAGAIN;
+ *val = temp;
+ return 0;
+}
+
+static int set_reg(void *data, u64 val)
+{
+ int i = (int)data;
+ int ret;
+ u8 temp;
+
+ temp = (u8) val;
+ ret = pm8xxx_writeb(pm8058_chg.dev->parent, i, temp);
+ mb();
+ if (ret)
+ return -EAGAIN;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "%llu\n");
+
+#ifdef CONFIG_DEBUG_FS
+static void create_debugfs_entries(void)
+{
+ pm8058_chg.dent = debugfs_create_dir("pm8058_usb_chg", NULL);
+
+ if (IS_ERR(pm8058_chg.dent)) {
+ pr_err("pmic charger couldnt create debugfs dir\n");
+ return;
+ }
+
+ debugfs_create_file("CHG_CNTRL", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_CNTRL, ®_fops);
+ debugfs_create_file("CHG_CNTRL_2", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_CNTRL_2, ®_fops);
+ debugfs_create_file("CHG_VMAX_SEL", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_VMAX_SEL, ®_fops);
+ debugfs_create_file("CHG_VBAT_DET", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_VBAT_DET, ®_fops);
+ debugfs_create_file("CHG_IMAX", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_IMAX, ®_fops);
+ debugfs_create_file("CHG_TRICKLE", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_TRICKLE, ®_fops);
+ debugfs_create_file("CHG_ITERM", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_ITERM, ®_fops);
+ debugfs_create_file("CHG_TTRKL_MAX", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_TTRKL_MAX, ®_fops);
+ debugfs_create_file("CHG_TCHG_MAX", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_TCHG_MAX, ®_fops);
+ debugfs_create_file("CHG_TEMP_THRESH", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_TEMP_THRESH, ®_fops);
+ debugfs_create_file("CHG_TEMP_REG", 0644, pm8058_chg.dent,
+ (void *)PM8058_CHG_TEMP_REG, ®_fops);
+
+ debugfs_create_file("FSM_STATE", 0644, pm8058_chg.dent, NULL,
+ &fsm_fops);
+
+ debugfs_create_file("stop", 0644, pm8058_chg.dent, NULL,
+ &chg_fops);
+
+ if (pm8058_chg.pmic_chg_irq[CHGVAL_IRQ])
+ debugfs_create_file("CHGVAL", 0444, pm8058_chg.dent,
+ (void *)pm8058_chg.pmic_chg_irq[CHGVAL_IRQ],
+ &rt_fops);
+
+ if (pm8058_chg.pmic_chg_irq[CHGINVAL_IRQ])
+ debugfs_create_file("CHGINVAL", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[CHGINVAL_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[CHGILIM_IRQ])
+ debugfs_create_file("CHGILIM", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[CHGILIM_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[VCP_IRQ])
+ debugfs_create_file("VCP", 0444, pm8058_chg.dent,
+ (void *)pm8058_chg.pmic_chg_irq[VCP_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[ATC_DONE_IRQ])
+ debugfs_create_file("ATC_DONE", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[ATC_DONE_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[ATCFAIL_IRQ])
+ debugfs_create_file("ATCFAIL", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[ATCFAIL_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[AUTO_CHGDONE_IRQ])
+ debugfs_create_file("AUTO_CHGDONE", 0444, pm8058_chg.dent,
+ (void *)
+ pm8058_chg.pmic_chg_irq[AUTO_CHGDONE_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[AUTO_CHGFAIL_IRQ])
+ debugfs_create_file("AUTO_CHGFAIL", 0444, pm8058_chg.dent,
+ (void *)
+ pm8058_chg.pmic_chg_irq[AUTO_CHGFAIL_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[CHGSTATE_IRQ])
+ debugfs_create_file("CHGSTATE", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[CHGSTATE_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[FASTCHG_IRQ])
+ debugfs_create_file("FASTCHG", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[FASTCHG_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[CHG_END_IRQ])
+ debugfs_create_file("CHG_END", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[CHG_END_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ])
+ debugfs_create_file("BATTTEMP", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[BATTTEMP_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[CHGHOT_IRQ])
+ debugfs_create_file("CHGHOT", 0444, pm8058_chg.dent,
+ (void *)pm8058_chg.pmic_chg_irq[CHGHOT_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[CHGTLIMIT_IRQ])
+ debugfs_create_file("CHGTLIMIT", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[CHGTLIMIT_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[CHG_GONE_IRQ])
+ debugfs_create_file("CHG_GONE", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[CHG_GONE_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[VCPMAJOR_IRQ])
+ debugfs_create_file("VCPMAJOR", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[VCPMAJOR_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[BATFET_IRQ])
+ debugfs_create_file("BATFET", 0444, pm8058_chg.dent,
+ (void *)pm8058_chg.pmic_chg_irq[BATFET_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ])
+ debugfs_create_file("BATT_REPLACE", 0444, pm8058_chg.dent,
+ (void *)
+ pm8058_chg.pmic_chg_irq[BATT_REPLACE_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ])
+ debugfs_create_file("BATTCONNECT", 0444, pm8058_chg.dent,
+ (void *)
+ pm8058_chg.pmic_chg_irq[BATTCONNECT_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[VBATDET_IRQ])
+ debugfs_create_file("VBATDET", 0444, pm8058_chg.dent, (void *)
+ pm8058_chg.pmic_chg_irq[VBATDET_IRQ],
+ &rt_fops);
+ if (pm8058_chg.pmic_chg_irq[VBATDET_LOW_IRQ])
+ debugfs_create_file("VBATDET_LOW", 0444, pm8058_chg.dent,
+ (void *)
+ pm8058_chg.pmic_chg_irq[VBATDET_LOW_IRQ],
+ &rt_fops);
+ debugfs_create_file("CHARGE_BATT", 0444, pm8058_chg.dent,
+ NULL,
+ &fet_fops);
+}
+#else
+static inline void create_debugfs_entries(void)
+{
+}
+#endif
+
+static void remove_debugfs_entries(void)
+{
+ debugfs_remove_recursive(pm8058_chg.dent);
+}
+
+static struct msm_hardware_charger usb_hw_chg = {
+ .type = CHG_TYPE_USB,
+ .rating = 1,
+ .name = "pm8058-usb",
+ .start_charging = pm8058_start_charging,
+ .stop_charging = pm8058_stop_charging,
+ .charging_switched = pm8058_charging_switched,
+ .start_system_current = pm8058_start_system_current,
+ .stop_system_current = pm8058_stop_system_current,
+};
+
+static int batt_read_adc(int channel, int *mv_reading)
+{
+ int ret;
+ void *h;
+ struct adc_chan_result adc_chan_result;
+ struct completion conv_complete_evt;
+
+ pr_debug("%s: called for %d\n", __func__, channel);
+ ret = adc_channel_open(channel, &h);
+ if (ret) {
+ pr_err("%s: couldnt open channel %d ret=%d\n",
+ __func__, channel, ret);
+ goto out;
+ }
+ init_completion(&conv_complete_evt);
+ ret = adc_channel_request_conv(h, &conv_complete_evt);
+ if (ret) {
+ pr_err("%s: couldnt request conv channel %d ret=%d\n",
+ __func__, channel, ret);
+ goto out;
+ }
+ wait_for_completion(&conv_complete_evt);
+ ret = adc_channel_read_result(h, &adc_chan_result);
+ if (ret) {
+ pr_err("%s: couldnt read result channel %d ret=%d\n",
+ __func__, channel, ret);
+ goto out;
+ }
+ ret = adc_channel_close(h);
+ if (ret) {
+ pr_err("%s: couldnt close channel %d ret=%d\n",
+ __func__, channel, ret);
+ }
+ if (mv_reading)
+ *mv_reading = adc_chan_result.measurement;
+
+ pr_debug("%s: done for %d\n", __func__, channel);
+ return adc_chan_result.physical;
+out:
+ pr_debug("%s: done for %d\n", __func__, channel);
+ return -EINVAL;
+
+}
+
+#define BATT_THERM_OPEN_MV 2000
+static int pm8058_is_battery_present(void)
+{
+ int mv_reading;
+
+ mv_reading = 0;
+ batt_read_adc(CHANNEL_ADC_BATT_THERM, &mv_reading);
+ pr_debug("%s: therm_raw is %d\n", __func__, mv_reading);
+ if (mv_reading > 0 && mv_reading < BATT_THERM_OPEN_MV)
+ return 1;
+
+ return 0;
+}
+
+static int pm8058_get_battery_temperature(void)
+{
+ return batt_read_adc(CHANNEL_ADC_BATT_THERM, NULL);
+}
+
+#define BATT_THERM_OPERATIONAL_MAX_CELCIUS 40
+#define BATT_THERM_OPERATIONAL_MIN_CELCIUS 0
+static int pm8058_is_battery_temp_within_range(void)
+{
+ int therm_celcius;
+
+ therm_celcius = pm8058_get_battery_temperature();
+ pr_debug("%s: therm_celcius is %d\n", __func__, therm_celcius);
+ if (therm_celcius > 0
+ && therm_celcius > BATT_THERM_OPERATIONAL_MIN_CELCIUS
+ && therm_celcius < BATT_THERM_OPERATIONAL_MAX_CELCIUS)
+ return 1;
+
+ return 0;
+}
+
+#define BATT_ID_MAX_MV 800
+#define BATT_ID_MIN_MV 600
+static int pm8058_is_battery_id_valid(void)
+{
+ int batt_id_mv;
+
+ batt_id_mv = batt_read_adc(CHANNEL_ADC_BATT_ID, NULL);
+ pr_debug("%s: batt_id_mv is %d\n", __func__, batt_id_mv);
+
+ /*
+ * The readings are not in range
+ * assume battery is present for now
+ */
+ return 1;
+
+ if (batt_id_mv > 0
+ && batt_id_mv > BATT_ID_MIN_MV
+ && batt_id_mv < BATT_ID_MAX_MV)
+ return 1;
+
+ return 0;
+}
+
+/* returns voltage in mV */
+static int pm8058_get_battery_mvolts(void)
+{
+ int vbatt_mv;
+
+ vbatt_mv = batt_read_adc(CHANNEL_ADC_VBATT, NULL);
+ pr_debug("%s: vbatt_mv is %d\n", __func__, vbatt_mv);
+ if (vbatt_mv > 0)
+ return vbatt_mv;
+ /*
+ * return 0 to tell the upper layers
+ * we couldnt read the battery voltage
+ */
+ return 0;
+}
+
+static int msm_battery_gauge_alarm_notify(struct notifier_block *nb,
+ unsigned long status, void *unused)
+{
+ int rc;
+
+ pr_info("%s: status: %lu\n", __func__, status);
+
+ switch (status) {
+ case 0:
+ dev_err(pm8058_chg.dev,
+ "%s: spurious interrupt\n", __func__);
+ break;
+ /* expected case - trip of low threshold */
+ case 1:
+ rc = pm8xxx_batt_alarm_disable(
+ PM8XXX_BATT_ALARM_UPPER_COMPARATOR);
+ if (!rc)
+ rc = pm8xxx_batt_alarm_disable(
+ PM8XXX_BATT_ALARM_LOWER_COMPARATOR);
+ if (rc)
+ dev_err(pm8058_chg.dev,
+ "%s: unable to set alarm state\n", __func__);
+ msm_charger_notify_event(NULL, CHG_BATT_NEEDS_RECHARGING);
+ break;
+ case 2:
+ dev_err(pm8058_chg.dev,
+ "%s: trip of high threshold\n", __func__);
+ break;
+ default:
+ dev_err(pm8058_chg.dev,
+ "%s: error received\n", __func__);
+ };
+
+ return 0;
+}
+
+static int pm8058_monitor_for_recharging(void)
+{
+ int rc;
+ /* enable low comparator */
+ rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR);
+ if (!rc)
+ return pm8xxx_batt_alarm_enable(
+ PM8XXX_BATT_ALARM_LOWER_COMPARATOR);
+
+ return rc;
+
+}
+
+static struct msm_battery_gauge pm8058_batt_gauge = {
+ .get_battery_mvolts = pm8058_get_battery_mvolts,
+ .get_battery_temperature = pm8058_get_battery_temperature,
+ .is_battery_present = pm8058_is_battery_present,
+ .is_battery_temp_within_range = pm8058_is_battery_temp_within_range,
+ .is_battery_id_valid = pm8058_is_battery_id_valid,
+ .monitor_for_recharging = pm8058_monitor_for_recharging,
+};
+
+static int pm8058_usb_voltage_lower_limit(void)
+{
+ u8 temp, old;
+ int ret = 0;
+
+ temp = 0x10;
+ ret |= pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST, temp);
+ ret |= pm8xxx_readb(pm8058_chg.dev->parent, PM8058_CHG_TEST, &old);
+ old = old & ~BIT(IGNORE_LL);
+ temp = 0x90 | (0xF & old);
+ ret |= pm8xxx_writeb(pm8058_chg.dev->parent, PM8058_CHG_TEST, temp);
+
+ return ret;
+}
+
+static int __devinit pm8058_charger_probe(struct platform_device *pdev)
+{
+ struct pmic8058_charger_data *pdata;
+ int rc = 0;
+
+ pm8058_chg.pdata = pdev->dev.platform_data;
+ pm8058_chg.dev = &pdev->dev;
+ pdata = (struct pmic8058_charger_data *) pm8058_chg.pdata;
+
+ if (pdata == NULL) {
+ pr_err("%s: pdata not present\n", __func__);
+ return -EINVAL;
+ }
+
+ if (pdata->charger_data_valid) {
+ usb_hw_chg.type = pdata->charger_type;
+ chg_data.charger_type = pdata->charger_type;
+ chg_data.max_source_current = pdata->max_source_current;
+ }
+
+ rc = request_irqs(pdev);
+ if (rc) {
+ pr_err("%s: couldnt register interrupts\n", __func__);
+ goto out;
+ }
+
+ rc = pm8058_usb_voltage_lower_limit();
+ if (rc) {
+ pr_err("%s: couldnt set ignore lower limit bit to 0\n",
+ __func__);
+ goto free_irq;
+ }
+
+ rc = msm_charger_register(&usb_hw_chg);
+ if (rc) {
+ pr_err("%s: msm_charger_register failed ret=%d\n",
+ __func__, rc);
+ goto free_irq;
+ }
+
+ pm_chg_batt_temp_disable(0);
+ msm_battery_gauge_register(&pm8058_batt_gauge);
+ __dump_chg_regs();
+
+ create_debugfs_entries();
+ INIT_DELAYED_WORK(&pm8058_chg.veoc_begin_work, veoc_begin_work);
+ INIT_DELAYED_WORK(&pm8058_chg.check_vbat_low_work, vbat_low_work);
+ INIT_DELAYED_WORK(&pm8058_chg.chg_done_check_work, chg_done_check_work);
+ INIT_DELAYED_WORK(&pm8058_chg.charging_check_work, charging_check_work);
+
+ /* determine what state the charger is in */
+ pm8058_chg_determine_initial_state();
+
+ pm8058_chg_enable_irq(BATTTEMP_IRQ);
+ pm8058_chg_enable_irq(BATTCONNECT_IRQ);
+
+ rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR);
+ if (!rc)
+ rc = pm8xxx_batt_alarm_disable(
+ PM8XXX_BATT_ALARM_LOWER_COMPARATOR);
+ if (rc) {
+ pr_err("%s: unable to set batt alarm state\n", __func__);
+ goto free_irq;
+ }
+
+ /*
+ * The batt-alarm driver requires sane values for both min / max,
+ * regardless of whether they're both activated.
+ */
+ rc = pm8xxx_batt_alarm_threshold_set(
+ PM8XXX_BATT_ALARM_LOWER_COMPARATOR, resume_mv);
+ if (!rc)
+ rc = pm8xxx_batt_alarm_threshold_set(
+ PM8XXX_BATT_ALARM_UPPER_COMPARATOR, 4300);
+ if (rc) {
+ pr_err("%s: unable to set batt alarm threshold\n", __func__);
+ goto free_irq;
+ }
+
+ rc = pm8xxx_batt_alarm_hold_time_set(
+ PM8XXX_BATT_ALARM_HOLD_TIME_16_MS);
+ if (rc) {
+ pr_err("%s: unable to set batt alarm hold time\n", __func__);
+ goto free_irq;
+ }
+
+ /* PWM enabled at 2Hz */
+ rc = pm8xxx_batt_alarm_pwm_rate_set(1, 7, 4);
+ if (rc) {
+ pr_err("%s: unable to set batt alarm pwm rate\n", __func__);
+ goto free_irq;
+ }
+
+ rc = pm8xxx_batt_alarm_register_notifier(&alarm_notifier);
+ if (rc) {
+ pr_err("%s: unable to register alarm notifier\n", __func__);
+ goto free_irq;
+ }
+
+ pm8058_chg.inited = 1;
+
+ return 0;
+
+free_irq:
+ free_irqs();
+out:
+ return rc;
+}
+
+static int __devexit pm8058_charger_remove(struct platform_device *pdev)
+{
+ struct pm8058_charger_chip *chip = platform_get_drvdata(pdev);
+ int rc;
+
+ msm_charger_notify_event(&usb_hw_chg, CHG_REMOVED_EVENT);
+ msm_charger_unregister(&usb_hw_chg);
+ cancel_delayed_work_sync(&pm8058_chg.veoc_begin_work);
+ cancel_delayed_work_sync(&pm8058_chg.check_vbat_low_work);
+ cancel_delayed_work_sync(&pm8058_chg.charging_check_work);
+ free_irqs();
+ remove_debugfs_entries();
+ kfree(chip);
+
+ rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR);
+ if (!rc)
+ rc = pm8xxx_batt_alarm_disable(
+ PM8XXX_BATT_ALARM_LOWER_COMPARATOR);
+ if (rc)
+ pr_err("%s: unable to set batt alarm state\n", __func__);
+
+ rc |= pm8xxx_batt_alarm_unregister_notifier(&alarm_notifier);
+ if (rc)
+ pr_err("%s: unable to register alarm notifier\n", __func__);
+ return rc;
+}
+
+static struct platform_driver pm8058_charger_driver = {
+ .probe = pm8058_charger_probe,
+ .remove = __devexit_p(pm8058_charger_remove),
+ .driver = {
+ .name = "pm8058-charger",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pm8058_charger_init(void)
+{
+ return platform_driver_register(&pm8058_charger_driver);
+}
+
+static void __exit pm8058_charger_exit(void)
+{
+ platform_driver_unregister(&pm8058_charger_driver);
+}
+
+late_initcall(pm8058_charger_init);
+module_exit(pm8058_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8058 BATTERY driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:pm8058_charger");
diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c
index f58d1805..be6ba04 100644
--- a/drivers/power/power_supply_core.c
+++ b/drivers/power/power_supply_core.c
@@ -25,6 +25,61 @@
static struct device_type power_supply_dev_type;
+/**
+ * power_supply_set_current_limit - set current limit
+ * @psy: the power supply to control
+ * @limit: current limit in uA from the power supply.
+ * 0 will disable the power supply.
+ *
+ * This function will set a maximum supply current from a source
+ * and it will disable the charger when limit is 0.
+ */
+int power_supply_set_current_limit(struct power_supply *psy, int limit)
+{
+ const union power_supply_propval ret = {limit,};
+
+ if (psy->set_property)
+ return psy->set_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX,
+ &ret);
+
+ return -ENXIO;
+}
+EXPORT_SYMBOL_GPL(power_supply_set_current_limit);
+
+/**
+ * power_supply_set_online - set online state of the power supply
+ * @psy: the power supply to control
+ * @enable: sets online property of power supply
+ */
+int power_supply_set_online(struct power_supply *psy, bool enable)
+{
+ const union power_supply_propval ret = {enable,};
+
+ if (psy->set_property)
+ return psy->set_property(psy, POWER_SUPPLY_PROP_ONLINE,
+ &ret);
+
+ return -ENXIO;
+}
+EXPORT_SYMBOL_GPL(power_supply_set_online);
+
+/**
+ * power_supply_set_charge_type - set charge type of the power supply
+ * @psy: the power supply to control
+ * @enable: sets charge type property of power supply
+ */
+int power_supply_set_charge_type(struct power_supply *psy, int charge_type)
+{
+ const union power_supply_propval ret = {charge_type,};
+
+ if (psy->set_property)
+ return psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_TYPE,
+ &ret);
+
+ return -ENXIO;
+}
+EXPORT_SYMBOL_GPL(power_supply_set_charge_type);
+
static int __power_supply_changed_work(struct device *dev, void *data)
{
struct power_supply *psy = (struct power_supply *)data;
diff --git a/drivers/power/qci_battery.c b/drivers/power/qci_battery.c
new file mode 100644
index 0000000..724bcba
--- /dev/null
+++ b/drivers/power/qci_battery.c
@@ -0,0 +1,662 @@
+/* Quanta I2C Battery Driver
+ *
+ * Copyright (C) 2009 Quanta Computer Inc.
+ *
+ * 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.
+ *
+ */
+
+/*
+ *
+ * The Driver with I/O communications via the I2C Interface for ST15 platform.
+ * And it is only working on the nuvoTon WPCE775x Embedded Controller.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/sched.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/wpce775x.h>
+#include <linux/delay.h>
+
+#include "qci_battery.h"
+
+#define QCIBAT_DEFAULT_CHARGE_FULL_CAPACITY 2200 /* 2200 mAh */
+#define QCIBAT_DEFAULT_CHARGE_FULL_DESIGN 2200
+#define QCIBAT_DEFAULT_VOLTAGE_DESIGN 10800 /* 10.8 V */
+#define QCIBAT_STRING_SIZE 16
+
+/* General structure to hold the driver data */
+struct i2cbat_drv_data {
+ struct i2c_client *bi2c_client;
+ struct work_struct work;
+ unsigned int qcibat_irq;
+ unsigned int qcibat_gpio;
+ u8 battery_state;
+ u8 battery_dev_name[QCIBAT_STRING_SIZE];
+ u8 serial_number[QCIBAT_STRING_SIZE];
+ u8 manufacturer_name[QCIBAT_STRING_SIZE];
+ unsigned int charge_full;
+ unsigned int charge_full_design;
+ unsigned int voltage_full_design;
+ unsigned int energy_full;
+};
+
+static struct i2cbat_drv_data context;
+static struct mutex qci_i2c_lock;
+static struct mutex qci_transaction_lock;
+/*********************************************************************
+ * Power
+ *********************************************************************/
+
+static int get_bat_info(u8 ec_data)
+{
+ u8 byte_read;
+
+ mutex_lock(&qci_i2c_lock);
+ i2c_smbus_write_byte(context.bi2c_client, ec_data);
+ byte_read = i2c_smbus_read_byte(context.bi2c_client);
+ mutex_unlock(&qci_i2c_lock);
+ return byte_read;
+}
+
+static int qci_ac_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (get_bat_info(ECRAM_POWER_SOURCE) & EC_FLAG_ADAPTER_IN)
+ val->intval = EC_ADAPTER_PRESENT;
+ else
+ val->intval = EC_ADAPTER_NOT_PRESENT;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property qci_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property qci_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_ENERGY_EMPTY,
+};
+
+static int read_data_from_battery(u8 smb_cmd, u8 smb_prtcl)
+{
+ if (context.battery_state & MAIN_BATTERY_STATUS_BAT_IN) {
+ mutex_lock(&qci_i2c_lock);
+ i2c_smbus_write_byte_data(context.bi2c_client,
+ ECRAM_SMB_STS, 0);
+ i2c_smbus_write_byte_data(context.bi2c_client, ECRAM_SMB_ADDR,
+ BATTERY_SLAVE_ADDRESS);
+ i2c_smbus_write_byte_data(context.bi2c_client,
+ ECRAM_SMB_CMD, smb_cmd);
+ i2c_smbus_write_byte_data(context.bi2c_client,
+ ECRAM_SMB_PRTCL, smb_prtcl);
+ mutex_unlock(&qci_i2c_lock);
+ msleep(100);
+ return get_bat_info(ECRAM_SMB_STS);
+ } else
+ return SMBUS_DEVICE_NOACK;
+}
+
+static int qbat_get_status(union power_supply_propval *val)
+{
+ int status;
+
+ status = get_bat_info(ECRAM_BATTERY_STATUS);
+
+ if ((status & MAIN_BATTERY_STATUS_BAT_IN) == 0x0)
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ else if (status & MAIN_BATTERY_STATUS_BAT_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (status & MAIN_BATTERY_STATUS_BAT_FULL)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (status & MAIN_BATTERY_STATUS_BAT_DISCHRG)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ return 0;
+}
+
+static int qbat_get_present(union power_supply_propval *val)
+{
+ if (context.battery_state & MAIN_BATTERY_STATUS_BAT_IN)
+ val->intval = EC_BAT_PRESENT;
+ else
+ val->intval = EC_BAT_NOT_PRESENT;
+ return 0;
+}
+
+static int qbat_get_health(union power_supply_propval *val)
+{
+ u8 health;
+
+ health = get_bat_info(ECRAM_CHARGER_ALARM);
+ if (!(context.battery_state & MAIN_BATTERY_STATUS_BAT_IN))
+ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+ else if (health & ALARM_OVER_TEMP)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (health & ALARM_REMAIN_CAPACITY)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else if (health & ALARM_OVER_CHARGE)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ return 0;
+}
+
+static int qbat_get_voltage_avg(union power_supply_propval *val)
+{
+ val->intval = (get_bat_info(ECRAM_BATTERY_VOLTAGE_MSB) << 8 |
+ get_bat_info(ECRAM_BATTERY_VOLTAGE_LSB)) * 1000;
+ return 0;
+}
+
+static int qbat_get_current_avg(union power_supply_propval *val)
+{
+ val->intval = (get_bat_info(ECRAM_BATTERY_CURRENT_MSB) << 8 |
+ get_bat_info(ECRAM_BATTERY_CURRENT_LSB));
+ return 0;
+}
+
+static int qbat_get_capacity(union power_supply_propval *val)
+{
+ if (!(context.battery_state & MAIN_BATTERY_STATUS_BAT_IN))
+ val->intval = 0xFF;
+ else
+ val->intval = get_bat_info(ECRAM_BATTERY_CAPACITY);
+ return 0;
+}
+
+static int qbat_get_temp_avg(union power_supply_propval *val)
+{
+ int temp;
+ int rc = 0;
+
+ if (!(context.battery_state & MAIN_BATTERY_STATUS_BAT_IN)) {
+ val->intval = 0xFFFF;
+ rc = -ENODATA;
+ } else {
+ temp = (get_bat_info(ECRAM_BATTERY_TEMP_MSB) << 8) |
+ get_bat_info(ECRAM_BATTERY_TEMP_LSB);
+ val->intval = (temp - 2730) / 10;
+ }
+ return rc;
+}
+
+static int qbat_get_charge_full_design(union power_supply_propval *val)
+{
+ val->intval = context.charge_full_design;
+ return 0;
+}
+
+static int qbat_get_charge_full(union power_supply_propval *val)
+{
+ val->intval = context.charge_full;
+ return 0;
+}
+
+static int qbat_get_charge_counter(union power_supply_propval *val)
+{
+ u16 charge = 0;
+ int rc = 0;
+
+ mutex_lock(&qci_transaction_lock);
+ if (read_data_from_battery(BATTERY_CYCLE_COUNT,
+ SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) {
+ charge = get_bat_info(ECRAM_SMB_DATA1);
+ charge = charge << 8;
+ charge |= get_bat_info(ECRAM_SMB_DATA0);
+ } else
+ rc = -ENODATA;
+ mutex_unlock(&qci_transaction_lock);
+ val->intval = charge;
+ return rc;
+}
+
+static int qbat_get_time_empty_avg(union power_supply_propval *val)
+{
+ u16 avg = 0;
+ int rc = 0;
+
+ mutex_lock(&qci_transaction_lock);
+ if (read_data_from_battery(BATTERY_AVERAGE_TIME_TO_EMPTY,
+ SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) {
+ avg = get_bat_info(ECRAM_SMB_DATA1);
+ avg = avg << 8;
+ avg |= get_bat_info(ECRAM_SMB_DATA0);
+ } else
+ rc = -ENODATA;
+ mutex_unlock(&qci_transaction_lock);
+ val->intval = avg;
+ return rc;
+}
+
+static int qbat_get_time_full_avg(union power_supply_propval *val)
+{
+ u16 avg = 0;
+ int rc = 0;
+
+ mutex_lock(&qci_transaction_lock);
+ if (read_data_from_battery(BATTERY_AVERAGE_TIME_TO_FULL,
+ SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) {
+ avg = get_bat_info(ECRAM_SMB_DATA1);
+ avg = avg << 8;
+ avg |= get_bat_info(ECRAM_SMB_DATA0);
+ } else
+ rc = -ENODATA;
+ mutex_unlock(&qci_transaction_lock);
+ val->intval = avg;
+ return rc;
+}
+
+static int qbat_get_model_name(union power_supply_propval *val)
+{
+ unsigned char i, size;
+
+ mutex_lock(&qci_transaction_lock);
+ if (read_data_from_battery(BATTERY_DEVICE_NAME,
+ SMBUS_READ_BLOCK_PRTCL) == SMBUS_DONE) {
+ size = min(get_bat_info(ECRAM_SMB_BCNT), QCIBAT_STRING_SIZE);
+ for (i = 0; i < size; i++) {
+ context.battery_dev_name[i] =
+ get_bat_info(ECRAM_SMB_DATA_START + i);
+ }
+ val->strval = context.battery_dev_name;
+ } else
+ val->strval = "Unknown";
+ mutex_unlock(&qci_transaction_lock);
+ return 0;
+}
+
+static int qbat_get_manufacturer_name(union power_supply_propval *val)
+{
+ val->strval = context.manufacturer_name;
+ return 0;
+}
+
+static int qbat_get_serial_number(union power_supply_propval *val)
+{
+ val->strval = context.serial_number;
+ return 0;
+}
+
+static int qbat_get_technology(union power_supply_propval *val)
+{
+ val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+ return 0;
+}
+
+static int qbat_get_energy_now(union power_supply_propval *val)
+{
+ if (!(get_bat_info(ECRAM_BATTERY_STATUS) & MAIN_BATTERY_STATUS_BAT_IN))
+ val->intval = 0;
+ else
+ val->intval = (get_bat_info(ECRAM_BATTERY_CAPACITY) *
+ context.energy_full) / 100;
+ return 0;
+}
+
+static int qbat_get_energy_full(union power_supply_propval *val)
+{
+ val->intval = context.energy_full;
+ return 0;
+}
+
+static int qbat_get_energy_empty(union power_supply_propval *val)
+{
+ val->intval = 0;
+ return 0;
+}
+
+static void qbat_init_get_charge_full(void)
+{
+ u16 charge = QCIBAT_DEFAULT_CHARGE_FULL_CAPACITY;
+
+ mutex_lock(&qci_transaction_lock);
+ if (read_data_from_battery(BATTERY_FULL_CAPACITY,
+ SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) {
+ charge = get_bat_info(ECRAM_SMB_DATA1);
+ charge = charge << 8;
+ charge |= get_bat_info(ECRAM_SMB_DATA0);
+ }
+ mutex_unlock(&qci_transaction_lock);
+ context.charge_full = charge;
+}
+
+static void qbat_init_get_charge_full_design(void)
+{
+ u16 charge = QCIBAT_DEFAULT_CHARGE_FULL_DESIGN;
+
+ mutex_lock(&qci_transaction_lock);
+ if (read_data_from_battery(BATTERY_DESIGN_CAPACITY,
+ SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) {
+ charge = get_bat_info(ECRAM_SMB_DATA1);
+ charge = charge << 8;
+ charge |= get_bat_info(ECRAM_SMB_DATA0);
+ }
+ mutex_unlock(&qci_transaction_lock);
+ context.charge_full_design = charge;
+}
+
+static void qbat_init_get_voltage_full_design(void)
+{
+ u16 voltage = QCIBAT_DEFAULT_VOLTAGE_DESIGN;
+
+ mutex_lock(&qci_transaction_lock);
+ if (read_data_from_battery(BATTERY_DESIGN_VOLTAGE,
+ SMBUS_READ_WORD_PRTCL) == SMBUS_DONE) {
+ voltage = get_bat_info(ECRAM_SMB_DATA1);
+ voltage = voltage << 8;
+ voltage |= get_bat_info(ECRAM_SMB_DATA0);
+ }
+ mutex_unlock(&qci_transaction_lock);
+ context.voltage_full_design = voltage;
+}
+
+static void qbat_init_get_manufacturer_name(void)
+{
+ u8 size;
+ u8 i;
+ int rc;
+
+ mutex_lock(&qci_transaction_lock);
+ rc = read_data_from_battery(BATTERY_MANUFACTURE_NAME,
+ SMBUS_READ_BLOCK_PRTCL);
+ if (rc == SMBUS_DONE) {
+ size = min(get_bat_info(ECRAM_SMB_BCNT), QCIBAT_STRING_SIZE);
+ for (i = 0; i < size; i++) {
+ context.manufacturer_name[i] =
+ get_bat_info(ECRAM_SMB_DATA_START + i);
+ }
+ } else
+ strcpy(context.manufacturer_name, "Unknown");
+ mutex_unlock(&qci_transaction_lock);
+}
+
+static void qbat_init_get_serial_number(void)
+{
+ u8 size;
+ u8 i;
+ int rc;
+
+ mutex_lock(&qci_transaction_lock);
+ rc = read_data_from_battery(BATTERY_SERIAL_NUMBER,
+ SMBUS_READ_BLOCK_PRTCL);
+ if (rc == SMBUS_DONE) {
+ size = min(get_bat_info(ECRAM_SMB_BCNT), QCIBAT_STRING_SIZE);
+ for (i = 0; i < size; i++) {
+ context.serial_number[i] =
+ get_bat_info(ECRAM_SMB_DATA_START + i);
+ }
+ } else
+ strcpy(context.serial_number, "Unknown");
+ mutex_unlock(&qci_transaction_lock);
+}
+
+static void init_battery_stats(void)
+{
+ int i;
+
+ context.battery_state = get_bat_info(ECRAM_BATTERY_STATUS);
+ if (!(context.battery_state & MAIN_BATTERY_STATUS_BAT_IN))
+ return;
+ /* EC bug? needs some initial priming */
+ for (i = 0; i < 5; i++) {
+ read_data_from_battery(BATTERY_DESIGN_CAPACITY,
+ SMBUS_READ_WORD_PRTCL);
+ }
+
+ qbat_init_get_charge_full_design();
+ qbat_init_get_charge_full();
+ qbat_init_get_voltage_full_design();
+
+ context.energy_full = context.voltage_full_design *
+ context.charge_full;
+
+ qbat_init_get_serial_number();
+ qbat_init_get_manufacturer_name();
+}
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+static int qbat_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_STATUS:
+ ret = qbat_get_status(val);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = qbat_get_present(val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = qbat_get_health(val);
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ ret = qbat_get_manufacturer_name(val);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ ret = qbat_get_technology(val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ ret = qbat_get_voltage_avg(val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = qbat_get_current_avg(val);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = qbat_get_capacity(val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = qbat_get_temp_avg(val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ ret = qbat_get_charge_full_design(val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = qbat_get_charge_full(val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = qbat_get_charge_counter(val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ ret = qbat_get_time_empty_avg(val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+ ret = qbat_get_time_full_avg(val);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ ret = qbat_get_model_name(val);
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ ret = qbat_get_serial_number(val);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ ret = qbat_get_energy_now(val);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ ret = qbat_get_energy_full(val);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_EMPTY:
+ ret = qbat_get_energy_empty(val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static struct power_supply qci_ac = {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = qci_ac_props,
+ .num_properties = ARRAY_SIZE(qci_ac_props),
+ .get_property = qci_ac_get_prop,
+};
+
+static struct power_supply qci_bat = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = qci_bat_props,
+ .num_properties = ARRAY_SIZE(qci_bat_props),
+ .get_property = qbat_get_property,
+ .use_for_apm = 1,
+};
+
+static irqreturn_t qbat_interrupt(int irq, void *dev_id)
+{
+ struct i2cbat_drv_data *ibat_drv_data = dev_id;
+ schedule_work(&ibat_drv_data->work);
+ return IRQ_HANDLED;
+}
+
+static void qbat_work(struct work_struct *_work)
+{
+ u8 status;
+
+ status = get_bat_info(ECRAM_BATTERY_EVENTS);
+ if (status & EC_EVENT_AC) {
+ context.battery_state = get_bat_info(ECRAM_BATTERY_STATUS);
+ power_supply_changed(&qci_ac);
+ }
+
+ if (status & (EC_EVENT_BATTERY | EC_EVENT_CHARGER | EC_EVENT_TIMER)) {
+ context.battery_state = get_bat_info(ECRAM_BATTERY_STATUS);
+ power_supply_changed(&qci_bat);
+ if (status & EC_EVENT_BATTERY)
+ init_battery_stats();
+ }
+}
+
+static struct platform_device *bat_pdev;
+
+static int __init qbat_init(void)
+{
+ int err = 0;
+
+ mutex_init(&qci_i2c_lock);
+ mutex_init(&qci_transaction_lock);
+
+ context.bi2c_client = wpce_get_i2c_client();
+ if (context.bi2c_client == NULL)
+ return -1;
+
+ i2c_set_clientdata(context.bi2c_client, &context);
+ context.qcibat_gpio = context.bi2c_client->irq;
+
+ /*battery device register*/
+ bat_pdev = platform_device_register_simple("battery", 0, NULL, 0);
+ if (IS_ERR(bat_pdev))
+ return PTR_ERR(bat_pdev);
+
+ err = power_supply_register(&bat_pdev->dev, &qci_ac);
+ if (err)
+ goto ac_failed;
+
+ qci_bat.name = bat_pdev->name;
+ err = power_supply_register(&bat_pdev->dev, &qci_bat);
+ if (err)
+ goto battery_failed;
+
+ /*battery irq configure*/
+ INIT_WORK(&context.work, qbat_work);
+ err = gpio_request(context.qcibat_gpio, "qci-bat");
+ if (err) {
+ dev_err(&context.bi2c_client->dev,
+ "[BAT] err gpio request\n");
+ goto gpio_request_fail;
+ }
+ context.qcibat_irq = gpio_to_irq(context.qcibat_gpio);
+ err = request_irq(context.qcibat_irq, qbat_interrupt,
+ IRQF_TRIGGER_FALLING, BATTERY_ID_NAME, &context);
+ if (err) {
+ dev_err(&context.bi2c_client->dev,
+ "[BAT] unable to get IRQ\n");
+ goto request_irq_fail;
+ }
+
+ init_battery_stats();
+ goto success;
+
+request_irq_fail:
+ gpio_free(context.qcibat_gpio);
+
+gpio_request_fail:
+ power_supply_unregister(&qci_bat);
+
+battery_failed:
+ power_supply_unregister(&qci_ac);
+
+ac_failed:
+ platform_device_unregister(bat_pdev);
+
+ i2c_set_clientdata(context.bi2c_client, NULL);
+success:
+ return err;
+}
+
+static void __exit qbat_exit(void)
+{
+ free_irq(context.qcibat_irq, &context);
+ gpio_free(context.qcibat_gpio);
+ power_supply_unregister(&qci_bat);
+ power_supply_unregister(&qci_ac);
+ platform_device_unregister(bat_pdev);
+ i2c_set_clientdata(context.bi2c_client, NULL);
+}
+
+late_initcall(qbat_init);
+module_exit(qbat_exit);
+
+MODULE_AUTHOR("Quanta Computer Inc.");
+MODULE_DESCRIPTION("Quanta Embedded Controller I2C Battery Driver");
+MODULE_LICENSE("GPL v2");
+
diff --git a/drivers/power/qci_battery.h b/drivers/power/qci_battery.h
new file mode 100644
index 0000000..dcbb62b
--- /dev/null
+++ b/drivers/power/qci_battery.h
@@ -0,0 +1,121 @@
+/* Header file for Quanta I2C Battery Driver
+ *
+ * Copyright (C) 2009 Quanta Computer Inc.
+ *
+ * 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.
+ *
+ */
+
+ /*
+ *
+ * The Driver with I/O communications via the I2C Interface for ON2 of AP BU.
+ * And it is only working on the nuvoTon WPCE775x Embedded Controller.
+ *
+ */
+
+#ifndef __QCI_BATTERY_H__
+#define __QCI_BATTERY_H__
+
+#define BAT_I2C_ADDRESS 0x1A
+#define BATTERY_ID_NAME "qci-i2cbat"
+#define EC_FLAG_ADAPTER_IN 0x01
+#define EC_FLAG_POWER_ON 0x02
+#define EC_FLAG_ENTER_S3 0x04
+#define EC_FLAG_ENTER_S4 0x08
+#define EC_FLAG_IN_STANDBY 0x10
+#define EC_FLAG_SYSTEM_ON 0x20
+#define EC_FLAG_WAIT_HWPG 0x40
+#define EC_FLAG_S5_POWER_ON 0x80
+
+#define MAIN_BATTERY_STATUS_BAT_DISCHRG 0x01
+#define MAIN_BATTERY_STATUS_BAT_CHARGING 0x02
+#define MAIN_BATTERY_STATUS_BAT_ABNORMAL 0x04
+#define MAIN_BATTERY_STATUS_BAT_IN 0x08
+#define MAIN_BATTERY_STATUS_BAT_FULL 0x10
+#define MAIN_BATTERY_STATUS_BAT_LOW 0x20
+#define MAIN_BATTERY_STATUS_BAT_SMB_VALID 0x80
+
+#define CHG_STATUS_BAT_CHARGE 0x01
+#define CHG_STATUS_BAT_PRECHG 0x02
+#define CHG_STATUS_BAT_OVERTEMP 0x04
+#define CHG_STATUS_BAT_TYPE 0x08
+#define CHG_STATUS_BAT_GWROK 0x10
+#define CHG_STATUS_BAT_INCHARGE 0x20
+#define CHG_STATUS_BAT_WAKECHRG 0x40
+#define CHG_STATUS_BAT_CHGTIMEOUT 0x80
+
+#define EC_ADAPTER_PRESENT 0x1
+#define EC_BAT_PRESENT 0x1
+#define EC_ADAPTER_NOT_PRESENT 0x0
+#define EC_BAT_NOT_PRESENT 0x0
+
+#define ECRAM_POWER_SOURCE 0x40
+#define ECRAM_CHARGER_ALARM 0x42
+#define ECRAM_BATTERY_STATUS 0x82
+#define ECRAM_BATTERY_CURRENT_LSB 0x83
+#define ECRAM_BATTERY_CURRENT_MSB 0x84
+#define ECRAM_BATTERY_VOLTAGE_LSB 0x87
+#define ECRAM_BATTERY_VOLTAGE_MSB 0x88
+#define ECRAM_BATTERY_CAPACITY 0x89
+#define ECRAM_BATTERY_TEMP_LSB 0x8C
+#define ECRAM_BATTERY_TEMP_MSB 0x8D
+#define ECRAM_BATTERY_EVENTS 0x99
+
+#define EC_EVENT_BATTERY 0x01
+#define EC_EVENT_CHARGER 0x02
+#define EC_EVENT_AC 0x10
+#define EC_EVENT_TIMER 0x40
+
+/* smbus access */
+#define SMBUS_READ_BYTE_PRTCL 0x07
+#define SMBUS_READ_WORD_PRTCL 0x09
+#define SMBUS_READ_BLOCK_PRTCL 0x0B
+
+/* smbus status code */
+#define SMBUS_OK 0x00
+#define SMBUS_DONE 0x80
+#define SMBUS_ALARM 0x40
+#define SMBUS_UNKNOW_FAILURE 0x07
+#define SMBUS_DEVICE_NOACK 0x10
+#define SMBUS_DEVICE_ERROR 0x11
+#define SMBUS_UNKNOW_ERROR 0x13
+#define SMBUS_TIME_OUT 0x18
+#define SMBUS_BUSY 0x1A
+
+/* ec ram mapping */
+#define ECRAM_SMB_PRTCL 0
+#define ECRAM_SMB_STS 1
+#define ECRAM_SMB_ADDR 2
+#define ECRAM_SMB_CMD 3
+#define ECRAM_SMB_DATA_START 4
+#define ECRAM_SMB_DATA0 4
+#define ECRAM_SMB_DATA1 5
+#define ECRAM_SMB_BCNT 36
+#define ECRAM_SMB_ALARM_ADDR 37
+#define ECRAM_SMB_ALARM_DATA0 38
+#define ECRAM_SMB_ALARM_DATA1 39
+
+/* smart battery commands */
+#define BATTERY_SLAVE_ADDRESS 0x16
+#define BATTERY_FULL_CAPACITY 0x10
+#define BATTERY_AVERAGE_TIME_TO_EMPTY 0x12
+#define BATTERY_AVERAGE_TIME_TO_FULL 0x13
+#define BATTERY_CYCLE_COUNT 0x17
+#define BATTERY_DESIGN_CAPACITY 0x18
+#define BATTERY_DESIGN_VOLTAGE 0x19
+#define BATTERY_SERIAL_NUMBER 0x1C
+#define BATTERY_MANUFACTURE_NAME 0x20
+#define BATTERY_DEVICE_NAME 0x21
+
+/* alarm bit */
+#define ALARM_REMAIN_CAPACITY 0x02
+#define ALARM_OVER_TEMP 0x10
+#define ALARM_OVER_CHARGE 0x80
+#endif
diff --git a/drivers/power/smb137b.c b/drivers/power/smb137b.c
new file mode 100644
index 0000000..7ff8e28
--- /dev/null
+++ b/drivers/power/smb137b.c
@@ -0,0 +1,857 @@
+/* Copyright (c) 2010-2011 Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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/i2c.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/i2c/smb137b.h>
+#include <linux/power_supply.h>
+#include <linux/msm-charger.h>
+
+#define SMB137B_MASK(BITS, POS) ((unsigned char)(((1 << BITS) - 1) << POS))
+
+#define CHG_CURRENT_REG 0x00
+#define FAST_CHG_CURRENT_MASK SMB137B_MASK(3, 5)
+#define PRE_CHG_CURRENT_MASK SMB137B_MASK(2, 3)
+#define TERM_CHG_CURRENT_MASK SMB137B_MASK(2, 1)
+
+#define INPUT_CURRENT_LIMIT_REG 0x01
+#define IN_CURRENT_MASK SMB137B_MASK(3, 5)
+#define IN_CURRENT_LIMIT_EN_BIT BIT(2)
+#define IN_CURRENT_DET_THRESH_MASK SMB137B_MASK(2, 0)
+
+#define FLOAT_VOLTAGE_REG 0x02
+#define STAT_OUT_POLARITY_BIT BIT(7)
+#define FLOAT_VOLTAGE_MASK SMB137B_MASK(7, 0)
+
+#define CONTROL_A_REG 0x03
+#define AUTO_RECHARGE_DIS_BIT BIT(7)
+#define CURR_CYCLE_TERM_BIT BIT(6)
+#define PRE_TO_FAST_V_MASK SMB137B_MASK(3, 3)
+#define TEMP_BEHAV_BIT BIT(2)
+#define THERM_NTC_CURR_MODE_BIT BIT(1)
+#define THERM_NTC_47KOHM_BIT BIT(0)
+
+#define CONTROL_B_REG 0x04
+#define STAT_OUTPUT_MODE_MASK SMB137B_MASK(2, 6)
+#define BATT_OV_ENDS_CYCLE_BIT BIT(5)
+#define AUTO_PRE_TO_FAST_DIS_BIT BIT(4)
+#define SAFETY_TIMER_EN_BIT BIT(3)
+#define OTG_LBR_WD_EN_BIT BIT(2)
+#define CHG_WD_TIMER_EN_BIT BIT(1)
+#define IRQ_OP_MASK BIT(0)
+
+#define PIN_CTRL_REG 0x05
+#define AUTO_CHG_EN_BIT BIT(7)
+#define AUTO_LBR_EN_BIT BIT(6)
+#define OTG_LBR_BIT BIT(5)
+#define I2C_PIN_BIT BIT(4)
+#define PIN_EN_CTRL_MASK SMB137B_MASK(2, 2)
+#define OTG_LBR_PIN_CTRL_MASK SMB137B_MASK(2, 0)
+
+#define OTG_LBR_CTRL_REG 0x06
+#define BATT_MISSING_DET_EN_BIT BIT(7)
+#define AUTO_RECHARGE_THRESH_MASK BIT(6)
+#define USB_DP_DN_DET_EN_MASK BIT(5)
+#define OTG_LBR_BATT_CURRENT_LIMIT_MASK SMB137B_MASK(2, 3)
+#define OTG_LBR_UVLO_THRESH_MASK SMB137B_MASK(3, 0)
+
+#define FAULT_INTR_REG 0x07
+#define SAFETY_TIMER_EXP_MASK SMB137B_MASK(1, 7)
+#define BATT_TEMP_UNSAFE_MASK SMB137B_MASK(1, 6)
+#define INPUT_OVLO_IVLO_MASK SMB137B_MASK(1, 5)
+#define BATT_OVLO_MASK SMB137B_MASK(1, 4)
+#define INTERNAL_OVER_TEMP_MASK SMB137B_MASK(1, 2)
+#define ENTER_TAPER_CHG_MASK SMB137B_MASK(1, 1)
+#define CHG_MASK SMB137B_MASK(1, 0)
+
+#define CELL_TEMP_MON_REG 0x08
+#define THERMISTOR_CURR_MASK SMB137B_MASK(2, 6)
+#define LOW_TEMP_CHG_INHIBIT_MASK SMB137B_MASK(3, 3)
+#define HIGH_TEMP_CHG_INHIBIT_MASK SMB137B_MASK(3, 0)
+
+#define SAFETY_TIMER_THERMAL_SHUTDOWN_REG 0x09
+#define DCIN_OVLO_SEL_MASK SMB137B_MASK(2, 7)
+#define RELOAD_EN_INPUT_VOLTAGE_MASK SMB137B_MASK(1, 6)
+#define THERM_SHUTDN_EN_MASK SMB137B_MASK(1, 5)
+#define STANDBY_WD_TIMER_EN_MASK SMB137B_MASK(1, 4)
+#define COMPLETE_CHG_TMOUT_MASK SMB137B_MASK(2, 2)
+#define PRE_CHG_TMOUT_MASK SMB137B_MASK(2, 0)
+
+#define VSYS_REG 0x0A
+#define VSYS_MASK SMB137B_MASK(3, 4)
+
+#define IRQ_RESET_REG 0x30
+
+#define COMMAND_A_REG 0x31
+#define VOLATILE_REGS_WRITE_PERM_BIT BIT(7)
+#define POR_BIT BIT(6)
+#define FAST_CHG_SETTINGS_BIT BIT(5)
+#define BATT_CHG_EN_BIT BIT(4)
+#define USBIN_MODE_500_BIT BIT(3)
+#define USBIN_MODE_HCMODE_BIT BIT(2)
+#define OTG_LBR_EN_BIT BIT(1)
+#define STAT_OE_BIT BIT(0)
+
+#define STATUS_A_REG 0x32
+#define INTERNAL_TEMP_IRQ_STAT BIT(4)
+#define DCIN_OV_IRQ_STAT BIT(3)
+#define DCIN_UV_IRQ_STAT BIT(2)
+#define USBIN_OV_IRQ_STAT BIT(1)
+#define USBIN_UV_IRQ_STAT BIT(0)
+
+#define STATUS_B_REG 0x33
+#define USB_PIN_STAT BIT(7)
+#define USB51_MODE_STAT BIT(6)
+#define USB51_HC_MODE_STAT BIT(5)
+#define INTERNAL_TEMP_LIMIT_B_STAT BIT(4)
+#define DC_IN_OV_STAT BIT(3)
+#define DC_IN_UV_STAT BIT(2)
+#define USB_IN_OV_STAT BIT(1)
+#define USB_IN_UV_STAT BIT(0)
+
+#define STATUS_C_REG 0x34
+#define AUTO_IN_CURR_LIMIT_MASK SMB137B_MASK(4, 4)
+#define AUTO_IN_CURR_LIMIT_STAT BIT(3)
+#define AUTO_SOURCE_DET_COMP_STAT_MASK SMB137B_MASK(2, 1)
+#define AUTO_SOURCE_DET_RESULT_STAT BIT(0)
+
+#define STATUS_D_REG 0x35
+#define VBATT_LESS_THAN_VSYS_STAT BIT(7)
+#define USB_FAIL_STAT BIT(6)
+#define BATT_TEMP_STAT_MASK SMB137B_MASK(2, 4)
+#define INTERNAL_TEMP_LIMIT_STAT BIT(2)
+#define OTG_LBR_MODE_EN_STAT BIT(1)
+#define OTG_LBR_VBATT_UVLO_STAT BIT(0)
+
+#define STATUS_E_REG 0x36
+#define CHARGE_CYCLE_COUNT_STAT BIT(7)
+#define CHARGER_TERM_STAT BIT(6)
+#define SAFETY_TIMER_STAT_MASK SMB137B_MASK(2, 4)
+#define CHARGER_ERROR_STAT BIT(3)
+#define CHARGING_STAT_E SMB137B_MASK(2, 1)
+#define CHARGING_EN BIT(0)
+
+#define STATUS_F_REG 0x37
+#define WD_IRQ_ACTIVE_STAT BIT(7)
+#define OTG_OVERCURRENT_STAT BIT(6)
+#define BATT_PRESENT_STAT BIT(4)
+#define BATT_OV_LATCHED_STAT BIT(3)
+#define CHARGER_OVLO_STAT BIT(2)
+#define CHARGER_UVLO_STAT BIT(1)
+#define BATT_LOW_STAT BIT(0)
+
+#define STATUS_G_REG 0x38
+#define CHARGE_TIMEOUT_IRQ_STAT BIT(7)
+#define PRECHARGE_TIMEOUT_IRQ_STAT BIT(6)
+#define BATT_HOT_IRQ_STAT BIT(5)
+#define BATT_COLD_IRQ_STAT BIT(4)
+#define BATT_OV_IRQ_STAT BIT(3)
+#define TAPER_CHG_IRQ_STAT BIT(2)
+#define FAST_CHG_IRQ_STAT BIT(1)
+#define CHARGING_IRQ_STAT BIT(0)
+
+#define STATUS_H_REG 0x39
+#define CHARGE_TIMEOUT_STAT BIT(7)
+#define PRECHARGE_TIMEOUT_STAT BIT(6)
+#define BATT_HOT_STAT BIT(5)
+#define BATT_COLD_STAT BIT(4)
+#define BATT_OV_STAT BIT(3)
+#define TAPER_CHG_STAT BIT(2)
+#define FAST_CHG_STAT BIT(1)
+#define CHARGING_STAT_H BIT(0)
+
+#define DEV_ID_REG 0x3B
+
+#define COMMAND_B_REG 0x3C
+#define THERM_NTC_CURR_VERRIDE BIT(7)
+
+#define SMB137B_CHG_PERIOD ((HZ) * 150)
+
+#define INPUT_CURRENT_REG_DEFAULT 0xE1
+#define INPUT_CURRENT_REG_MIN 0x01
+#define COMMAND_A_REG_DEFAULT 0xA0
+#define COMMAND_A_REG_OTG_MODE 0xA2
+
+#define PIN_CTRL_REG_DEFAULT 0x00
+#define PIN_CTRL_REG_CHG_OFF 0x04
+
+#define FAST_CHG_E_STATUS 0x2
+
+#define SMB137B_DEFAULT_BATT_RATING 950
+struct smb137b_data {
+ struct i2c_client *client;
+ struct delayed_work charge_work;
+
+ bool charging;
+ int chgcurrent;
+ int cur_charging_mode;
+ int max_system_voltage;
+ int min_system_voltage;
+
+ int valid_n_gpio;
+
+ int batt_status;
+ int batt_chg_type;
+ int batt_present;
+ int min_design;
+ int max_design;
+ int batt_mah_rating;
+
+ int usb_status;
+
+ u8 dev_id_reg;
+ struct msm_hardware_charger adapter_hw_chg;
+};
+
+static unsigned int disabled;
+static DEFINE_MUTEX(init_lock);
+static unsigned int init_otg_power;
+
+enum charger_stat {
+ SMB137B_ABSENT,
+ SMB137B_PRESENT,
+ SMB137B_ENUMERATED,
+};
+
+static struct smb137b_data *usb_smb137b_chg;
+
+static int smb137b_read_reg(struct i2c_client *client, int reg,
+ u8 *val)
+{
+ s32 ret;
+ struct smb137b_data *smb137b_chg;
+
+ smb137b_chg = i2c_get_clientdata(client);
+ ret = i2c_smbus_read_byte_data(smb137b_chg->client, reg);
+ if (ret < 0) {
+ dev_err(&smb137b_chg->client->dev,
+ "i2c read fail: can't read from %02x: %d\n", reg, ret);
+ return ret;
+ } else
+ *val = ret;
+
+ return 0;
+}
+
+static int smb137b_write_reg(struct i2c_client *client, int reg,
+ u8 val)
+{
+ s32 ret;
+ struct smb137b_data *smb137b_chg;
+
+ smb137b_chg = i2c_get_clientdata(client);
+ ret = i2c_smbus_write_byte_data(smb137b_chg->client, reg, val);
+ if (ret < 0) {
+ dev_err(&smb137b_chg->client->dev,
+ "i2c write fail: can't write %02x to %02x: %d\n",
+ val, reg, ret);
+ return ret;
+ }
+ return 0;
+}
+
+static ssize_t id_reg_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct smb137b_data *smb137b_chg;
+
+ smb137b_chg = i2c_get_clientdata(to_i2c_client(dev));
+
+ return sprintf(buf, "%02x\n", smb137b_chg->dev_id_reg);
+}
+static DEVICE_ATTR(id_reg, S_IRUGO | S_IWUSR, id_reg_show, NULL);
+
+#ifdef DEBUG
+static void smb137b_dbg_print_status_regs(struct smb137b_data *smb137b_chg)
+{
+ int ret;
+ u8 temp;
+
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_A_REG, &temp);
+ dev_dbg(&smb137b_chg->client->dev, "%s A=0x%x\n", __func__, temp);
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_B_REG, &temp);
+ dev_dbg(&smb137b_chg->client->dev, "%s B=0x%x\n", __func__, temp);
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_C_REG, &temp);
+ dev_dbg(&smb137b_chg->client->dev, "%s C=0x%x\n", __func__, temp);
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_D_REG, &temp);
+ dev_dbg(&smb137b_chg->client->dev, "%s D=0x%x\n", __func__, temp);
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_E_REG, &temp);
+ dev_dbg(&smb137b_chg->client->dev, "%s E=0x%x\n", __func__, temp);
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_F_REG, &temp);
+ dev_dbg(&smb137b_chg->client->dev, "%s F=0x%x\n", __func__, temp);
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_G_REG, &temp);
+ dev_dbg(&smb137b_chg->client->dev, "%s G=0x%x\n", __func__, temp);
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_H_REG, &temp);
+ dev_dbg(&smb137b_chg->client->dev, "%s H=0x%x\n", __func__, temp);
+}
+#else
+static void smb137b_dbg_print_status_regs(struct smb137b_data *smb137b_chg)
+{
+}
+#endif
+
+static int smb137b_start_charging(struct msm_hardware_charger *hw_chg,
+ int chg_voltage, int chg_current)
+{
+ int cmd_val = COMMAND_A_REG_DEFAULT;
+ u8 temp = 0;
+ int ret = 0;
+ struct smb137b_data *smb137b_chg;
+
+ smb137b_chg = container_of(hw_chg, struct smb137b_data, adapter_hw_chg);
+ if (disabled) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s called when disabled\n", __func__);
+ goto out;
+ }
+
+ if (smb137b_chg->charging == true
+ && smb137b_chg->chgcurrent == chg_current)
+ /* we are already charging with the same current*/
+ dev_err(&smb137b_chg->client->dev,
+ "%s charge with same current %d called again\n",
+ __func__, chg_current);
+
+ dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__);
+ if (chg_current < 500)
+ cmd_val &= ~USBIN_MODE_500_BIT;
+ else if (chg_current == 500)
+ cmd_val |= USBIN_MODE_500_BIT;
+ else
+ cmd_val |= USBIN_MODE_HCMODE_BIT;
+
+ smb137b_chg->chgcurrent = chg_current;
+ smb137b_chg->cur_charging_mode = cmd_val;
+
+ /* Due to non-volatile reload feature,always enable volatile
+ * mirror writes before modifying any 00h~09h control register.
+ * Current mode needs to be programmed according to what's detected
+ * Otherwise default 100mA mode might cause VOUTL drop and fail
+ * the system in case of dead battery.
+ */
+ ret = smb137b_write_reg(smb137b_chg->client,
+ COMMAND_A_REG, cmd_val);
+ if (ret) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s couldn't write to command_reg\n", __func__);
+ goto out;
+ }
+ ret = smb137b_write_reg(smb137b_chg->client,
+ PIN_CTRL_REG, PIN_CTRL_REG_DEFAULT);
+ if (ret) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s couldn't write to pin ctrl reg\n", __func__);
+ goto out;
+ }
+ smb137b_chg->charging = true;
+ smb137b_chg->batt_status = POWER_SUPPLY_STATUS_CHARGING;
+ smb137b_chg->batt_chg_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_E_REG, &temp);
+ if (ret) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s couldn't read status e reg %d\n", __func__, ret);
+ } else {
+ if (temp & CHARGER_ERROR_STAT) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s chg error E=0x%x\n", __func__, temp);
+ smb137b_dbg_print_status_regs(smb137b_chg);
+ }
+ if (((temp & CHARGING_STAT_E) >> 1) >= FAST_CHG_E_STATUS)
+ smb137b_chg->batt_chg_type
+ = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ }
+ /*schedule charge_work to keep track of battery charging state*/
+ schedule_delayed_work(&smb137b_chg->charge_work, SMB137B_CHG_PERIOD);
+
+out:
+ return ret;
+}
+
+static int smb137b_stop_charging(struct msm_hardware_charger *hw_chg)
+{
+ int ret = 0;
+ struct smb137b_data *smb137b_chg;
+
+ smb137b_chg = container_of(hw_chg, struct smb137b_data, adapter_hw_chg);
+
+ dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__);
+ if (smb137b_chg->charging == false)
+ return 0;
+
+ smb137b_chg->charging = false;
+ smb137b_chg->batt_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ smb137b_chg->batt_chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ ret = smb137b_write_reg(smb137b_chg->client, COMMAND_A_REG,
+ smb137b_chg->cur_charging_mode);
+ if (ret) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s couldn't write to command_reg\n", __func__);
+ goto out;
+ }
+
+ ret = smb137b_write_reg(smb137b_chg->client,
+ PIN_CTRL_REG, PIN_CTRL_REG_CHG_OFF);
+ if (ret)
+ dev_err(&smb137b_chg->client->dev,
+ "%s couldn't write to pin ctrl reg\n", __func__);
+
+out:
+ return ret;
+}
+
+static int smb137b_charger_switch(struct msm_hardware_charger *hw_chg)
+{
+ struct smb137b_data *smb137b_chg;
+
+ smb137b_chg = container_of(hw_chg, struct smb137b_data, adapter_hw_chg);
+ dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__);
+ return 0;
+}
+
+static irqreturn_t smb137b_valid_handler(int irq, void *dev_id)
+{
+ int val;
+ struct smb137b_data *smb137b_chg;
+ struct i2c_client *client = dev_id;
+
+ smb137b_chg = i2c_get_clientdata(client);
+
+ pr_debug("%s Cable Detected USB inserted\n", __func__);
+ /*extra delay needed to allow CABLE_DET_N settling down and debounce
+ before trying to sample its correct value*/
+ usleep_range(1000, 1200);
+ val = gpio_get_value_cansleep(smb137b_chg->valid_n_gpio);
+ if (val < 0) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s gpio_get_value failed for %d ret=%d\n", __func__,
+ smb137b_chg->valid_n_gpio, val);
+ goto err;
+ }
+ dev_dbg(&smb137b_chg->client->dev, "%s val=%d\n", __func__, val);
+
+ if (val) {
+ if (smb137b_chg->usb_status != SMB137B_ABSENT) {
+ smb137b_chg->usb_status = SMB137B_ABSENT;
+ msm_charger_notify_event(&smb137b_chg->adapter_hw_chg,
+ CHG_REMOVED_EVENT);
+ }
+ } else {
+ if (smb137b_chg->usb_status == SMB137B_ABSENT) {
+ smb137b_chg->usb_status = SMB137B_PRESENT;
+ msm_charger_notify_event(&smb137b_chg->adapter_hw_chg,
+ CHG_INSERTED_EVENT);
+ }
+ }
+err:
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static struct dentry *dent;
+static int debug_fs_otg;
+static int otg_get(void *data, u64 *value)
+{
+ *value = debug_fs_otg;
+ return 0;
+}
+static int otg_set(void *data, u64 value)
+{
+ smb137b_otg_power(debug_fs_otg);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(smb137b_otg_fops, otg_get, otg_set, "%llu\n");
+
+static void smb137b_create_debugfs_entries(struct smb137b_data *smb137b_chg)
+{
+ dent = debugfs_create_dir("smb137b", NULL);
+ if (dent) {
+ debugfs_create_file("otg", 0644, dent, NULL, &smb137b_otg_fops);
+ }
+}
+static void smb137b_destroy_debugfs_entries(void)
+{
+ if (dent)
+ debugfs_remove_recursive(dent);
+}
+#else
+static void smb137b_create_debugfs_entries(struct smb137b_data *smb137b_chg)
+{
+}
+static void smb137b_destroy_debugfs_entries(void)
+{
+}
+#endif
+
+static int set_disable_status_param(const char *val, struct kernel_param *kp)
+{
+ int ret;
+
+ ret = param_set_int(val, kp);
+ if (ret)
+ return ret;
+
+ if (usb_smb137b_chg && disabled)
+ msm_charger_notify_event(&usb_smb137b_chg->adapter_hw_chg,
+ CHG_DONE_EVENT);
+
+ pr_debug("%s disabled =%d\n", __func__, disabled);
+ return 0;
+}
+module_param_call(disabled, set_disable_status_param, param_get_uint,
+ &disabled, 0644);
+static void smb137b_charge_sm(struct work_struct *smb137b_work)
+{
+ int ret;
+ struct smb137b_data *smb137b_chg;
+ u8 temp = 0;
+ int notify_batt_changed = 0;
+
+ smb137b_chg = container_of(smb137b_work, struct smb137b_data,
+ charge_work.work);
+
+ /*if not charging, exit smb137b charging state transition*/
+ if (!smb137b_chg->charging)
+ return;
+
+ dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__);
+
+ ret = smb137b_read_reg(smb137b_chg->client, STATUS_F_REG, &temp);
+ if (ret) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s couldn't read status f reg %d\n", __func__, ret);
+ goto out;
+ }
+ if (smb137b_chg->batt_present != !(temp & BATT_PRESENT_STAT)) {
+ smb137b_chg->batt_present = !(temp & BATT_PRESENT_STAT);
+ notify_batt_changed = 1;
+ }
+
+ if (!smb137b_chg->batt_present)
+ smb137b_chg->batt_chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ if (!smb137b_chg->batt_present && smb137b_chg->charging)
+ msm_charger_notify_event(&smb137b_chg->adapter_hw_chg,
+ CHG_DONE_EVENT);
+
+ if (smb137b_chg->batt_present
+ && smb137b_chg->charging
+ && smb137b_chg->batt_chg_type
+ != POWER_SUPPLY_CHARGE_TYPE_FAST) {
+ ret = smb137b_read_reg(smb137b_chg->client,
+ STATUS_E_REG, &temp);
+ if (ret) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s couldn't read cntrl reg\n", __func__);
+ goto out;
+
+ } else {
+ if (temp & CHARGER_ERROR_STAT) {
+ dev_err(&smb137b_chg->client->dev,
+ "%s error E=0x%x\n", __func__, temp);
+ smb137b_dbg_print_status_regs(smb137b_chg);
+ }
+ if (((temp & CHARGING_STAT_E) >> 1)
+ >= FAST_CHG_E_STATUS) {
+ smb137b_chg->batt_chg_type
+ = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ notify_batt_changed = 1;
+ msm_charger_notify_event(
+ &smb137b_chg->adapter_hw_chg,
+ CHG_BATT_BEGIN_FAST_CHARGING);
+ } else {
+ smb137b_chg->batt_chg_type
+ = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ }
+ }
+ }
+
+out:
+ schedule_delayed_work(&smb137b_chg->charge_work,
+ SMB137B_CHG_PERIOD);
+}
+
+static void __smb137b_otg_power(int on)
+{
+ int ret;
+
+ if (on) {
+ ret = smb137b_write_reg(usb_smb137b_chg->client,
+ PIN_CTRL_REG, PIN_CTRL_REG_CHG_OFF);
+ if (ret) {
+ pr_err("%s turning off charging in pin_ctrl err=%d\n",
+ __func__, ret);
+ /*
+ * don't change the command register if charging in
+ * pin control cannot be turned off
+ */
+ return;
+ }
+
+ ret = smb137b_write_reg(usb_smb137b_chg->client,
+ COMMAND_A_REG, COMMAND_A_REG_OTG_MODE);
+ if (ret)
+ pr_err("%s failed turning on OTG mode ret=%d\n",
+ __func__, ret);
+ } else {
+ ret = smb137b_write_reg(usb_smb137b_chg->client,
+ COMMAND_A_REG, COMMAND_A_REG_DEFAULT);
+ if (ret)
+ pr_err("%s failed turning off OTG mode ret=%d\n",
+ __func__, ret);
+ ret = smb137b_write_reg(usb_smb137b_chg->client,
+ PIN_CTRL_REG, PIN_CTRL_REG_DEFAULT);
+ if (ret)
+ pr_err("%s failed writing to pn_ctrl ret=%d\n",
+ __func__, ret);
+ }
+}
+static int __devinit smb137b_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct smb137b_platform_data *pdata;
+ struct smb137b_data *smb137b_chg;
+ int ret = 0;
+
+ pdata = client->dev.platform_data;
+
+ if (pdata == NULL) {
+ dev_err(&client->dev, "%s no platform data\n", __func__);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ smb137b_chg = kzalloc(sizeof(*smb137b_chg), GFP_KERNEL);
+ if (!smb137b_chg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ INIT_DELAYED_WORK(&smb137b_chg->charge_work, smb137b_charge_sm);
+ smb137b_chg->client = client;
+ smb137b_chg->valid_n_gpio = pdata->valid_n_gpio;
+ smb137b_chg->batt_mah_rating = pdata->batt_mah_rating;
+ if (smb137b_chg->batt_mah_rating == 0)
+ smb137b_chg->batt_mah_rating = SMB137B_DEFAULT_BATT_RATING;
+
+ /*It supports USB-WALL charger and PC USB charger */
+ smb137b_chg->adapter_hw_chg.type = CHG_TYPE_USB;
+ smb137b_chg->adapter_hw_chg.rating = pdata->batt_mah_rating;
+ smb137b_chg->adapter_hw_chg.name = "smb137b-charger";
+ smb137b_chg->adapter_hw_chg.start_charging = smb137b_start_charging;
+ smb137b_chg->adapter_hw_chg.stop_charging = smb137b_stop_charging;
+ smb137b_chg->adapter_hw_chg.charging_switched = smb137b_charger_switch;
+
+ if (pdata->chg_detection_config)
+ ret = pdata->chg_detection_config();
+ if (ret) {
+ dev_err(&client->dev, "%s valid config failed ret=%d\n",
+ __func__, ret);
+ goto free_smb137b_chg;
+ }
+
+ ret = gpio_request(pdata->valid_n_gpio, "smb137b_charger_valid");
+ if (ret) {
+ dev_err(&client->dev, "%s gpio_request failed for %d ret=%d\n",
+ __func__, pdata->valid_n_gpio, ret);
+ goto free_smb137b_chg;
+ }
+
+ i2c_set_clientdata(client, smb137b_chg);
+
+ ret = msm_charger_register(&smb137b_chg->adapter_hw_chg);
+ if (ret) {
+ dev_err(&client->dev, "%s msm_charger_register\
+ failed for ret=%d\n", __func__, ret);
+ goto free_valid_gpio;
+ }
+
+ ret = irq_set_irq_wake(client->irq, 1);
+ if (ret) {
+ dev_err(&client->dev, "%s failed for irq_set_irq_wake %d ret =%d\n",
+ __func__, client->irq, ret);
+ goto unregister_charger;
+ }
+
+ ret = request_threaded_irq(client->irq, NULL,
+ smb137b_valid_handler,
+ IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
+ "smb137b_charger_valid", client);
+ if (ret) {
+ dev_err(&client->dev,
+ "%s request_threaded_irq failed for %d ret =%d\n",
+ __func__, client->irq, ret);
+ goto disable_irq_wake;
+ }
+
+ ret = gpio_get_value_cansleep(smb137b_chg->valid_n_gpio);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "%s gpio_get_value failed for %d ret=%d\n", __func__,
+ pdata->valid_n_gpio, ret);
+ /* assume absent */
+ ret = 1;
+ }
+ if (!ret) {
+ msm_charger_notify_event(&smb137b_chg->adapter_hw_chg,
+ CHG_INSERTED_EVENT);
+ smb137b_chg->usb_status = SMB137B_PRESENT;
+ }
+
+ ret = smb137b_read_reg(smb137b_chg->client, DEV_ID_REG,
+ &smb137b_chg->dev_id_reg);
+
+ ret = device_create_file(&smb137b_chg->client->dev, &dev_attr_id_reg);
+
+ /* TODO read min_design and max_design from chip registers */
+ smb137b_chg->min_design = 3200;
+ smb137b_chg->max_design = 4200;
+
+ smb137b_chg->batt_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ smb137b_chg->batt_chg_type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ device_init_wakeup(&client->dev, 1);
+
+ mutex_lock(&init_lock);
+ usb_smb137b_chg = smb137b_chg;
+ if (init_otg_power)
+ __smb137b_otg_power(init_otg_power);
+ mutex_unlock(&init_lock);
+
+ smb137b_create_debugfs_entries(smb137b_chg);
+ dev_dbg(&client->dev,
+ "%s OK device_id = %x chg_state=%d\n", __func__,
+ smb137b_chg->dev_id_reg, smb137b_chg->usb_status);
+ return 0;
+
+disable_irq_wake:
+ irq_set_irq_wake(client->irq, 0);
+unregister_charger:
+ msm_charger_unregister(&smb137b_chg->adapter_hw_chg);
+free_valid_gpio:
+ gpio_free(pdata->valid_n_gpio);
+free_smb137b_chg:
+ kfree(smb137b_chg);
+out:
+ return ret;
+}
+
+void smb137b_otg_power(int on)
+{
+ pr_debug("%s Enter on=%d\n", __func__, on);
+
+ mutex_lock(&init_lock);
+ if (!usb_smb137b_chg) {
+ init_otg_power = !!on;
+ pr_warning("%s called when not initialized\n", __func__);
+ mutex_unlock(&init_lock);
+ return;
+ }
+ __smb137b_otg_power(on);
+ mutex_unlock(&init_lock);
+}
+
+static int __devexit smb137b_remove(struct i2c_client *client)
+{
+ const struct smb137b_platform_data *pdata;
+ struct smb137b_data *smb137b_chg = i2c_get_clientdata(client);
+
+ pdata = client->dev.platform_data;
+ device_init_wakeup(&client->dev, 0);
+ irq_set_irq_wake(client->irq, 0);
+ free_irq(client->irq, client);
+ gpio_free(pdata->valid_n_gpio);
+ cancel_delayed_work_sync(&smb137b_chg->charge_work);
+
+ msm_charger_notify_event(&smb137b_chg->adapter_hw_chg,
+ CHG_REMOVED_EVENT);
+ msm_charger_unregister(&smb137b_chg->adapter_hw_chg);
+ smb137b_destroy_debugfs_entries();
+ kfree(smb137b_chg);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int smb137b_suspend(struct device *dev)
+{
+ struct smb137b_data *smb137b_chg = dev_get_drvdata(dev);
+
+ dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__);
+ if (smb137b_chg->charging)
+ return -EBUSY;
+ return 0;
+}
+
+static int smb137b_resume(struct device *dev)
+{
+ struct smb137b_data *smb137b_chg = dev_get_drvdata(dev);
+
+ dev_dbg(&smb137b_chg->client->dev, "%s\n", __func__);
+ return 0;
+}
+
+static const struct dev_pm_ops smb137b_pm_ops = {
+ .suspend = smb137b_suspend,
+ .resume = smb137b_resume,
+};
+#endif
+
+static const struct i2c_device_id smb137b_id[] = {
+ {"smb137b", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, smb137b_id);
+
+static struct i2c_driver smb137b_driver = {
+ .driver = {
+ .name = "smb137b",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &smb137b_pm_ops,
+#endif
+ },
+ .probe = smb137b_probe,
+ .remove = __devexit_p(smb137b_remove),
+ .id_table = smb137b_id,
+};
+
+static int __init smb137b_init(void)
+{
+ return i2c_add_driver(&smb137b_driver);
+}
+module_init(smb137b_init);
+
+static void __exit smb137b_exit(void)
+{
+ return i2c_del_driver(&smb137b_driver);
+}
+module_exit(smb137b_exit);
+
+MODULE_AUTHOR("Abhijeet Dharmapurikar <adharmap@codeaurora.org>");
+MODULE_DESCRIPTION("Driver for SMB137B Charger chip");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("i2c:smb137b");
diff --git a/drivers/power/smb349.c b/drivers/power/smb349.c
new file mode 100644
index 0000000..4c07285
--- /dev/null
+++ b/drivers/power/smb349.c
@@ -0,0 +1,700 @@
+/* Copyright (c) 2012 Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/i2c/smb349.h>
+#include <linux/power_supply.h>
+
+#define SMB349_MASK(BITS, POS) ((unsigned char)(((1 << BITS) - 1) << POS))
+
+/* Register definitions */
+#define CHG_CURRENT_REG 0x00
+#define CHG_OTHER_CURRENT_REG 0x01
+#define VAR_FUNC_REG 0x02
+#define FLOAT_VOLTAGE_REG 0x03
+#define CHG_CTRL_REG 0x04
+#define STAT_TIMER_REG 0x05
+#define PIN_ENABLE_CTRL_REG 0x06
+#define THERM_CTRL_A_REG 0x07
+#define SYSOK_USB3_SELECT_REG 0x08
+#define CTRL_FUNCTIONS_REG 0x09
+#define OTG_TLIM_THERM_CNTRL_REG 0x0A
+#define HARD_SOFT_LIMIT_CELL_TEMP_MONITOR_REG 0x0B
+#define FAULT_IRQ_REG 0x0C
+#define STATUS_IRQ_REG 0x0D
+#define SYSOK_REG 0x0E
+#define CMD_A_REG 0x30
+#define CMD_B_REG 0x31
+#define CMD_C_REG 0x33
+#define IRQ_A_REG 0x35
+#define IRQ_B_REG 0x36
+#define IRQ_C_REG 0x37
+#define IRQ_D_REG 0x38
+#define IRQ_E_REG 0x39
+#define IRQ_F_REG 0x3A
+#define STATUS_A_REG 0x3B
+#define STATUS_B_REG 0x3C
+#define STATUS_C_REG 0x3D
+#define STATUS_D_REG 0x3E
+#define STATUS_E_REG 0x3F
+
+/* Status bits and masks */
+#define CHG_STATUS_MASK SMB349_MASK(2, 1)
+#define CHG_ENABLE_STATUS_BIT BIT(0)
+
+/* Control bits and masks */
+#define FAST_CHG_CURRENT_MASK SMB349_MASK(4, 4)
+#define AC_INPUT_CURRENT_LIMIT_MASK SMB349_MASK(4, 0)
+#define PRE_CHG_CURRENT_MASK SMB349_MASK(3, 5)
+#define TERMINATION_CURRENT_MASK SMB349_MASK(3, 2)
+#define PRE_CHG_TO_FAST_CHG_THRESH_MASK SMB349_MASK(2, 6)
+#define FLOAT_VOLTAGE_MASK SMB349_MASK(6, 0)
+#define CHG_ENABLE_BIT BIT(1)
+#define VOLATILE_W_PERM_BIT BIT(7)
+#define USB_SELECTION_BIT BIT(1)
+#define SYSTEM_FET_ENABLE_BIT BIT(7)
+#define AUTOMATIC_INPUT_CURR_LIMIT_BIT BIT(4)
+#define AUTOMATIC_POWER_SOURCE_DETECTION_BIT BIT(2)
+#define BATT_OV_END_CHG_BIT BIT(1)
+#define VCHG_FUNCTION BIT(0)
+#define CURR_TERM_END_CHG_BIT BIT(6)
+
+struct smb349_struct {
+ struct i2c_client *client;
+ bool charging;
+ bool present;
+ int chg_current_ma;
+
+ int en_n_gpio;
+ int chg_susp_gpio;
+ struct dentry *dent;
+ spinlock_t lock;
+ struct work_struct hwinit_work;
+
+ struct power_supply dc_psy;
+};
+
+struct chg_ma_limit_entry {
+ int fast_chg_ma_limit;
+ int ac_input_ma_limit;
+ u8 chg_current_value;
+};
+
+static struct smb349_struct *the_smb349_chg;
+
+static int smb349_read_reg(struct i2c_client *client, int reg,
+ u8 *val)
+{
+ s32 ret;
+ struct smb349_struct *smb349_chg;
+
+ smb349_chg = i2c_get_clientdata(client);
+ ret = i2c_smbus_read_byte_data(smb349_chg->client, reg);
+ if (ret < 0) {
+ dev_err(&smb349_chg->client->dev,
+ "i2c read fail: can't read from %02x: %d\n", reg, ret);
+ return ret;
+ } else {
+ *val = ret;
+ }
+
+ return 0;
+}
+
+static int smb349_write_reg(struct i2c_client *client, int reg,
+ u8 val)
+{
+ s32 ret;
+ struct smb349_struct *smb349_chg;
+
+ smb349_chg = i2c_get_clientdata(client);
+ ret = i2c_smbus_write_byte_data(smb349_chg->client, reg, val);
+ if (ret < 0) {
+ dev_err(&smb349_chg->client->dev,
+ "i2c write fail: can't write %02x to %02x: %d\n",
+ val, reg, ret);
+ return ret;
+ }
+ return 0;
+}
+
+static int smb349_masked_write(struct i2c_client *client, int reg,
+ u8 mask, u8 val)
+{
+ s32 rc;
+ u8 temp;
+
+ rc = smb349_read_reg(client, reg, &temp);
+ if (rc) {
+ pr_err("smb349_read_reg failed: reg=%03X, rc=%d\n", reg, rc);
+ return rc;
+ }
+ temp &= ~mask;
+ temp |= val & mask;
+ rc = smb349_write_reg(client, reg, temp);
+ if (rc) {
+ pr_err("smb349_write failed: reg=%03X, rc=%d\n", reg, rc);
+ return rc;
+ }
+ return 0;
+}
+
+static enum power_supply_property pm_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+};
+
+static char *pm_power_supplied_to[] = {
+ "battery",
+};
+
+static int get_prop_charge_type(struct smb349_struct *smb349_chg)
+{
+ if (smb349_chg->charging)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+static int pm_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smb349_struct *smb349_chg = container_of(psy,
+ struct smb349_struct,
+ dc_psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = smb349_chg->chg_current_ma;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = (int)smb349_chg->present;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = get_prop_charge_type(smb349_chg);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#define SMB349_FAST_CHG_MIN_MA 1000
+#define SMB349_FAST_CHG_STEP_MA 200
+#define SMB349_FAST_CHG_MAX_MA 4000
+#define SMB349_FAST_CHG_SHIFT 4
+static int chg_current_set(struct smb349_struct *smb349_chg)
+{
+ u8 temp;
+
+ if ((smb349_chg->chg_current_ma < SMB349_FAST_CHG_MIN_MA) ||
+ (smb349_chg->chg_current_ma > SMB349_FAST_CHG_MAX_MA)) {
+ pr_err("bad mA=%d asked to set\n", smb349_chg->chg_current_ma);
+ return -EINVAL;
+ }
+
+ temp = (smb349_chg->chg_current_ma - SMB349_FAST_CHG_MIN_MA)
+ / SMB349_FAST_CHG_STEP_MA;
+
+ temp = temp << SMB349_FAST_CHG_SHIFT;
+ pr_debug("fastchg limit=%d setting %02x\n",
+ smb349_chg->chg_current_ma, temp);
+ return smb349_masked_write(smb349_chg->client, CHG_CURRENT_REG,
+ FAST_CHG_CURRENT_MASK, temp);
+}
+
+static int set_reg(void *data, u64 val)
+{
+ int addr = (int)data;
+ int ret;
+ u8 temp;
+
+ temp = (u16) val;
+ ret = smb349_write_reg(the_smb349_chg->client, addr, temp);
+
+ if (ret) {
+ pr_err("smb349_write_reg to %x value =%d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+ return 0;
+}
+static int get_reg(void *data, u64 *val)
+{
+ int addr = (int)data;
+ int ret;
+ u8 temp;
+
+ ret = smb349_read_reg(the_smb349_chg->client, addr, &temp);
+ if (ret) {
+ pr_err("smb349_read_reg to %x value =%d errored = %d\n",
+ addr, temp, ret);
+ return -EAGAIN;
+ }
+
+ *val = temp;
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n");
+
+static void create_debugfs_entries(struct smb349_struct *smb349_chg)
+{
+ struct dentry *file;
+ smb349_chg->dent = debugfs_create_dir(SMB349_NAME, NULL);
+ if (IS_ERR(smb349_chg->dent)) {
+ pr_err("smb349 driver couldn't create debugfs dir\n");
+ return;
+ }
+
+ file = debugfs_create_file("CHG_CURRENT_REG", 0644, smb349_chg->dent,
+ (void *) CHG_CURRENT_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("CHG_OTHER_CURRENT_REG", 0644,
+ smb349_chg->dent, (void *) CHG_OTHER_CURRENT_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("VAR_FUNC_REG", 0644, smb349_chg->dent,
+ (void *) VAR_FUNC_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("FLOAT_VOLTAGE_REG", 0644, smb349_chg->dent,
+ (void *) FLOAT_VOLTAGE_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("CHG_CTRL_REG", 0644, smb349_chg->dent,
+ (void *) CHG_CTRL_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("STAT_TIMER_REG", 0644, smb349_chg->dent,
+ (void *) STAT_TIMER_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("PIN_ENABLE_CTRL_REG", 0644,
+ smb349_chg->dent, (void *) PIN_ENABLE_CTRL_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("PIN_ENABLE_CTRL_REG", 0644,
+ smb349_chg->dent, (void *) PIN_ENABLE_CTRL_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("PIN_ENABLE_CTRL_REG", 0644,
+ smb349_chg->dent, (void *) PIN_ENABLE_CTRL_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("THERM_CTRL_A_REG", 0644, smb349_chg->dent,
+ (void *) THERM_CTRL_A_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("SYSOK_USB3_SELECT_REG", 0644,
+ smb349_chg->dent, (void *) SYSOK_USB3_SELECT_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("CTRL_FUNCTIONS_REG", 0644,
+ smb349_chg->dent, (void *) CTRL_FUNCTIONS_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("OTG_TLIM_THERM_CNTRL_REG", 0644,
+ smb349_chg->dent, (void *) OTG_TLIM_THERM_CNTRL_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("HARD_SOFT_LIMIT_CELL_TEMP_MONITOR_REG",
+ 0644, smb349_chg->dent,
+ (void *) HARD_SOFT_LIMIT_CELL_TEMP_MONITOR_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("SYSOK_REG", 0644, smb349_chg->dent,
+ (void *) SYSOK_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("CMD_A_REG", 0644, smb349_chg->dent,
+ (void *) CMD_A_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("CMD_B_REG", 0644, smb349_chg->dent,
+ (void *) CMD_B_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("CMD_C_REG", 0644, smb349_chg->dent,
+ (void *) CMD_C_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("STATUS_A_REG", 0644, smb349_chg->dent,
+ (void *) STATUS_A_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("STATUS_B_REG", 0644, smb349_chg->dent,
+ (void *) STATUS_B_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("STATUS_C_REG", 0644, smb349_chg->dent,
+ (void *) STATUS_C_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("STATUS_D_REG", 0644, smb349_chg->dent,
+ (void *) STATUS_D_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+ file = debugfs_create_file("STATUS_E_REG", 0644, smb349_chg->dent,
+ (void *) STATUS_E_REG, ®_fops);
+ if (IS_ERR(file)) {
+ pr_err("smb349 driver couldn't create debugfs files\n");
+ return;
+ }
+}
+
+static void remove_debugfs_entries(struct smb349_struct *smb349_chg)
+{
+ if (smb349_chg->dent)
+ debugfs_remove_recursive(smb349_chg->dent);
+}
+
+static int smb349_hwinit(struct smb349_struct *smb349_chg)
+{
+ int ret;
+
+ ret = smb349_write_reg(smb349_chg->client, CMD_A_REG,
+ VOLATILE_W_PERM_BIT);
+ if (ret) {
+ pr_err("Failed to set VOLATILE_W_PERM_BIT rc=%d\n", ret);
+ return ret;
+ }
+
+ ret = smb349_masked_write(smb349_chg->client, CHG_CTRL_REG,
+ CURR_TERM_END_CHG_BIT, CURR_TERM_END_CHG_BIT);
+ if (ret) {
+ pr_err("Failed to set CURR_TERM_END_CHG_BIT rc=%d\n", ret);
+ return ret;
+ }
+
+ ret = chg_current_set(smb349_chg);
+ if (ret) {
+ pr_err("Failed to set FAST_CHG_CURRENT rc=%d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int smb349_stop_charging(struct smb349_struct *smb349_chg)
+{
+ unsigned long flags;
+
+ if (smb349_chg->charging)
+ gpio_set_value_cansleep(smb349_chg->en_n_gpio, 0);
+
+ spin_lock_irqsave(&smb349_chg->lock, flags);
+ pr_debug("stop charging %d\n", smb349_chg->charging);
+ smb349_chg->charging = 0;
+ spin_unlock_irqrestore(&smb349_chg->lock, flags);
+ power_supply_changed(&smb349_chg->dc_psy);
+ return 0;
+}
+
+static int smb349_start_charging(struct smb349_struct *smb349_chg)
+{
+ unsigned long flags;
+ int rc;
+
+ rc = 0;
+ if (!smb349_chg->charging) {
+ gpio_set_value_cansleep(smb349_chg->en_n_gpio, 1);
+ /*
+ * Write non-default values, charger chip reloads from
+ * non-volatile memory if it was in suspend mode
+ *
+ */
+ rc = schedule_work(&smb349_chg->hwinit_work);
+ }
+
+ spin_lock_irqsave(&smb349_chg->lock, flags);
+ pr_debug("start charging %d\n", smb349_chg->charging);
+ smb349_chg->charging = 1;
+ spin_unlock_irqrestore(&smb349_chg->lock, flags);
+ power_supply_changed(&smb349_chg->dc_psy);
+ return rc;
+}
+
+static int pm_power_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smb349_struct *smb349_chg = container_of(psy,
+ struct smb349_struct,
+ dc_psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (val->intval) {
+ smb349_chg->present = val->intval;
+ } else {
+ smb349_chg->present = 0;
+ if (smb349_chg->charging)
+ return smb349_stop_charging(smb349_chg);
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ if (val->intval) {
+ if (smb349_chg->chg_current_ma != val->intval)
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (val->intval && smb349_chg->present) {
+ if (val->intval == POWER_SUPPLY_CHARGE_TYPE_FAST)
+ return smb349_start_charging(smb349_chg);
+ if (val->intval == POWER_SUPPLY_CHARGE_TYPE_NONE)
+ return smb349_stop_charging(smb349_chg);
+ } else {
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ power_supply_changed(&smb349_chg->dc_psy);
+ return 0;
+}
+
+static void hwinit_worker(struct work_struct *work)
+{
+ int ret;
+ struct smb349_struct *smb349_chg = container_of(work,
+ struct smb349_struct, hwinit_work);
+
+ ret = smb349_hwinit(smb349_chg);
+ if (ret)
+ pr_err("Failed to re-initilaze registers\n");
+}
+
+static int __devinit smb349_init_ext_chg(struct smb349_struct *smb349_chg)
+{
+ int ret;
+
+ smb349_chg->dc_psy.name = "dc";
+ smb349_chg->dc_psy.type = POWER_SUPPLY_TYPE_MAINS;
+ smb349_chg->dc_psy.supplied_to = pm_power_supplied_to;
+ smb349_chg->dc_psy.num_supplicants = ARRAY_SIZE(pm_power_supplied_to);
+ smb349_chg->dc_psy.properties = pm_power_props;
+ smb349_chg->dc_psy.num_properties = ARRAY_SIZE(pm_power_props);
+ smb349_chg->dc_psy.get_property = pm_power_get_property;
+ smb349_chg->dc_psy.set_property = pm_power_set_property;
+
+ ret = power_supply_register(&smb349_chg->client->dev,
+ &smb349_chg->dc_psy);
+ if (ret) {
+ pr_err("failed to register power_supply. ret=%d.\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __devinit smb349_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ const struct smb349_platform_data *pdata;
+ struct smb349_struct *smb349_chg;
+ int ret = 0;
+
+ pdata = client->dev.platform_data;
+
+ if (pdata == NULL) {
+ dev_err(&client->dev, "%s no platform data\n", __func__);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA)) {
+ ret = -EIO;
+ goto out;
+ }
+
+ smb349_chg = kzalloc(sizeof(*smb349_chg), GFP_KERNEL);
+ if (!smb349_chg) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ smb349_chg->client = client;
+ smb349_chg->chg_current_ma = pdata->chg_current_ma;
+ ret = gpio_request(pdata->chg_susp_gpio, "smb349_suspend");
+ if (ret) {
+ dev_err(&client->dev, "%s gpio_request failed for %d ret=%d\n",
+ __func__, pdata->chg_susp_gpio, ret);
+ goto free_smb349_chg;
+ }
+ smb349_chg->chg_susp_gpio = pdata->chg_susp_gpio;
+
+ ret = gpio_request(pdata->en_n_gpio, "smb349_charger_enable");
+ if (ret) {
+ dev_err(&client->dev, "%s gpio_request failed for %d ret=%d\n",
+ __func__, pdata->en_n_gpio, ret);
+ goto chg_susp_gpio_fail;
+ }
+ smb349_chg->en_n_gpio = pdata->en_n_gpio;
+
+ i2c_set_clientdata(client, smb349_chg);
+
+ ret = smb349_hwinit(smb349_chg);
+ if (ret)
+ goto free_smb349_chg;
+
+ ret = smb349_init_ext_chg(smb349_chg);
+ if (ret)
+ goto chg_en_gpio_fail;
+
+ the_smb349_chg = smb349_chg;
+
+ create_debugfs_entries(smb349_chg);
+ INIT_WORK(&smb349_chg->hwinit_work, hwinit_worker);
+
+ pr_info("OK connector present = %d\n", smb349_chg->present);
+ return 0;
+
+chg_en_gpio_fail:
+ gpio_free(smb349_chg->en_n_gpio);
+chg_susp_gpio_fail:
+ gpio_free(smb349_chg->chg_susp_gpio);
+free_smb349_chg:
+ kfree(smb349_chg);
+out:
+ return ret;
+}
+
+static int __devexit smb349_remove(struct i2c_client *client)
+{
+ const struct smb349_platform_data *pdata;
+ struct smb349_struct *smb349_chg = i2c_get_clientdata(client);
+
+ flush_work(&smb349_chg->hwinit_work);
+ pdata = client->dev.platform_data;
+ power_supply_unregister(&smb349_chg->dc_psy);
+ gpio_free(pdata->en_n_gpio);
+ gpio_free(pdata->chg_susp_gpio);
+ remove_debugfs_entries(smb349_chg);
+ kfree(smb349_chg);
+ return 0;
+}
+
+static int smb349_suspend(struct device *dev)
+{
+ struct smb349_struct *smb349_chg = dev_get_drvdata(dev);
+
+ pr_debug("suspend\n");
+ if (smb349_chg->charging)
+ return -EBUSY;
+ return 0;
+}
+
+static int smb349_resume(struct device *dev)
+{
+ pr_debug("resume\n");
+
+ return 0;
+}
+
+static const struct dev_pm_ops smb349_pm_ops = {
+ .suspend = smb349_suspend,
+ .resume = smb349_resume,
+};
+
+static const struct i2c_device_id smb349_id[] = {
+ {SMB349_NAME, 0},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, smb349_id);
+
+static struct i2c_driver smb349_driver = {
+ .driver = {
+ .name = SMB349_NAME,
+ .owner = THIS_MODULE,
+ .pm = &smb349_pm_ops,
+ },
+ .probe = smb349_probe,
+ .remove = __devexit_p(smb349_remove),
+ .id_table = smb349_id,
+};
+
+static int __init smb349_init(void)
+{
+ return i2c_add_driver(&smb349_driver);
+}
+module_init(smb349_init);
+
+static void __exit smb349_exit(void)
+{
+ return i2c_del_driver(&smb349_driver);
+}
+module_exit(smb349_exit);
+
+MODULE_DESCRIPTION("Driver for SMB349 charger chip");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("i2c:" SMB349_NAME);