1287 lines
35 KiB
C
1287 lines
35 KiB
C
/* 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 __devinit msm_charger_probe(struct platform_device *pdev)
|
|
{
|
|
msm_chg.dev = &pdev->dev;
|
|
if (pdev->dev.platform_data) {
|
|
unsigned int milli_secs;
|
|
|
|
struct msm_charger_platform_data *pdata
|
|
=
|
|
(struct msm_charger_platform_data *)pdev->dev.platform_data;
|
|
|
|
milli_secs = pdata->safety_time * 60 * MSEC_PER_SEC;
|
|
if (milli_secs > jiffies_to_msecs(MAX_JIFFY_OFFSET)) {
|
|
dev_warn(&pdev->dev, "%s: safety time too large"
|
|
"%dms\n", __func__, milli_secs);
|
|
milli_secs = jiffies_to_msecs(MAX_JIFFY_OFFSET);
|
|
}
|
|
msm_chg.safety_time = milli_secs;
|
|
|
|
milli_secs = pdata->update_time * 60 * MSEC_PER_SEC;
|
|
if (milli_secs > jiffies_to_msecs(MAX_JIFFY_OFFSET)) {
|
|
dev_warn(&pdev->dev, "%s: safety time too large"
|
|
"%dms\n", __func__, milli_secs);
|
|
milli_secs = jiffies_to_msecs(MAX_JIFFY_OFFSET);
|
|
}
|
|
msm_chg.update_time = milli_secs;
|
|
|
|
msm_chg.max_voltage = pdata->max_voltage;
|
|
msm_chg.min_voltage = pdata->min_voltage;
|
|
msm_chg.get_batt_capacity_percent =
|
|
pdata->get_batt_capacity_percent;
|
|
}
|
|
if (msm_chg.safety_time == 0)
|
|
msm_chg.safety_time = CHARGING_TEOC_MS;
|
|
if (msm_chg.update_time == 0)
|
|
msm_chg.update_time = UPDATE_TIME_MS;
|
|
if (msm_chg.max_voltage == 0)
|
|
msm_chg.max_voltage = DEFAULT_BATT_MAX_V;
|
|
if (msm_chg.min_voltage == 0)
|
|
msm_chg.min_voltage = DEFAULT_BATT_MIN_V;
|
|
if (msm_chg.get_batt_capacity_percent == NULL)
|
|
msm_chg.get_batt_capacity_percent =
|
|
msm_chg_get_batt_capacity_percent;
|
|
|
|
mutex_init(&msm_chg.status_lock);
|
|
INIT_DELAYED_WORK(&msm_chg.teoc_work, teoc);
|
|
INIT_DELAYED_WORK(&msm_chg.update_heartbeat_work, update_heartbeat);
|
|
|
|
wake_lock_init(&msm_chg.wl, WAKE_LOCK_SUSPEND, "msm_charger");
|
|
return 0;
|
|
}
|
|
|
|
static int __devexit msm_charger_remove(struct platform_device *pdev)
|
|
{
|
|
wake_lock_destroy(&msm_chg.wl);
|
|
mutex_destroy(&msm_chg.status_lock);
|
|
power_supply_unregister(&msm_psy_batt);
|
|
return 0;
|
|
}
|
|
|
|
int msm_charger_notify_event(struct msm_hardware_charger *hw_chg,
|
|
enum msm_hardware_charger_event event)
|
|
{
|
|
msm_chg_enqueue_event(hw_chg, event);
|
|
queue_work(msm_chg.event_wq_thread, &msm_chg.queue_work);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_charger_notify_event);
|
|
|
|
int msm_charger_register(struct msm_hardware_charger *hw_chg)
|
|
{
|
|
struct msm_hardware_charger_priv *priv;
|
|
int rc = 0;
|
|
|
|
if (!msm_chg.inited) {
|
|
pr_err("%s: msm_chg is NULL,Too early to register\n", __func__);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
if (hw_chg->start_charging == NULL
|
|
|| hw_chg->stop_charging == NULL
|
|
|| hw_chg->name == NULL
|
|
|| hw_chg->rating == 0) {
|
|
pr_err("%s: invalid hw_chg\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv = kzalloc(sizeof *priv, GFP_KERNEL);
|
|
if (priv == NULL) {
|
|
dev_err(msm_chg.dev, "%s kzalloc failed\n", __func__);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->psy.name = hw_chg->name;
|
|
if (hw_chg->type == CHG_TYPE_USB)
|
|
priv->psy.type = POWER_SUPPLY_TYPE_USB;
|
|
else
|
|
priv->psy.type = POWER_SUPPLY_TYPE_MAINS;
|
|
|
|
priv->psy.supplied_to = msm_power_supplied_to;
|
|
priv->psy.num_supplicants = ARRAY_SIZE(msm_power_supplied_to);
|
|
priv->psy.properties = msm_power_props;
|
|
priv->psy.num_properties = ARRAY_SIZE(msm_power_props);
|
|
priv->psy.get_property = msm_power_get_property;
|
|
|
|
rc = power_supply_register(NULL, &priv->psy);
|
|
if (rc) {
|
|
dev_err(msm_chg.dev, "%s power_supply_register failed\n",
|
|
__func__);
|
|
goto out;
|
|
}
|
|
|
|
priv->hw_chg = hw_chg;
|
|
priv->hw_chg_state = CHG_ABSENT_STATE;
|
|
INIT_LIST_HEAD(&priv->list);
|
|
mutex_lock(&msm_chg.msm_hardware_chargers_lock);
|
|
list_add_tail(&priv->list, &msm_chg.msm_hardware_chargers);
|
|
mutex_unlock(&msm_chg.msm_hardware_chargers_lock);
|
|
hw_chg->charger_private = (void *)priv;
|
|
return 0;
|
|
|
|
out:
|
|
kfree(priv);
|
|
return rc;
|
|
}
|
|
EXPORT_SYMBOL(msm_charger_register);
|
|
|
|
void msm_battery_gauge_register(struct msm_battery_gauge *batt_gauge)
|
|
{
|
|
int rc;
|
|
|
|
if (msm_batt_gauge) {
|
|
msm_batt_gauge = batt_gauge;
|
|
pr_err("msm-charger %s multiple battery gauge called\n",
|
|
__func__);
|
|
} else {
|
|
rc = power_supply_register(msm_chg.dev, &msm_psy_batt);
|
|
if (rc < 0) {
|
|
dev_err(msm_chg.dev, "%s: power_supply_register failed"
|
|
" rc=%d\n", __func__, rc);
|
|
return;
|
|
}
|
|
|
|
msm_batt_gauge = batt_gauge;
|
|
determine_initial_batt_status();
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(msm_battery_gauge_register);
|
|
|
|
void msm_battery_gauge_unregister(struct msm_battery_gauge *batt_gauge)
|
|
{
|
|
msm_batt_gauge = NULL;
|
|
}
|
|
EXPORT_SYMBOL(msm_battery_gauge_unregister);
|
|
|
|
int msm_charger_unregister(struct msm_hardware_charger *hw_chg)
|
|
{
|
|
struct msm_hardware_charger_priv *priv;
|
|
|
|
priv = (struct msm_hardware_charger_priv *)(hw_chg->charger_private);
|
|
mutex_lock(&msm_chg.msm_hardware_chargers_lock);
|
|
list_del(&priv->list);
|
|
mutex_unlock(&msm_chg.msm_hardware_chargers_lock);
|
|
power_supply_unregister(&priv->psy);
|
|
kfree(priv);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(msm_charger_unregister);
|
|
|
|
static int msm_charger_suspend(struct device *dev)
|
|
{
|
|
dev_dbg(msm_chg.dev, "%s suspended\n", __func__);
|
|
msm_chg.stop_update = 1;
|
|
cancel_delayed_work(&msm_chg.update_heartbeat_work);
|
|
mutex_lock(&msm_chg.status_lock);
|
|
handle_battery_removed();
|
|
mutex_unlock(&msm_chg.status_lock);
|
|
return 0;
|
|
}
|
|
|
|
static int msm_charger_resume(struct device *dev)
|
|
{
|
|
dev_dbg(msm_chg.dev, "%s resumed\n", __func__);
|
|
msm_chg.stop_update = 0;
|
|
/* start updaing the battery powersupply every msm_chg.update_time
|
|
* milliseconds */
|
|
queue_delayed_work(msm_chg.event_wq_thread,
|
|
&msm_chg.update_heartbeat_work,
|
|
round_jiffies_relative(msecs_to_jiffies
|
|
(msm_chg.update_time)));
|
|
mutex_lock(&msm_chg.status_lock);
|
|
handle_battery_inserted();
|
|
mutex_unlock(&msm_chg.status_lock);
|
|
return 0;
|
|
}
|
|
|
|
static SIMPLE_DEV_PM_OPS(msm_charger_pm_ops,
|
|
msm_charger_suspend, msm_charger_resume);
|
|
|
|
static struct platform_driver msm_charger_driver = {
|
|
.probe = msm_charger_probe,
|
|
.remove = __devexit_p(msm_charger_remove),
|
|
.driver = {
|
|
.name = "msm-charger",
|
|
.owner = THIS_MODULE,
|
|
.pm = &msm_charger_pm_ops,
|
|
},
|
|
};
|
|
|
|
static int __init msm_charger_init(void)
|
|
{
|
|
int rc;
|
|
|
|
INIT_LIST_HEAD(&msm_chg.msm_hardware_chargers);
|
|
msm_chg.count_chargers = 0;
|
|
mutex_init(&msm_chg.msm_hardware_chargers_lock);
|
|
|
|
msm_chg.queue = kzalloc(sizeof(struct msm_charger_event)
|
|
* MSM_CHG_MAX_EVENTS,
|
|
GFP_KERNEL);
|
|
if (!msm_chg.queue) {
|
|
rc = -ENOMEM;
|
|
goto out;
|
|
}
|
|
msm_chg.tail = 0;
|
|
msm_chg.head = 0;
|
|
spin_lock_init(&msm_chg.queue_lock);
|
|
msm_chg.queue_count = 0;
|
|
INIT_WORK(&msm_chg.queue_work, process_events);
|
|
msm_chg.event_wq_thread = create_workqueue("msm_charger_eventd");
|
|
if (!msm_chg.event_wq_thread) {
|
|
rc = -ENOMEM;
|
|
goto free_queue;
|
|
}
|
|
rc = platform_driver_register(&msm_charger_driver);
|
|
if (rc < 0) {
|
|
pr_err("%s: FAIL: platform_driver_register. rc = %d\n",
|
|
__func__, rc);
|
|
goto destroy_wq_thread;
|
|
}
|
|
msm_chg.inited = 1;
|
|
return 0;
|
|
|
|
destroy_wq_thread:
|
|
destroy_workqueue(msm_chg.event_wq_thread);
|
|
free_queue:
|
|
kfree(msm_chg.queue);
|
|
out:
|
|
return rc;
|
|
}
|
|
|
|
static void __exit msm_charger_exit(void)
|
|
{
|
|
flush_workqueue(msm_chg.event_wq_thread);
|
|
destroy_workqueue(msm_chg.event_wq_thread);
|
|
kfree(msm_chg.queue);
|
|
platform_driver_unregister(&msm_charger_driver);
|
|
}
|
|
|
|
module_init(msm_charger_init);
|
|
module_exit(msm_charger_exit);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("Abhijeet Dharmapurikar <adharmap@codeaurora.org>");
|
|
MODULE_DESCRIPTION("Battery driver for Qualcomm MSM chipsets.");
|
|
MODULE_VERSION("1.0");
|