Messages in this thread Patch in this message | | | Date | Fri, 20 Apr 2007 15:58:13 +0400 | From | Anton Vorontsov <> | Subject | [PATCH 3/7] Universal battery class |
| |
Signed-off-by: Anton Vorontsov <cbou@mail.ru> --- Documentation/battery-class.txt | 150 ++++++++++++++++++ drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/battery/Kconfig | 11 ++ drivers/battery/Makefile | 1 + drivers/battery/battery.c | 323 +++++++++++++++++++++++++++++++++++++++ include/linux/battery.h | 125 +++++++++++++++ 7 files changed, 613 insertions(+), 0 deletions(-) create mode 100644 Documentation/battery-class.txt create mode 100644 drivers/battery/Kconfig create mode 100644 drivers/battery/Makefile create mode 100644 drivers/battery/battery.c create mode 100644 include/linux/battery.h
diff --git a/Documentation/battery-class.txt b/Documentation/battery-class.txt new file mode 100644 index 0000000..6a7d591 --- /dev/null +++ b/Documentation/battery-class.txt @@ -0,0 +1,150 @@ +Linux battery class +=================== + +Synopsis +~~~~~~~~ +Battery class used to export battery properties to userspace in consistent +manner. + +It defines core set of battery attributes, available via sysfs, which +should be applicable to (almost) every battery out there. Each attribute +has well defined meaning, up to unit of measure used. While the attributes +provided are believed to be universally applicable to any battery, +specific monitoring hardware may not be able to provide them all, so +any of them may be skipped. + +Battery class is extensible, and allows to define drivers own attributes. +The core attribute set is subject to the standard Linux evolution (i.e. +if it will be found that some attribute is applicable to many batteries +or their drivers, it can be added to the core set). + +Battery class integrates with External Power framework, for the purpose of +notification battery drivers when charging power is available. Note that +specific charge control is left to the battery drivers. + +It also integrates with LED framework, for the purpose of providing +typically expected (at least for portable devices) feedback of battery +status (charging/fully charged) via LEDs. (Note that specific details of +the indication (including whether to use it at all) are fully controllable +by user and/or specific machine defaults, per design principles of LED +framework). + + +Attributes/properties +~~~~~~~~~~~~~~~~~~~~~ +Battery class has predefined set of attributes, this eliminates code +duplication across battery drivers. Battery class insist on reusing its +predefined attributes *and* their units. + +So, userspace gets expected set of attributes and their units for +any kind of battery, and can process/present them to a user in consistent +manner. Results for different batteries and machines are also directly +comparable. + +See drivers/battery/ds2760_battery.c for the example how to declare and +handle attributes. + + +Units +~~~~~ +Quoting include/linux/battery.h: + + All voltages, currents, charges, energies, time and temperatures in uV, + uA, uAh, uWh, seconds and tenths of degree Celsius unless otherwise + stated. It's driver's job to convert its raw values to units in which + this class operates. + + +Attributes/properties detailed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +~ ~ ~ ~ ~ ~ ~ Charge/Energy/Capacity - how to not confuse ~ ~ ~ ~ ~ ~ ~ +~ ~ +~ Because both "charge" (uAh) and "energy" (uWh) represents "capacity" ~ +~ of battery, battery class distinguish these terms. Don't mix them! ~ +~ ~ +~ CHARGE_* attributes represents capacity in uAh only. ~ +~ ENERGY_* attributes represents capacity in uWh only. ~ +~ CAPACITY attribute represents capacity in *percents*, from 0 to 100. ~ +~ ~ +~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ + +Postfixes: +_AVG - *hardware* averaged value, use it if your hardware is really able to +report averaged values. +_NOW - momentary/instantaneous values. + +STATUS - this attribute represents operating status (charging, full, +discharging (i.e. powering a load), etc.). This corresponds to +BATTERY_STATUS_* values, as defined in battery.h. + +HEALTH - represents health of the battery, values corresponds to +BATTERY_HEALTH_*, defined in battery.h. + +VOLTAGE_MAX_DESIGN, VOLTAGE_MIN_DESIGN - design values for maximal and +minimal battery voltages. Maximal/minimal means values of voltages when +battery considered "full"/"empty" at normal conditions. Yes, there is +no direct relation between voltage and battery capacity, but some dumb +batteries use voltage for very approximated calculation of capacity. +Battery driver also can use this attribute just to inform userspace +about maximal and minimal voltage thresholds of a given battery. + +CHARGE_FULL_DESIGN, CHARGE_EMPTY_DESIGN - design charge values, when +battery considered full/empty. + +ENERGY_FULL_DESIGN, ENERGY_EMPTY_DESIGN - same as above but for energy. + +CHARGE_FULL, CHARGE_EMPTY - These attributes means "last remembered value +of charge when battery became full/empty". It also could mean "value of +charge when battery considered full/empty at given conditions (temperature, +age)". I.e. these attributes represents real thresholds, not design values. + +ENERGY_FULL, ENERGY_EMPTY - same as above but for energy. + +CAPACITY - capacity in percents. + +TEMP - temperature of the battery. + +TIME_TO_EMPTY - seconds left for battery to be considered empty (i.e. +while battery powers a load) +TIME_TO_FULL - seconds left for battery to be considered full (i.e. +while battery is charging) + + +QA +~~ +Q: Where is BATTERY_PROP_XYZ attribute? +A: If you cannot find attribute suitable for your battery needs, feel free + to add it and send patch along with your battery driver. + + The attributes available currently are the ones currently provided by the + drivers written. + + Good candidates to add in future: battery technology, model/part#, + cycle_time, manufacturer, etc. + + +Q: I have some very specific attribute (e.g. battery color), should I add + this attribute to standard ones? +A: Most likely, no. Such attribute can be placed in the driver itself, if + it is useful. Of course, if the attribute in question applicable to + large set of batteries, provided by many drivers, and/or comes from + some general battery specification/standard, it may be a candidate to + be added to the core attribute set. + + +Q: Suppose, my battery monitoring chip/firmware does not provides capacity + in percents, but provides charge_{now,full,empty}. Should I calculate + percentage capacity manually, inside the driver, and register CAPACITY + attribute? The same question about time_to_empty/time_to_full. +A: Most likely, no. Battery class is designed to export battery properties + which are directly measurable by the specific hardware available. + Inferring not available properties using some heuristics or mathematical + model is not subject of work for a battery driver. Such functionality + should be factored out, and in fact, apm_power, the driver to serve + legacy APM API on top of battery class, uses a simple heuristic of + approximating remaining battery capacity based on its charge, current, + voltage and so on. But full-fledged battery model is likely not subject + for kernel at all, as it would require floating point calculation to deal + with things like differential equations and Kalman filters. This is + better be handled by batteryd/libbattery, yet to be written. diff --git a/drivers/Kconfig b/drivers/Kconfig index c546de3..c3a0038 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -56,6 +56,8 @@ source "drivers/w1/Kconfig" source "drivers/power/Kconfig" +source "drivers/battery/Kconfig" + source "drivers/hwmon/Kconfig" source "drivers/mfd/Kconfig" diff --git a/drivers/Makefile b/drivers/Makefile index 2bdaae7..7cbfd37 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_RTC_LIB) += rtc/ obj-$(CONFIG_I2C) += i2c/ obj-$(CONFIG_W1) += w1/ obj-$(CONFIG_EXTERNAL_POWER) += power/ +obj-$(CONFIG_BATTERY) += battery/ obj-$(CONFIG_HWMON) += hwmon/ obj-$(CONFIG_PHONE) += telephony/ obj-$(CONFIG_MD) += md/ diff --git a/drivers/battery/Kconfig b/drivers/battery/Kconfig new file mode 100644 index 0000000..c386593 --- /dev/null +++ b/drivers/battery/Kconfig @@ -0,0 +1,11 @@ + +menu "Battery support" + +config BATTERY + tristate "Battery monitoring support" + select EXTERNAL_POWER + help + Say Y here to enable generic battery status reporting in + the /sys filesystem. + +endmenu diff --git a/drivers/battery/Makefile b/drivers/battery/Makefile new file mode 100644 index 0000000..a2239cb --- /dev/null +++ b/drivers/battery/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_BATTERY) += battery.o diff --git a/drivers/battery/battery.c b/drivers/battery/battery.c new file mode 100644 index 0000000..66dc727 --- /dev/null +++ b/drivers/battery/battery.c @@ -0,0 +1,323 @@ +/* + * Universal battery monitor class + * + * Copyright (c) 2007 Anton Vorontsov <cbou@mail.ru> + * Copyright (c) 2004 Szabolcs Gyurko + * Copyright (c) 2003 Ian Molton <spyro@f2s.com> + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + * + * All voltages, currents, charges, energies, time and temperatures in uV, + * uA, uAh, uWh, seconds and tenths of degree Celsius unless otherwise + * stated. It's driver's job to convert its raw values to units in which + * this class operates. + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/battery.h> + +/* If we have hwtimer trigger, then use it to blink charging LED */ +#if defined(CONFIG_LEDS_TRIGGER_HWTIMER) || \ + (defined(CONFIG_BATTERY_MODULE) && \ + defined(CONFIG_LEDS_TRIGGER_HWTIMER_MODULE)) + #define led_trigger_register_charging led_trigger_register_hwtimer + #define led_trigger_unregister_charging led_trigger_unregister_hwtimer +#else + #define led_trigger_register_charging led_trigger_register_simple + #define led_trigger_unregister_charging led_trigger_unregister_simple +#endif + +struct class *battery_class; + +static void battery_external_power_changed(struct power_supplicant *pst, + struct power_supply *psy) +{ + struct battery *bat = container_of(pst, struct battery, pst); + + pr_debug("%s\n", __FUNCTION__); + if (bat->external_power_changed) + bat->external_power_changed(bat); + + return; +} + +int battery_is_external_power_supplied(struct battery *bat) +{ + pr_debug("%s\n", __FUNCTION__); + return power_supplicant_am_i_supplied(&bat->pst); +} + +void battery_status_changed(struct battery *bat) +{ + void *value; + + pr_debug("%s\n", __FUNCTION__); + + value = bat->get_property(bat, BATTERY_PROP_STATUS); + if (!value) + return; + +#ifdef CONFIG_LEDS_TRIGGERS + switch(*(int *)value) { + case BATTERY_STATUS_FULL: + led_trigger_event(bat->charging_full_trig, LED_FULL); + led_trigger_event(bat->charging_trig, LED_OFF); + led_trigger_event(bat->full_trig, LED_FULL); + break; + case BATTERY_STATUS_CHARGING: + led_trigger_event(bat->charging_full_trig, LED_FULL); + led_trigger_event(bat->charging_trig, LED_FULL); + led_trigger_event(bat->full_trig, LED_OFF); + break; + default: + led_trigger_event(bat->charging_full_trig, LED_OFF); + led_trigger_event(bat->charging_trig, LED_OFF); + led_trigger_event(bat->full_trig, LED_OFF); + break; + } +#endif /* CONFIG_LEDS_TRIGGERS */ + return; +} + +/* + * This is because the name "current" breaks the device attr macro. + * The "current" word resolvs to "(get_current())" so instead of + * "current" "(get_current())" appears in the sysfs. + * + * The source of this definition is the device.h which calls __ATTR + * macro in sysfs.h which calls the __stringify macro. + * + * Only modification that the name is not tried to be resolved + * (as a macro let's say). + */ + +#define BATTERY_ATTR(_name) \ +{ \ + .attr = { .name = #_name, .mode = 0444, .owner = THIS_MODULE }, \ + .show = battery_show_property, \ + .store = NULL, \ +} + +static struct device_attribute battery_attrs[]; + +static ssize_t battery_show_property(struct device *dev, + struct device_attribute *attr, + char *buf) { + static char *status_text[] = { + "Unknown", "Charging", "Discharging", "Not charging", "Full" + }; + static char *health_text[] = { + "Unknown", "Good", "Overheat", "Dead" + }; + struct battery *bat = dev_get_drvdata(dev); + const off_t off = attr - battery_attrs; + void *value = bat->get_property(bat, off); + + if (!value) + return sprintf(buf, "Driver can't report this property, " + "but claimed it can. Fix it.\n"); + + if (off == BATTERY_PROP_STATUS) + return sprintf(buf, "%s\n", status_text[*(int *)value]); + else if (off == BATTERY_PROP_HEALTH) + return sprintf(buf, "%s\n", health_text[*(int *)value]); + else + return sprintf(buf, "%d\n", *(int *)value); +} + +/* Must be in the same order as BATTERY_PROP_*, defined in battery.h */ +static struct device_attribute battery_attrs[] = { + BATTERY_ATTR(status), + BATTERY_ATTR(health), + BATTERY_ATTR(voltage_max_design), + BATTERY_ATTR(voltage_min_design), + BATTERY_ATTR(voltage_now), + BATTERY_ATTR(voltage_avg), + BATTERY_ATTR(current_now), + BATTERY_ATTR(current_avg), + BATTERY_ATTR(charge_full_design), + BATTERY_ATTR(charge_empty_design), + BATTERY_ATTR(charge_full), + BATTERY_ATTR(charge_empty), + BATTERY_ATTR(charge_now), + BATTERY_ATTR(charge_avg), + BATTERY_ATTR(energy_full_design), + BATTERY_ATTR(energy_empty_design), + BATTERY_ATTR(energy_full), + BATTERY_ATTR(energy_empty), + BATTERY_ATTR(energy_now), + BATTERY_ATTR(energy_avg), + BATTERY_ATTR(capacity), + BATTERY_ATTR(temp), + BATTERY_ATTR(time_to_empty_now), + BATTERY_ATTR(time_to_empty_avg), + BATTERY_ATTR(time_to_full_now), + BATTERY_ATTR(time_to_full_avg), +}; + +static int battery_create_attrs(struct battery *bat) +{ + int rc = 0; + int i; + + for (i = 0; i < bat->num_properties; i++) { + rc = device_create_file(bat->dev, + &battery_attrs[bat->properties[i]]); + if (rc) + goto failed; + } + + goto succeed; + +failed: + while (i--) + device_remove_file(bat->dev, + &battery_attrs[bat->properties[i]]); +succeed: + return rc; +} + +static void battery_remove_attrs(struct battery *bat) +{ + int i; + + for (i = 0; i < bat->num_properties; i++) + device_remove_file(bat->dev, + &battery_attrs[bat->properties[i]]); + + return; +} + +int battery_register(struct device *parent, struct battery *bat) +{ + int rc = 0; + + bat->dev = device_create(battery_class, parent, 0, "%s", bat->name); + if (IS_ERR(bat->dev)) { + rc = PTR_ERR(bat->dev); + goto dev_create_failed; + } + + dev_set_drvdata(bat->dev, bat); + + rc = battery_create_attrs(bat); + if (rc) + goto create_bat_attrs_failed; + + bat->pst.name = bat->name; + bat->pst.power_supply_changed = battery_external_power_changed; + rc = power_supplicant_register(&bat->pst); + if (rc) + goto power_supplicant_failed; + +#ifdef CONFIG_LEDS_TRIGGERS + bat->charging_full_trig_name = kmalloc(strlen(bat->name) + + sizeof("-charging-or-full"), GFP_KERNEL); + if (!bat->charging_full_trig_name) { + rc = -ENOMEM; + goto charging_full_trig_name_failed; + } + + bat->charging_trig_name = kmalloc(strlen(bat->name) + + sizeof("-charging"), GFP_KERNEL); + if (!bat->charging_trig_name) { + rc = -ENOMEM; + goto charging_trig_name_failed; + } + + bat->full_trig_name = kmalloc(strlen(bat->name) + + sizeof("-full"), GFP_KERNEL); + if (!bat->full_trig_name) { + rc = -ENOMEM; + goto full_trig_name_failed; + } + + strcpy(bat->charging_full_trig_name, bat->name); + strcat(bat->charging_full_trig_name, "-charging-or-full"); + strcpy(bat->charging_trig_name, bat->name); + strcat(bat->charging_trig_name, "-charging"); + strcpy(bat->full_trig_name, bat->name); + strcat(bat->full_trig_name, "-full"); + + led_trigger_register_simple(bat->charging_full_trig_name, + &bat->charging_full_trig); + led_trigger_register_charging(bat->charging_trig_name, + &bat->charging_trig); + led_trigger_register_simple(bat->full_trig_name, + &bat->full_trig); +#endif /* CONFIG_LEDS_TRIGGERS */ + + goto success; + +#ifdef CONFIG_LEDS_TRIGGERS +full_trig_name_failed: + kfree(bat->charging_trig_name); +charging_trig_name_failed: + kfree(bat->charging_full_trig_name); +charging_full_trig_name_failed: +#endif + power_supplicant_unregister(&bat->pst); +power_supplicant_failed: + battery_remove_attrs(bat); +create_bat_attrs_failed: + device_unregister(bat->dev); +dev_create_failed: +success: + return rc; +} + +void battery_unregister(struct battery *bat) +{ + power_supplicant_unregister(&bat->pst); + battery_remove_attrs(bat); + device_unregister(bat->dev); + +#ifdef CONFIG_LEDS_TRIGGERS + led_trigger_unregister_charging(bat->charging_full_trig); + led_trigger_unregister_charging(bat->charging_trig); + led_trigger_unregister_simple(bat->full_trig); + kfree(bat->full_trig_name); + kfree(bat->charging_trig_name); + kfree(bat->charging_full_trig_name); +#endif + return; +} + +static int __init battery_class_init(void) +{ + battery_class = class_create(THIS_MODULE, "battery"); + + if (IS_ERR(battery_class)) + return PTR_ERR(battery_class); + + return 0; +} + +static void __exit battery_class_exit(void) +{ + class_destroy(battery_class); + return; +} + +EXPORT_SYMBOL_GPL(battery_register); +EXPORT_SYMBOL_GPL(battery_unregister); +EXPORT_SYMBOL_GPL(battery_status_changed); +EXPORT_SYMBOL_GPL(battery_is_external_power_supplied); + +/* exported for the APM Power driver, APM emulation */ +EXPORT_SYMBOL_GPL(battery_class); + +subsys_initcall(battery_class_init); +module_exit(battery_class_exit); + +MODULE_DESCRIPTION("Universal battery monitor class"); +MODULE_AUTHOR("Ian Molton <spyro@f2s.com>, " + "Szabolcs Gyurko, " + "Anton Vorontsov <cbou@mail.ru>"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/battery.h b/include/linux/battery.h new file mode 100644 index 0000000..2743f88 --- /dev/null +++ b/include/linux/battery.h @@ -0,0 +1,125 @@ +/* + * Universal battery monitor class + * + * Copyright (c) 2007 Anton Vorontsov <cbou@mail.ru> + * Copyright (c) 2004 Szabolcs Gyurko + * Copyright (c) 2003 Ian Molton <spyro@f2s.com> + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + * + * All voltages, currents, charges, energies, time and temperatures in uV, + * uA, uAh, uWh, seconds and tenths of degree Celsius unless otherwise + * stated. It's driver's job to convert its raw values to units in which + * this class operates. + */ + +#ifndef __LINUX_BATTERY_H__ +#define __LINUX_BATTERY_H__ + +#include <linux/device.h> +#include <linux/external_power.h> +#include <linux/leds.h> + +#define BATTERY_STATUS_UNKNOWN 0 +#define BATTERY_STATUS_CHARGING 1 +#define BATTERY_STATUS_DISCHARGING 2 +#define BATTERY_STATUS_NOT_CHARGING 3 +#define BATTERY_STATUS_FULL 4 + +#define BATTERY_HEALTH_UNKNOWN 0 +#define BATTERY_HEALTH_GOOD 1 +#define BATTERY_HEALTH_OVERHEAT 2 +#define BATTERY_HEALTH_DEAD 3 + +/* + * For systems where the charger determines the maximum battery capacity + * the min and max fields should be used to present these values to user + * space. Unused/unknown fields will not appear in sysfs. + */ + +enum battery_property { + BATTERY_PROP_STATUS = 0, + BATTERY_PROP_HEALTH, + BATTERY_PROP_VOLTAGE_MAX_DESIGN, + BATTERY_PROP_VOLTAGE_MIN_DESIGN, + BATTERY_PROP_VOLTAGE_NOW, + BATTERY_PROP_VOLTAGE_AVG, + BATTERY_PROP_CURRENT_NOW, + BATTERY_PROP_CURRENT_AVG, + BATTERY_PROP_CHARGE_FULL_DESIGN, + BATTERY_PROP_CHARGE_EMPTY_DESIGN, + BATTERY_PROP_CHARGE_FULL, + BATTERY_PROP_CHARGE_EMPTY, + BATTERY_PROP_CHARGE_NOW, + BATTERY_PROP_CHARGE_AVG, + BATTERY_PROP_ENERGY_FULL_DESIGN, + BATTERY_PROP_ENERGY_EMPTY_DESIGN, + BATTERY_PROP_ENERGY_FULL, + BATTERY_PROP_ENERGY_EMPTY, + BATTERY_PROP_ENERGY_NOW, + BATTERY_PROP_ENERGY_AVG, + BATTERY_PROP_CAPACITY, /* in percents! */ + BATTERY_PROP_TEMP, + BATTERY_PROP_TIME_TO_EMPTY_NOW, + BATTERY_PROP_TIME_TO_EMPTY_AVG, + BATTERY_PROP_TIME_TO_FULL_NOW, + BATTERY_PROP_TIME_TO_FULL_AVG, +}; + +struct battery { + char *name; + enum battery_property *properties; + size_t num_properties; + + /* For APM emulation, think legacy userspace. */ + int main_battery; + + /* executed in userspace, feel free to sleep */ + void *(*get_property)(struct battery *bat, enum battery_property); + + /* drivers should not sleep inside it, you'll get there from ISRs */ + void (*external_power_changed)(struct battery *bat); + + /* private */ + struct device *dev; + struct power_supplicant pst; + +#ifdef CONFIG_LEDS_TRIGGERS + struct led_trigger *charging_full_trig; + char *charging_full_trig_name; + struct led_trigger *charging_trig; + char *charging_trig_name; + struct led_trigger *full_trig; + char *full_trig_name; +#endif +}; + +/* + * This is recommended structure to specify static battery parameters. + * Generic one, parametrizable for different batteries. Battery device + * itself does not use it, but that's what implementing most drivers, + * should try reuse for consistency. + */ + +struct battery_info { + char *name; + int voltage_max_design; + int voltage_min_design; + int charge_full_design; + int charge_empty_design; + int energy_full_design; + int energy_empty_design; + int main_battery; +}; + +extern void battery_status_changed(struct battery *bat); +extern int battery_is_external_power_supplied(struct battery *bat); +extern int battery_register(struct device *parent, struct battery *bat); +extern void battery_unregister(struct battery *bat); + +/* For APM emulation, think legacy userspace. */ +extern struct class *battery_class; + +#endif /* __LINUX_BATTERY_H__ */ -- 1.5.1.1-dirty - To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/
| |