blob: 8d45ff0f3677c466cf143b2e1486b0dabf90af72 [file] [log] [blame]
/*
* android_battery.c
* Android Battery Driver
*
* Copyright (C) 2012 Google, Inc.
* Copyright (C) 2012 Samsung Electronics
*
* Based on work by himihee.seo@samsung.com, ms925.kim@samsung.com, and
* joshua.chang@samsung.com.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/types.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
#include <linux/wakelock.h>
#include <linux/workqueue.h>
#include <linux/alarmtimer.h>
#include <linux/timer.h>
#include <linux/mutex.h>
#include <linux/debugfs.h>
#include <linux/platform_data/android_battery.h>
#define FAST_POLL (1 * 60)
#define SLOW_POLL (10 * 60)
struct android_bat_data {
struct android_bat_platform_data *pdata;
struct android_bat_callbacks callbacks;
struct device *dev;
struct power_supply psy_bat;
struct wake_lock monitor_wake_lock;
struct wake_lock charger_wake_lock;
int charge_source;
int batt_temp;
int batt_current;
unsigned int batt_health;
unsigned int batt_vcell;
unsigned int batt_soc;
unsigned int charging_status;
bool recharging;
unsigned long charging_start_time;
struct workqueue_struct *monitor_wqueue;
struct work_struct monitor_work;
struct work_struct charger_work;
struct alarm monitor_alarm;
ktime_t last_poll;
struct dentry *debugfs_entry;
};
static enum power_supply_property android_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
static DEFINE_MUTEX(android_bat_state_lock);
static void android_bat_update_data(struct android_bat_data *battery);
static int android_bat_enable_charging(struct android_bat_data *battery,
bool enable);
static char *charge_source_str(int charge_source)
{
switch (charge_source) {
case CHARGE_SOURCE_NONE:
return "none";
case CHARGE_SOURCE_AC:
return "ac";
case CHARGE_SOURCE_USB:
return "usb";
default:
break;
}
return "?";
}
static int android_bat_get_property(struct power_supply *ps,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct android_bat_data *battery =
container_of(ps, struct android_bat_data, psy_bat);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = battery->charging_status;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = battery->batt_health;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval = battery->batt_temp;
break;
case POWER_SUPPLY_PROP_ONLINE:
val->intval = 1;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
android_bat_update_data(battery);
val->intval = battery->batt_vcell;
if (val->intval == -1)
return -EINVAL;
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = battery->batt_soc;
if (val->intval == -1)
return -EINVAL;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
android_bat_update_data(battery);
val->intval = battery->batt_current;
break;
default:
return -EINVAL;
}
return 0;
}
static void android_bat_get_temp(struct android_bat_data *battery)
{
int batt_temp = 42; /* 4.2C */
int health = battery->batt_health;
if (battery->pdata->get_temperature)
battery->pdata->get_temperature(&batt_temp);
if (battery->charge_source != CHARGE_SOURCE_NONE) {
if (batt_temp >= battery->pdata->temp_high_threshold) {
if (health != POWER_SUPPLY_HEALTH_OVERHEAT &&
health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) {
pr_info("battery overheat (%d>=%d), " \
"charging unavailable\n",
batt_temp,
battery->pdata->temp_high_threshold);
battery->batt_health =
POWER_SUPPLY_HEALTH_OVERHEAT;
}
} else if (batt_temp <= battery->pdata->temp_high_recovery &&
batt_temp >= battery->pdata->temp_low_recovery) {
if (health == POWER_SUPPLY_HEALTH_OVERHEAT ||
health == POWER_SUPPLY_HEALTH_COLD) {
pr_info("battery recovery (%d,%d~%d)," \
"charging available\n",
batt_temp,
battery->pdata->temp_low_recovery,
battery->pdata->temp_high_recovery);
battery->batt_health =
POWER_SUPPLY_HEALTH_GOOD;
}
} else if (batt_temp <= battery->pdata->temp_low_threshold) {
if (health != POWER_SUPPLY_HEALTH_COLD &&
health != POWER_SUPPLY_HEALTH_UNSPEC_FAILURE) {
pr_info("battery cold (%d <= %d)," \
"charging unavailable\n",
batt_temp,
battery->pdata->temp_low_threshold);
battery->batt_health =
POWER_SUPPLY_HEALTH_COLD;
}
}
}
battery->batt_temp = batt_temp;
}
/*
* android_bat_state_lock not held, may call back into
* android_bat_charge_source_changed. Gathering data here can be
* non-atomic; updating our state based on the data may need to be
* atomic.
*/
static void android_bat_update_data(struct android_bat_data *battery)
{
int ret;
int v;
if (battery->pdata->poll_charge_source)
battery->charge_source = battery->pdata->poll_charge_source();
if (battery->pdata->get_voltage_now) {
ret = battery->pdata->get_voltage_now();
battery->batt_vcell = ret >= 0 ? ret : 4242000;
}
if (battery->pdata->get_capacity) {
ret = battery->pdata->get_capacity();
battery->batt_soc = ret >= 0 ? ret : 42;
}
if (battery->pdata->get_current_now) {
ret = battery->pdata->get_current_now(&v);
if (!ret)
battery->batt_current = v;
}
android_bat_get_temp(battery);
}
static void android_bat_set_charge_time(struct android_bat_data *battery,
bool enable)
{
if (enable && !battery->charging_start_time) {
struct timespec cur_time;
get_monotonic_boottime(&cur_time);
/* record start time for charge timeout timer */
battery->charging_start_time = cur_time.tv_sec;
} else if (!enable) {
/* clear charge timeout timer */
battery->charging_start_time = 0;
}
}
static int android_bat_enable_charging(struct android_bat_data *battery,
bool enable)
{
if (enable && (battery->batt_health != POWER_SUPPLY_HEALTH_GOOD)) {
battery->charging_status =
POWER_SUPPLY_STATUS_NOT_CHARGING;
return -EPERM;
}
if (enable) {
if (battery->pdata && battery->pdata->set_charging_current)
battery->pdata->set_charging_current
(battery->charge_source);
}
if (battery->pdata && battery->pdata->set_charging_enable)
battery->pdata->set_charging_enable(enable);
android_bat_set_charge_time(battery, enable);
pr_info("battery: enable=%d charger: %s\n", enable,
charge_source_str(battery->charge_source));
return 0;
}
static bool android_bat_charge_timeout(struct android_bat_data *battery,
unsigned long timeout)
{
struct timespec cur_time;
if (!battery->charging_start_time)
return 0;
get_monotonic_boottime(&cur_time);
pr_debug("%s: Start time: %ld, End time: %ld, current time: %ld\n",
__func__, battery->charging_start_time,
battery->charging_start_time + timeout,
cur_time.tv_sec);
return cur_time.tv_sec >= battery->charging_start_time + timeout;
}
static void android_bat_charging_timer(struct android_bat_data *battery)
{
if (!battery->charging_start_time &&
battery->charging_status == POWER_SUPPLY_STATUS_CHARGING) {
android_bat_enable_charging(battery, true);
battery->recharging = true;
pr_debug("%s: charge status charging but timer is expired\n",
__func__);
} else if (battery->charging_start_time == 0) {
pr_debug("%s: charging_start_time never initialized\n",
__func__);
return;
}
if (android_bat_charge_timeout(
battery,
battery->recharging ? battery->pdata->recharging_time :
battery->pdata->full_charging_time)) {
android_bat_enable_charging(battery, false);
if (battery->batt_vcell >
battery->pdata->recharging_voltage &&
battery->batt_soc == 100)
battery->charging_status =
POWER_SUPPLY_STATUS_FULL;
battery->recharging = false;
battery->charging_start_time = 0;
pr_info("battery: charging timer expired\n");
}
return;
}
static void android_bat_charge_source_changed(struct android_bat_callbacks *ptr,
int charge_source)
{
struct android_bat_data *battery =
container_of(ptr, struct android_bat_data, callbacks);
wake_lock(&battery->charger_wake_lock);
mutex_lock(&android_bat_state_lock);
battery->charge_source = charge_source;
pr_info("battery: charge source type was changed: %s\n",
charge_source_str(battery->charge_source));
mutex_unlock(&android_bat_state_lock);
queue_work(battery->monitor_wqueue, &battery->charger_work);
}
static void android_bat_set_full_status(struct android_bat_callbacks *ptr)
{
struct android_bat_data *battery =
container_of(ptr, struct android_bat_data, callbacks);
mutex_lock(&android_bat_state_lock);
pr_info("battery: battery full\n");
battery->charging_status = POWER_SUPPLY_STATUS_FULL;
android_bat_enable_charging(battery, false);
battery->recharging = false;
mutex_unlock(&android_bat_state_lock);
power_supply_changed(&battery->psy_bat);
}
static void android_bat_charger_work(struct work_struct *work)
{
struct android_bat_data *battery =
container_of(work, struct android_bat_data, charger_work);
mutex_lock(&android_bat_state_lock);
switch (battery->charge_source) {
case CHARGE_SOURCE_NONE:
battery->charging_status = POWER_SUPPLY_STATUS_DISCHARGING;
android_bat_enable_charging(battery, false);
battery->batt_health = POWER_SUPPLY_HEALTH_GOOD;
battery->recharging = false;
battery->charging_start_time = 0;
break;
case CHARGE_SOURCE_USB:
case CHARGE_SOURCE_AC:
/*
* If charging status indicates a charger was already
* connected prior to this and the status is something
* other than charging ("full" or "not-charging"), leave
* the status alone.
*/
if (battery->charging_status ==
POWER_SUPPLY_STATUS_DISCHARGING ||
battery->charging_status == POWER_SUPPLY_STATUS_UNKNOWN)
battery->charging_status = POWER_SUPPLY_STATUS_CHARGING;
/*
* Don't re-enable charging if the battery is full and we
* are not actively re-charging it, or if "not-charging"
* status is set.
*/
if (!((battery->charging_status == POWER_SUPPLY_STATUS_FULL
&& !battery->recharging) || battery->charging_status ==
POWER_SUPPLY_STATUS_NOT_CHARGING))
android_bat_enable_charging(battery, true);
break;
default:
pr_err("%s: Invalid charger type\n", __func__);
break;
}
mutex_unlock(&android_bat_state_lock);
wake_lock_timeout(&battery->charger_wake_lock, HZ * 2);
power_supply_changed(&battery->psy_bat);
}
static void android_bat_monitor_set_alarm(struct android_bat_data *battery,
int seconds)
{
alarm_start(&battery->monitor_alarm,
ktime_add(battery->last_poll, ktime_set(seconds, 0)));
}
static void android_bat_monitor_work(struct work_struct *work)
{
struct android_bat_data *battery =
container_of(work, struct android_bat_data, monitor_work);
struct timespec cur_time;
wake_lock(&battery->monitor_wake_lock);
android_bat_update_data(battery);
mutex_lock(&android_bat_state_lock);
switch (battery->charging_status) {
case POWER_SUPPLY_STATUS_FULL:
if (battery->batt_vcell < battery->pdata->recharging_voltage &&
!battery->recharging) {
battery->recharging = true;
android_bat_enable_charging(battery, true);
pr_info("battery: start recharging, v=%d\n",
battery->batt_vcell/1000);
}
break;
case POWER_SUPPLY_STATUS_DISCHARGING:
break;
case POWER_SUPPLY_STATUS_CHARGING:
switch (battery->batt_health) {
case POWER_SUPPLY_HEALTH_OVERHEAT:
case POWER_SUPPLY_HEALTH_COLD:
case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
case POWER_SUPPLY_HEALTH_DEAD:
case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
battery->charging_status =
POWER_SUPPLY_STATUS_NOT_CHARGING;
android_bat_enable_charging(battery, false);
pr_info("battery: Not charging, health=%d\n",
battery->batt_health);
break;
default:
break;
}
break;
case POWER_SUPPLY_STATUS_NOT_CHARGING:
if (battery->batt_health == POWER_SUPPLY_HEALTH_GOOD) {
pr_info("battery: battery health recovered\n");
if (battery->charge_source != CHARGE_SOURCE_NONE) {
android_bat_enable_charging(battery, true);
battery->charging_status
= POWER_SUPPLY_STATUS_CHARGING;
} else {
battery->charging_status
= POWER_SUPPLY_STATUS_DISCHARGING;
}
}
break;
default:
pr_err("%s: Undefined battery status: %d\n", __func__,
battery->charging_status);
break;
}
android_bat_charging_timer(battery);
get_monotonic_boottime(&cur_time);
pr_info("battery: l=%d v=%d c=%d temp=%s%ld.%ld h=%d st=%d%s ct=%lu type=%s\n",
battery->batt_soc, battery->batt_vcell/1000,
battery->batt_current, battery->batt_temp < 0 ? "-" : "",
abs(battery->batt_temp / 10), abs(battery->batt_temp % 10),
battery->batt_health, battery->charging_status,
battery->recharging ? "r" : "",
battery->charging_start_time ?
cur_time.tv_sec - battery->charging_start_time : 0,
charge_source_str(battery->charge_source));
mutex_unlock(&android_bat_state_lock);
power_supply_changed(&battery->psy_bat);
battery->last_poll = ktime_get_boottime();
android_bat_monitor_set_alarm(battery, FAST_POLL);
wake_unlock(&battery->monitor_wake_lock);
return;
}
static enum alarmtimer_restart android_bat_monitor_alarm(
struct alarm *alarm, ktime_t now)
{
struct android_bat_data *battery =
container_of(alarm, struct android_bat_data, monitor_alarm);
wake_lock(&battery->monitor_wake_lock);
queue_work(battery->monitor_wqueue, &battery->monitor_work);
return ALARMTIMER_NORESTART;
}
static int android_power_debug_dump(struct seq_file *s, void *unused)
{
struct android_bat_data *battery = s->private;
struct timespec cur_time;
android_bat_update_data(battery);
get_monotonic_boottime(&cur_time);
mutex_lock(&android_bat_state_lock);
seq_printf(s, "l=%d v=%d c=%d temp=%s%ld.%ld h=%d st=%d%s ct=%lu type=%s\n",
battery->batt_soc, battery->batt_vcell/1000,
battery->batt_current, battery->batt_temp < 0 ? "-" : "",
abs(battery->batt_temp / 10), abs(battery->batt_temp % 10),
battery->batt_health, battery->charging_status,
battery->recharging ? "r" : "",
battery->charging_start_time ?
cur_time.tv_sec - battery->charging_start_time : 0,
charge_source_str(battery->charge_source));
mutex_unlock(&android_bat_state_lock);
return 0;
}
static int android_power_debug_open(struct inode *inode, struct file *file)
{
return single_open(file, android_power_debug_dump, inode->i_private);
}
static const struct file_operations android_power_debug_fops = {
.open = android_power_debug_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int android_bat_probe(struct platform_device *pdev)
{
struct android_bat_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct android_bat_data *battery;
int ret = 0;
dev_info(&pdev->dev, "Android Battery Driver\n");
battery = kzalloc(sizeof(*battery), GFP_KERNEL);
if (!battery)
return -ENOMEM;
battery->pdata = pdata;
if (!battery->pdata) {
pr_err("%s : No platform data\n", __func__);
ret = -EINVAL;
goto err_pdata;
}
battery->dev = &pdev->dev;
platform_set_drvdata(pdev, battery);
battery->batt_health = POWER_SUPPLY_HEALTH_GOOD;
battery->psy_bat.name = "android-battery",
battery->psy_bat.type = POWER_SUPPLY_TYPE_BATTERY,
battery->psy_bat.properties = android_battery_props,
battery->psy_bat.num_properties = ARRAY_SIZE(android_battery_props),
battery->psy_bat.get_property = android_bat_get_property,
battery->batt_vcell = -1;
battery->batt_soc = -1;
wake_lock_init(&battery->monitor_wake_lock, WAKE_LOCK_SUSPEND,
"android-battery-monitor");
wake_lock_init(&battery->charger_wake_lock, WAKE_LOCK_SUSPEND,
"android-chargerdetect");
ret = power_supply_register(&pdev->dev, &battery->psy_bat);
if (ret) {
dev_err(battery->dev, "%s: failed to register psy_bat\n",
__func__);
goto err_psy_bat_reg;
}
battery->monitor_wqueue =
alloc_workqueue(dev_name(&pdev->dev), WQ_FREEZABLE, 1);
if (!battery->monitor_wqueue) {
dev_err(battery->dev, "%s: fail to create workqueue\n",
__func__);
goto err_wq;
}
INIT_WORK(&battery->monitor_work, android_bat_monitor_work);
INIT_WORK(&battery->charger_work, android_bat_charger_work);
battery->callbacks.charge_source_changed =
android_bat_charge_source_changed;
battery->callbacks.battery_set_full =
android_bat_set_full_status;
if (battery->pdata && battery->pdata->register_callbacks)
battery->pdata->register_callbacks(&battery->callbacks);
/* get initial charger status */
if (battery->pdata->poll_charge_source)
battery->charge_source = battery->pdata->poll_charge_source();
wake_lock(&battery->charger_wake_lock);
queue_work(battery->monitor_wqueue, &battery->charger_work);
wake_lock(&battery->monitor_wake_lock);
battery->last_poll = ktime_get_boottime();
alarm_init(&battery->monitor_alarm, ALARM_BOOTTIME,
android_bat_monitor_alarm);
queue_work(battery->monitor_wqueue, &battery->monitor_work);
battery->debugfs_entry =
debugfs_create_file("android-power", S_IRUGO, NULL,
battery, &android_power_debug_fops);
if (!battery->debugfs_entry)
pr_err("failed to create android-power debugfs entry\n");
return 0;
err_wq:
power_supply_unregister(&battery->psy_bat);
err_psy_bat_reg:
wake_lock_destroy(&battery->monitor_wake_lock);
wake_lock_destroy(&battery->charger_wake_lock);
err_pdata:
kfree(battery);
return ret;
}
static int android_bat_remove(struct platform_device *pdev)
{
struct android_bat_data *battery = platform_get_drvdata(pdev);
alarm_cancel(&battery->monitor_alarm);
flush_workqueue(battery->monitor_wqueue);
destroy_workqueue(battery->monitor_wqueue);
power_supply_unregister(&battery->psy_bat);
wake_lock_destroy(&battery->monitor_wake_lock);
wake_lock_destroy(&battery->charger_wake_lock);
debugfs_remove(battery->debugfs_entry);
kfree(battery);
return 0;
}
static int android_bat_suspend(struct device *dev)
{
struct android_bat_data *battery = dev_get_drvdata(dev);
cancel_work_sync(&battery->monitor_work);
android_bat_monitor_set_alarm(
battery,
battery->charge_source == CHARGE_SOURCE_NONE ?
SLOW_POLL : FAST_POLL);
return 0;
}
static void android_bat_resume(struct device *dev)
{
struct android_bat_data *battery = dev_get_drvdata(dev);
android_bat_monitor_set_alarm(battery, FAST_POLL);
return;
}
static const struct dev_pm_ops android_bat_pm_ops = {
.prepare = android_bat_suspend,
.complete = android_bat_resume,
};
static struct platform_driver android_bat_driver = {
.driver = {
.name = "android-battery",
.owner = THIS_MODULE,
.pm = &android_bat_pm_ops,
},
.probe = android_bat_probe,
.remove = android_bat_remove,
};
static int __init android_bat_init(void)
{
return platform_driver_register(&android_bat_driver);
}
static void __exit android_bat_exit(void)
{
platform_driver_unregister(&android_bat_driver);
}
late_initcall(android_bat_init);
module_exit(android_bat_exit);
MODULE_DESCRIPTION("Android battery driver");
MODULE_LICENSE("GPL");