From a4ef8bd7d80e74ad34b4d7c17401fcfb333c3434 Mon Sep 17 00:00:00 2001 From: Todd Poynor Date: Fri, 27 Jul 2012 11:27:33 -0700 Subject: power: android battery: add generic android battery driver Add a generic battery power supply and glue logic for talking to the board battery driver. This driver handles common chores such as: * periodic battery level and health monitoring * kernel log reporting and other debugging features for key properties provided by different charger, fuel gauge, etc. components * ensure properties such as battery health are made available to userspace * common processing for board-level battery/case temperature sensors and policy for charging status based on battery health Based on work by himihee.seo@samsung.com, ms925.kim@samsung.com, and joshua.chang@samsung.com. Change-Id: I5fa8e8d68811d84820b7a130b0245ad2b5b6d36b Signed-off-by: Todd Poynor --- drivers/power/Kconfig | 10 + drivers/power/Makefile | 1 + drivers/power/android_battery.c | 475 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 486 insertions(+) create mode 100644 drivers/power/android_battery.c (limited to 'drivers/power') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 7b8979c63f4..430ef8c8b6a 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -204,6 +204,16 @@ config BATTERY_MAX17042 with MAX17042. This driver also supports max17047/50 chips which are improved version of max17042. +config BATTERY_ANDROID + tristate "Battery driver for Android" + help + Say Y to enable generic support for battery charging according + to common Android policies. + This driver adds periodic battery level and health monitoring, + kernel log reporting and other debugging features, common board + battery file glue logic for battery/case temperature sensors, + etc. + config BATTERY_Z2 tristate "Z2 battery driver" depends on I2C && MACH_ZIPIT2 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 653bf6ceff3..27d9deef51b 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_WM8350_POWER) += wm8350_power.o obj-$(CONFIG_TEST_POWER) += test_power.o obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o +obj-$(CONFIG_BATTERY_ANDROID) += android_battery.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o diff --git a/drivers/power/android_battery.c b/drivers/power/android_battery.c new file mode 100644 index 00000000000..ded43502967 --- /dev/null +++ b/drivers/power/android_battery.c @@ -0,0 +1,475 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; + struct work_struct charger_work; + + bool slow_poll; + ktime_t last_poll; +}; + +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 void android_bat_update_data(struct android_bat_data *battery); + +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 = 25000; + int health = battery->batt_health; + + if (battery->pdata->get_temperature) + battery->pdata->get_temperature(&batt_temp); + + 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/1000; +} + +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 : -1; + } + + if (battery->pdata->get_capacity) { + ret = battery->pdata->get_capacity(); + battery->batt_soc = ret >= 0 ? ret : -1; + } + + 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 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); + + pr_info("battery: enable=%d charger: %s\n", enable, + charge_source_str(battery->charge_source)); + + return 0; +} + +static void android_bat_charge_source_changed(struct android_bat_callbacks *ptr, + int charge_source) +{ + struct android_bat_data *battery; + + battery = container_of(ptr, struct android_bat_data, callbacks); + wake_lock(&battery->charger_wake_lock); + battery->charge_source = charge_source; + + pr_info("battery: charge source type was changed: %s\n", + charge_source_str(battery->charge_source)); + + queue_work(battery->monitor_wqueue, &battery->charger_work); +} + +static void android_bat_charger_work(struct work_struct *work) +{ + struct android_bat_data *battery = + container_of(work, struct android_bat_data, charger_work); + + switch (battery->charge_source) { + case CHARGE_SOURCE_NONE: + battery->charging_status = POWER_SUPPLY_STATUS_DISCHARGING; + android_bat_enable_charging(battery, false); + if (battery->batt_health == POWER_SUPPLY_HEALTH_OVERVOLTAGE) + battery->batt_health = POWER_SUPPLY_HEALTH_GOOD; + break; + case CHARGE_SOURCE_USB: + case CHARGE_SOURCE_AC: + battery->charging_status = POWER_SUPPLY_STATUS_CHARGING; + android_bat_enable_charging(battery, true); + break; + default: + pr_err("%s: Invalid charger type\n", __func__); + break; + } + + wake_lock_timeout(&battery->charger_wake_lock, HZ * 2); +} + +static void android_bat_monitor_work(struct work_struct *work) +{ + struct android_bat_data *battery; + battery = container_of(work, struct android_bat_data, + monitor_work.work); + + wake_lock(&battery->monitor_wake_lock); + android_bat_update_data(battery); + + switch (battery->charging_status) { + case POWER_SUPPLY_STATUS_FULL: + 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: + wake_unlock(&battery->monitor_wake_lock); + pr_err("%s: Undefined battery status: %d\n", __func__, + battery->charging_status); + return; + } + + pr_info("battery: l=%d v=%d c=%d temp=%d h=%d st=%d type=%s\n", + battery->batt_soc, battery->batt_vcell/1000, + battery->batt_current, battery->batt_temp, battery->batt_health, + battery->charging_status, + charge_source_str(battery->charge_source)); + power_supply_changed(&battery->psy_bat); + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, msecs_to_jiffies(50000)); + wake_unlock(&battery->monitor_wake_lock); + + return; +} + +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_reg; + } + + battery->monitor_wqueue = + create_singlethread_workqueue(dev_name(&pdev->dev)); + if (!battery->monitor_wqueue) { + dev_err(battery->dev, "%s: fail to create workqueue\n", + __func__); + goto err_wq; + } + + INIT_DELAYED_WORK_DEFERRABLE(&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; + 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); + + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, msecs_to_jiffies(0)); + return 0; + + +err_wq: + power_supply_unregister(&battery->psy_bat); +err_psy_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); + + 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); + kfree(battery); + return 0; +} + +static int android_bat_suspend(struct device *dev) +{ + struct android_bat_data *battery = dev_get_drvdata(dev); + + cancel_delayed_work_sync(&battery->monitor_work); + return 0; +} + +static int android_bat_resume(struct device *dev) +{ + struct android_bat_data *battery = dev_get_drvdata(dev); + + queue_delayed_work(battery->monitor_wqueue, + &battery->monitor_work, msecs_to_jiffies(0)); + return 0; +} + +static const struct dev_pm_ops android_bat_pm_ops = { + .suspend = android_bat_suspend, + .resume = 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"); -- cgit v1.2.3