blob: 8449b1bb4edb9b402bc82eee53855cea49bca651 [file] [log] [blame]
/* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/mfd/pmic8058.h>
#include <linux/interrupt.h>
#include <linux/power_supply.h>
#include <linux/delay.h>
#include <linux/bitops.h>
#include <linux/debugfs.h>
#include <linux/msm-charger.h>
#include <linux/time.h>
#include <linux/slab.h>
#include <linux/wakelock.h>
#include <asm/atomic.h>
#include <mach/msm_hsusb.h>
#define MSM_CHG_MAX_EVENTS 16
#define CHARGING_TEOC_MS 9000000
#define UPDATE_TIME_MS 60000
#define RESUME_CHECK_PERIOD_MS 60000
#define DEFAULT_BATT_MAX_V 4200
#define DEFAULT_BATT_MIN_V 3200
#define MSM_CHARGER_GAUGE_MISSING_VOLTS 3500
#define MSM_CHARGER_GAUGE_MISSING_TEMP 35
/**
* enum msm_battery_status
* @BATT_STATUS_ABSENT: battery not present
* @BATT_STATUS_ID_INVALID: battery present but the id is invalid
* @BATT_STATUS_DISCHARGING: battery is present and is discharging
* @BATT_STATUS_TRKL_CHARGING: battery is being trickle charged
* @BATT_STATUS_FAST_CHARGING: battery is being fast charged
* @BATT_STATUS_JUST_FINISHED_CHARGING: just finished charging,
* battery is fully charged. Do not begin charging untill the
* voltage falls below a threshold to avoid overcharging
* @BATT_STATUS_TEMPERATURE_OUT_OF_RANGE: battery present,
no charging, temp is hot/cold
*/
enum msm_battery_status {
BATT_STATUS_ABSENT,
BATT_STATUS_ID_INVALID,
BATT_STATUS_DISCHARGING,
BATT_STATUS_TRKL_CHARGING,
BATT_STATUS_FAST_CHARGING,
BATT_STATUS_JUST_FINISHED_CHARGING,
BATT_STATUS_TEMPERATURE_OUT_OF_RANGE,
};
struct msm_hardware_charger_priv {
struct list_head list;
struct msm_hardware_charger *hw_chg;
enum msm_hardware_charger_state hw_chg_state;
unsigned int max_source_current;
struct power_supply psy;
};
struct msm_charger_event {
enum msm_hardware_charger_event event;
struct msm_hardware_charger *hw_chg;
};
struct msm_charger_mux {
int inited;
struct list_head msm_hardware_chargers;
int count_chargers;
struct mutex msm_hardware_chargers_lock;
struct device *dev;
unsigned int max_voltage;
unsigned int min_voltage;
unsigned int safety_time;
struct delayed_work teoc_work;
unsigned int update_time;
int stop_update;
struct delayed_work update_heartbeat_work;
struct mutex status_lock;
enum msm_battery_status batt_status;
struct msm_hardware_charger_priv *current_chg_priv;
struct msm_hardware_charger_priv *current_mon_priv;
unsigned int (*get_batt_capacity_percent) (void);
struct msm_charger_event *queue;
int tail;
int head;
spinlock_t queue_lock;
int queue_count;
struct work_struct queue_work;
struct workqueue_struct *event_wq_thread;
struct wake_lock wl;
};
static struct msm_charger_mux msm_chg;
static struct msm_battery_gauge *msm_batt_gauge;
static int is_chg_capable_of_charging(struct msm_hardware_charger_priv *priv)
{
if (priv->hw_chg_state == CHG_READY_STATE
|| priv->hw_chg_state == CHG_CHARGING_STATE)
return 1;
return 0;
}
static int is_batt_status_capable_of_charging(void)
{
if (msm_chg.batt_status == BATT_STATUS_ABSENT
|| msm_chg.batt_status == BATT_STATUS_TEMPERATURE_OUT_OF_RANGE
|| msm_chg.batt_status == BATT_STATUS_ID_INVALID
|| msm_chg.batt_status == BATT_STATUS_JUST_FINISHED_CHARGING)
return 0;
return 1;
}
static int is_batt_status_charging(void)
{
if (msm_chg.batt_status == BATT_STATUS_TRKL_CHARGING
|| msm_chg.batt_status == BATT_STATUS_FAST_CHARGING)
return 1;
return 0;
}
static int is_battery_present(void)
{
if (msm_batt_gauge && msm_batt_gauge->is_battery_present)
return msm_batt_gauge->is_battery_present();
else {
pr_err("msm-charger: no batt gauge batt=absent\n");
return 0;
}
}
static int is_battery_temp_within_range(void)
{
if (msm_batt_gauge && msm_batt_gauge->is_battery_temp_within_range)
return msm_batt_gauge->is_battery_temp_within_range();
else {
pr_err("msm-charger no batt gauge batt=out_of_temperatur\n");
return 0;
}
}
static int is_battery_id_valid(void)
{
if (msm_batt_gauge && msm_batt_gauge->is_battery_id_valid)
return msm_batt_gauge->is_battery_id_valid();
else {
pr_err("msm-charger no batt gauge batt=id_invalid\n");
return 0;
}
}
static int get_prop_battery_mvolts(void)
{
if (msm_batt_gauge && msm_batt_gauge->get_battery_mvolts)
return msm_batt_gauge->get_battery_mvolts();
else {
pr_err("msm-charger no batt gauge assuming 3.5V\n");
return MSM_CHARGER_GAUGE_MISSING_VOLTS;
}
}
static int get_battery_temperature(void)
{
if (msm_batt_gauge && msm_batt_gauge->get_battery_temperature)
return msm_batt_gauge->get_battery_temperature();
else {
pr_err("msm-charger no batt gauge assuming 35 deg G\n");
return MSM_CHARGER_GAUGE_MISSING_TEMP;
}
}
static int get_prop_batt_capacity(void)
{
int capacity;
if (msm_batt_gauge && msm_batt_gauge->get_batt_remaining_capacity)
capacity = msm_batt_gauge->get_batt_remaining_capacity();
else
capacity = msm_chg.get_batt_capacity_percent();
if (capacity <= 10)
pr_err("battery capacity very low = %d\n", capacity);
return capacity;
}
static int get_prop_batt_health(void)
{
int status = 0;
if (msm_chg.batt_status == BATT_STATUS_TEMPERATURE_OUT_OF_RANGE)
status = POWER_SUPPLY_HEALTH_OVERHEAT;
else
status = POWER_SUPPLY_HEALTH_GOOD;
return status;
}
static int get_prop_charge_type(void)
{
int status = 0;
if (msm_chg.batt_status == BATT_STATUS_TRKL_CHARGING)
status = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
else if (msm_chg.batt_status == BATT_STATUS_FAST_CHARGING)
status = POWER_SUPPLY_CHARGE_TYPE_FAST;
else
status = POWER_SUPPLY_CHARGE_TYPE_NONE;
return status;
}
static int get_prop_batt_status(void)
{
int status = 0;
if (msm_batt_gauge && msm_batt_gauge->get_battery_status) {
status = msm_batt_gauge->get_battery_status();
if (status == POWER_SUPPLY_STATUS_CHARGING ||
status == POWER_SUPPLY_STATUS_FULL ||
status == POWER_SUPPLY_STATUS_DISCHARGING)
return status;
}
if (is_batt_status_charging())
status = POWER_SUPPLY_STATUS_CHARGING;
else if (msm_chg.batt_status ==
BATT_STATUS_JUST_FINISHED_CHARGING
&& msm_chg.current_chg_priv != NULL)
status = POWER_SUPPLY_STATUS_FULL;
else
status = POWER_SUPPLY_STATUS_DISCHARGING;
return status;
}
/* This function should only be called within handle_event or resume */
static void update_batt_status(void)
{
if (is_battery_present()) {
if (is_battery_id_valid()) {
if (msm_chg.batt_status == BATT_STATUS_ABSENT
|| msm_chg.batt_status
== BATT_STATUS_ID_INVALID) {
msm_chg.batt_status = BATT_STATUS_DISCHARGING;
}
} else
msm_chg.batt_status = BATT_STATUS_ID_INVALID;
} else
msm_chg.batt_status = BATT_STATUS_ABSENT;
}
static enum power_supply_property msm_power_props[] = {
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
};
static char *msm_power_supplied_to[] = {
"battery",
};
static int msm_power_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct msm_hardware_charger_priv *priv;
priv = container_of(psy, struct msm_hardware_charger_priv, psy);
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !(priv->hw_chg_state == CHG_ABSENT_STATE);
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = (priv->hw_chg_state == CHG_READY_STATE)
|| (priv->hw_chg_state == CHG_CHARGING_STATE);
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property msm_batt_power_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CAPACITY,
};
static int msm_batt_power_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = get_prop_batt_status();
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = get_prop_charge_type();
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = get_prop_batt_health();
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !(msm_chg.batt_status == BATT_STATUS_ABSENT);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
val->intval = msm_chg.max_voltage * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = msm_chg.min_voltage * 1000;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = get_prop_battery_mvolts();
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = get_prop_batt_capacity();
break;
default:
return -EINVAL;
}
return 0;
}
static struct power_supply msm_psy_batt = {
.name = "battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = msm_batt_power_props,
.num_properties = ARRAY_SIZE(msm_batt_power_props),
.get_property = msm_batt_power_get_property,
};
static int usb_chg_current;
static struct msm_hardware_charger_priv *usb_hw_chg_priv;
static void (*notify_vbus_state_func_ptr)(int);
static int usb_notified_of_insertion;
/* this is passed to the hsusb via platform_data msm_otg_pdata */
int msm_charger_register_vbus_sn(void (*callback)(int))
{
pr_debug(KERN_INFO "%s\n", __func__);
notify_vbus_state_func_ptr = callback;
return 0;
}
/* this is passed to the hsusb via platform_data msm_otg_pdata */
void msm_charger_unregister_vbus_sn(void (*callback)(int))
{
pr_debug(KERN_INFO "%s\n", __func__);
notify_vbus_state_func_ptr = NULL;
}
static void notify_usb_of_the_plugin_event(struct msm_hardware_charger_priv
*hw_chg, int plugin)
{
plugin = !!plugin;
if (plugin == 1 && usb_notified_of_insertion == 0) {
usb_notified_of_insertion = 1;
if (notify_vbus_state_func_ptr) {
dev_dbg(msm_chg.dev, "%s notifying plugin\n", __func__);
(*notify_vbus_state_func_ptr) (plugin);
} else
dev_dbg(msm_chg.dev, "%s unable to notify plugin\n",
__func__);
usb_hw_chg_priv = hw_chg;
}
if (plugin == 0 && usb_notified_of_insertion == 1) {
if (notify_vbus_state_func_ptr) {
dev_dbg(msm_chg.dev, "%s notifying unplugin\n",
__func__);
(*notify_vbus_state_func_ptr) (plugin);
} else
dev_dbg(msm_chg.dev, "%s unable to notify unplugin\n",
__func__);
usb_notified_of_insertion = 0;
usb_hw_chg_priv = NULL;
}
}
static unsigned int msm_chg_get_batt_capacity_percent(void)
{
unsigned int current_voltage = get_prop_battery_mvolts();
unsigned int low_voltage = msm_chg.min_voltage;
unsigned int high_voltage = msm_chg.max_voltage;
if (current_voltage <= low_voltage)
return 0;
else if (current_voltage >= high_voltage)
return 100;
else
return (current_voltage - low_voltage) * 100
/ (high_voltage - low_voltage);
}
#ifdef DEBUG
static inline void debug_print(const char *func,
struct msm_hardware_charger_priv *hw_chg_priv)
{
dev_dbg(msm_chg.dev,
"%s current=(%s)(s=%d)(r=%d) new=(%s)(s=%d)(r=%d) batt=%d En\n",
func,
msm_chg.current_chg_priv ? msm_chg.current_chg_priv->
hw_chg->name : "none",
msm_chg.current_chg_priv ? msm_chg.
current_chg_priv->hw_chg_state : -1,
msm_chg.current_chg_priv ? msm_chg.current_chg_priv->
hw_chg->rating : -1,
hw_chg_priv ? hw_chg_priv->hw_chg->name : "none",
hw_chg_priv ? hw_chg_priv->hw_chg_state : -1,
hw_chg_priv ? hw_chg_priv->hw_chg->rating : -1,
msm_chg.batt_status);
}
#else
static inline void debug_print(const char *func,
struct msm_hardware_charger_priv *hw_chg_priv)
{
}
#endif
static struct msm_hardware_charger_priv *find_best_charger(void)
{
struct msm_hardware_charger_priv *hw_chg_priv;
struct msm_hardware_charger_priv *better;
int rating;
better = NULL;
rating = 0;
list_for_each_entry(hw_chg_priv, &msm_chg.msm_hardware_chargers, list) {
if (is_chg_capable_of_charging(hw_chg_priv)) {
if (hw_chg_priv->hw_chg->rating > rating) {
rating = hw_chg_priv->hw_chg->rating;
better = hw_chg_priv;
}
}
}
return better;
}
static int msm_charging_switched(struct msm_hardware_charger_priv *priv)
{
int ret = 0;
if (priv->hw_chg->charging_switched)
ret = priv->hw_chg->charging_switched(priv->hw_chg);
return ret;
}
static int msm_stop_charging(struct msm_hardware_charger_priv *priv)
{
int ret;
ret = priv->hw_chg->stop_charging(priv->hw_chg);
if (!ret)
wake_unlock(&msm_chg.wl);
return ret;
}
static void msm_enable_system_current(struct msm_hardware_charger_priv *priv)
{
if (priv->hw_chg->start_system_current)
priv->hw_chg->start_system_current(priv->hw_chg,
priv->max_source_current);
}
static void msm_disable_system_current(struct msm_hardware_charger_priv *priv)
{
if (priv->hw_chg->stop_system_current)
priv->hw_chg->stop_system_current(priv->hw_chg);
}
/* the best charger has been selected -start charging from current_chg_priv */
static int msm_start_charging(void)
{
int ret;
struct msm_hardware_charger_priv *priv;
priv = msm_chg.current_chg_priv;
wake_lock(&msm_chg.wl);
ret = priv->hw_chg->start_charging(priv->hw_chg, msm_chg.max_voltage,
priv->max_source_current);
if (ret) {
wake_unlock(&msm_chg.wl);
dev_err(msm_chg.dev, "%s couldnt start chg error = %d\n",
priv->hw_chg->name, ret);
} else
priv->hw_chg_state = CHG_CHARGING_STATE;
return ret;
}
static void handle_charging_done(struct msm_hardware_charger_priv *priv)
{
if (msm_chg.current_chg_priv == priv) {
if (msm_chg.current_chg_priv->hw_chg_state ==
CHG_CHARGING_STATE)
if (msm_stop_charging(msm_chg.current_chg_priv)) {
dev_err(msm_chg.dev, "%s couldnt stop chg\n",
msm_chg.current_chg_priv->hw_chg->name);
}
msm_chg.current_chg_priv->hw_chg_state = CHG_READY_STATE;
msm_chg.batt_status = BATT_STATUS_JUST_FINISHED_CHARGING;
dev_info(msm_chg.dev, "%s: stopping safety timer work\n",
__func__);
cancel_delayed_work(&msm_chg.teoc_work);
if (msm_batt_gauge && msm_batt_gauge->monitor_for_recharging)
msm_batt_gauge->monitor_for_recharging();
else
dev_err(msm_chg.dev,
"%s: no batt gauge recharge monitor\n", __func__);
}
}
static void teoc(struct work_struct *work)
{
/* we have been charging too long - stop charging */
dev_info(msm_chg.dev, "%s: safety timer work expired\n", __func__);
mutex_lock(&msm_chg.status_lock);
if (msm_chg.current_chg_priv != NULL
&& msm_chg.current_chg_priv->hw_chg_state == CHG_CHARGING_STATE) {
handle_charging_done(msm_chg.current_chg_priv);
}
mutex_unlock(&msm_chg.status_lock);
}
static void handle_battery_inserted(void)
{
/* if a charger is already present start charging */
if (msm_chg.current_chg_priv != NULL &&
is_batt_status_capable_of_charging() &&
!is_batt_status_charging()) {
if (msm_start_charging()) {
dev_err(msm_chg.dev, "%s couldnt start chg\n",
msm_chg.current_chg_priv->hw_chg->name);
return;
}
msm_chg.batt_status = BATT_STATUS_TRKL_CHARGING;
dev_info(msm_chg.dev, "%s: starting safety timer work\n",
__func__);
queue_delayed_work(msm_chg.event_wq_thread,
&msm_chg.teoc_work,
round_jiffies_relative(msecs_to_jiffies
(msm_chg.
safety_time)));
}
}
static void handle_battery_removed(void)
{
/* if a charger is charging the battery stop it */
if (msm_chg.current_chg_priv != NULL
&& msm_chg.current_chg_priv->hw_chg_state == CHG_CHARGING_STATE) {
if (msm_stop_charging(msm_chg.current_chg_priv)) {
dev_err(msm_chg.dev, "%s couldnt stop chg\n",
msm_chg.current_chg_priv->hw_chg->name);
}
msm_chg.current_chg_priv->hw_chg_state = CHG_READY_STATE;
dev_info(msm_chg.dev, "%s: stopping safety timer work\n",
__func__);
cancel_delayed_work(&msm_chg.teoc_work);
}
}
static void update_heartbeat(struct work_struct *work)
{
int temperature;
if (msm_chg.batt_status == BATT_STATUS_ABSENT
|| msm_chg.batt_status == BATT_STATUS_ID_INVALID) {
if (is_battery_present())
if (is_battery_id_valid()) {
msm_chg.batt_status = BATT_STATUS_DISCHARGING;
handle_battery_inserted();
}
} else {
if (!is_battery_present()) {
msm_chg.batt_status = BATT_STATUS_ABSENT;
handle_battery_removed();
}
/*
* check battery id because a good battery could be removed
* and replaced with a invalid battery.
*/
if (!is_battery_id_valid()) {
msm_chg.batt_status = BATT_STATUS_ID_INVALID;
handle_battery_removed();
}
}
pr_debug("msm-charger %s batt_status= %d\n",
__func__, msm_chg.batt_status);
if (msm_chg.current_chg_priv
&& msm_chg.current_chg_priv->hw_chg_state
== CHG_CHARGING_STATE) {
temperature = get_battery_temperature();
/* TODO implement JEITA SPEC*/
}
/* notify that the voltage has changed
* the read of the capacity will trigger a
* voltage read*/
power_supply_changed(&msm_psy_batt);
if (msm_chg.stop_update) {
msm_chg.stop_update = 0;
return;
}
queue_delayed_work(msm_chg.event_wq_thread,
&msm_chg.update_heartbeat_work,
round_jiffies_relative(msecs_to_jiffies
(msm_chg.update_time)));
}
/* set the charger state to READY before calling this */
static void handle_charger_ready(struct msm_hardware_charger_priv *hw_chg_priv)
{
struct msm_hardware_charger_priv *old_chg_priv = NULL;
debug_print(__func__, hw_chg_priv);
if (msm_chg.current_chg_priv != NULL
&& hw_chg_priv->hw_chg->rating >
msm_chg.current_chg_priv->hw_chg->rating) {
/*
* a better charger was found, ask the current charger
* to stop charging if it was charging
*/
if (msm_chg.current_chg_priv->hw_chg_state ==
CHG_CHARGING_STATE) {
if (msm_stop_charging(msm_chg.current_chg_priv)) {
dev_err(msm_chg.dev, "%s couldnt stop chg\n",
msm_chg.current_chg_priv->hw_chg->name);
return;
}
if (msm_charging_switched(msm_chg.current_chg_priv)) {
dev_err(msm_chg.dev, "%s couldnt stop chg\n",
msm_chg.current_chg_priv->hw_chg->name);
return;
}
}
msm_chg.current_chg_priv->hw_chg_state = CHG_READY_STATE;
old_chg_priv = msm_chg.current_chg_priv;
msm_chg.current_chg_priv = NULL;
}
if (msm_chg.current_chg_priv == NULL) {
msm_chg.current_chg_priv = hw_chg_priv;
dev_info(msm_chg.dev,
"%s: best charger = %s\n", __func__,
msm_chg.current_chg_priv->hw_chg->name);
msm_enable_system_current(msm_chg.current_chg_priv);
/*
* since a better charger was chosen, ask the old
* charger to stop providing system current
*/
if (old_chg_priv != NULL)
msm_disable_system_current(old_chg_priv);
if (!is_batt_status_capable_of_charging())
return;
/* start charging from the new charger */
if (!msm_start_charging()) {
/* if we simply switched chg continue with teoc timer
* else we update the batt state and set the teoc
* timer */
if (!is_batt_status_charging()) {
dev_info(msm_chg.dev,
"%s: starting safety timer\n", __func__);
queue_delayed_work(msm_chg.event_wq_thread,
&msm_chg.teoc_work,
round_jiffies_relative
(msecs_to_jiffies
(msm_chg.safety_time)));
msm_chg.batt_status = BATT_STATUS_TRKL_CHARGING;
}
} else {
/* we couldnt start charging from the new readied
* charger */
if (is_batt_status_charging())
msm_chg.batt_status = BATT_STATUS_DISCHARGING;
}
}
}
static void handle_charger_removed(struct msm_hardware_charger_priv
*hw_chg_removed, int new_state)
{
struct msm_hardware_charger_priv *hw_chg_priv;
debug_print(__func__, hw_chg_removed);
if (msm_chg.current_chg_priv == hw_chg_removed) {
msm_disable_system_current(hw_chg_removed);
if (msm_chg.current_chg_priv->hw_chg_state
== CHG_CHARGING_STATE) {
if (msm_stop_charging(hw_chg_removed)) {
dev_err(msm_chg.dev, "%s couldnt stop chg\n",
msm_chg.current_chg_priv->hw_chg->name);
}
}
msm_chg.current_chg_priv = NULL;
}
hw_chg_removed->hw_chg_state = new_state;
if (msm_chg.current_chg_priv == NULL) {
hw_chg_priv = find_best_charger();
if (hw_chg_priv == NULL) {
dev_info(msm_chg.dev, "%s: no chargers\n", __func__);
/* if the battery was Just finished charging
* we keep that state as is so that we dont rush
* in to charging the battery when a charger is
* plugged in shortly. */
if (is_batt_status_charging())
msm_chg.batt_status = BATT_STATUS_DISCHARGING;
} else {
msm_chg.current_chg_priv = hw_chg_priv;
msm_enable_system_current(hw_chg_priv);
dev_info(msm_chg.dev,
"%s: best charger = %s\n", __func__,
msm_chg.current_chg_priv->hw_chg->name);
if (!is_batt_status_capable_of_charging())
return;
if (msm_start_charging()) {
/* we couldnt start charging for some reason */
msm_chg.batt_status = BATT_STATUS_DISCHARGING;
}
}
}
/* if we arent charging stop the safety timer */
if (!is_batt_status_charging()) {
dev_info(msm_chg.dev, "%s: stopping safety timer work\n",
__func__);
cancel_delayed_work(&msm_chg.teoc_work);
}
}
static void handle_event(struct msm_hardware_charger *hw_chg, int event)
{
struct msm_hardware_charger_priv *priv = NULL;
/*
* if hw_chg is NULL then this event comes from non-charger
* parties like battery gauge
*/
if (hw_chg)
priv = hw_chg->charger_private;
mutex_lock(&msm_chg.status_lock);
switch (event) {
case CHG_INSERTED_EVENT:
if (priv->hw_chg_state != CHG_ABSENT_STATE) {
dev_info(msm_chg.dev,
"%s insertion detected when cbl present",
hw_chg->name);
break;
}
update_batt_status();
if (hw_chg->type == CHG_TYPE_USB) {
priv->hw_chg_state = CHG_PRESENT_STATE;
notify_usb_of_the_plugin_event(priv, 1);
if (usb_chg_current) {
priv->max_source_current = usb_chg_current;
usb_chg_current = 0;
/* usb has already indicated us to charge */
priv->hw_chg_state = CHG_READY_STATE;
handle_charger_ready(priv);
}
} else {
priv->hw_chg_state = CHG_READY_STATE;
handle_charger_ready(priv);
}
break;
case CHG_ENUMERATED_EVENT: /* only in USB types */
if (priv->hw_chg_state == CHG_ABSENT_STATE) {
dev_info(msm_chg.dev, "%s enum withuot presence\n",
hw_chg->name);
break;
}
update_batt_status();
dev_dbg(msm_chg.dev, "%s enum with %dmA to draw\n",
hw_chg->name, priv->max_source_current);
if (priv->max_source_current == 0) {
/* usb subsystem doesnt want us to draw
* charging current */
/* act as if the charge is removed */
if (priv->hw_chg_state != CHG_PRESENT_STATE)
handle_charger_removed(priv, CHG_PRESENT_STATE);
} else {
if (priv->hw_chg_state != CHG_READY_STATE) {
priv->hw_chg_state = CHG_READY_STATE;
handle_charger_ready(priv);
}
}
break;
case CHG_REMOVED_EVENT:
if (priv->hw_chg_state == CHG_ABSENT_STATE) {
dev_info(msm_chg.dev, "%s cable already removed\n",
hw_chg->name);
break;
}
update_batt_status();
if (hw_chg->type == CHG_TYPE_USB) {
usb_chg_current = 0;
notify_usb_of_the_plugin_event(priv, 0);
}
handle_charger_removed(priv, CHG_ABSENT_STATE);
break;
case CHG_DONE_EVENT:
if (priv->hw_chg_state == CHG_CHARGING_STATE)
handle_charging_done(priv);
break;
case CHG_BATT_BEGIN_FAST_CHARGING:
/* only update if we are TRKL charging */
if (msm_chg.batt_status == BATT_STATUS_TRKL_CHARGING)
msm_chg.batt_status = BATT_STATUS_FAST_CHARGING;
break;
case CHG_BATT_NEEDS_RECHARGING:
msm_chg.batt_status = BATT_STATUS_DISCHARGING;
handle_battery_inserted();
priv = msm_chg.current_chg_priv;
break;
case CHG_BATT_TEMP_OUTOFRANGE:
/* the batt_temp out of range can trigger
* when the battery is absent */
if (!is_battery_present()
&& msm_chg.batt_status != BATT_STATUS_ABSENT) {
msm_chg.batt_status = BATT_STATUS_ABSENT;
handle_battery_removed();
break;
}
if (msm_chg.batt_status == BATT_STATUS_TEMPERATURE_OUT_OF_RANGE)
break;
msm_chg.batt_status = BATT_STATUS_TEMPERATURE_OUT_OF_RANGE;
handle_battery_removed();
break;
case CHG_BATT_TEMP_INRANGE:
if (msm_chg.batt_status != BATT_STATUS_TEMPERATURE_OUT_OF_RANGE)
break;
msm_chg.batt_status = BATT_STATUS_ID_INVALID;
/* check id */
if (!is_battery_id_valid())
break;
/* assume that we are discharging from the battery
* and act as if the battery was inserted
* if a charger is present charging will be resumed */
msm_chg.batt_status = BATT_STATUS_DISCHARGING;
handle_battery_inserted();
break;
case CHG_BATT_INSERTED:
if (msm_chg.batt_status != BATT_STATUS_ABSENT)
break;
/* debounce */
if (!is_battery_present())
break;
msm_chg.batt_status = BATT_STATUS_ID_INVALID;
if (!is_battery_id_valid())
break;
/* assume that we are discharging from the battery */
msm_chg.batt_status = BATT_STATUS_DISCHARGING;
/* check if a charger is present */
handle_battery_inserted();
break;
case CHG_BATT_REMOVED:
if (msm_chg.batt_status == BATT_STATUS_ABSENT)
break;
/* debounce */
if (is_battery_present())
break;
msm_chg.batt_status = BATT_STATUS_ABSENT;
handle_battery_removed();
break;
case CHG_BATT_STATUS_CHANGE:
/* TODO battery SOC like battery-alarm/charging-full features
can be added here for future improvement */
break;
}
dev_dbg(msm_chg.dev, "%s %d done batt_status=%d\n", __func__,
event, msm_chg.batt_status);
/* update userspace */
if (msm_batt_gauge)
power_supply_changed(&msm_psy_batt);
if (priv)
power_supply_changed(&priv->psy);
mutex_unlock(&msm_chg.status_lock);
}
static int msm_chg_dequeue_event(struct msm_charger_event **event)
{
unsigned long flags;
spin_lock_irqsave(&msm_chg.queue_lock, flags);
if (msm_chg.queue_count == 0) {
spin_unlock_irqrestore(&msm_chg.queue_lock, flags);
return -EINVAL;
}
*event = &msm_chg.queue[msm_chg.head];
msm_chg.head = (msm_chg.head + 1) % MSM_CHG_MAX_EVENTS;
pr_debug("%s dequeueing %d\n", __func__, (*event)->event);
msm_chg.queue_count--;
spin_unlock_irqrestore(&msm_chg.queue_lock, flags);
return 0;
}
static int msm_chg_enqueue_event(struct msm_hardware_charger *hw_chg,
enum msm_hardware_charger_event event)
{
unsigned long flags;
spin_lock_irqsave(&msm_chg.queue_lock, flags);
if (msm_chg.queue_count == MSM_CHG_MAX_EVENTS) {
spin_unlock_irqrestore(&msm_chg.queue_lock, flags);
pr_err("%s: queue full cannot enqueue %d\n",
__func__, event);
return -EAGAIN;
}
pr_debug("%s queueing %d\n", __func__, event);
msm_chg.queue[msm_chg.tail].event = event;
msm_chg.queue[msm_chg.tail].hw_chg = hw_chg;
msm_chg.tail = (msm_chg.tail + 1)%MSM_CHG_MAX_EVENTS;
msm_chg.queue_count++;
spin_unlock_irqrestore(&msm_chg.queue_lock, flags);
return 0;
}
static void process_events(struct work_struct *work)
{
struct msm_charger_event *event;
int rc;
do {
rc = msm_chg_dequeue_event(&event);
if (!rc)
handle_event(event->hw_chg, event->event);
} while (!rc);
}
/* USB calls these to tell us how much charging current we should draw */
void msm_charger_vbus_draw(unsigned int mA)
{
if (usb_hw_chg_priv) {
usb_hw_chg_priv->max_source_current = mA;
msm_charger_notify_event(usb_hw_chg_priv->hw_chg,
CHG_ENUMERATED_EVENT);
} else
/* remember the current, to be used when charger is ready */
usb_chg_current = mA;
}
static int determine_initial_batt_status(void)
{
if (is_battery_present())
if (is_battery_id_valid())
if (is_battery_temp_within_range())
msm_chg.batt_status = BATT_STATUS_DISCHARGING;
else
msm_chg.batt_status
= BATT_STATUS_TEMPERATURE_OUT_OF_RANGE;
else
msm_chg.batt_status = BATT_STATUS_ID_INVALID;
else
msm_chg.batt_status = BATT_STATUS_ABSENT;
if (is_batt_status_capable_of_charging())
handle_battery_inserted();
/* start updaing the battery powersupply every msm_chg.update_time
* milliseconds */
queue_delayed_work(msm_chg.event_wq_thread,
&msm_chg.update_heartbeat_work,
round_jiffies_relative(msecs_to_jiffies
(msm_chg.update_time)));
pr_debug("%s:OK batt_status=%d\n", __func__, msm_chg.batt_status);
return 0;
}
static int 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 msm_charger_remove(struct platform_device *pdev)
{
wake_lock_destroy(&msm_chg.wl);
mutex_destroy(&msm_chg.status_lock);
power_supply_unregister(&msm_psy_batt);
return 0;
}
int msm_charger_notify_event(struct msm_hardware_charger *hw_chg,
enum msm_hardware_charger_event event)
{
msm_chg_enqueue_event(hw_chg, event);
queue_work(msm_chg.event_wq_thread, &msm_chg.queue_work);
return 0;
}
EXPORT_SYMBOL(msm_charger_notify_event);
int msm_charger_register(struct msm_hardware_charger *hw_chg)
{
struct msm_hardware_charger_priv *priv;
int rc = 0;
if (!msm_chg.inited) {
pr_err("%s: msm_chg is NULL,Too early to register\n", __func__);
return -EAGAIN;
}
if (hw_chg->start_charging == NULL
|| hw_chg->stop_charging == NULL
|| hw_chg->name == NULL
|| hw_chg->rating == 0) {
pr_err("%s: invalid hw_chg\n", __func__);
return -EINVAL;
}
priv = kzalloc(sizeof *priv, GFP_KERNEL);
if (priv == NULL) {
dev_err(msm_chg.dev, "%s kzalloc failed\n", __func__);
return -ENOMEM;
}
priv->psy.name = hw_chg->name;
if (hw_chg->type == CHG_TYPE_USB)
priv->psy.type = POWER_SUPPLY_TYPE_USB;
else
priv->psy.type = POWER_SUPPLY_TYPE_MAINS;
priv->psy.supplied_to = msm_power_supplied_to;
priv->psy.num_supplicants = ARRAY_SIZE(msm_power_supplied_to);
priv->psy.properties = msm_power_props;
priv->psy.num_properties = ARRAY_SIZE(msm_power_props);
priv->psy.get_property = msm_power_get_property;
rc = power_supply_register(NULL, &priv->psy);
if (rc) {
dev_err(msm_chg.dev, "%s power_supply_register failed\n",
__func__);
goto out;
}
priv->hw_chg = hw_chg;
priv->hw_chg_state = CHG_ABSENT_STATE;
INIT_LIST_HEAD(&priv->list);
mutex_lock(&msm_chg.msm_hardware_chargers_lock);
list_add_tail(&priv->list, &msm_chg.msm_hardware_chargers);
mutex_unlock(&msm_chg.msm_hardware_chargers_lock);
hw_chg->charger_private = (void *)priv;
return 0;
out:
kfree(priv);
return rc;
}
EXPORT_SYMBOL(msm_charger_register);
void msm_battery_gauge_register(struct msm_battery_gauge *batt_gauge)
{
int rc;
if (msm_batt_gauge) {
msm_batt_gauge = batt_gauge;
pr_err("msm-charger %s multiple battery gauge called\n",
__func__);
} else {
rc = power_supply_register(msm_chg.dev, &msm_psy_batt);
if (rc < 0) {
dev_err(msm_chg.dev, "%s: power_supply_register failed"
" rc=%d\n", __func__, rc);
return;
}
msm_batt_gauge = batt_gauge;
determine_initial_batt_status();
}
}
EXPORT_SYMBOL(msm_battery_gauge_register);
void msm_battery_gauge_unregister(struct msm_battery_gauge *batt_gauge)
{
msm_batt_gauge = NULL;
}
EXPORT_SYMBOL(msm_battery_gauge_unregister);
int msm_charger_unregister(struct msm_hardware_charger *hw_chg)
{
struct msm_hardware_charger_priv *priv;
priv = (struct msm_hardware_charger_priv *)(hw_chg->charger_private);
mutex_lock(&msm_chg.msm_hardware_chargers_lock);
list_del(&priv->list);
mutex_unlock(&msm_chg.msm_hardware_chargers_lock);
power_supply_unregister(&priv->psy);
kfree(priv);
return 0;
}
EXPORT_SYMBOL(msm_charger_unregister);
static int msm_charger_suspend(struct device *dev)
{
dev_dbg(msm_chg.dev, "%s suspended\n", __func__);
msm_chg.stop_update = 1;
cancel_delayed_work(&msm_chg.update_heartbeat_work);
mutex_lock(&msm_chg.status_lock);
handle_battery_removed();
mutex_unlock(&msm_chg.status_lock);
return 0;
}
static int msm_charger_resume(struct device *dev)
{
dev_dbg(msm_chg.dev, "%s resumed\n", __func__);
msm_chg.stop_update = 0;
/* start updaing the battery powersupply every msm_chg.update_time
* milliseconds */
queue_delayed_work(msm_chg.event_wq_thread,
&msm_chg.update_heartbeat_work,
round_jiffies_relative(msecs_to_jiffies
(msm_chg.update_time)));
mutex_lock(&msm_chg.status_lock);
handle_battery_inserted();
mutex_unlock(&msm_chg.status_lock);
return 0;
}
static SIMPLE_DEV_PM_OPS(msm_charger_pm_ops,
msm_charger_suspend, msm_charger_resume);
static struct platform_driver msm_charger_driver = {
.probe = msm_charger_probe,
.remove = 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");