/* drivers/power/ds2784_battery.c * * Copyright (C) 2009 HTC Corporation * Copyright (C) 2009 Google, 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../w1/w1.h" #include "w1_ds2784.h" extern int is_ac_power_supplied(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 */ u8 charge_mode; } __attribute__((packed)); #define SOURCE_NONE 0 #define SOURCE_USB 1 #define SOURCE_AC 2 #define CHARGE_OFF 0 #define CHARGE_SLOW 1 #define CHARGE_FAST 2 #define CHARGE_BATT_DISABLE 3 /* disable charging at battery */ #define TEMP_CRITICAL 600 /* no charging at all */ #define TEMP_HOT 500 /* no fast charge, no charge > 4.1v */ #define TEMP_WARM 450 /* no fast charge above this */ #define TEMP_HOT_MAX_MV 4100 /* stop charging here when hot */ #define TEMP_HOT_MIN_MV 3800 /* resume charging here when hot */ #define CE_DISABLE_MIN_MV 4100 #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; 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[3] = { "none", " usb", " ac" }; static const char *battery_mode[4] = { " off", "slow", "fast", "full" }; 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 mode 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 %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], battery_mode[s->charge_mode], s->status_reg, s->battery_full); } mutex_unlock(&battery_log_lock); return 0; } struct ds2784_device_info { struct device *dev; /* DS2784 data, valid after calling ds2784_battery_read_status() */ char raw[DS2784_DATA_SIZE]; /* raw DS2784 data */ struct battery_status status; struct power_supply bat; struct workqueue_struct *monitor_wqueue; struct work_struct monitor_work; struct alarm alarm; struct wake_lock work_wake_lock; int (*charge)(int on, int fast); struct w1_slave *w1_slave; u8 dummy; /* dummy battery flag */ u8 last_charge_mode; /* previous charger state */ u8 slow_poll; ktime_t last_poll; ktime_t last_charge_seen; }; #define psy_to_dev_info(x) container_of((x), struct ds2784_device_info, bat) static struct wake_lock vbus_wake_lock; #define BATT_RSNSP (67) /*Passion battery source 1*/ 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, }; static int battery_initial; static int battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val); static void battery_ext_power_changed(struct power_supply *psy); #define to_ds2784_device_info(x) container_of((x), struct ds2784_device_info, \ bat); static void ds2784_parse_data(u8 *raw, struct battery_status *s) { short n; /* Get status reg */ s->status_reg = raw[DS2784_REG_STS]; /* Get Level */ s->percentage = raw[DS2784_REG_RARC]; /* Get Voltage: Unit=4.886mV, range is 0V to 4.99V */ n = (((raw[DS2784_REG_VOLT_MSB] << 8) | (raw[DS2784_REG_VOLT_LSB])) >> 5); s->voltage_uV = n * 4886; /* Get Current: Unit= 1.5625uV x Rsnsp(67)=104.68 */ n = ((raw[DS2784_REG_CURR_MSB]) << 8) | raw[DS2784_REG_CURR_LSB]; s->current_uA = ((n * 15625) / 10000) * 67; n = ((raw[DS2784_REG_AVG_CURR_MSB]) << 8) | raw[DS2784_REG_AVG_CURR_LSB]; s->current_avg_uA = ((n * 15625) / 10000) * 67; /* Get Temperature: * 11 bit signed result in Unit=0.125 degree C. * Convert to integer tenths of degree C. */ n = ((raw[DS2784_REG_TEMP_MSB] << 8) | (raw[DS2784_REG_TEMP_LSB])) >> 5; s->temp_C = (n * 10) / 8; /* RAAC is in units of 1.6mAh */ s->charge_uAh = ((raw[DS2784_REG_RAAC_MSB] << 8) | raw[DS2784_REG_RAAC_LSB]) * 1600; } static int w1_ds2784_io(struct w1_slave *sl, char *buf, int addr, size_t count, int io) { if (!sl) return 0; mutex_lock(&sl->master->mutex); if (addr > DS2784_DATA_SIZE || addr < 0) { count = 0; goto out; } if (addr + count > DS2784_DATA_SIZE) count = DS2784_DATA_SIZE - addr; if (!w1_reset_select_slave(sl)) { if (!io) { w1_write_8(sl->master, W1_DS2784_READ_DATA); w1_write_8(sl->master, addr); count = w1_read_block(sl->master, buf, count); } else { w1_write_8(sl->master, W1_DS2784_WRITE_DATA); w1_write_8(sl->master, addr); w1_write_block(sl->master, buf, count); /* XXX w1_write_block returns void, not n_written */ } } out: mutex_unlock(&sl->master->mutex); return count; } static int w1_ds2784_read(struct w1_slave *sl, char *buf, int addr, size_t count) { return w1_ds2784_io(sl, buf, addr, count, 0); } static int w1_ds2784_write(struct w1_slave *sl, char *buf, int addr, size_t count) { return w1_ds2784_io(sl, buf, addr, count, 1); } static int ds2784_set_cc(struct ds2784_device_info *di, bool enable) { int ret; if (enable) di->raw[DS2784_REG_PORT] |= 0x02; else di->raw[DS2784_REG_PORT] &= ~0x02; ret = w1_ds2784_write(di->w1_slave, di->raw + DS2784_REG_PORT, DS2784_REG_PORT, 1); if (ret != 1) { dev_warn(di->dev, "call to w1_ds2784_write failed (0x%p)\n", di->w1_slave); return 1; } return 0; } static int ds2784_battery_read_status(struct ds2784_device_info *di) { int ret, start, 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[DS2784_REG_RSNSP] == 0x00) { start = 0; count = DS2784_DATA_SIZE; } else { start = DS2784_REG_PORT; count = DS2784_REG_CURR_LSB - start + 1; } ret = w1_ds2784_read(di->w1_slave, di->raw + start, start, count); if (ret != count) { dev_warn(di->dev, "call to w1_ds2784_read failed (0x%p)\n", di->w1_slave); return 1; } if (battery_initial == 0) { if (!memcmp(di->raw + 0x20, "DUMMY!", 6)) { unsigned char acr[2]; di->dummy = 1; pr_info("batt: dummy battery detected\n"); /* reset ACC register to ~500mAh, since it may have zeroed out */ acr[0] = 0x05; acr[1] = 0x06; w1_ds2784_write(di->w1_slave, acr, DS2784_REG_ACCUMULATE_CURR_MSB, 2); } battery_initial = 1; } ds2784_parse_data(di->raw, &di->status); 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); return 0; } static int battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { struct ds2784_device_info *di = psy_to_dev_info(psy); switch (psp) { case POWER_SUPPLY_PROP_STATUS: switch (di->status.charge_source) { case CHARGE_OFF: val->intval = POWER_SUPPLY_STATUS_DISCHARGING; break; case CHARGE_FAST: case CHARGE_SLOW: if (di->status.battery_full) val->intval = POWER_SUPPLY_STATUS_FULL; else if (di->status.charge_mode == CHARGE_OFF || di->status.charge_mode == CHARGE_BATT_DISABLE) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; else val->intval = POWER_SUPPLY_STATUS_CHARGING; break; default: val->intval = POWER_SUPPLY_STATUS_UNKNOWN; break; } 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: /* XXX todo */ val->intval = 1; break; case POWER_SUPPLY_PROP_TECHNOLOGY: if (di->dummy) val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; else val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_CAPACITY: if (di->dummy) val->intval = 75; 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: return -EINVAL; } return 0; } static void ds2784_battery_update_status(struct ds2784_device_info *di) { u8 last_level; last_level = di->status.percentage; ds2784_battery_read_status(di); if ((last_level != di->status.percentage) || (di->status.temp_C > 450)) power_supply_changed(&di->bat); } static DEFINE_MUTEX(charge_state_lock); static bool check_timeout(ktime_t now, ktime_t last, int seconds) { ktime_t timeout = ktime_add(last, ktime_set(seconds, 0)); return ktime_sub(timeout, now).tv64 < 0; } static int battery_adjust_charge_state(struct ds2784_device_info *di) { unsigned source; int rc = 0; int temp, volt; u8 charge_mode; bool charge_timeout = false; mutex_lock(&charge_state_lock); temp = di->status.temp_C; volt = di->status.voltage_uV / 1000; source = di->status.charge_source; /* initially our charge mode matches our source: * NONE:OFF, USB:SLOW, AC:FAST */ charge_mode = source; /* shut off charger when full: * - CHGTF flag is set */ if (di->status.status_reg & 0x80) { di->status.battery_full = 1; charge_mode = CHARGE_BATT_DISABLE; } else di->status.battery_full = 0; if (temp >= TEMP_HOT) { if (temp >= TEMP_CRITICAL) charge_mode = CHARGE_BATT_DISABLE; /* once we charge to max voltage when hot, disable * charging until the temp drops or the voltage drops */ if (volt >= TEMP_HOT_MAX_MV) di->status.cooldown = 1; } /* when the battery is warm, only charge in slow charge mode */ if ((temp >= TEMP_WARM) && (charge_mode == CHARGE_FAST)) charge_mode = CHARGE_SLOW; if (di->status.cooldown) { if ((temp < TEMP_WARM) || (volt <= TEMP_HOT_MIN_MV)) di->status.cooldown = 0; else charge_mode = CHARGE_BATT_DISABLE; } if (di->status.current_uA > 1024) di->last_charge_seen = di->last_poll; else if (di->last_charge_mode != CHARGE_OFF && check_timeout(di->last_poll, di->last_charge_seen, 60 * 60)) { if (di->last_charge_mode == CHARGE_BATT_DISABLE) { /* The charger is only powering the phone. Toggle the * enable line periodically to prevent auto shutdown. */ di->last_charge_seen = di->last_poll; pr_info("batt: charging POKE CHARGER\n"); di->charge(0, 0); udelay(10); di->charge(1, source == CHARGE_FAST); } else { /* The charger has probably stopped charging. Turn it * off until the next sample period. */ charge_timeout = true; charge_mode = CHARGE_OFF; } } if (source == CHARGE_OFF) charge_mode = CHARGE_OFF; /* Don't use CHARGE_BATT_DISABLE unless the voltage is high since the * voltage drop over the discharge-path diode can cause a shutdown. */ if (charge_mode == CHARGE_BATT_DISABLE && volt < CE_DISABLE_MIN_MV) charge_mode = CHARGE_OFF; if (di->last_charge_mode == charge_mode) goto done; di->last_charge_mode = charge_mode; di->status.charge_mode = charge_mode; switch (charge_mode) { case CHARGE_OFF: di->charge(0, 0); ds2784_set_cc(di, true); if (temp >= TEMP_CRITICAL) pr_info("batt: charging OFF [OVERTEMP]\n"); else if (di->status.cooldown) pr_info("batt: charging OFF [COOLDOWN]\n"); else if (di->status.battery_full) pr_info("batt: charging OFF [FULL]\n"); else if (charge_timeout) pr_info("batt: charging OFF [TIMEOUT]\n"); else pr_info("batt: charging OFF\n"); break; case CHARGE_BATT_DISABLE: di->last_charge_seen = di->last_poll; ds2784_set_cc(di, false); di->charge(1, source == CHARGE_FAST); if (temp >= TEMP_CRITICAL) pr_info("batt: charging BATTOFF [OVERTEMP]\n"); else if (di->status.cooldown) pr_info("batt: charging BATTOFF [COOLDOWN]\n"); else if (di->status.battery_full) pr_info("batt: charging BATTOFF [FULL]\n"); else pr_info("batt: charging BATTOFF [UNKNOWN]\n"); break; case CHARGE_SLOW: di->last_charge_seen = di->last_poll; ds2784_set_cc(di, true); di->charge(1, 0); pr_info("batt: charging SLOW\n"); break; case CHARGE_FAST: di->last_charge_seen = di->last_poll; ds2784_set_cc(di, true); di->charge(1, 1); pr_info("batt: charging FAST\n"); break; } rc = 1; done: mutex_unlock(&charge_state_lock); return rc; } static void ds2784_program_alarm(struct ds2784_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 ds2784_battery_work(struct work_struct *work) { struct ds2784_device_info *di = container_of(work, struct ds2784_device_info, monitor_work); struct timespec ts; unsigned long flags; ds2784_battery_update_status(di); di->last_poll = alarm_get_elapsed_realtime(); if (battery_adjust_charge_state(di)) power_supply_changed(&di->bat); ts = ktime_to_timespec(di->last_poll); di->status.timestamp = ts.tv_sec; battery_log_status(&di->status); /* prevent suspend before starting the alarm */ local_irq_save(flags); wake_unlock(&di->work_wake_lock); ds2784_program_alarm(di, FAST_POLL); local_irq_restore(flags); } static void ds2784_battery_alarm(struct alarm *alarm) { struct ds2784_device_info *di = container_of(alarm, struct ds2784_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 ds2784_device_info *di; int got_power; di = psy_to_dev_info(psy); got_power = power_supply_am_i_supplied(psy); if (got_power) { if (is_ac_power_supplied()) di->status.charge_source = SOURCE_AC; else di->status.charge_source = SOURCE_USB; wake_lock(&vbus_wake_lock); } else { di->status.charge_source = SOURCE_NONE; /* give userspace some time to see the uevent and update * LED state or whatnot... */ wake_lock_timeout(&vbus_wake_lock, HZ / 2); } battery_adjust_charge_state(di); power_supply_changed(psy); } static int ds2784_battery_probe(struct platform_device *pdev) { int rc; struct ds2784_device_info *di; struct ds2784_platform_data *pdata; di = kzalloc(sizeof(*di), GFP_KERNEL); if (!di) return -ENOMEM; platform_set_drvdata(pdev, di); pdata = pdev->dev.platform_data; if (!pdata || !pdata->charge || !pdata->w1_slave) { pr_err("%s: pdata missing or invalid\n", __func__); rc = -EINVAL; goto fail_register; } di->charge = pdata->charge; di->w1_slave = pdata->w1_slave; di->dev = &pdev->dev; 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; di->last_charge_mode = 0xff; rc = power_supply_register(&pdev->dev, &di->bat); if (rc) goto fail_register; INIT_WORK(&di->monitor_work, ds2784_battery_work); di->monitor_wqueue = create_freezeable_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, "ds2784-battery"); alarm_init(&di->alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, ds2784_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 ds2784_suspend(struct device *dev) { struct ds2784_device_info *di = dev_get_drvdata(dev); /* If we are on battery, reduce our update rate until * we next resume. */ if (di->status.charge_source == SOURCE_NONE) { ds2784_program_alarm(di, SLOW_POLL); di->slow_poll = 1; } return 0; } static int ds2784_resume(struct device *dev) { struct ds2784_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) { ds2784_program_alarm(di, FAST_POLL); di->slow_poll = 0; } return 0; } static struct dev_pm_ops ds2784_pm_ops = { .suspend = ds2784_suspend, .resume = ds2784_resume, }; static struct platform_driver ds2784_battery_driver = { .driver = { .name = "ds2784-battery", .pm = &ds2784_pm_ops, }, .probe = ds2784_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 ds2784_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(&ds2784_battery_driver); } module_init(ds2784_battery_init); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Justin Lin "); MODULE_DESCRIPTION("ds2784 battery driver");