From 69e6b4673580467c6291b552bf7626128505be09 Mon Sep 17 00:00:00 2001 From: Greg Meiste Date: Wed, 26 May 2010 16:31:51 -0500 Subject: [PATCH] power: ds2781: Add DS2781 battery driver Initial implementation of the DS2781 battery driver. Change-Id: I97a80b81b50ffa892759fca6e8336c6ca4e62e86 Signed-off-by: Greg Meiste --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/ds2781_battery.c | 491 +++++++++++++++++++++++++++++++++ 3 files changed, 499 insertions(+) create mode 100644 drivers/power/ds2781_battery.c diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 76c12ad2c48d..68caa517b41e 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -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 diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 137cd88b4440..d5dee79b117d 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -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 index 000000000000..940a940991bd --- /dev/null +++ b/drivers/power/ds2781_battery.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); -- 2.34.1