diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/Kconfig | 2 | ||||
-rw-r--r-- | drivers/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/Kconfig | 51 | ||||
-rw-r--r-- | drivers/power/Makefile | 22 | ||||
-rw-r--r-- | drivers/power/apm_power.c | 243 | ||||
-rw-r--r-- | drivers/power/ds2760_battery.c | 470 | ||||
-rw-r--r-- | drivers/power/olpc_battery.c | 352 | ||||
-rw-r--r-- | drivers/power/pda_power.c | 261 | ||||
-rw-r--r-- | drivers/power/pmu_battery.c | 215 | ||||
-rw-r--r-- | drivers/power/power_supply.h | 42 | ||||
-rw-r--r-- | drivers/power/power_supply_core.c | 168 | ||||
-rw-r--r-- | drivers/power/power_supply_leds.c | 176 | ||||
-rw-r--r-- | drivers/power/power_supply_sysfs.c | 299 | ||||
-rw-r--r-- | drivers/w1/slaves/Kconfig | 13 | ||||
-rw-r--r-- | drivers/w1/slaves/Makefile | 1 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2760.c | 213 | ||||
-rw-r--r-- | drivers/w1/slaves/w1_ds2760.h | 50 | ||||
-rw-r--r-- | drivers/w1/w1_family.h | 1 |
18 files changed, 2580 insertions, 0 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index 4e6487d461f..7916f4b86d2 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -52,6 +52,8 @@ source "drivers/spi/Kconfig" source "drivers/w1/Kconfig" +source "drivers/power/Kconfig" + source "drivers/hwmon/Kconfig" source "drivers/mfd/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index adad2f3d438..503d8256944 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_I2O) += message/ obj-$(CONFIG_RTC_LIB) += rtc/ obj-y += i2c/ obj-$(CONFIG_W1) += w1/ +obj-$(CONFIG_POWER_SUPPLY) += power/ obj-$(CONFIG_HWMON) += hwmon/ obj-$(CONFIG_PHONE) += telephony/ obj-$(CONFIG_MD) += md/ diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig new file mode 100644 index 00000000000..ab9c3e5a7c1 --- /dev/null +++ b/drivers/power/Kconfig @@ -0,0 +1,51 @@ +menuconfig POWER_SUPPLY + tristate "Power supply class support" + help + Say Y here to enable power supply class support. This allows + power supply (batteries, AC, USB) monitoring by userspace + via sysfs and uevent (if available) and/or APM kernel interface + (if selected below). + +if POWER_SUPPLY + +config POWER_SUPPLY_DEBUG + bool "Power supply debug" + help + Say Y here to enable debugging messages for power supply class + and drivers. + +config PDA_POWER + tristate "Generic PDA/phone power driver" + help + Say Y here to enable generic power driver for PDAs and phones with + one or two external power supplies (AC/USB) connected to main and + backup batteries, and optional builtin charger. + +config APM_POWER + tristate "APM emulation for class batteries" + depends on APM_EMULATION + help + Say Y here to enable support APM status emulation using + battery class devices. + +config BATTERY_DS2760 + tristate "DS2760 battery driver (HP iPAQ & others)" + select W1 + select W1_SLAVE_DS2760 + help + Say Y here to enable support for batteries with ds2760 chip. + +config BATTERY_PMU + tristate "Apple PMU battery" + depends on ADB_PMU + help + Say Y here to expose battery information on Apple machines + through the generic battery class. + +config BATTERY_OLPC + tristate "One Laptop Per Child battery" + depends on X86_32 && OLPC + help + Say Y to enable support for the battery on the OLPC laptop. + +endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile new file mode 100644 index 00000000000..6413ded5fe5 --- /dev/null +++ b/drivers/power/Makefile @@ -0,0 +1,22 @@ +power_supply-objs := power_supply_core.o + +ifeq ($(CONFIG_SYSFS),y) +power_supply-objs += power_supply_sysfs.o +endif + +ifeq ($(CONFIG_LEDS_TRIGGERS),y) +power_supply-objs += power_supply_leds.o +endif + +ifeq ($(CONFIG_POWER_SUPPLY_DEBUG),y) +EXTRA_CFLAGS += -DDEBUG +endif + +obj-$(CONFIG_POWER_SUPPLY) += power_supply.o + +obj-$(CONFIG_PDA_POWER) += pda_power.o +obj-$(CONFIG_APM_POWER) += apm_power.o + +obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o +obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o +obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o diff --git a/drivers/power/apm_power.c b/drivers/power/apm_power.c new file mode 100644 index 00000000000..042bd950d03 --- /dev/null +++ b/drivers/power/apm_power.c @@ -0,0 +1,243 @@ +/* + * Copyright © 2007 Anton Vorontsov <cbou@mail.ru> + * Copyright © 2007 Eugeny Boger <eugenyboger@dgap.mipt.ru> + * + * Author: Eugeny Boger <eugenyboger@dgap.mipt.ru> + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + */ + +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/apm-emulation.h> + +#define PSY_PROP(psy, prop, val) psy->get_property(psy, \ + POWER_SUPPLY_PROP_##prop, val) + +#define _MPSY_PROP(prop, val) main_battery->get_property(main_battery, \ + prop, val) + +#define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val) + +static struct power_supply *main_battery; + +static void find_main_battery(void) +{ + struct device *dev; + struct power_supply *bat, *batm; + union power_supply_propval full; + int max_charge = 0; + + main_battery = NULL; + batm = NULL; + list_for_each_entry(dev, &power_supply_class->devices, node) { + bat = dev_get_drvdata(dev); + /* If none of battery devices cantains 'use_for_apm' flag, + choice one with maximum design charge */ + if (!PSY_PROP(bat, CHARGE_FULL_DESIGN, &full)) { + if (full.intval > max_charge) { + batm = bat; + max_charge = full.intval; + } + } + + if (bat->use_for_apm) + main_battery = bat; + } + if (!main_battery) + main_battery = batm; + + return; +} + +static int calculate_time(int status) +{ + union power_supply_propval charge_full, charge_empty; + union power_supply_propval charge, I; + + if (MPSY_PROP(CHARGE_FULL, &charge_full)) { + /* if battery can't report this property, use design value */ + if (MPSY_PROP(CHARGE_FULL_DESIGN, &charge_full)) + return -1; + } + + if (MPSY_PROP(CHARGE_EMPTY, &charge_empty)) { + /* if battery can't report this property, use design value */ + if (MPSY_PROP(CHARGE_EMPTY_DESIGN, &charge_empty)) + charge_empty.intval = 0; + } + + if (MPSY_PROP(CHARGE_AVG, &charge)) { + /* if battery can't report average value, use momentary */ + if (MPSY_PROP(CHARGE_NOW, &charge)) + return -1; + } + + if (MPSY_PROP(CURRENT_AVG, &I)) { + /* if battery can't report average value, use momentary */ + if (MPSY_PROP(CURRENT_NOW, &I)) + return -1; + } + + if (status == POWER_SUPPLY_STATUS_CHARGING) + return ((charge.intval - charge_full.intval) * 60L) / + I.intval; + else + return -((charge.intval - charge_empty.intval) * 60L) / + I.intval; +} + +static int calculate_capacity(int using_charge) +{ + enum power_supply_property full_prop, empty_prop; + enum power_supply_property full_design_prop, empty_design_prop; + enum power_supply_property now_prop, avg_prop; + union power_supply_propval empty, full, cur; + int ret; + + if (using_charge) { + full_prop = POWER_SUPPLY_PROP_CHARGE_FULL; + empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY; + full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN; + empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN; + now_prop = POWER_SUPPLY_PROP_CHARGE_NOW; + avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG; + } else { + full_prop = POWER_SUPPLY_PROP_ENERGY_FULL; + empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY; + full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN; + empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN; + now_prop = POWER_SUPPLY_PROP_ENERGY_NOW; + avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG; + } + + if (_MPSY_PROP(full_prop, &full)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(full_design_prop, &full)) + return -1; + } + + if (_MPSY_PROP(avg_prop, &cur)) { + /* if battery can't report average value, use momentary */ + if (_MPSY_PROP(now_prop, &cur)) + return -1; + } + + if (_MPSY_PROP(empty_prop, &empty)) { + /* if battery can't report this property, use design value */ + if (_MPSY_PROP(empty_design_prop, &empty)) + empty.intval = 0; + } + + if (full.intval - empty.intval) + ret = ((cur.intval - empty.intval) * 100L) / + (full.intval - empty.intval); + else + return -1; + + if (ret > 100) + return 100; + else if (ret < 0) + return 0; + + return ret; +} + +static void apm_battery_apm_get_power_status(struct apm_power_info *info) +{ + union power_supply_propval status; + union power_supply_propval capacity, time_to_full, time_to_empty; + + down(&power_supply_class->sem); + find_main_battery(); + if (!main_battery) { + up(&power_supply_class->sem); + return; + } + + /* status */ + + if (MPSY_PROP(STATUS, &status)) + status.intval = POWER_SUPPLY_STATUS_UNKNOWN; + + /* ac line status */ + + if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) || + (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) || + (status.intval == POWER_SUPPLY_STATUS_FULL)) + info->ac_line_status = APM_AC_ONLINE; + else + info->ac_line_status = APM_AC_OFFLINE; + + /* battery life (i.e. capacity, in percents) */ + + if (MPSY_PROP(CAPACITY, &capacity) == 0) { + info->battery_life = capacity.intval; + } else { + /* try calculate using energy */ + info->battery_life = calculate_capacity(0); + /* if failed try calculate using charge instead */ + if (info->battery_life == -1) + info->battery_life = calculate_capacity(1); + } + + /* charging status */ + + if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { + info->battery_status = APM_BATTERY_STATUS_CHARGING; + } else { + if (info->battery_life > 50) + info->battery_status = APM_BATTERY_STATUS_HIGH; + else if (info->battery_life > 5) + info->battery_status = APM_BATTERY_STATUS_LOW; + else + info->battery_status = APM_BATTERY_STATUS_CRITICAL; + } + info->battery_flag = info->battery_status; + + /* time */ + + info->units = APM_UNITS_MINS; + + if (status.intval == POWER_SUPPLY_STATUS_CHARGING) { + if (MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full)) { + if (MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full)) + info->time = calculate_time(status.intval); + else + info->time = time_to_full.intval / 60; + } + } else { + if (MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty)) { + if (MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty)) + info->time = calculate_time(status.intval); + else + info->time = time_to_empty.intval / 60; + } + } + + up(&power_supply_class->sem); + return; +} + +static int __init apm_battery_init(void) +{ + printk(KERN_INFO "APM Battery Driver\n"); + + apm_get_power_status = apm_battery_apm_get_power_status; + return 0; +} + +static void __exit apm_battery_exit(void) +{ + apm_get_power_status = NULL; + return; +} + +module_init(apm_battery_init); +module_exit(apm_battery_exit); + +MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>"); +MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c new file mode 100644 index 00000000000..00e1ea6f1de --- /dev/null +++ b/drivers/power/ds2760_battery.c @@ -0,0 +1,470 @@ +/* + * Driver for batteries with DS2760 chips inside. + * + * Copyright © 2007 Anton Vorontsov + * 2004-2007 Matt Reimer + * 2004 Szabolcs Gyurko + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * Author: Anton Vorontsov <cbou@mail.ru> + * February 2007 + * + * Matt Reimer <mreimer@vpop.net> + * April 2004, 2005, 2007 + * + * Szabolcs Gyurko <szabolcs.gyurko@tlt.hu> + * September 2004 + */ + +#include <linux/module.h> +#include <linux/param.h> +#include <linux/jiffies.h> +#include <linux/workqueue.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> + +#include "../w1/w1.h" +#include "../w1/slaves/w1_ds2760.h" + +struct ds2760_device_info { + struct device *dev; + + /* DS2760 data, valid after calling ds2760_battery_read_status() */ + unsigned long update_time; /* jiffies when data read */ + char raw[DS2760_DATA_SIZE]; /* raw DS2760 data */ + int voltage_raw; /* units of 4.88 mV */ + int voltage_uV; /* units of µV */ + int current_raw; /* units of 0.625 mA */ + int current_uA; /* units of µA */ + int accum_current_raw; /* units of 0.25 mAh */ + int accum_current_uAh; /* units of µAh */ + int temp_raw; /* units of 0.125 °C */ + int temp_C; /* units of 0.1 °C */ + int rated_capacity; /* units of µAh */ + int rem_capacity; /* percentage */ + int full_active_uAh; /* units of µAh */ + int empty_uAh; /* units of µAh */ + int life_sec; /* units of seconds */ + int charge_status; /* POWER_SUPPLY_STATUS_* */ + + int full_counter; + struct power_supply bat; + struct device *w1_dev; + struct workqueue_struct *monitor_wqueue; + struct delayed_work monitor_work; +}; + +static unsigned int cache_time = 1000; +module_param(cache_time, uint, 0644); +MODULE_PARM_DESC(cache_time, "cache time in milliseconds"); + +/* Some batteries have their rated capacity stored a N * 10 mAh, while + * others use an index into this table. */ +static int rated_capacities[] = { + 0, + 920, /* Samsung */ + 920, /* BYD */ + 920, /* Lishen */ + 920, /* NEC */ + 1440, /* Samsung */ + 1440, /* BYD */ + 1440, /* Lishen */ + 1440, /* NEC */ + 2880, /* Samsung */ + 2880, /* BYD */ + 2880, /* Lishen */ + 2880 /* NEC */ +}; + +/* array is level at temps 0°C, 10°C, 20°C, 30°C, 40°C + * temp is in Celsius */ +static int battery_interpolate(int array[], int temp) +{ + int index, dt; + + if (temp <= 0) + return array[0]; + if (temp >= 40) + return array[4]; + + index = temp / 10; + dt = temp % 10; + + return array[index] + (((array[index + 1] - array[index]) * dt) / 10); +} + +static int ds2760_battery_read_status(struct ds2760_device_info *di) +{ + int ret, i, start, count, scale[5]; + + if (di->update_time && time_before(jiffies, di->update_time + + msecs_to_jiffies(cache_time))) + return 0; + + /* The first time we read the entire contents of SRAM/EEPROM, + * but after that we just read the interesting bits that change. */ + if (di->update_time == 0) { + start = 0; + count = DS2760_DATA_SIZE; + } else { + start = DS2760_VOLTAGE_MSB; + count = DS2760_TEMP_LSB - start + 1; + } + + ret = w1_ds2760_read(di->w1_dev, di->raw + start, start, count); + if (ret != count) { + dev_warn(di->dev, "call to w1_ds2760_read failed (0x%p)\n", + di->w1_dev); + return 1; + } + + di->update_time = jiffies; + + /* DS2760 reports voltage in units of 4.88mV, but the battery class + * reports in units of uV, so convert by multiplying by 4880. */ + di->voltage_raw = (di->raw[DS2760_VOLTAGE_MSB] << 3) | + (di->raw[DS2760_VOLTAGE_LSB] >> 5); + di->voltage_uV = di->voltage_raw * 4880; + + /* DS2760 reports current in signed units of 0.625mA, but the battery + * class reports in units of µA, so convert by multiplying by 625. */ + di->current_raw = + (((signed char)di->raw[DS2760_CURRENT_MSB]) << 5) | + (di->raw[DS2760_CURRENT_LSB] >> 3); + di->current_uA = di->current_raw * 625; + + /* DS2760 reports accumulated current in signed units of 0.25mAh. */ + di->accum_current_raw = + (((signed char)di->raw[DS2760_CURRENT_ACCUM_MSB]) << 8) | + di->raw[DS2760_CURRENT_ACCUM_LSB]; + di->accum_current_uAh = di->accum_current_raw * 250; + + /* DS2760 reports temperature in signed units of 0.125°C, but the + * battery class reports in units of 1/10 °C, so we convert by + * multiplying by .125 * 10 = 1.25. */ + di->temp_raw = (((signed char)di->raw[DS2760_TEMP_MSB]) << 3) | + (di->raw[DS2760_TEMP_LSB] >> 5); + di->temp_C = di->temp_raw + (di->temp_raw / 4); + + /* At least some battery monitors (e.g. HP iPAQ) store the battery's + * maximum rated capacity. */ + if (di->raw[DS2760_RATED_CAPACITY] < ARRAY_SIZE(rated_capacities)) + di->rated_capacity = rated_capacities[ + (unsigned int)di->raw[DS2760_RATED_CAPACITY]]; + else + di->rated_capacity = di->raw[DS2760_RATED_CAPACITY] * 10; + + di->rated_capacity *= 1000; /* convert to µAh */ + + /* Calculate the full level at the present temperature. */ + di->full_active_uAh = di->raw[DS2760_ACTIVE_FULL] << 8 | + di->raw[DS2760_ACTIVE_FULL + 1]; + + scale[0] = di->raw[DS2760_ACTIVE_FULL] << 8 | + di->raw[DS2760_ACTIVE_FULL + 1]; + for (i = 1; i < 5; i++) + scale[i] = scale[i - 1] + di->raw[DS2760_ACTIVE_FULL + 2 + i]; + + di->full_active_uAh = battery_interpolate(scale, di->temp_C / 10); + di->full_active_uAh *= 1000; /* convert to µAh */ + + /* Calculate the empty level at the present temperature. */ + scale[4] = di->raw[DS2760_ACTIVE_EMPTY + 4]; + for (i = 3; i >= 0; i--) + scale[i] = scale[i + 1] + di->raw[DS2760_ACTIVE_EMPTY + i]; + + di->empty_uAh = battery_interpolate(scale, di->temp_C / 10); + di->empty_uAh *= 1000; /* convert to µAh */ + + /* From Maxim Application Note 131: remaining capacity = + * ((ICA - Empty Value) / (Full Value - Empty Value)) x 100% */ + di->rem_capacity = ((di->accum_current_uAh - di->empty_uAh) * 100L) / + (di->full_active_uAh - di->empty_uAh); + + if (di->rem_capacity < 0) + di->rem_capacity = 0; + if (di->rem_capacity > 100) + di->rem_capacity = 100; + + if (di->current_uA) + di->life_sec = -((di->accum_current_uAh - di->empty_uAh) * + 3600L) / di->current_uA; + else + di->life_sec = 0; + + return 0; +} + +static void ds2760_battery_update_status(struct ds2760_device_info *di) +{ + int old_charge_status = di->charge_status; + + ds2760_battery_read_status(di); + + if (di->charge_status == POWER_SUPPLY_STATUS_UNKNOWN) + di->full_counter = 0; + + if (power_supply_am_i_supplied(&di->bat)) { + if (di->current_uA > 10000) { + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + di->full_counter = 0; + } else if (di->current_uA < -5000) { + if (di->charge_status != POWER_SUPPLY_STATUS_NOT_CHARGING) + dev_notice(di->dev, "not enough power to " + "charge\n"); + di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING; + di->full_counter = 0; + } else if (di->current_uA < 10000 && + di->charge_status != POWER_SUPPLY_STATUS_FULL) { + + /* Don't consider the battery to be full unless + * we've seen the current < 10 mA at least two + * consecutive times. */ + + di->full_counter++; + + if (di->full_counter < 2) { + di->charge_status = POWER_SUPPLY_STATUS_CHARGING; + } else { + unsigned char acr[2]; + int acr_val; + + /* acr is in units of 0.25 mAh */ + acr_val = di->full_active_uAh * 4L / 1000; + + acr[0] = acr_val >> 8; + acr[1] = acr_val & 0xff; + + if (w1_ds2760_write(di->w1_dev, acr, + DS2760_CURRENT_ACCUM_MSB, 2) < 2) + dev_warn(di->dev, + "ACR reset failed\n"); + + di->charge_status = POWER_SUPPLY_STATUS_FULL; + } + } + } else { + di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING; + di->full_counter = 0; + } + + if (di->charge_status != old_charge_status) + power_supply_changed(&di->bat); + + return; +} + +static void ds2760_battery_work(struct work_struct *work) +{ + struct ds2760_device_info *di = container_of(work, + struct ds2760_device_info, monitor_work.work); + const int interval = HZ * 60; + + dev_dbg(di->dev, "%s\n", __FUNCTION__); + + ds2760_battery_update_status(di); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval); + + return; +} + +#define to_ds2760_device_info(x) container_of((x), struct ds2760_device_info, \ + bat); + +static void ds2760_battery_external_power_changed(struct power_supply *psy) +{ + struct ds2760_device_info *di = to_ds2760_device_info(psy); + + dev_dbg(di->dev, "%s\n", __FUNCTION__); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ/10); + + return; +} + +static int ds2760_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct ds2760_device_info *di = to_ds2760_device_info(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = di->charge_status; + return 0; + default: + break; + } + + ds2760_battery_read_status(di); + + switch (psp) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = di->voltage_uV; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = di->current_uA; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = di->rated_capacity; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = di->full_active_uAh; + break; + case POWER_SUPPLY_PROP_CHARGE_EMPTY: + val->intval = di->empty_uAh; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = di->accum_current_uAh; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = di->temp_C; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property ds2760_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_EMPTY, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, +}; + +static int ds2760_battery_probe(struct platform_device *pdev) +{ + int retval = 0; + struct ds2760_device_info *di; + struct ds2760_platform_data *pdata; + + di = kzalloc(sizeof(*di), GFP_KERNEL); + if (!di) { + retval = -ENOMEM; + goto di_alloc_failed; + } + + platform_set_drvdata(pdev, di); + + pdata = pdev->dev.platform_data; + di->dev = &pdev->dev; + di->w1_dev = pdev->dev.parent; + di->bat.name = pdev->dev.bus_id; + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = ds2760_battery_props; + di->bat.num_properties = ARRAY_SIZE(ds2760_battery_props); + di->bat.get_property = ds2760_battery_get_property; + di->bat.external_power_changed = + ds2760_battery_external_power_changed; + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + retval = power_supply_register(&pdev->dev, &di->bat); + if (retval) { + dev_err(di->dev, "failed to register battery"); + goto batt_failed; + } + + INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work); + di->monitor_wqueue = create_singlethread_workqueue(pdev->dev.bus_id); + if (!di->monitor_wqueue) { + retval = -ESRCH; + goto workqueue_failed; + } + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 1); + + goto success; + +workqueue_failed: + power_supply_unregister(&di->bat); +batt_failed: + kfree(di); +di_alloc_failed: +success: + return retval; +} + +static int ds2760_battery_remove(struct platform_device *pdev) +{ + struct ds2760_device_info *di = platform_get_drvdata(pdev); + + cancel_rearming_delayed_workqueue(di->monitor_wqueue, + &di->monitor_work); + destroy_workqueue(di->monitor_wqueue); + power_supply_unregister(&di->bat); + + return 0; +} + +#ifdef CONFIG_PM + +static int ds2760_battery_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ds2760_device_info *di = platform_get_drvdata(pdev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + + return 0; +} + +static int ds2760_battery_resume(struct platform_device *pdev) +{ + struct ds2760_device_info *di = platform_get_drvdata(pdev); + + di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; + power_supply_changed(&di->bat); + + cancel_delayed_work(&di->monitor_work); + queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ); + + return 0; +} + +#else + +#define ds2760_battery_suspend NULL +#define ds2760_battery_resume NULL + +#endif /* CONFIG_PM */ + +static struct platform_driver ds2760_battery_driver = { + .driver = { + .name = "ds2760-battery", + }, + .probe = ds2760_battery_probe, + .remove = ds2760_battery_remove, + .suspend = ds2760_battery_suspend, + .resume = ds2760_battery_resume, +}; + +static int __init ds2760_battery_init(void) +{ + return platform_driver_register(&ds2760_battery_driver); +} + +static void __exit ds2760_battery_exit(void) +{ + platform_driver_unregister(&ds2760_battery_driver); + return; +} + +module_init(ds2760_battery_init); +module_exit(ds2760_battery_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, " + "Matt Reimer <mreimer@vpop.net>, " + "Anton Vorontsov <cbou@mail.ru>"); +MODULE_DESCRIPTION("ds2760 battery driver"); diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c new file mode 100644 index 00000000000..878684df766 --- /dev/null +++ b/drivers/power/olpc_battery.c @@ -0,0 +1,352 @@ +/* + * Battery driver for One Laptop Per Child board. + * + * 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/jiffies.h> +#include <linux/sched.h> +#include <asm/olpc.h> + + +#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */ +#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */ +#define EC_BAT_ACR 0x12 +#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */ +#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */ +#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */ +#define EC_BAT_SOC 0x16 /* uint8_t, percentage */ +#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */ +#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */ +#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */ + +#define BAT_STAT_PRESENT 0x01 +#define BAT_STAT_FULL 0x02 +#define BAT_STAT_LOW 0x04 +#define BAT_STAT_DESTROY 0x08 +#define BAT_STAT_AC 0x10 +#define BAT_STAT_CHARGING 0x20 +#define BAT_STAT_DISCHARGING 0x40 + +#define BAT_ERR_INFOFAIL 0x02 +#define BAT_ERR_OVERVOLTAGE 0x04 +#define BAT_ERR_OVERTEMP 0x05 +#define BAT_ERR_GAUGESTOP 0x06 +#define BAT_ERR_OUT_OF_CONTROL 0x07 +#define BAT_ERR_ID_FAIL 0x09 +#define BAT_ERR_ACR_FAIL 0x10 + +#define BAT_ADDR_MFR_TYPE 0x5F + +/********************************************************************* + * Power + *********************************************************************/ + +static int olpc_ac_get_prop(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + uint8_t status; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1); + if (ret) + return ret; + + val->intval = !!(status & BAT_STAT_AC); + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static enum power_supply_property olpc_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static struct power_supply olpc_ac = { + .name = "olpc-ac", + .type = POWER_SUPPLY_TYPE_MAINS, + .properties = olpc_ac_props, + .num_properties = ARRAY_SIZE(olpc_ac_props), + .get_property = olpc_ac_get_prop, +}; + +/********************************************************************* + * Battery properties + *********************************************************************/ +static int olpc_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret = 0; + int16_t ec_word; + uint8_t ec_byte; + + ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1); + if (ret) + return ret; + + /* Theoretically there's a race here -- the battery could be + removed immediately after we check whether it's present, and + then we query for some other property of the now-absent battery. + It doesn't matter though -- the EC will return the last-known + information, and it's as if we just ran that _little_ bit faster + and managed to read it out before the battery went away. */ + if (!(ec_byte & BAT_STAT_PRESENT) && psp != POWER_SUPPLY_PROP_PRESENT) + return -ENODEV; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (olpc_platform_info.ecver > 0x44) { + if (ec_byte & BAT_STAT_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (ec_byte & BAT_STAT_DISCHARGING) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (ec_byte & BAT_STAT_FULL) + val->intval = POWER_SUPPLY_STATUS_FULL; + else /* er,... */ + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else { + /* Older EC didn't report charge/discharge bits */ + if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */ + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (ec_byte & BAT_STAT_FULL) + val->intval = POWER_SUPPLY_STATUS_FULL; + else /* Not _necessarily_ true but EC doesn't tell all yet */ + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + } + case POWER_SUPPLY_PROP_PRESENT: + val->intval = !!(ec_byte & BAT_STAT_PRESENT); + break; + + case POWER_SUPPLY_PROP_HEALTH: + if (ec_byte & BAT_STAT_DESTROY) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else { + ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1); + if (ret) + return ret; + + switch (ec_byte) { + case 0: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + + case BAT_ERR_OVERTEMP: + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + + case BAT_ERR_OVERVOLTAGE: + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + + case BAT_ERR_INFOFAIL: + case BAT_ERR_OUT_OF_CONTROL: + case BAT_ERR_ID_FAIL: + c |