power: ds2781: Add DS2781 battery driver
authorGreg Meiste <w30289@motorola.com>
Wed, 26 May 2010 21:31:51 +0000 (16:31 -0500)
committerColin Cross <ccross@android.com>
Wed, 6 Oct 2010 23:33:12 +0000 (16:33 -0700)
Initial implementation of the DS2781 battery driver.

Change-Id: I97a80b81b50ffa892759fca6e8336c6ca4e62e86
Signed-off-by: Greg Meiste <w30289@motorola.com>
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/ds2781_battery.c [new file with mode: 0644]

index 76c12ad2c48d23f4a00e1bc955b07a42012deaa6..68caa517b41e7fe8491673412f69d0df289ee2a1 100644 (file)
@@ -69,6 +69,13 @@ config BATTERY_DS2760
        help
          Say Y here to enable support for batteries with ds2760 chip.
 
+config BATTERY_DS2781
+       tristate "DS2781 battery driver"
+       select W1
+       select W1_SLAVE_DS2781
+       help
+         Say Y here to enable support for batteries with ds2781 chip.
+
 config BATTERY_DS2782
        tristate "DS2782/DS2786 standalone gas-gauge"
        depends on I2C
index 137cd88b444060b913c93622265d0405f4cde648..d5dee79b117d4165d67e22e4fc2388fe9f18325c 100644 (file)
@@ -23,6 +23,7 @@ obj-$(CONFIG_WM8350_POWER)    += wm8350_power.o
 obj-$(CONFIG_TEST_POWER)       += test_power.o
 
 obj-$(CONFIG_BATTERY_DS2760)   += ds2760_battery.o
+obj-$(CONFIG_BATTERY_DS2781)   += ds2781_battery.o
 obj-$(CONFIG_BATTERY_DS2782)   += ds2782_battery.o
 obj-$(CONFIG_BATTERY_PMU)      += pmu_battery.o
 obj-$(CONFIG_BATTERY_OLPC)     += olpc_battery.o
diff --git a/drivers/power/ds2781_battery.c b/drivers/power/ds2781_battery.c
new file mode 100644 (file)
index 0000000..940a940
--- /dev/null
@@ -0,0 +1,491 @@
+/*
+ * Copyright (C) 2010 Motorola, Inc.
+ *
+ * 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.
+ *
+ * Based on ds2784_battery.c which is:
+ * Copyright (C) 2009 HTC Corporation
+ * Copyright (C) 2009 Google, Inc.
+ */
+
+#include <linux/android_alarm.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/param.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/power_supply.h>
+#include <linux/seq_file.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/wakelock.h>
+#include <linux/workqueue.h>
+
+#include "../w1/w1.h"
+#include "../w1/slaves/w1_ds2781.h"
+
+extern int is_ac_charge_complete(void);
+
+struct battery_status {
+       int timestamp;
+
+       int voltage_uV;         /* units of uV */
+       int current_uA;         /* units of uA */
+       int current_avg_uA;
+       int charge_uAh;
+
+       u16 temp_C;             /* units of 0.1 C */
+
+       u8 percentage;          /* battery percentage */
+       u8 charge_source;
+       u8 status_reg;
+       u8 battery_full;        /* battery full (don't charge) */
+
+       u8 cooldown;            /* was overtemp */
+};
+
+
+#define TEMP_HOT       450 /* 45.0 degrees Celcius */
+
+#define BATTERY_LOG_MAX 1024
+#define BATTERY_LOG_MASK (BATTERY_LOG_MAX - 1)
+
+/* When we're awake or running on wall power, sample the battery
+ * gauge every FAST_POLL seconds.  If we're asleep and on battery
+ * power, sample every SLOW_POLL seconds
+ */
+#define FAST_POLL      (1 * 60)
+#define SLOW_POLL      (10 * 60)
+
+static DEFINE_MUTEX(battery_log_lock);
+static struct battery_status battery_log[BATTERY_LOG_MAX];
+static unsigned battery_log_head;
+static unsigned battery_log_tail;
+
+static int battery_log_en;
+module_param(battery_log_en, int, S_IRUGO | S_IWUSR | S_IWGRP);
+
+void battery_log_status(struct battery_status *s)
+{
+       unsigned n;
+       mutex_lock(&battery_log_lock);
+       n = battery_log_head;
+       memcpy(battery_log + n, s, sizeof(struct battery_status));
+       n = (n + 1) & BATTERY_LOG_MASK;
+       if (n == battery_log_tail)
+               battery_log_tail = (battery_log_tail + 1) & BATTERY_LOG_MASK;
+       battery_log_head = n;
+       mutex_unlock(&battery_log_lock);
+}
+
+static const char *battery_source[2] = { "none", "  ac" };
+
+static int battery_log_print(struct seq_file *sf, void *private)
+{
+       unsigned n;
+       mutex_lock(&battery_log_lock);
+       seq_printf(sf, "timestamp    mV     mA avg mA      uAh   dC   %%   src   reg full\n");
+       for (n = battery_log_tail; n != battery_log_head; n = (n + 1) & BATTERY_LOG_MASK) {
+               struct battery_status *s = battery_log + n;
+               seq_printf(sf, "%9d %5d %6d %6d %8d %4d %3d  %s  0x%02x %d\n",
+                          s->timestamp, s->voltage_uV / 1000,
+                          s->current_uA / 1000, s->current_avg_uA / 1000,
+                          s->charge_uAh, s->temp_C,
+                          s->percentage,
+                          battery_source[s->charge_source],
+                          s->status_reg, s->battery_full);
+       }
+       mutex_unlock(&battery_log_lock);
+       return 0;
+}
+
+
+struct ds2781_device_info {
+       struct device *dev;
+
+       /* DS2781 data, valid after calling ds2781_battery_read_status() */
+       char raw[DS2781_DATA_SIZE];     /* raw DS2781 data */
+
+       struct battery_status status;
+       struct mutex status_lock;
+
+       struct power_supply bat;
+       struct device *w1_dev;
+       struct workqueue_struct *monitor_wqueue;
+       struct work_struct monitor_work;
+       struct alarm alarm;
+       struct wake_lock work_wake_lock;
+
+       u8 slow_poll;
+       ktime_t last_poll;
+};
+
+#define psy_to_dev_info(x) container_of((x), struct ds2781_device_info, bat)
+
+static struct wake_lock vbus_wake_lock;
+
+static enum power_supply_property battery_properties[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_TECHNOLOGY,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_TEMP,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+       POWER_SUPPLY_PROP_CURRENT_AVG,
+       POWER_SUPPLY_PROP_CHARGE_COUNTER,
+};
+
+#define to_ds2781_device_info(x) container_of((x), struct ds2781_device_info, \
+                                             bat);
+
+static void ds2781_parse_data(u8 *raw, struct battery_status *s)
+{
+       short n;
+
+       /* Get status reg */
+       s->status_reg = raw[DS2781_REG_STATUS];
+
+       /* Get Level */
+       s->percentage = raw[DS2781_REG_RARC];
+
+       /* Get Voltage: Unit=9.76mV, range is 0V to 9.9902V */
+       n = (((raw[DS2781_REG_VOLT_MSB] << 8) |
+             (raw[DS2781_REG_VOLT_LSB])) >> 5);
+
+       s->voltage_uV = n * 9760;
+
+       /* Get Current: Unit= 1.5625uV x Rsnsp(67)=104.68 */
+       n = ((raw[DS2781_REG_CURR_MSB]) << 8) |
+               raw[DS2781_REG_CURR_LSB];
+       s->current_uA = ((n * 15625) / 10000) * 67;
+
+       n = ((raw[DS2781_REG_AVG_CURR_MSB]) << 8) |
+               raw[DS2781_REG_AVG_CURR_LSB];
+       s->current_avg_uA = ((n * 15625) / 10000) * 67;
+
+       /* Get Temperature:
+        * Unit=0.125 degree C,therefore, give up LSB ,
+        * just caculate MSB for temperature only.
+        */
+       n = (((signed char)raw[DS2781_REG_TEMP_MSB]) << 3) |
+               (raw[DS2781_REG_TEMP_LSB] >> 5);
+
+       s->temp_C = n + (n / 4);
+
+       /* RAAC is in units of 1.6mAh */
+       s->charge_uAh = ((raw[DS2781_REG_RAAC_MSB] << 8) |
+                         raw[DS2781_REG_RAAC_LSB]) * 1600;
+}
+
+static int ds2781_battery_read_status(struct ds2781_device_info *di)
+{
+       int ret;
+       int start;
+       int count;
+
+       /* The first time we read the entire contents of SRAM/EEPROM,
+        * but after that we just read the interesting bits that change. */
+       if (di->raw[DS2781_REG_RSNSP] == 0x00) {
+               start = DS2781_REG_STATUS;
+               count = DS2781_DATA_SIZE - start;
+       } else {
+               start = DS2781_REG_STATUS;
+               count = DS2781_REG_CURR_LSB - start + 1;
+       }
+
+       ret = w1_ds2781_read(di->w1_dev, di->raw + start, start, count);
+       if (ret != count) {
+               dev_warn(di->dev, "call to w1_ds2781_read failed (0x%p)\n",
+                        di->w1_dev);
+               return 1;
+       }
+
+       mutex_lock(&di->status_lock);
+       ds2781_parse_data(di->raw, &di->status);
+
+       if (battery_log_en)
+               pr_info("batt: %3d%%, %d mV, %d mA (%d avg), %d.%d C, %d mAh\n",
+                       di->status.percentage,
+                       di->status.voltage_uV / 1000,
+                       di->status.current_uA / 1000,
+                       di->status.current_avg_uA / 1000,
+                       di->status.temp_C / 10, di->status.temp_C % 10,
+                       di->status.charge_uAh / 1000);
+       mutex_unlock(&di->status_lock);
+
+       return 0;
+}
+
+static int battery_get_property(struct power_supply *psy,
+                               enum power_supply_property psp,
+                               union power_supply_propval *val)
+{
+       struct ds2781_device_info *di = psy_to_dev_info(psy);
+       int retval = 0;
+
+       mutex_lock(&di->status_lock);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               if (di->status.charge_source) {
+                       if ((di->status.battery_full) ||
+                           (di->status.percentage >= 100))
+                               val->intval = POWER_SUPPLY_STATUS_FULL;
+                       else
+                               val->intval = POWER_SUPPLY_STATUS_CHARGING;
+               } else
+                       val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               if (di->status.temp_C >= TEMP_HOT)
+                       val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+               else
+                       val->intval = POWER_SUPPLY_HEALTH_GOOD;
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = 1;
+               break;
+       case POWER_SUPPLY_PROP_TECHNOLOGY:
+               val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+               break;
+       case POWER_SUPPLY_PROP_CAPACITY:
+               if (di->status.battery_full)
+                       val->intval = 100;
+               else
+                       val->intval = di->status.percentage;
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               val->intval = di->status.voltage_uV;
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+               val->intval = di->status.temp_C;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               val->intval = di->status.current_uA;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+               val->intval = di->status.current_avg_uA;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+               val->intval = di->status.charge_uAh;
+               break;
+       default:
+               retval = -EINVAL;
+       }
+
+       mutex_unlock(&di->status_lock);
+
+       return retval;
+}
+
+static void ds2781_battery_update_status(struct ds2781_device_info *di)
+{
+       u8 last_level;
+       last_level = di->status.percentage;
+
+       ds2781_battery_read_status(di);
+
+       if (last_level != di->status.percentage)
+               power_supply_changed(&di->bat);
+}
+
+static void ds2781_program_alarm(struct ds2781_device_info *di, int seconds)
+{
+       ktime_t low_interval = ktime_set(seconds - 10, 0);
+       ktime_t slack = ktime_set(20, 0);
+       ktime_t next;
+
+       next = ktime_add(di->last_poll, low_interval);
+
+       alarm_start_range(&di->alarm, next, ktime_add(next, slack));
+}
+
+static void ds2781_battery_work(struct work_struct *work)
+{
+       struct ds2781_device_info *di =
+               container_of(work, struct ds2781_device_info, monitor_work);
+       struct timespec ts;
+
+       ds2781_battery_update_status(di);
+
+       di->last_poll = alarm_get_elapsed_realtime();
+
+       ts = ktime_to_timespec(di->last_poll);
+       di->status.timestamp = ts.tv_sec;
+
+       if (battery_log_en)
+               battery_log_status(&di->status);
+
+       ds2781_program_alarm(di, FAST_POLL);
+       wake_unlock(&di->work_wake_lock);
+}
+
+static void ds2781_battery_alarm(struct alarm *alarm)
+{
+       struct ds2781_device_info *di =
+               container_of(alarm, struct ds2781_device_info, alarm);
+       wake_lock(&di->work_wake_lock);
+       queue_work(di->monitor_wqueue, &di->monitor_work);
+}
+
+static void battery_ext_power_changed(struct power_supply *psy)
+{
+       struct ds2781_device_info *di;
+       int got_power;
+
+       di = psy_to_dev_info(psy);
+       got_power = power_supply_am_i_supplied(psy);
+
+       mutex_lock(&di->status_lock);
+       if (got_power) {
+               di->status.charge_source = 1;
+               if (is_ac_charge_complete())
+                       di->status.battery_full = 1;
+
+               wake_lock(&vbus_wake_lock);
+       } else {
+               di->status.charge_source = 0;
+               di->status.battery_full = 0;
+
+               /* give userspace some time to see the uevent and update
+                * LED state or whatnot...
+                */
+               wake_lock_timeout(&vbus_wake_lock, HZ / 2);
+       }
+       mutex_unlock(&di->status_lock);
+
+       power_supply_changed(psy);
+}
+
+static int ds2781_battery_probe(struct platform_device *pdev)
+{
+       int rc;
+       struct ds2781_device_info *di;
+
+       di = kzalloc(sizeof(*di), GFP_KERNEL);
+       if (!di)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, di);
+
+       di->dev = &pdev->dev;
+       di->w1_dev = pdev->dev.parent;
+       mutex_init(&di->status_lock);
+
+       di->bat.name = "battery";
+       di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+       di->bat.properties = battery_properties;
+       di->bat.num_properties = ARRAY_SIZE(battery_properties);
+       di->bat.external_power_changed = battery_ext_power_changed;
+       di->bat.get_property = battery_get_property;
+
+       rc = power_supply_register(&pdev->dev, &di->bat);
+       if (rc)
+               goto fail_register;
+
+       INIT_WORK(&di->monitor_work, ds2781_battery_work);
+       di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev));
+
+       /* init to something sane */
+       di->last_poll = alarm_get_elapsed_realtime();
+
+       if (!di->monitor_wqueue) {
+               rc = -ESRCH;
+               goto fail_workqueue;
+       }
+       wake_lock_init(&di->work_wake_lock, WAKE_LOCK_SUSPEND,
+                       "ds2781-battery");
+       alarm_init(&di->alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP,
+                       ds2781_battery_alarm);
+       wake_lock(&di->work_wake_lock);
+       queue_work(di->monitor_wqueue, &di->monitor_work);
+       return 0;
+
+fail_workqueue:
+       power_supply_unregister(&di->bat);
+fail_register:
+       kfree(di);
+       return rc;
+}
+
+static int ds2781_suspend(struct device *dev)
+{
+       struct ds2781_device_info *di = dev_get_drvdata(dev);
+
+       /* If on battery, reduce update rate until next resume. */
+       if (!di->status.charge_source) {
+               ds2781_program_alarm(di, SLOW_POLL);
+               di->slow_poll = 1;
+       }
+       return 0;
+}
+
+static int ds2781_resume(struct device *dev)
+{
+       struct ds2781_device_info *di = dev_get_drvdata(dev);
+
+       /* We might be on a slow sample cycle.  If we're
+        * resuming we should resample the battery state
+        * if it's been over a minute since we last did
+        * so, and move back to sampling every minute until
+        * we suspend again.
+        */
+       if (di->slow_poll) {
+               ds2781_program_alarm(di, FAST_POLL);
+               di->slow_poll = 0;
+       }
+       return 0;
+}
+
+static struct dev_pm_ops ds2781_pm_ops = {
+       .suspend        = ds2781_suspend,
+       .resume         = ds2781_resume,
+};
+
+static struct platform_driver ds2781_battery_driver = {
+       .driver = {
+               .name = "ds2781-battery",
+               .pm = &ds2781_pm_ops,
+       },
+       .probe    = ds2781_battery_probe,
+};
+
+static int battery_log_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, battery_log_print, NULL);
+}
+
+static struct file_operations battery_log_fops = {
+       .open = battery_log_open,
+       .read = seq_read,
+       .llseek = seq_lseek,
+       .release = single_release,
+};
+
+static int __init ds2781_battery_init(void)
+{
+       debugfs_create_file("battery_log", 0444, NULL, NULL, &battery_log_fops);
+       wake_lock_init(&vbus_wake_lock, WAKE_LOCK_SUSPEND, "vbus_present");
+       return platform_driver_register(&ds2781_battery_driver);
+}
+
+module_init(ds2781_battery_init);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Motorola");
+MODULE_DESCRIPTION("ds2781 battery driver");