Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 359bc164 authored by Todd Poynor's avatar Todd Poynor Committed by Ruchi Kandoi
Browse files

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: default avatarTodd Poynor <toddpoynor@google.com>
parent 948f4f0d
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -212,6 +212,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
+1 −0
Original line number Diff line number Diff line
@@ -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
+475 −0
Original line number Diff line number Diff line
/*
 *  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/timer.h>
#include <linux/platform_data/android_battery.h>

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");
+42 −0
Original line number Diff line number Diff line
/*
 *  android_battery.h
 *
 *  Copyright (C) 2012 Samsung Electronics
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#ifndef _LINUX_ANDROID_BATTERY_H
#define _LINUX_ANDROID_BATTERY_H

enum {
	CHARGE_SOURCE_NONE = 0,
	CHARGE_SOURCE_AC,
	CHARGE_SOURCE_USB,
};

struct android_bat_callbacks {
	void (*charge_source_changed)
		(struct android_bat_callbacks *, int);
};

struct android_bat_platform_data {
	void (*register_callbacks)(struct android_bat_callbacks *);
	void (*unregister_callbacks)(void);
	void (*set_charging_current) (int);
	void (*set_charging_enable) (int);
	int (*poll_charge_source) (void);
	int (*get_capacity) (void);
	int (*get_temperature) (int *);
	int (*get_voltage_now)(void);
	int (*get_current_now)(int *);

	int temp_high_threshold;
	int temp_high_recovery;
	int temp_low_recovery;
	int temp_low_threshold;
};

#endif