diff options
author | Len Brown <len.brown@intel.com> | 2009-01-09 11:39:43 +0300 |
---|---|---|
committer | Len Brown <len.brown@intel.com> | 2009-01-09 11:39:43 +0300 |
commit | b2576e1d4408e134e2188c967b1f28af39cd79d4 (patch) | |
tree | 004f3c82faab760f304ce031d6d2f572e7746a50 /drivers/power | |
parent | 3cc8a5f4ba91f67bbdb81a43a99281a26aab8d77 (diff) | |
parent | 2150edc6c5cf00f7adb54538b9ea2a3e9cedca3f (diff) | |
download | linux-b2576e1d4408e134e2188c967b1f28af39cd79d4.tar.xz |
Merge branch 'linus' into release
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/Kconfig | 14 | ||||
-rw-r--r-- | drivers/power/Makefile | 2 | ||||
-rw-r--r-- | drivers/power/da9030_battery.c | 600 | ||||
-rw-r--r-- | drivers/power/ds2760_battery.c | 4 | ||||
-rw-r--r-- | drivers/power/power_supply_sysfs.c | 2 | ||||
-rw-r--r-- | drivers/power/wm8350_power.c | 532 |
6 files changed, 1151 insertions, 3 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 8e0c2b47803c..668472405a57 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -29,6 +29,13 @@ config APM_POWER Say Y here to enable support APM status emulation using battery class devices. +config WM8350_POWER + tristate "WM8350 PMU support" + depends on MFD_WM8350 + help + Say Y here to enable support for the power management unit + provided by the Wolfson Microelectronics WM8350 PMIC. + config BATTERY_DS2760 tristate "DS2760 battery driver (HP iPAQ & others)" select W1 @@ -68,4 +75,11 @@ config BATTERY_BQ27x00 help Say Y here to enable support for batteries with BQ27200(I2C) chip. +config BATTERY_DA9030 + tristate "DA9030 battery driver" + depends on PMIC_DA903X + help + Say Y here to enable support for batteries charger integrated into + DA9030 PMIC. + endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index e8f1ecec5d8f..eebb15505a40 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_POWER_SUPPLY) += power_supply.o obj-$(CONFIG_PDA_POWER) += pda_power.o obj-$(CONFIG_APM_POWER) += apm_power.o +obj-$(CONFIG_WM8350_POWER) += wm8350_power.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o @@ -23,3 +24,4 @@ obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o +obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o diff --git a/drivers/power/da9030_battery.c b/drivers/power/da9030_battery.c new file mode 100644 index 000000000000..1662bb0f23a5 --- /dev/null +++ b/drivers/power/da9030_battery.c @@ -0,0 +1,600 @@ +/* + * Battery charger driver for Dialog Semiconductor DA9030 + * + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/device.h> +#include <linux/workqueue.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/da903x.h> + +#include <linux/debugfs.h> +#include <linux/seq_file.h> + +#define DA9030_STATUS_CHDET (1 << 3) + +#define DA9030_FAULT_LOG 0x0a +#define DA9030_FAULT_LOG_OVER_TEMP (1 << 7) +#define DA9030_FAULT_LOG_VBAT_OVER (1 << 4) + +#define DA9030_CHARGE_CONTROL 0x28 +#define DA9030_CHRG_CHARGER_ENABLE (1 << 7) + +#define DA9030_ADC_MAN_CONTROL 0x30 +#define DA9030_ADC_TBATREF_ENABLE (1 << 5) +#define DA9030_ADC_LDO_INT_ENABLE (1 << 4) + +#define DA9030_ADC_AUTO_CONTROL 0x31 +#define DA9030_ADC_TBAT_ENABLE (1 << 5) +#define DA9030_ADC_VBAT_IN_TXON (1 << 4) +#define DA9030_ADC_VCH_ENABLE (1 << 3) +#define DA9030_ADC_ICH_ENABLE (1 << 2) +#define DA9030_ADC_VBAT_ENABLE (1 << 1) +#define DA9030_ADC_AUTO_SLEEP_ENABLE (1 << 0) + +#define DA9030_VBATMON 0x32 +#define DA9030_VBATMONTXON 0x33 +#define DA9030_TBATHIGHP 0x34 +#define DA9030_TBATHIGHN 0x35 +#define DA9030_TBATLOW 0x36 + +#define DA9030_VBAT_RES 0x41 +#define DA9030_VBATMIN_RES 0x42 +#define DA9030_VBATMINTXON_RES 0x43 +#define DA9030_ICHMAX_RES 0x44 +#define DA9030_ICHMIN_RES 0x45 +#define DA9030_ICHAVERAGE_RES 0x46 +#define DA9030_VCHMAX_RES 0x47 +#define DA9030_VCHMIN_RES 0x48 +#define DA9030_TBAT_RES 0x49 + +struct da9030_adc_res { + uint8_t vbat_res; + uint8_t vbatmin_res; + uint8_t vbatmintxon; + uint8_t ichmax_res; + uint8_t ichmin_res; + uint8_t ichaverage_res; + uint8_t vchmax_res; + uint8_t vchmin_res; + uint8_t tbat_res; + uint8_t adc_in4_res; + uint8_t adc_in5_res; +}; + +struct da9030_battery_thresholds { + int tbat_low; + int tbat_high; + int tbat_restart; + + int vbat_low; + int vbat_crit; + int vbat_charge_start; + int vbat_charge_stop; + int vbat_charge_restart; + + int vcharge_min; + int vcharge_max; +}; + +struct da9030_charger { + struct power_supply psy; + + struct device *master; + + struct da9030_adc_res adc; + struct delayed_work work; + unsigned int interval; + + struct power_supply_info *battery_info; + + struct da9030_battery_thresholds thresholds; + + unsigned int charge_milliamp; + unsigned int charge_millivolt; + + /* charger status */ + bool chdet; + uint8_t fault; + int mA; + int mV; + bool is_on; + + struct notifier_block nb; + + /* platform callbacks for battery low and critical events */ + void (*battery_low)(void); + void (*battery_critical)(void); + + struct dentry *debug_file; +}; + +static inline int da9030_reg_to_mV(int reg) +{ + return ((reg * 2650) >> 8) + 2650; +} + +static inline int da9030_millivolt_to_reg(int mV) +{ + return ((mV - 2650) << 8) / 2650; +} + +static inline int da9030_reg_to_mA(int reg) +{ + return ((reg * 24000) >> 8) / 15; +} + +#ifdef CONFIG_DEBUG_FS +static int bat_debug_show(struct seq_file *s, void *data) +{ + struct da9030_charger *charger = s->private; + + seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off"); + if (charger->chdet) { + seq_printf(s, "iset = %dmA, vset = %dmV\n", + charger->mA, charger->mV); + } + + seq_printf(s, "vbat_res = %d (%dmV)\n", + charger->adc.vbat_res, + da9030_reg_to_mV(charger->adc.vbat_res)); + seq_printf(s, "vbatmin_res = %d (%dmV)\n", + charger->adc.vbatmin_res, + da9030_reg_to_mV(charger->adc.vbatmin_res)); + seq_printf(s, "vbatmintxon = %d (%dmV)\n", + charger->adc.vbatmintxon, + da9030_reg_to_mV(charger->adc.vbatmintxon)); + seq_printf(s, "ichmax_res = %d (%dmA)\n", + charger->adc.ichmax_res, + da9030_reg_to_mV(charger->adc.ichmax_res)); + seq_printf(s, "ichmin_res = %d (%dmA)\n", + charger->adc.ichmin_res, + da9030_reg_to_mA(charger->adc.ichmin_res)); + seq_printf(s, "ichaverage_res = %d (%dmA)\n", + charger->adc.ichaverage_res, + da9030_reg_to_mA(charger->adc.ichaverage_res)); + seq_printf(s, "vchmax_res = %d (%dmV)\n", + charger->adc.vchmax_res, + da9030_reg_to_mA(charger->adc.vchmax_res)); + seq_printf(s, "vchmin_res = %d (%dmV)\n", + charger->adc.vchmin_res, + da9030_reg_to_mV(charger->adc.vchmin_res)); + + return 0; +} + +static int debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, bat_debug_show, inode->i_private); +} + +static const struct file_operations bat_debug_fops = { + .open = debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) +{ + charger->debug_file = debugfs_create_file("charger", 0666, 0, charger, + &bat_debug_fops); + return charger->debug_file; +} + +static void da9030_bat_remove_debugfs(struct da9030_charger *charger) +{ + debugfs_remove(charger->debug_file); +} +#else +static inline struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) +{ + return NULL; +} +static inline void da9030_bat_remove_debugfs(struct da9030_charger *charger) +{ +} +#endif + +static inline void da9030_read_adc(struct da9030_charger *charger, + struct da9030_adc_res *adc) +{ + da903x_reads(charger->master, DA9030_VBAT_RES, + sizeof(*adc), (uint8_t *)adc); +} + +static void da9030_charger_update_state(struct da9030_charger *charger) +{ + uint8_t val; + + da903x_read(charger->master, DA9030_CHARGE_CONTROL, &val); + charger->is_on = (val & DA9030_CHRG_CHARGER_ENABLE) ? 1 : 0; + charger->mA = ((val >> 3) & 0xf) * 100; + charger->mV = (val & 0x7) * 50 + 4000; + + da9030_read_adc(charger, &charger->adc); + da903x_read(charger->master, DA9030_FAULT_LOG, &charger->fault); + charger->chdet = da903x_query_status(charger->master, + DA9030_STATUS_CHDET); +} + +static void da9030_set_charge(struct da9030_charger *charger, int on) +{ + uint8_t val; + + if (on) { + val = DA9030_CHRG_CHARGER_ENABLE; + val |= (charger->charge_milliamp / 100) << 3; + val |= (charger->charge_millivolt - 4000) / 50; + charger->is_on = 1; + } else { + val = 0; + charger->is_on = 0; + } + + da903x_write(charger->master, DA9030_CHARGE_CONTROL, val); +} + +static void da9030_charger_check_state(struct da9030_charger *charger) +{ + da9030_charger_update_state(charger); + + /* we wake or boot with external power on */ + if (!charger->is_on) { + if ((charger->chdet) && + (charger->adc.vbat_res < + charger->thresholds.vbat_charge_start)) { + da9030_set_charge(charger, 1); + } + } else { + if (charger->adc.vbat_res >= + charger->thresholds.vbat_charge_stop) { + da9030_set_charge(charger, 0); + da903x_write(charger->master, DA9030_VBATMON, + charger->thresholds.vbat_charge_restart); + } else if (charger->adc.vbat_res > + charger->thresholds.vbat_low) { + /* we are charging and passed LOW_THRESH, + so upate DA9030 VBAT threshold + */ + da903x_write(charger->master, DA9030_VBATMON, + charger->thresholds.vbat_low); + } + if (charger->adc.vchmax_res > charger->thresholds.vcharge_max || + charger->adc.vchmin_res < charger->thresholds.vcharge_min || + /* Tempreture readings are negative */ + charger->adc.tbat_res < charger->thresholds.tbat_high || + charger->adc.tbat_res > charger->thresholds.tbat_low) { + /* disable charger */ + da9030_set_charge(charger, 0); + } + } +} + +static void da9030_charging_monitor(struct work_struct *work) +{ + struct da9030_charger *charger; + + charger = container_of(work, struct da9030_charger, work.work); + + da9030_charger_check_state(charger); + + /* reschedule for the next time */ + schedule_delayed_work(&charger->work, charger->interval); +} + +static enum power_supply_property da9030_battery_props[] = { + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_AVG, +}; + +static void da9030_battery_check_status(struct da9030_charger *charger, + union power_supply_propval *val) +{ + if (charger->chdet) { + if (charger->is_on) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } +} + +static void da9030_battery_check_health(struct da9030_charger *charger, + union power_supply_propval *val) +{ + if (charger->fault & DA9030_FAULT_LOG_OVER_TEMP) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (charger->fault & DA9030_FAULT_LOG_VBAT_OVER) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; +} + +static int da9030_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct da9030_charger *charger; + charger = container_of(psy, struct da9030_charger, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + da9030_battery_check_status(charger, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + da9030_battery_check_health(charger, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = charger->battery_info->technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = charger->battery_info->voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = charger->battery_info->voltage_min_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = da9030_reg_to_mV(charger->adc.vbat_res) * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + val->intval = + da9030_reg_to_mA(charger->adc.ichaverage_res) * 1000; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = charger->battery_info->name; + break; + default: + break; + } + + return 0; +} + +static void da9030_battery_vbat_event(struct da9030_charger *charger) +{ + da9030_read_adc(charger, &charger->adc); + + if (charger->is_on) + return; + + if (charger->adc.vbat_res < charger->thresholds.vbat_low) { + /* set VBAT threshold for critical */ + da903x_write(charger->master, DA9030_VBATMON, + charger->thresholds.vbat_crit); + if (charger->battery_low) + charger->battery_low(); + } else if (charger->adc.vbat_res < + charger->thresholds.vbat_crit) { + /* notify the system of battery critical */ + if (charger->battery_critical) + charger->battery_critical(); + } +} + +static int da9030_battery_event(struct notifier_block *nb, unsigned long event, + void *data) +{ + struct da9030_charger *charger = + container_of(nb, struct da9030_charger, nb); + int status; + + switch (event) { + case DA9030_EVENT_CHDET: + status = da903x_query_status(charger->master, + DA9030_STATUS_CHDET); + da9030_set_charge(charger, status); + break; + case DA9030_EVENT_VBATMON: + da9030_battery_vbat_event(charger); + break; + case DA9030_EVENT_CHIOVER: + case DA9030_EVENT_TBAT: + da9030_set_charge(charger, 0); + break; + } + + return 0; +} + +static void da9030_battery_convert_thresholds(struct da9030_charger *charger, + struct da9030_battery_info *pdata) +{ + charger->thresholds.tbat_low = pdata->tbat_low; + charger->thresholds.tbat_high = pdata->tbat_high; + charger->thresholds.tbat_restart = pdata->tbat_restart; + + charger->thresholds.vbat_low = + da9030_millivolt_to_reg(pdata->vbat_low); + charger->thresholds.vbat_crit = + da9030_millivolt_to_reg(pdata->vbat_crit); + charger->thresholds.vbat_charge_start = + da9030_millivolt_to_reg(pdata->vbat_charge_start); + charger->thresholds.vbat_charge_stop = + da9030_millivolt_to_reg(pdata->vbat_charge_stop); + charger->thresholds.vbat_charge_restart = + da9030_millivolt_to_reg(pdata->vbat_charge_restart); + + charger->thresholds.vcharge_min = + da9030_millivolt_to_reg(pdata->vcharge_min); + charger->thresholds.vcharge_max = + da9030_millivolt_to_reg(pdata->vcharge_max); +} + +static void da9030_battery_setup_psy(struct da9030_charger *charger) +{ + struct power_supply *psy = &charger->psy; + struct power_supply_info *info = charger->battery_info; + + psy->name = info->name; + psy->use_for_apm = info->use_for_apm; + psy->type = POWER_SUPPLY_TYPE_BATTERY; + psy->get_property = da9030_battery_get_property; + + psy->properties = da9030_battery_props; + psy->num_properties = ARRAY_SIZE(da9030_battery_props); +}; + +static int da9030_battery_charger_init(struct da9030_charger *charger) +{ + char v[5]; + int ret; + + v[0] = v[1] = charger->thresholds.vbat_low; + v[2] = charger->thresholds.tbat_high; + v[3] = charger->thresholds.tbat_restart; + v[4] = charger->thresholds.tbat_low; + + ret = da903x_writes(charger->master, DA9030_VBATMON, 5, v); + if (ret) + return ret; + + /* + * Enable reference voltage supply for ADC from the LDO_INTERNAL + * regulator. Must be set before ADC measurements can be made. + */ + ret = da903x_write(charger->master, DA9030_ADC_MAN_CONTROL, + DA9030_ADC_LDO_INT_ENABLE | + DA9030_ADC_TBATREF_ENABLE); + if (ret) + return ret; + + /* enable auto ADC measuremnts */ + return da903x_write(charger->master, DA9030_ADC_AUTO_CONTROL, + DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON | + DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE | + DA9030_ADC_VBAT_ENABLE | + DA9030_ADC_AUTO_SLEEP_ENABLE); +} + +static int da9030_battery_probe(struct platform_device *pdev) +{ + struct da9030_charger *charger; + struct da9030_battery_info *pdata = pdev->dev.platform_data; + int ret; + + if (pdata == NULL) + return -EINVAL; + + if (pdata->charge_milliamp >= 1500 || + pdata->charge_millivolt < 4000 || + pdata->charge_millivolt > 4350) + return -EINVAL; + + charger = kzalloc(sizeof(*charger), GFP_KERNEL); + if (charger == NULL) + return -ENOMEM; + + charger->master = pdev->dev.parent; + + /* 10 seconds between monotor runs unless platfrom defines other + interval */ + charger->interval = msecs_to_jiffies( + (pdata->batmon_interval ? : 10) * 1000); + + charger->charge_milliamp = pdata->charge_milliamp; + charger->charge_millivolt = pdata->charge_millivolt; + charger->battery_info = pdata->battery_info; + charger->battery_low = pdata->battery_low; + charger->battery_critical = pdata->battery_critical; + + da9030_battery_convert_thresholds(charger, pdata); + + ret = da9030_battery_charger_init(charger); + if (ret) + goto err_charger_init; + + INIT_DELAYED_WORK(&charger->work, da9030_charging_monitor); + schedule_delayed_work(&charger->work, charger->interval); + + charger->nb.notifier_call = da9030_battery_event; + ret = da903x_register_notifier(charger->master, &charger->nb, + DA9030_EVENT_CHDET | + DA9030_EVENT_VBATMON | + DA9030_EVENT_CHIOVER | + DA9030_EVENT_TBAT); + if (ret) + goto err_notifier; + + da9030_battery_setup_psy(charger); + ret = power_supply_register(&pdev->dev, &charger->psy); + if (ret) + goto err_ps_register; + + charger->debug_file = da9030_bat_create_debugfs(charger); + platform_set_drvdata(pdev, charger); + return 0; + +err_ps_register: + da903x_unregister_notifier(charger->master, &charger->nb, + DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | + DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); +err_notifier: + cancel_delayed_work(&charger->work); + +err_charger_init: + kfree(charger); + + return ret; +} + +static int da9030_battery_remove(struct platform_device *dev) +{ + struct da9030_charger *charger = platform_get_drvdata(dev); + + da9030_bat_remove_debugfs(charger); + + da903x_unregister_notifier(charger->master, &charger->nb, + DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON | + DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT); + cancel_delayed_work(&charger->work); + power_supply_unregister(&charger->psy); + + kfree(charger); + + return 0; +} + +static struct platform_driver da903x_battery_driver = { + .driver = { + .name = "da903x-battery", + .owner = THIS_MODULE, + }, + .probe = da9030_battery_probe, + .remove = da9030_battery_remove, +}; + +static int da903x_battery_init(void) +{ + return platform_driver_register(&da903x_battery_driver); +} + +static void da903x_battery_exit(void) +{ + platform_driver_unregister(&da903x_battery_driver); +} + +module_init(da903x_battery_init); +module_exit(da903x_battery_exit); + +MODULE_DESCRIPTION("DA9030 battery charger driver"); +MODULE_AUTHOR("Mike Rapoport, CompuLab"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c index 308ddb201b66..1d768928e0bb 100644 --- a/drivers/power/ds2760_battery.c +++ b/drivers/power/ds2760_battery.c @@ -354,7 +354,7 @@ static int ds2760_battery_probe(struct platform_device *pdev) pdata = pdev->dev.platform_data; di->dev = &pdev->dev; di->w1_dev = pdev->dev.parent; - di->bat.name = pdev->dev.bus_id; + di->bat.name = dev_name(&pdev->dev); di->bat.type = POWER_SUPPLY_TYPE_BATTERY; di->bat.properties = ds2760_battery_props; di->bat.num_properties = ARRAY_SIZE(ds2760_battery_props); @@ -371,7 +371,7 @@ static int ds2760_battery_probe(struct platform_device *pdev) } INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work); - di->monitor_wqueue = create_singlethread_workqueue(pdev->dev.bus_id); + di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); if (!di->monitor_wqueue) { retval = -ESRCH; goto workqueue_failed; diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 23ae8460f5c1..ac01e06817fb 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -45,7 +45,7 @@ static ssize_t power_supply_show_property(struct device *dev, }; static char *health_text[] = { "Unknown", "Good", "Overheat", "Dead", "Over voltage", - "Unspecified failure" + "Unspecified failure", "Cold", }; static char *technology_text[] = { "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c new file mode 100644 index 000000000000..1b16bf343f2f --- /dev/null +++ b/drivers/power/wm8350_power.c @@ -0,0 +1,532 @@ +/* + * Battery driver for wm8350 PMIC + * + * Copyright 2007, 2008 Wolfson Microelectronics PLC. + * + * Based on OLPC Battery Driver + * + * Copyright 2006 David Woodhouse <dwmw2@infradead.org> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/wm8350/supply.h> +#include <linux/mfd/wm8350/core.h> +#include <linux/mfd/wm8350/comparator.h> + +static int wm8350_read_battery_uvolts(struct wm8350 *wm8350) +{ + return wm8350_read_auxadc(wm8350, WM8350_AUXADC_BATT, 0, 0) + * WM8350_AUX_COEFF; +} + +static int wm8350_read_line_uvolts(struct wm8350 *wm8350) +{ + return wm8350_read_auxadc(wm8350, WM8350_AUXADC_LINE, 0, 0) + * WM8350_AUX_COEFF; +} + +static int wm8350_read_usb_uvolts(struct wm8350 *wm8350) +{ + return wm8350_read_auxadc(wm8350, WM8350_AUXADC_USB, 0, 0) + * WM8350_AUX_COEFF; +} + +#define WM8350_BATT_SUPPLY 1 +#define WM8350_USB_SUPPLY 2 +#define WM8350_LINE_SUPPLY 4 + +static inline int wm8350_charge_time_min(struct wm8350 *wm8350, int min) +{ + if (!wm8350->power.rev_g_coeff) + return (((min - 30) / 15) & 0xf) << 8; + else + return (((min - 30) / 30) & 0xf) << 8; +} + +static int wm8350_get_supplies(struct wm8350 *wm8350) +{ + u16 sm, ov, co, chrg; + int supplies = 0; + + sm = wm8350_reg_read(wm8350, WM8350_STATE_MACHINE_STATUS); + ov = wm8350_reg_read(wm8350, WM8350_MISC_OVERRIDES); + co = wm8350_reg_read(wm8350, WM8350_COMPARATOR_OVERRIDES); + chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); + + /* USB_SM */ + sm = (sm & WM8350_USB_SM_MASK) >> WM8350_USB_SM_SHIFT; + + /* CHG_ISEL */ + chrg &= WM8350_CHG_ISEL_MASK; + + /* If the USB state machine is active then we're using that with or + * without battery, otherwise check for wall supply */ + if (((sm == WM8350_USB_SM_100_SLV) || + (sm == WM8350_USB_SM_500_SLV) || + (sm == WM8350_USB_SM_STDBY_SLV)) + && !(ov & WM8350_USB_LIMIT_OVRDE)) + supplies = WM8350_USB_SUPPLY; + else if (((sm == WM8350_USB_SM_100_SLV) || + (sm == WM8350_USB_SM_500_SLV) || + (sm == WM8350_USB_SM_STDBY_SLV)) + && (ov & WM8350_USB_LIMIT_OVRDE) && (chrg == 0)) + supplies = WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY; + else if (co & WM8350_WALL_FB_OVRDE) + supplies = WM8350_LINE_SUPPLY; + else + supplies = WM8350_BATT_SUPPLY; + + return supplies; +} + +static int wm8350_charger_config(struct wm8350 *wm8350, + struct wm8350_charger_policy *policy) +{ + u16 reg, eoc_mA, fast_limit_mA; + + if (!policy) { + dev_warn(wm8350->dev, + "No charger policy, charger not configured.\n"); + return -EINVAL; + } + + /* make sure USB fast charge current is not > 500mA */ + if (policy->fast_limit_USB_mA > 500) { + dev_err(wm8350->dev, "USB fast charge > 500mA\n"); + return -EINVAL; + } + + eoc_mA = WM8350_CHG_EOC_mA(policy->eoc_mA); + + wm8350_reg_unlock(wm8350); + + reg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1) + & WM8350_CHG_ENA_R168; + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, + reg | eoc_mA | policy->trickle_start_mV | + WM8350_CHG_TRICKLE_TEMP_CHOKE | + WM8350_CHG_TRICKLE_USB_CHOKE | + WM8350_CHG_FAST_USB_THROTTLE); + + if (wm8350_get_supplies(wm8350) & WM8350_USB_SUPPLY) { + fast_limit_mA = + WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_USB_mA); + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2, + policy->charge_mV | policy->trickle_charge_USB_mA | + fast_limit_mA | wm8350_charge_time_min(wm8350, + policy->charge_timeout)); + + } else { + fast_limit_mA = + WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_mA); + wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2, + policy->charge_mV | policy->trickle_charge_mA | + fast_limit_mA | wm8350_charge_time_min(wm8350, + policy->charge_timeout)); + } + + wm8350_reg_lock(wm8350); + return 0; +} + +static int wm8350_batt_status(struct wm8350 *wm8350) +{ + u16 state; + + state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2); + state &= WM8350_CHG_STS_MASK; + + switch (state) { + case WM8350_CHG_STS_OFF: + return POWER_SUPPLY_STATUS_DISCHARGING; + + case WM8350_CHG_STS_TRICKLE: + case WM8350_CHG_STS_FAST: + return POWER_SUPPLY_STATUS_CHARGING; + + default: + return POWER_SUPPLY_STATUS_UNKNOWN; + } +} + +static ssize_t charger_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct wm8350 *wm8350 = dev_get_drvdata(dev); + char *charge; + int state; + + state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) & + WM8350_CHG_STS_MASK; + switch (state) { + case WM8350_CHG_STS_OFF: + charge = "Charger Off"; + break; + case WM8350_CHG_STS_TRICKLE: + charge = "Trickle Charging"; + break; + case WM8350_CHG_STS_FAST: + charge = "Fast Charging"; + break; + default: + return 0; + } + + return sprintf(buf, "%s\n", charge); +} + +static DEVICE_ATTR(charger_state, 0444, charger_state_show, NULL); + +static void wm8350_charger_handler(struct wm8350 *wm8350, int irq, void *data) +{ + struct wm8350_power *power = &wm8350->power; + struct wm8350_charger_policy *policy = power->policy; + + switch (irq) { + case WM8350_IRQ_CHG_BAT_FAIL: + dev_err(wm8350->dev, "battery failed\n"); + break; + case WM8350_IRQ_CHG_TO: + dev_err(wm8350->dev, "charger timeout\n"); + power_supply_changed(&power->battery); + break; + + case WM8350_IRQ_CHG_BAT_HOT: + case WM8350_IRQ_CHG_BAT_COLD: + case WM8350_IRQ_CHG_START: + case WM8350_IRQ_CHG_END: + power_supply_changed(&power->battery); + break; + + case WM8350_IRQ_CHG_FAST_RDY: + dev_dbg(wm8350->dev, "fast charger ready\n"); + wm8350_charger_config(wm8350, policy); + wm8350_reg_unlock(wm8350); + wm8350_set_bits(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1, + WM8350_CHG_FAST); + wm8350_reg_lock(wm8350); + break; + + case WM8350_IRQ_CHG_VBATT_LT_3P9: + dev_warn(wm8350->dev, "battery < 3.9V\n"); + break; + case WM8350_IRQ_CHG_VBATT_LT_3P1: + dev_warn(wm8350->dev, "battery < 3.1V\n"); + break; + case WM8350_IRQ_CHG_VBATT_LT_2P85: + dev_warn(wm8350->dev, "battery < 2.85V\n"); + break; + + /* Supply change. We will overnotify but it should do + * no harm. */ + case WM8350_IRQ_EXT_USB_FB: + case WM8350_IRQ_EXT_WALL_FB: + wm8350_charger_config(wm8350, policy); + case WM8350_IRQ_EXT_BAT_FB: /* Fall through */ + power_supply_changed(&power->battery); + power_supply_changed(&power->usb); + power_supply_changed(&power->ac); + break; + + default: + dev_err(wm8350->dev, "Unknown interrupt %d\n", irq); + } +} + +/********************************************************************* + * AC Power + *********************************************************************/ +static int wm8350_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_LINE_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_line_uvolts(wm8350); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property wm8350_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * USB Power + *********************************************************************/ +static int wm8350_usb_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_USB_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_usb_uvolts(wm8350); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property wm8350_usb_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, +}; + +/********************************************************************* + * Battery properties + *********************************************************************/ + +static int wm8350_bat_check_health(struct wm8350 *wm8350) +{ + u16 reg; + + if (wm8350_read_battery_uvolts(wm8350) < 2850000) + return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + + reg = wm8350_reg_read(wm8350, WM8350_CHARGER_OVERRIDES); + if (reg & WM8350_CHG_BATT_HOT_OVRDE) + return POWER_SUPPLY_HEALTH_OVERHEAT; + + if (reg & WM8350_CHG_BATT_COLD_OVRDE) + return POWER_SUPPLY_HEALTH_COLD; + + return POWER_SUPPLY_HEALTH_GOOD; +} + +static int wm8350_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct wm8350 *wm8350 = dev_get_drvdata(psy->dev->parent); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = wm8350_batt_status(wm8350); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = !!(wm8350_get_supplies(wm8350) & + WM8350_BATT_SUPPLY); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = wm8350_read_battery_uvolts(wm8350); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = wm8350_bat_check_health(wm8350); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static enum power_supply_property wm8350_bat_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_HEALTH, +}; + +/********************************************************************* + * Initialisation + *********************************************************************/ + +static void wm8350_init_charger(struct wm8350 *wm8350) +{ + /* register our interest in charger events */ + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_TO); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_END); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_START); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1); + wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85); + + /* and supply change events */ + wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_EXT_USB_FB); + wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_EXT_WALL_FB); + wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, + wm8350_charger_handler, NULL); + wm8350_unmask_irq(wm8350, WM8350_IRQ_EXT_BAT_FB); +} + +static void free_charger_irq(struct wm8350 *wm8350) +{ + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_TO); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_END); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_START); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1); + wm8350_mask_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85); + wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85); + wm8350_mask_irq(wm8350, WM8350_IRQ_EXT_USB_FB); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB); + wm8350_mask_irq(wm8350, WM8350_IRQ_EXT_WALL_FB); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB); + wm8350_mask_irq(wm8350, WM8350_IRQ_EXT_BAT_FB); + wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB); +} + +static __devinit int wm8350_power_probe(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + struct wm8350_power *power = &wm8350->power; + struct wm8350_charger_policy *policy = power->policy; + struct power_supply *usb = &power->usb; + struct power_supply *battery = &power->battery; + struct power_supply *ac = &power->ac; + int ret; + + ac->name = "wm8350-ac"; + ac->type = POWER_SUPPLY_TYPE_MAINS; + ac->properties = wm8350_ac_props; + ac->num_properties = ARRAY_SIZE(wm8350_ac_props); + ac->get_property = wm8350_ac_get_prop; + ret = power_supply_register(&pdev->dev, ac); + if (ret) + return ret; + + battery->name = "wm8350-battery"; + battery->properties = wm8350_bat_props; + battery->num_properties = ARRAY_SIZE(wm8350_bat_props); + battery->get_property = wm8350_bat_get_property; + battery->use_for_apm = 1; + ret = power_supply_register(&pdev->dev, battery); + if (ret) + goto battery_failed; + + usb->name = "wm8350-usb", + usb->type = POWER_SUPPLY_TYPE_USB; + usb->properties = wm8350_usb_props; + usb->num_properties = ARRAY_SIZE(wm8350_usb_props); + usb->get_property = wm8350_usb_get_prop; + ret = power_supply_register(&pdev->dev, usb); + if (ret) + goto usb_failed; + + ret = device_create_file(&pdev->dev, &dev_attr_charger_state); + if (ret < 0) + dev_warn(wm8350->dev, "failed to add charge sysfs: %d\n", ret); + ret = 0; + + wm8350_init_charger(wm8350); + if (wm8350_charger_config(wm8350, policy) == 0) { + wm8350_reg_unlock(wm8350); + wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA); + wm8350_reg_lock(wm8350); + } + + return ret; + +usb_failed: + power_supply_unregister(battery); +battery_failed: + power_supply_unregister(ac); + + return ret; +} + +static __devexit int wm8350_power_remove(struct platform_device *pdev) +{ + struct wm8350 *wm8350 = platform_get_drvdata(pdev); + struct wm8350_power *power = &wm8350->power; + + free_charger_irq(wm8350); + device_remove_file(&pdev->dev, &dev_attr_charger_state); + power_supply_unregister(&power->battery); + power_supply_unregister(&power->ac); + power_supply_unregister(&power->usb); + return 0; +} + +static struct platform_driver wm8350_power_driver = { + .probe = wm8350_power_probe, + .remove = __devexit_p(wm8350_power_remove), + .driver = { + .name = "wm8350-power", + }, +}; + +static int __init wm8350_power_init(void) +{ + return platform_driver_register(&wm8350_power_driver); +} +module_init(wm8350_power_init); + +static void __exit wm8350_power_exit(void) +{ + platform_driver_unregister(&wm8350_power_driver); +} +module_exit(wm8350_power_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Power supply driver for WM8350"); +MODULE_ALIAS("platform:wm8350-power"); |