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/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");