Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index e57b50b..179a4ac 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -235,4 +235,106 @@
This driver can be build as a module. If so, the module will be
called gpio-charger.
+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 PMIC8058_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
+ 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 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 PM8921_BMS
+ tristate "PM8921 Battery Monitoring System driver"
+ depends on MFD_PM8921_CORE
+ help
+ Say Y here to enable support for pm8921 chip bms subdevice
+
endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 009a90f..f61c88a 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -36,3 +36,14 @@
obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-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_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_PM8921_BMS) += pm8921-bms.o
+obj-$(CONFIG_PM8921_CHARGER) += pm8921-charger.o
diff --git a/drivers/power/bq27520_fuelgauger.c b/drivers/power/bq27520_fuelgauger.c
new file mode 100644
index 0000000..284b134
--- /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_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();
+ msm_battery_gauge_register(&bq27520_batt_gauge);
+
+ 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..4954a45
--- /dev/null
+++ b/drivers/power/isl9519q.c
@@ -0,0 +1,517 @@
+/* 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/msm-charger.h>
+#include <linux/slab.h>
+#include <linux/i2c/isl9519.h>
+#include <linux/msm_adc.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 ((HZ) * 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;
+};
+
+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;
+
+ return 0;
+}
+
+static int isl9519q_write_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_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;
+}
+
+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("%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;
+ }
+ ret = wait_for_completion_interruptible(&conv_complete_evt);
+ if (ret) {
+ pr_err("%s: wait interrupted channel %d ret=%d\n",
+ __func__, channel, ret);
+ goto out;
+ }
+ 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 = (int)adc_chan_result.measurement;
+
+ pr_debug("%s: done for %d\n", __func__, channel);
+ return adc_chan_result.physical;
+out:
+ *mv_reading = 0;
+ pr_debug("%s: done with error for %d\n", __func__, channel);
+ return -EINVAL;
+
+}
+
+static void isl9519q_charge(struct work_struct *isl9519_work)
+{
+ u16 temp;
+ int ret;
+ struct isl9519q_struct *isl_chg;
+ int isl_charger_current;
+ int mv_reading;
+
+ 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) {
+ isl_charger_current = 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__, isl_charger_current);
+ if (isl_charger_current >= 0
+ && isl_charger_current <= isl_chg->term_current) {
+ msm_charger_notify_event(
+ &isl_chg->adapter_hw_chg,
+ CHG_DONE_EVENT);
+ }
+ isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG,
+ isl_chg->chgcurrent);
+ ret = isl9519q_read_reg(isl_chg->client, CONTROL_REG, &temp);
+ if (!ret) {
+ if (!(temp & TRCKL_CHG_STATUS_BIT))
+ msm_charger_notify_event(
+ &isl_chg->adapter_hw_chg,
+ CHG_BATT_BEGIN_FAST_CHARGING);
+ } else {
+ dev_err(&isl_chg->client->dev,
+ "%s couldnt read cntrl reg\n", __func__);
+ }
+ schedule_delayed_work(&isl_chg->charge_work,
+ ISL9519_CHG_PERIOD);
+ }
+}
+
+static int isl9519q_start_charging(struct msm_hardware_charger *hw_chg,
+ int chg_voltage, int chg_current)
+{
+ struct isl9519q_struct *isl_chg;
+ int ret = 0;
+
+ isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
+ if (isl_chg->charging)
+ /* we are already charging */
+ return 0;
+
+ dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
+
+ ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG,
+ isl_chg->chgcurrent);
+ if (ret) {
+ dev_err(&isl_chg->client->dev,
+ "%s coulnt write to current_reg\n", __func__);
+ goto out;
+ }
+
+ dev_dbg(&isl_chg->client->dev, "%s starting timed work\n",
+ __func__);
+ schedule_delayed_work(&isl_chg->charge_work,
+ ISL9519_CHG_PERIOD);
+ isl_chg->charging = true;
+
+out:
+ return ret;
+}
+
+static int isl9519q_stop_charging(struct msm_hardware_charger *hw_chg)
+{
+ struct isl9519q_struct *isl_chg;
+ int ret = 0;
+
+ isl_chg = container_of(hw_chg, struct isl9519q_struct, adapter_hw_chg);
+ if (!(isl_chg->charging))
+ /* we arent charging */
+ return 0;
+
+ dev_dbg(&isl_chg->client->dev, "%s\n", __func__);
+
+ ret = isl9519q_write_reg(isl_chg->client, CHG_CURRENT_REG, 0);
+ if (ret) {
+ dev_err(&isl_chg->client->dev,
+ "%s coulnt write to current_reg\n", __func__);
+ goto out;
+ }
+
+ isl_chg->charging = false;
+ cancel_delayed_work(&isl_chg->charge_work);
+out:
+ return ret;
+}
+
+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;
+}
+
+#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_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;
+
+ 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;
+ }
+
+ INIT_DELAYED_WORK(&isl_chg->charge_work, isl9519q_charge);
+ 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_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 = isl9519q_start_charging;
+ isl_chg->adapter_hw_chg.stop_charging = isl9519q_stop_charging;
+ isl_chg->adapter_hw_chg.charging_switched = isl9519q_charging_switched;
+
+ 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;
+ }
+ }
+
+ 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 free_isl_chg;
+ }
+
+ i2c_set_clientdata(client, isl_chg);
+
+ 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);
+
+ 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_write_reg(isl_chg->client, MAX_SYS_VOLTAGE_REG,
+ isl_chg->max_system_voltage);
+ if (ret) {
+ dev_err(&client->dev,
+ "%s couldnt write to MAX_SYS_VOLTAGE_REG ret=%d\n",
+ __func__, ret);
+ goto free_irq;
+ }
+
+ ret = isl9519q_write_reg(isl_chg->client, MIN_SYS_VOLTAGE_REG,
+ isl_chg->min_system_voltage);
+ if (ret) {
+ dev_err(&client->dev,
+ "%s couldnt write to MIN_SYS_VOLTAGE_REG ret=%d\n",
+ __func__, ret);
+ goto free_irq;
+ }
+
+ if (isl_chg->input_current) {
+ ret = isl9519q_write_reg(isl_chg->client,
+ INPUT_CURRENT_REG,
+ isl_chg->input_current);
+ if (ret) {
+ dev_err(&client->dev,
+ "%s couldnt write INPUT_CURRENT_REG ret=%d\n",
+ __func__, ret);
+ goto free_irq;
+ }
+ }
+
+ 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;
+ }
+
+ pr_debug("%s OK chg_present=%d\n", __func__, isl_chg->present);
+ return 0;
+
+free_irq:
+ free_irq(client->irq, NULL);
+unregister:
+ msm_charger_register(&isl_chg->adapter_hw_chg);
+free_gpio:
+ gpio_free(pdata->valid_n_gpio);
+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);
+ msm_charger_notify_event(&isl_chg->adapter_hw_chg, CHG_REMOVED_EVENT);
+ msm_charger_unregister(&isl_chg->adapter_hw_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;
+ 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__);
+ 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);
+}
+
+module_init(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/msm_battery.c b/drivers/power/msm_battery.c
new file mode 100644
index 0000000..464a1b5
--- /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 1
+
+#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..f40477a
--- /dev/null
+++ b/drivers/power/msm_charger.c
@@ -0,0 +1,1250 @@
+/* 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/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)
+{
+ if (msm_batt_gauge && msm_batt_gauge->get_batt_remaining_capacity)
+ return msm_batt_gauge->get_batt_remaining_capacity();
+
+ return msm_chg.get_batt_capacity_percent();
+}
+
+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;
+}
+
+/* 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)
+{
+ 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) {
+ 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;
+ 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);
+
+ 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) {
+ 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;
+ 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 __init determine_initial_batt_status(void)
+{
+ int rc;
+
+ 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();
+
+ 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 rc;
+ }
+
+ /* 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)
+{
+ 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:
+ wake_lock_destroy(&msm_chg.wl);
+ kfree(priv);
+ return rc;
+}
+EXPORT_SYMBOL(msm_charger_register);
+
+void msm_battery_gauge_register(struct msm_battery_gauge *batt_gauge)
+{
+ if (msm_batt_gauge) {
+ msm_batt_gauge = batt_gauge;
+ pr_err("msm-charger %s multiple battery gauge called\n",
+ __func__);
+ } else {
+ 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);
+ wake_lock_destroy(&msm_chg.wl);
+ 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..6ad6a18
--- /dev/null
+++ b/drivers/power/pm8921-bms.c
@@ -0,0 +1,1178 @@
+/* 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/errno.h>
+#include <linux/mfd/pm8xxx/pm8921-bms.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+
+#define BMS_CONTROL 0x224
+#define BMS_OUTPUT0 0x230
+#define BMS_OUTPUT1 0x231
+#define BMS_TEST1 0x237
+#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 ADC_ARB_SECP_CNTRL 0x190
+#define ADC_ARB_SECP_AMUX_CNTRL 0x191
+#define ADC_ARB_SECP_ANA_PARAM 0x192
+#define ADC_ARB_SECP_RSV 0x194
+#define ADC_ARB_SECP_DATA1 0x195
+#define ADC_ARB_SECP_DATA0 0x196
+
+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_bms_chip -device information
+ * @dev: device pointer to access the parent
+ * @dent: debugfs directory
+ * @r_sense: batt sense resistance value
+ * @i_test: peak current
+ * @v_failure: battery dead voltage
+ * @fcc: battery capacity
+ *
+ */
+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 pc_sf_lut *pc_sf_lut;
+ struct delayed_work calib_work;
+ unsigned int calib_delay_ms;
+ unsigned int pmic_bms_irq[PM_BMS_MAX_INTS];
+ DECLARE_BITMAP(enabled_irqs, PM_BMS_MAX_INTS);
+};
+
+static struct pm8921_bms_chip *the_chip;
+
+#define DEFAULT_RBATT_MOHMS 128
+#define DEFAULT_UNUSABLE_CHARGE_MAH 10
+#define DEFAULT_OCV_MICROVOLTS 3900000
+#define DEFAULT_REMAINING_CHARGE_MAH 990
+#define DEFAULT_COULUMB_COUNTER 0
+#define DEFAULT_CHARGE_CYCLES 0
+
+static int last_rbatt = -EINVAL;
+static int last_fcc = -EINVAL;
+static int last_unusable_charge = -EINVAL;
+static int last_ocv_uv = -EINVAL;
+static int last_remaining_charge = -EINVAL;
+static int last_coulumb_counter = -EINVAL;
+static int last_soc = -EINVAL;
+
+static int last_chargecycles = DEFAULT_CHARGE_CYCLES;
+static int last_charge_increase;
+
+module_param(last_rbatt, int, 0644);
+module_param(last_fcc, int, 0644);
+module_param(last_unusable_charge, int, 0644);
+module_param(last_ocv_uv, int, 0644);
+module_param(last_remaining_charge, int, 0644);
+module_param(last_coulumb_counter, int, 0644);
+module_param(last_chargecycles, int, 0644);
+module_param(last_charge_increase, int, 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_disable_irq(struct pm8921_bms_chip *chip, int interrupt)
+{
+ if (__test_and_clear_bit(interrupt, chip->enabled_irqs)) {
+ dev_dbg(chip->dev, "%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;
+}
+
+#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 977
+#define V_PER_BIT_DIV_FACTOR 10
+#define CONV_READING(a) (((a) * (int)V_PER_BIT_MUL_FACTOR)\
+ /V_PER_BIT_DIV_FACTOR)
+
+/* 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 read_last_good_ocv(struct pm8921_bms_chip *chip, uint *result)
+{
+ int rc;
+ uint16_t reading;
+
+ rc = pm_bms_read_output_data(chip, LAST_GOOD_OCV_VALUE, &reading);
+ if (rc) {
+ pr_err("fail to read LAST_GOOD_OCV_VALUE rc = %d\n", rc);
+ return rc;
+ }
+ *result = CONV_READING(reading);
+ pr_debug("raw = %04x ocv_microV = %u\n", reading, *result);
+ return 0;
+}
+
+static int read_vbatt_for_rbatt(struct pm8921_bms_chip *chip, uint *result)
+{
+ int rc;
+ uint16_t reading;
+
+ rc = pm_bms_read_output_data(chip, VBATT_FOR_RBATT, &reading);
+ if (rc) {
+ pr_err("fail to read VBATT_FOR_RBATT rc = %d\n", rc);
+ return rc;
+ }
+ *result = CONV_READING(reading);
+ pr_debug("raw = %04x vbatt_for_r_microV = %u\n", reading, *result);
+ return 0;
+}
+
+static int read_vsense_for_rbatt(struct pm8921_bms_chip *chip, uint *result)
+{
+ int rc;
+ uint16_t reading;
+
+ rc = pm_bms_read_output_data(chip, VSENSE_FOR_RBATT, &reading);
+ if (rc) {
+ pr_err("fail to read VSENSE_FOR_RBATT rc = %d\n", rc);
+ return rc;
+ }
+ *result = CONV_READING(reading);
+ pr_debug("raw = %04x vsense_for_r_microV = %u\n", reading, *result);
+ return 0;
+}
+
+static int read_ocv_for_rbatt(struct pm8921_bms_chip *chip, uint *result)
+{
+ int rc;
+ uint16_t reading;
+
+ rc = pm_bms_read_output_data(chip, OCV_FOR_RBATT, &reading);
+ if (rc) {
+ pr_err("fail to read OCV_FOR_RBATT rc = %d\n", rc);
+ return rc;
+ }
+ *result = CONV_READING(reading);
+ pr_debug("read = %04x ocv_for_r_microV = %u\n", 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_err("x %d less than known range returning y = %d\n",
+ x, lut->y[0]);
+ return lut->y[0];
+ }
+ if (x > lut->x[lut->cols - 1]) {
+ pr_err("x %d more than known range returning y = %d\n",
+ x, lut->y[lut->cols - 1]);
+ 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)
+{
+ return interpolate_single_lut(chip->fcc_temp_lut, batt_temp);
+}
+
+static int interpolate_scalingfactor_fcc(struct pm8921_bms_chip *chip,
+ int cycles)
+{
+ return interpolate_single_lut(chip->fcc_sf_lut, cycles);
+}
+
+static int interpolate_scalingfactor_pc(struct pm8921_bms_chip *chip,
+ int cycles, int pc)
+{
+ int i, scalefactorrow1, scalefactorrow2, scalefactor, row1, row2;
+ int rows = chip->pc_sf_lut->rows;
+ int cols = chip->pc_sf_lut->cols;
+
+ if (pc > chip->pc_sf_lut->percent[0]) {
+ pr_err("pc %d greater than known pc ranges for sfd\n", pc);
+ row1 = 0;
+ row2 = 0;
+ }
+ if (pc < chip->pc_sf_lut->percent[rows - 1]) {
+ pr_err("pc %d less than known pc ranges for sf", pc);
+ row1 = rows - 1;
+ row2 = rows - 1;
+ }
+ for (i = 0; i < rows; i++) {
+ if (pc == chip->pc_sf_lut->percent[i]) {
+ row1 = i;
+ row2 = i;
+ break;
+ }
+ if (pc > chip->pc_sf_lut->percent[i]) {
+ row1 = i - 1;
+ row2 = i;
+ break;
+ }
+ }
+
+ if (cycles < chip->pc_sf_lut->cycles[0])
+ cycles = chip->pc_sf_lut->cycles[0];
+ if (cycles > chip->pc_sf_lut->cycles[cols - 1])
+ cycles = chip->pc_sf_lut->cycles[cols - 1];
+
+ for (i = 0; i < cols; i++)
+ if (cycles <= chip->pc_sf_lut->cycles[i])
+ break;
+ if (cycles == chip->pc_sf_lut->cycles[i]) {
+ scalefactor = linear_interpolate(
+ chip->pc_sf_lut->sf[row1][i],
+ chip->pc_sf_lut->percent[row1],
+ chip->pc_sf_lut->sf[row2][i],
+ chip->pc_sf_lut->percent[row2],
+ pc);
+ return scalefactor;
+ }
+
+ scalefactorrow1 = linear_interpolate(
+ chip->pc_sf_lut->sf[row1][i - 1],
+ chip->pc_sf_lut->cycles[i - 1],
+ chip->pc_sf_lut->sf[row1][i],
+ chip->pc_sf_lut->cycles[i],
+ cycles);
+
+ scalefactorrow2 = linear_interpolate(
+ chip->pc_sf_lut->sf[row2][i - 1],
+ chip->pc_sf_lut->cycles[i - 1],
+ chip->pc_sf_lut->sf[row2][i],
+ chip->pc_sf_lut->cycles[i],
+ cycles);
+
+ scalefactor = linear_interpolate(
+ scalefactorrow1,
+ chip->pc_sf_lut->percent[row1],
+ scalefactorrow2,
+ chip->pc_sf_lut->percent[row2],
+ pc);
+
+ return scalefactor;
+}
+
+static int interpolate_pc(struct pm8921_bms_chip *chip,
+ int batt_temp, int ocv)
+{
+ int i, j, ocvi, ocviplusone, pc = 0;
+ int rows = chip->pc_temp_ocv_lut->rows;
+ int cols = chip->pc_temp_ocv_lut->cols;
+
+ if (batt_temp < chip->pc_temp_ocv_lut->temp[0]) {
+ pr_err("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_err("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;
+ }
+ }
+ }
+
+ 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];
+ for (i = 0; i < rows; i++) {
+ if (ocv >= chip->pc_temp_ocv_lut->ocv[i][j - 1]
+ && ocv <= chip->pc_temp_ocv_lut->ocv[i][j]) {
+ pc = chip->pc_temp_ocv_lut->percent[i];
+
+ if (i < rows - 1
+ && ocv >=
+ chip->pc_temp_ocv_lut->ocv[i + 1][j - 1]
+ && ocv <=
+ chip->pc_temp_ocv_lut->ocv[i + 1][j]) {
+ ocvi = linear_interpolate(
+ chip->pc_temp_ocv_lut->ocv[i][j - 1],
+ chip->pc_temp_ocv_lut->temp[j - 1],
+ chip->pc_temp_ocv_lut->ocv[i][j],
+ chip->pc_temp_ocv_lut->temp[j],
+ batt_temp);
+
+ ocviplusone = linear_interpolate(
+ chip->pc_temp_ocv_lut
+ ->ocv[i + 1][j - 1],
+ chip->pc_temp_ocv_lut->temp[j - 1],
+ chip->pc_temp_ocv_lut->ocv[i + 1][j],
+ chip->pc_temp_ocv_lut->temp[j],
+ batt_temp);
+
+ pc = linear_interpolate(
+ chip->pc_temp_ocv_lut->percent[i],
+ ocvi,
+ chip->pc_temp_ocv_lut->percent[i + 1],
+ ocviplusone,
+ ocv);
+ }
+ return pc;
+ }
+ }
+
+ pr_err("%d ocv wasn't found for temp %d in the LUT returning pc = %d",
+ ocv, batt_temp, pc);
+ return pc;
+}
+
+static int calculate_rbatt(struct pm8921_bms_chip *chip)
+{
+ int rc;
+ unsigned int ocv, vsense, vbatt, r_batt;
+
+ rc = read_ocv_for_rbatt(chip, &ocv);
+ if (rc) {
+ pr_err("fail to read ocv_for_rbatt rc = %d\n", rc);
+ ocv = 0;
+ }
+ rc = read_vbatt_for_rbatt(chip, &vbatt);
+ if (rc) {
+ pr_err("fail to read vbatt_for_rbatt rc = %d\n", rc);
+ ocv = 0;
+ }
+ rc = read_vsense_for_rbatt(chip, &vsense);
+ if (rc) {
+ pr_err("fail to read vsense_for_rbatt rc = %d\n", rc);
+ ocv = 0;
+ }
+ if (ocv == 0
+ || ocv == vbatt
+ || vsense == 0) {
+ pr_warning("incorret reading ocv = %d, vbatt = %d, vsen = %d\n",
+ ocv, vbatt, vsense);
+ return -EINVAL;
+ }
+ r_batt = ((ocv - vbatt) * chip->r_sense) / vsense;
+ pr_debug("r_batt = %umilliOhms", r_batt);
+ return r_batt;
+}
+
+static int calculate_fcc(struct pm8921_bms_chip *chip, int batt_temp,
+ int chargecycles)
+{
+ int initfcc, result, scalefactor = 0;
+
+ initfcc = interpolate_fcc(chip, batt_temp);
+ pr_debug("intfcc = %umAh batt_temp = %d\n", initfcc, batt_temp);
+
+ scalefactor = interpolate_scalingfactor_fcc(chip, chargecycles);
+ pr_debug("scalefactor = %d batt_temp = %d\n", scalefactor, batt_temp);
+
+ /* Multiply the initial FCC value by the scale factor. */
+ result = (initfcc * scalefactor) / 100;
+ pr_debug("fcc mAh = %d\n", result);
+ return result;
+}
+
+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_pc(chip, 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;
+}
+
+#define CC_TO_MICROVOLT(cc) div_s64(cc * 1085069, 100000);
+#define CCMICROVOLT_TO_UVH(cc_uv) div_s64(cc_uv * 55, 32768 * 3600)
+
+static void calculate_cc_mvh(struct pm8921_bms_chip *chip, int64_t *val,
+ int *coulumb_counter, int *update_userspace)
+{
+ int rc;
+ int64_t cc_voltage_uv, cc_uvh, cc_mah;
+
+ rc = read_cc(the_chip, coulumb_counter);
+ if (rc) {
+ *coulumb_counter = (last_coulumb_counter < 0) ?
+ DEFAULT_COULUMB_COUNTER : last_coulumb_counter;
+ pr_err("couldn't read coulumb counter err = %d assuming %d\n",
+ rc, *coulumb_counter);
+ *update_userspace = 0;
+ }
+ cc_voltage_uv = (int64_t)*coulumb_counter;
+ cc_voltage_uv = CC_TO_MICROVOLT(cc_voltage_uv);
+ pr_debug("cc_voltage_uv = %lld microvolts\n", cc_voltage_uv);
+ cc_uvh = CCMICROVOLT_TO_UVH(cc_voltage_uv);
+ pr_debug("cc_uvh = %lld micro_volt_hour\n", cc_uvh);
+ cc_mah = div_s64(cc_uvh, chip->r_sense);
+ *val = cc_mah;
+}
+
+static int calculate_state_of_charge(struct pm8921_bms_chip *chip,
+ int batt_temp, int chargecycles)
+{
+ int remaining_usable_charge, fcc, unusable_charge;
+ int remaining_charge, soc, rc, ocv, pc, coulumb_counter;
+ int rbatt, voltage_unusable_uv, pc_unusable;
+ int update_userspace = 1;
+ int64_t cc_mah;
+
+ rbatt = calculate_rbatt(chip);
+ if (rbatt < 0) {
+ rbatt = (last_rbatt < 0) ? DEFAULT_RBATT_MOHMS : last_rbatt;
+ pr_err("failed to read rbatt assuming %d\n",
+ rbatt);
+ update_userspace = 0;
+ }
+ pr_debug("rbatt = %umilliOhms", rbatt);
+
+ fcc = calculate_fcc(chip, batt_temp, chargecycles);
+ if (fcc < -EINVAL) {
+ fcc = (last_fcc < 0) ? chip->fcc : last_fcc;
+ pr_err("failed to read fcc assuming %d\n", fcc);
+ update_userspace = 0;
+ }
+ pr_debug("fcc = %umAh", fcc);
+
+ /* calculate unusable charge */
+ voltage_unusable_uv = (rbatt * chip->i_test)
+ + (chip->v_failure * 1000);
+ pc_unusable = calculate_pc(chip, voltage_unusable_uv,
+ batt_temp, chargecycles);
+ if (pc_unusable < 0) {
+ unusable_charge = (last_unusable_charge < 0) ?
+ DEFAULT_UNUSABLE_CHARGE_MAH : last_unusable_charge;
+ pr_err("unusable_charge failed assuming %d\n", unusable_charge);
+ } else {
+ unusable_charge = (fcc * pc_unusable) / 100;
+ }
+ pr_debug("unusable_charge = %umAh at temp = %d, fcc = %umAh"
+ "unusable_voltage = %umicroVolts pc_unusable = %d\n",
+ unusable_charge, batt_temp, fcc,
+ voltage_unusable_uv, pc_unusable);
+
+ /* calculate remainging charge */
+ rc = read_last_good_ocv(chip, &ocv);
+ if (rc || ocv == 0) {
+ ocv = (last_ocv_uv < 0) ? DEFAULT_OCV_MICROVOLTS : last_ocv_uv;
+ pr_err("ocv failed assuming %d rc = %d\n", ocv, rc);
+ update_userspace = 0;
+ }
+ pc = calculate_pc(chip, ocv, batt_temp, chargecycles);
+ if (pc < 0) {
+ remaining_charge = (last_remaining_charge < 0) ?
+ DEFAULT_REMAINING_CHARGE_MAH : last_remaining_charge;
+ pr_err("calculate remaining charge failed assuming %d\n",
+ remaining_charge);
+ update_userspace = 0;
+ } else {
+ remaining_charge = (fcc * pc) / 100;
+ }
+ pr_debug("remaining_charge = %umAh ocv = %d pc = %d\n",
+ remaining_charge, ocv, pc);
+
+ /* calculate cc milli_volt_hour */
+ calculate_cc_mvh(chip, &cc_mah, &coulumb_counter, &update_userspace);
+ pr_debug("cc_mah = %lldmAh cc = %d\n", cc_mah, coulumb_counter);
+
+ /* calculate remaining usable charge */
+ remaining_usable_charge = remaining_charge - cc_mah - unusable_charge;
+ pr_debug("remaining_usable_charge = %dmAh\n", remaining_usable_charge);
+ if (remaining_usable_charge < 0) {
+ pr_err("bad rem_usb_chg cc_mah %lld, rem_chg %d unusb_chg %d\n",
+ cc_mah, remaining_charge, unusable_charge);
+ update_userspace = 0;
+ }
+
+ soc = (remaining_usable_charge * 100) / (fcc - unusable_charge);
+ if (soc > 100 || soc < 0) {
+ pr_err("bad soc rem_usb_chg %d fcc %d unusb_chg %d\n",
+ remaining_usable_charge, fcc, unusable_charge);
+ update_userspace = 0;
+ }
+ pr_debug("soc = %u%%\n", soc);
+
+ if (update_userspace) {
+ last_rbatt = rbatt;
+ last_fcc = fcc;
+ last_unusable_charge = unusable_charge;
+ last_ocv_uv = ocv;
+ last_remaining_charge = remaining_charge;
+ last_coulumb_counter = coulumb_counter;
+ last_soc = soc;
+ }
+ return soc;
+}
+
+int pm8921_bms_get_percent_charge(void)
+{
+ /* TODO get batt_temp from ADC */
+ int batt_temp = 73;
+
+ return calculate_state_of_charge(the_chip,
+ batt_temp, last_chargecycles);
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_get_percent_charge);
+
+static int start_percent;
+static int end_percent;
+void pm8921_bms_charging_began(void)
+{
+ /* TODO get batt_temp from ADC */
+ int batt_temp = 73;
+
+ start_percent = calculate_state_of_charge(the_chip,
+ batt_temp, last_chargecycles);
+ pr_debug("start_percent = %u%%\n", start_percent);
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_charging_began);
+
+void pm8921_bms_charging_end(void)
+{
+ /* TODO get batt_temp from ADC */
+ int batt_temp = 73;
+
+ end_percent = calculate_state_of_charge(the_chip,
+ batt_temp, last_chargecycles);
+ if (end_percent > start_percent) {
+ last_charge_increase = end_percent - 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",
+ end_percent,
+ last_charge_increase,
+ last_chargecycles);
+}
+EXPORT_SYMBOL_GPL(pm8921_bms_charging_end);
+
+static irqreturn_t pm8921_bms_sbi_write_ok_handler(int irq, void *data)
+{
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8921_bms_cc_thr_handler(int irq, void *data)
+{
+ return IRQ_HANDLED;
+}
+static irqreturn_t pm8921_bms_vsense_thr_handler(int irq, void *data)
+{
+ return IRQ_HANDLED;
+}
+static irqreturn_t pm8921_bms_vsense_for_r_handler(int irq, void *data)
+{
+ return IRQ_HANDLED;
+}
+static irqreturn_t pm8921_bms_ocv_for_r_handler(int irq, void *data)
+{
+ return IRQ_HANDLED;
+}
+static irqreturn_t pm8921_bms_good_ocv_handler(int irq, void *data)
+{
+ return IRQ_HANDLED;
+}
+static irqreturn_t pm8921_bms_vsense_avg_handler(int irq, void *data)
+{
+ 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;
+}
+
+#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);
+ }
+
+ return 0;
+}
+
+enum {
+ CALC_RBATT,
+ CALC_FCC,
+ CALC_PC,
+ CALC_SOC,
+};
+
+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;
+
+ *val = 0;
+
+ /* global irq number passed in via data */
+ switch (param) {
+ case CALC_RBATT:
+ *val = calculate_rbatt(the_chip);
+ break;
+ case CALC_FCC:
+ *val = calculate_fcc(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,
+ test_batt_temp, test_chargecycle);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+DEFINE_SIMPLE_ATTRIBUTE(calc_fops, get_calc, NULL, "%llu\n");
+
+static int get_reading(void *data, u64 * val)
+{
+ int param = (int)data;
+ int ret = 0;
+
+ *val = 0;
+
+ /* global irq number passed in via data */
+ switch (param) {
+ case CC_MSB:
+ case CC_LSB:
+ read_cc(the_chip, (int *)val);
+ break;
+ case LAST_GOOD_OCV_VALUE:
+ read_last_good_ocv(the_chip, (uint *)val);
+ break;
+ case VBATT_FOR_RBATT:
+ read_vbatt_for_rbatt(the_chip, (uint *)val);
+ break;
+ case VSENSE_FOR_RBATT:
+ read_vsense_for_rbatt(the_chip, (uint *)val);
+ break;
+ case OCV_FOR_RBATT:
+ read_ocv_for_rbatt(the_chip, (uint *)val);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ return ret;
+}
+DEFINE_SIMPLE_ATTRIBUTE(reading_fops, get_reading, NULL, "%llu\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("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("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("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);
+
+ 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);
+ }
+}
+
+static void calibrate_work(struct work_struct *work)
+{
+ /* TODO */
+}
+
+static int __devinit pm8921_bms_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ 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;
+ }
+
+ 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->fcc = pdata->batt_data->fcc;
+
+ chip->fcc_temp_lut = pdata->batt_data->fcc_temp_lut;
+ chip->fcc_sf_lut = pdata->batt_data->fcc_sf_lut;
+ chip->pc_temp_ocv_lut = pdata->batt_data->pc_temp_ocv_lut;
+ chip->pc_sf_lut = pdata->batt_data->pc_sf_lut;
+
+ rc = pm8921_bms_hw_init(chip);
+ if (rc) {
+ pr_err("couldn't init hardware rc = %d\n", rc);
+ goto free_chip;
+ }
+
+ rc = request_irqs(chip, pdev);
+ if (rc) {
+ pr_err("couldn't register interrupts rc = %d\n", rc);
+ goto free_chip;
+ }
+
+ platform_set_drvdata(pdev, chip);
+ the_chip = chip;
+ create_debugfs_entries(chip);
+
+ INIT_DELAYED_WORK(&chip->calib_work, calibrate_work);
+ schedule_delayed_work(&chip->calib_work,
+ round_jiffies_relative(msecs_to_jiffies
+ (chip->calib_delay_ms)));
+ return 0;
+
+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);
+ 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,
+ },
+};
+
+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..6775fba
--- /dev/null
+++ b/drivers/power/pm8921-charger.c
@@ -0,0 +1,1560 @@
+/* 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/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/pm8921-adc.h>
+#include <linux/mfd/pm8xxx/core.h>
+#include <linux/interrupt.h>
+#include <linux/power_supply.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
+
+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,
+};
+
+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_charging;
+ struct work_struct work;
+};
+
+/**
+ * struct pm8921_chg_chip -device information
+ * @dev: device pointer to access the parent
+ * @is_usb_path_used: indicates whether USB charging is used at all
+ * @is_usb_path_used: indicates whether DC charging is used at all
+ * @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: the max volts the batt should be charged up to
+ * @min_voltage: the min battery voltage before turning the FETon
+ * @resume_voltage: the voltage 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 pmic_chg_irq[PM_CHG_MAX_INTS];
+ unsigned int safety_time;
+ unsigned int update_time;
+ unsigned int max_voltage;
+ unsigned int min_voltage;
+ unsigned int resume_voltage;
+ unsigned int term_current;
+ unsigned int vbat_channel;
+ struct power_supply usb_psy;
+ struct power_supply dc_psy;
+ struct power_supply batt_psy;
+ struct dentry *dent;
+ struct bms_notify bms_notify;
+ DECLARE_BITMAP(enabled_irqs, PM_CHG_MAX_INTS);
+};
+
+static int charging_disabled;
+
+static struct pm8921_chg_chip *the_chip;
+
+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;
+}
+
+#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 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_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);
+}
+
+#define PM8921_CHG_V_MIN_MV 3240
+#define PM8921_CHG_V_STEP_MV 20
+#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)
+{
+ u8 temp;
+
+ 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;
+ pr_debug("voltage=%d setting %02x\n", voltage, temp);
+ return pm_chg_masked_write(chip, CHG_VDD_MAX, PM8921_CHG_V_MASK, temp);
+}
+
+#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_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 50
+#define PM8921_CHG_ITERM_MAX 200
+#define PM8921_CHG_ITERM_MIN_MA 50
+#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
+ || chg_current > PM8921_CHG_ITERM_MAX) {
+ 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_IBAT_SAFE, PM8921_CHG_ITERM_MASK,
+ temp);
+}
+
+#define PM8921_CHG_IUSB_MASK 0x1C
+#define PM8921_CHG_IUSB_MAX 7
+#define PM8921_CHG_IUSB_MIN 0
+static int pm_chg_iusbmax_set(struct pm8921_chg_chip *chip, int chg_current)
+{
+ u8 temp;
+
+ if (chg_current < PM8921_CHG_IUSB_MIN
+ || chg_current > PM8921_CHG_IUSB_MAX) {
+ pr_err("bad mA=%d asked to set\n", chg_current);
+ return -EINVAL;
+ }
+ temp = chg_current << 2;
+ return pm_chg_masked_write(chip, PBL_ACCESS2, PM8921_CHG_IUSB_MASK,
+ temp);
+}
+
+#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);
+}
+
+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 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)
+{
+ int pres, ov, uv;
+
+ pres = pm_chg_get_rt_status(chip, USBIN_VALID_IRQ);
+ ov = pm_chg_get_rt_status(chip, USBIN_OV_IRQ);
+ uv = pm_chg_get_rt_status(chip, USBIN_UV_IRQ);
+
+ return pres && !ov && !uv;
+}
+
+/* Treat OverVoltage/UnderVoltage as source missing */
+static int is_dc_chg_plugged_in(struct pm8921_chg_chip *chip)
+{
+ int pres, ov, uv;
+
+ pres = pm_chg_get_rt_status(chip, DCIN_VALID_IRQ);
+ ov = pm_chg_get_rt_status(chip, DCIN_OV_IRQ);
+ uv = pm_chg_get_rt_status(chip, DCIN_UV_IRQ);
+
+ return pres && !ov && !uv;
+}
+
+static int is_battery_charging(int fsm_state)
+{
+ 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();
+}
+
+static enum power_supply_property pm_power_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static char *pm_power_supplied_to[] = {
+ "battery",
+};
+
+static int pm_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pm8921_chg_chip *chip;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (psy->type == POWER_SUPPLY_TYPE_MAINS) {
+ chip = container_of(psy, struct pm8921_chg_chip,
+ dc_psy);
+ val->intval = is_dc_chg_plugged_in(chip);
+ }
+ if (psy->type == POWER_SUPPLY_TYPE_USB) {
+ chip = container_of(psy, struct pm8921_chg_chip,
+ usb_psy);
+ val->intval = is_usb_chg_plugged_in(chip);
+ }
+ 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 get_prop_battery_mvolts(struct pm8921_chg_chip *chip)
+{
+ int rc;
+ struct pm8921_adc_chan_result result;
+
+ rc = pm8921_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);
+ return (int)result.physical;
+}
+
+static int get_prop_batt_capacity(struct pm8921_chg_chip *chip)
+{
+ return pm8921_bms_get_percent_charge();
+}
+
+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_batt_present(struct pm8921_chg_chip *chip)
+{
+ return pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ);
+}
+
+static int get_prop_charge_type(struct pm8921_chg_chip *chip)
+{
+ int temp;
+
+ 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 temp = 0;
+
+ /* TODO reading the FSM state is more reliable */
+ temp = pm_chg_get_rt_status(chip, TRKLCHG_IRQ);
+
+ temp |= pm_chg_get_rt_status(chip, FASTCHG_IRQ);
+ if (temp)
+ return POWER_SUPPLY_STATUS_CHARGING;
+ /*
+ * The battery is not charging
+ * check the FET - if on battery is discharging
+ * - if off battery is isolated(full) and the system
+ * is being driven from a charger
+ */
+ temp = pm_chg_get_rt_status(chip, BATFET_IRQ);
+ if (temp)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ return POWER_SUPPLY_STATUS_FULL;
+}
+
+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;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = chip->min_voltage;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = get_prop_battery_mvolts(chip);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = get_prop_batt_capacity(chip);
+ 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");
+ }
+}
+
+struct usb_ma_limit_entry {
+ int usb_ma;
+ u8 chg_iusb_value;
+};
+
+static struct usb_ma_limit_entry usb_ma_table[] = {
+ {100, 0},
+ {500, 1},
+ {700, 2},
+ {850, 3},
+ {900, 4},
+ {1100, 5},
+ {1300, 6},
+ {1500, 7},
+};
+
+/* assumes vbus_lock is held */
+static void __pm8921_charger_vbus_draw(unsigned int mA)
+{
+ int i, rc;
+
+ if (mA > 0 && mA <= 2) {
+ usb_chg_current = 0;
+ rc = pm_chg_iusbmax_set(the_chip,
+ usb_ma_table[0].chg_iusb_value);
+ if (rc) {
+ pr_err("unable to set iusb to %d rc = %d\n",
+ usb_ma_table[0].chg_iusb_value, 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;
+ }
+ if (i < 0)
+ i = 0;
+ rc = pm_chg_iusbmax_set(the_chip,
+ usb_ma_table[i].chg_iusb_value);
+ if (rc) {
+ pr_err("unable to set iusb to %d rc = %d\n",
+ usb_ma_table[i].chg_iusb_value, 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);
+ spin_lock_irqsave(&vbus_lock, flags);
+ if (the_chip) {
+ __pm8921_charger_vbus_draw(mA);
+ } else {
+ /*
+ * called before pmic initialized,
+ * save this value and use it at probe
+ */
+ usb_chg_current = mA;
+ }
+ spin_unlock_irqrestore(&vbus_lock, flags);
+}
+EXPORT_SYMBOL_GPL(pm8921_charger_vbus_draw);
+
+static void handle_usb_insertion_removal(struct pm8921_chg_chip *chip)
+{
+ int usb_present;
+
+ 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);
+ }
+}
+
+static void handle_dc_removal_insertion(struct pm8921_chg_chip *chip)
+{
+ int dc_present;
+
+ dc_present = is_dc_chg_plugged_in(chip);
+ if (chip->dc_present ^ dc_present) {
+ chip->dc_present = dc_present;
+ power_supply_changed(&chip->dc_psy);
+ }
+}
+
+static irqreturn_t usbin_valid_irq_handler(int irq, void *data)
+{
+ handle_usb_insertion_removal(data);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t usbin_ov_irq_handler(int irq, void *data)
+{
+ handle_usb_insertion_removal(data);
+ 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);
+ pr_debug("battery present=%d", status);
+ power_supply_changed(&chip->batt_psy);
+ return IRQ_HANDLED;
+}
+/* this interrupt used to restart charging a battery */
+static irqreturn_t vbatdet_low_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ 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)
+{
+ handle_usb_insertion_removal(data);
+ 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_warning("VCP triggered BATDET forced on\n");
+ pr_debug("state_changed_to=%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));
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ power_supply_changed(&chip->dc_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chgfail_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);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chgstate_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+ int new_is_charging = 0, fsm_state;
+
+ 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);
+
+ 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));
+ }
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t loop_change_irq_handler(int irq, void *data)
+{
+ pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fastchg_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ power_supply_changed(&chip->batt_psy);
+ 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));
+ 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;
+
+ 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);
+ power_supply_changed(&chip->dc_psy);
+ 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));
+ power_supply_changed(&chip->batt_psy);
+ power_supply_changed(&chip->usb_psy);
+ power_supply_changed(&chip->dc_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chg_gone_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ 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);
+ power_supply_changed(&chip->dc_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t bat_temp_ok_irq_handler(int irq, void *data)
+{
+ struct pm8921_chg_chip *chip = data;
+
+ pr_debug("batt temp ok 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 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)
+{
+ handle_dc_removal_insertion(data);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dcin_ov_irq_handler(int irq, void *data)
+{
+ handle_dc_removal_insertion(data);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t dcin_uv_irq_handler(int irq, void *data)
+{
+ handle_dc_removal_insertion(data);
+ return IRQ_HANDLED;
+}
+
+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 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 and notifies msm_charger */
+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);
+
+ 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_REMOVED_IRQ);
+ pm8921_chg_enable_irq(chip, CHGSTATE_IRQ);
+
+ spin_lock_irqsave(&vbus_lock, flags);
+ if (usb_chg_current) {
+ /* reissue a vbus draw call */
+ __pm8921_charger_vbus_draw(usb_chg_current);
+ }
+ 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();
+ }
+
+ 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 | IRQF_TRIGGER_FALLING,
+ 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, 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, 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, chg_gone_irq_handler),
+ CHG_IRQ(BAT_TEMP_OK_IRQ, IRQF_TRIGGER_RISING, 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 | IRQF_TRIGGER_FALLING,
+ 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;
+ }
+ 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);
+ goto err_out;
+ }
+ chip->pmic_chg_irq[chg_irq_data[i].irq_id] = res->start;
+ pm8921_chg_disable_irq(chip, chg_irq_data[i].irq_id);
+ }
+ return 0;
+
+err_out:
+ free_irqs(chip);
+ return -EINVAL;
+}
+
+#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
+static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip)
+{
+ int rc;
+
+ 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;
+ }
+
+ rc = pm_chg_vddsafe_set(chip, chip->max_voltage);
+ if (rc) {
+ pr_err("Failed to set safe voltage to %d rc=%d\n",
+ chip->max_voltage, rc);
+ return rc;
+ }
+ rc = pm_chg_vbatdet_set(chip, chip->resume_voltage);
+ if (rc) {
+ pr_err("Failed to set vbatdet comprator voltage to %d rc=%d\n",
+ chip->resume_voltage, rc);
+ return rc;
+ }
+
+ rc = pm_chg_vddmax_set(chip, chip->max_voltage);
+ if (rc) {
+ pr_err("Failed to set max voltage to %d rc=%d\n",
+ chip->max_voltage, 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;
+ }
+
+ /* TODO needs to be changed as per the temeperature of the battery */
+ rc = pm_chg_ibatmax_set(chip, 400);
+ 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;
+ }
+
+ /* init with the lowest USB current */
+ rc = pm_chg_iusbmax_set(chip, usb_ma_table[0].chg_iusb_value);
+ if (rc) {
+ pr_err("Failed to set usb max to %d rc=%d\n",
+ usb_ma_table[0].chg_iusb_value, rc);
+ return rc;
+ }
+ 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;
+ }
+
+ /* 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, 0x8C);
+ pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xCE);
+ pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xD8);
+ }
+
+ 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(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_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);
+
+ 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 __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->update_time = pdata->update_time;
+ chip->max_voltage = pdata->max_voltage;
+ chip->min_voltage = pdata->min_voltage;
+ chip->resume_voltage = pdata->resume_voltage;
+ chip->term_current = pdata->term_current;
+ chip->vbat_channel = pdata->charger_cdata.vbat_channel;
+
+ 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,
+ chip->usb_psy.num_properties = ARRAY_SIZE(pm_power_props),
+ chip->usb_psy.get_property = pm_power_get_property,
+
+ chip->dc_psy.name = "ac",
+ 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,
+ chip->dc_psy.num_properties = ARRAY_SIZE(pm_power_props),
+ chip->dc_psy.get_property = pm_power_get_property,
+
+ 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,
+
+ 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_irq;
+ }
+
+ rc = power_supply_register(chip->dev, &chip->dc_psy);
+ if (rc < 0) {
+ pr_err("power_supply_register dc 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;
+ }
+
+ rc = request_irqs(chip, pdev);
+ if (rc) {
+ pr_err("couldn't register interrupts rc=%d\n", rc);
+ goto unregister_batt;
+ }
+
+ platform_set_drvdata(pdev, chip);
+ the_chip = chip;
+ create_debugfs_entries(chip);
+
+ INIT_WORK(&chip->bms_notify.work, bms_notify);
+ /* determine what state the charger is in */
+ determine_initial_state(chip);
+
+ 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 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,
+ },
+};
+
+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/pmic8058-charger.c b/drivers/power/pmic8058-charger.c
new file mode 100644
index 0000000..8ea7949
--- /dev/null
+++ b/drivers/power/pmic8058-charger.c
@@ -0,0 +1,1954 @@
+/* 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/mfd/pmic8058.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/pmic8058-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 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 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 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 = pm8058_batt_alarm_threshold_set(resume_mv, 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 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]);
+}
+
+#ifdef DEBUG
+static void __dump_chg_regs(void)
+{
+ u8 temp;
+ int temp2;
+
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_CNTRL = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_CNTRL_2 = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_VMAX_SEL, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_VMAX_SEL = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_VBAT_DET, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_VBAT_DET = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_IMAX, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_IMAX = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TRICKLE, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TRICKLE = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_ITERM, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_ITERM = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TTRKL_MAX, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TTRKL_MAX = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TCHG_MAX, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TCHG_MAX = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEMP_THRESH, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TEMP_THRESH = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEMP_REG, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_TEMP_REG = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_PULSE, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_PULSE = 0x%x\n", temp);
+
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_STATUS_CLEAR_IRQ_1,
+ &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_1 = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_STATUS_CLEAR_IRQ_3,
+ &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_3 = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_STATUS_CLEAR_IRQ_10,
+ &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_10 = 0x%x\n",
+ temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_STATUS_CLEAR_IRQ_11,
+ &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_STATUS_CLEAR_IRQ_11 = 0x%x\n",
+ temp);
+
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_MASK_IRQ_1, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_1 = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_MASK_IRQ_3, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_3 = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_MASK_IRQ_10, &temp, 1);
+ dev_dbg(pm8058_chg.dev, "PM8058_CHG_MASK_IRQ_10 = 0x%x\n", temp);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_MASK_IRQ_11, &temp, 1);
+ 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 = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(CHG_USB_SUSPEND);
+ else
+ temp &= ~BIT(CHG_USB_SUSPEND);
+
+ return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1);
+}
+
+static int pm_chg_auto_disable(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(CHARGE_AUTO_DIS);
+ else
+ temp &= ~BIT(CHARGE_AUTO_DIS);
+
+ return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1);
+}
+
+static int pm_chg_batt_temp_disable(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(CHG_BATT_TEMP_DIS);
+ else
+ temp &= ~BIT(CHG_BATT_TEMP_DIS);
+
+ return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1);
+}
+
+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 pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_VBAT_DET, &temp, 1);
+}
+
+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 pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_IMAX, &temp, 1);
+}
+
+#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 pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_VMAX_SEL, &temp, 1);
+}
+
+static int pm_chg_failed_clear(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(CHG_FAILED_CLEAR);
+ else
+ temp &= ~BIT(CHG_FAILED_CLEAR);
+ return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1);
+}
+
+static int pm_chg_iterm_set(int chg_current)
+{
+ u8 temp;
+
+ temp = (chg_current / PM8058_CHG_I_TERM_STEP_MA) - 1;
+ return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_ITERM, &temp, 1);
+}
+
+static int pm_chg_tchg_set(int minutes)
+{
+ u8 temp;
+
+ temp = (minutes >> PM8058_CHG_T_TCHG_SHIFT) - 1;
+ return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TCHG_MAX, &temp, 1);
+}
+
+static int pm_chg_ttrkl_set(int minutes)
+{
+ u8 temp;
+
+ temp = minutes - 1;
+ return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TTRKL_MAX, &temp, 1);
+}
+
+static int pm_chg_enum_done_enable(int value)
+{
+ u8 temp;
+ int ret;
+
+ ret = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1);
+ if (ret)
+ return ret;
+ if (value)
+ temp |= BIT(ENUM_DONE);
+ else
+ temp &= ~BIT(ENUM_DONE);
+
+ return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL_2, &temp, 1);
+}
+
+static uint32_t get_fsm_state(void)
+{
+ u8 temp;
+
+ temp = 0x00;
+ pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_3, &temp, 1);
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEST_3, &temp, 1);
+ return (uint32_t)temp;
+}
+
+static int get_fsm_status(void *data, u64 * val)
+{
+ *val = get_fsm_state();
+ return 0;
+}
+
+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 2:
+ case 7:
+ case 8:
+ rc = pm8058_batt_alarm_state_set(0, 0);
+ 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;
+ 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 = 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);
+ /* 10 ms sleep is for the VCHG to discharge */
+ msleep(10);
+ 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);
+
+ 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 = 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);
+ /* 10 ms sleep is for the VCHG to discharge */
+ msleep(10);
+ 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);
+
+ 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(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 (!pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_3, &temp, 1)) {
+ pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_TEST_3, &temp, 1);
+ 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_threaded_irq(res->start, NULL,
+ pm8058_chg_auto_chgdone_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[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_threaded_irq(res->start, NULL,
+ 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_threaded_irq(res->start, NULL,
+ 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_threaded_irq(res->start, NULL,
+ 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_threaded_irq(res->start, NULL,
+ 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_threaded_irq(res->start, NULL,
+ 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_threaded_irq(res->start, NULL,
+ 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 = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1);
+ 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 = pm8058_read(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1);
+ if (rc)
+ return rc;
+ if (on)
+ temp |= BIT(CHG_CHARGE_BAT);
+ else
+ temp &= ~BIT(CHG_CHARGE_BAT);
+
+ return pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_CNTRL, &temp, 1);
+
+}
+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__);
+ 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;
+
+ /* 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);
+ 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;
+ pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_2, &temp, 1);
+ temp = 0x84;
+ pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_2, &temp, 1);
+ msleep(2);
+ temp = 0x80;
+ pm8058_write(pm8058_chg.pm_chip, PM8058_CHG_TEST_2, &temp, 1);
+ return 0;
+}
+
+static int get_reg(void *data, u64 * val)
+{
+ int i = (int)data;
+ int ret;
+ u8 temp;
+
+ ret = pm8058_read(pm8058_chg.pm_chip, i, &temp, 1);
+ 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 = pm8058_write(pm8058_chg.pm_chip, i, &temp, 1);
+ 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,
+};
+
+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 = pm8058_batt_alarm_state_set(0, 0);
+ 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)
+{
+ /* enable low comparator */
+ return pm8058_batt_alarm_state_set(1, 0);
+}
+
+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 |= 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);
+ 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;
+ int rc = 0;
+
+ 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;
+
+ 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 = pm8058_batt_alarm_state_set(0, 0);
+ 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 = pm8058_batt_alarm_threshold_set(resume_mv, 4300);
+ if (rc) {
+ pr_err("%s: unable to set batt alarm threshold\n", __func__);
+ goto free_irq;
+ }
+
+ rc = pm8058_batt_alarm_hold_time_set(PM8058_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 = pm8058_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 = pm8058_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 = pm8058_batt_alarm_state_set(0, 0);
+ if (rc)
+ pr_err("%s: unable to set batt alarm state\n", __func__);
+
+ rc |= pm8058_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/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");