Re: [PATCH 3/7] [RFC] Battery monitoring class

From: Anton Vorontsov
Date: Fri Apr 13 2007 - 09:53:32 EST


On Thu, Apr 12, 2007 at 03:25:03AM +0400, Anton Vorontsov wrote:
> Here is battery monitor class. According to first copyright string, we're
> maintaining it since 2003. I've took few days and cleaned it up to be
> more suitable for mainline inclusion.
>
> It differs from battery class at git://git.infradead.org/battery-2.6.git:
>
> * It's using external power kernel interface, i.e. does not fake external
> powers as batteries. (Same thing David Woodhouse planed last year).
>
> * It have predefined set of attributes, this eliminates code duplication
> by battery drivers. And also gives opportunity to write emulation drivers
> for legacy stuff (APM emulation driver follow).
>
> If driver can't afford some attribute, it will not appear in sysfs.
>
> * It insists on reusing its predefined attributes *and* their units.
> So, userspace getting expected values for any battery.
>
> Also common units is required for APM/ACPI emulation.
>
> Though our battery class insisting on re-usage, but not forces it. If some
> battery driver can't convert its own raw values (can't imagine why), then
> driver is free to implement its own attributes *and* additional _units
> attribute. Though, this scheme is discouraged.
>
> * LEDs support. Each battery register its trigger, and gadgets with LEDs
> can quickly bind to battery-charging / battery-full triggers.
>
> Here how it looks like from user space:
>
> # ls /sys/class/battery/main-battery/
> capacity max_capacity max_voltage min_current power subsystem uevent
> current max_current min_capacity min_voltage status temp voltage
> # cat /sys/class/battery/main-battery/status
> Full
> # cat /sys/class/leds/h5400\:green-right/trigger
> none h5400-radio timer hwtimer main-battery-charging [main-battery-full]
> # cat /sys/class/leds/h5400\:green-right/brightness
> 255
>

Changes:

- Cleanups based on comments from Randy Dunlap.

- Attribute creation scheme changed drastically. No more tons of

macro-created functions. Compiled code should be much smaller.
Also adding new "standard" attributes is trivial task now (matter of
adding two lines, one in battery.c and another in battery.h).

- charge (as quantity) in mAh, energy in mWh.


I'll convert mXh to uXh a bit later, if there will no further objections
against uXh. Also I'd like to hear if there any objections on
mA/mV -> uA/uV conversion. I think we'd better keep all units at the
same order/precision.


Subject: [PATCH] [take2] Battery monitoring class


Signed-off-by: Anton Vorontsov <cbou@xxxxxxx>
---
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/battery/Kconfig | 11 ++
drivers/battery/Makefile | 1 +
drivers/battery/battery.c | 290 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/battery.h | 113 ++++++++++++++++++
6 files changed, 418 insertions(+), 0 deletions(-)
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/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..6c87fe3
--- /dev/null
+++ b/drivers/battery/battery.c
@@ -0,0 +1,290 @@
+/*
+ * Universal battery monitor class
+ *
+ * Copyright (c) 2007 Anton Vorontsov <cbou@xxxxxxx>
+ * Copyright (c) 2004 Szabolcs Gyurko
+ * Copyright (c) 2003 Ian Molton <spyro@xxxxxxx>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ *
+ * All voltages, currents, charges, energies and temperatures in mV, mA,
+ * mAh, mWh and tenths of a Celsius degree unless otherwise stated. It's
+ * driver's job to convert its raw values to which this class operates. If
+ * for some reason driver can't afford this requirement, then it have to
+ * create its own attributes, plus additional "XYZ_units" for each of them.
+ */
+
+#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_trig, LED_OFF);
+ led_trigger_event(bat->full_trig, LED_FULL);
+ break;
+ case BATTERY_STATUS_CHARGING:
+ led_trigger_event(bat->charging_trig, LED_FULL);
+ led_trigger_event(bat->full_trig, LED_OFF);
+ break;
+ default:
+ 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"
+ };
+ 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
+ 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(min_voltage),
+ BATTERY_ATTR(max_voltage),
+ BATTERY_ATTR(voltage),
+ BATTERY_ATTR(min_current),
+ BATTERY_ATTR(max_current),
+ BATTERY_ATTR(current),
+ BATTERY_ATTR(design_charge),
+ BATTERY_ATTR(min_charge),
+ BATTERY_ATTR(max_charge),
+ BATTERY_ATTR(charge),
+ BATTERY_ATTR(design_energy),
+ BATTERY_ATTR(min_energy),
+ BATTERY_ATTR(max_energy),
+ BATTERY_ATTR(energy),
+ BATTERY_ATTR(temp),
+};
+
+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_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_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_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:
+#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_trig);
+ led_trigger_unregister_simple(bat->full_trig);
+ kfree(bat->full_trig_name);
+ kfree(bat->charging_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@xxxxxxx>, "
+ "Szabolcs Gyurko, "
+ "Anton Vorontsov <cbou@xxxxxxx>");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/battery.h b/include/linux/battery.h
new file mode 100644
index 0000000..2dcc8ed
--- /dev/null
+++ b/include/linux/battery.h
@@ -0,0 +1,113 @@
+/*
+ * Universal battery monitor class
+ *
+ * Copyright (c) 2007 Anton Vorontsov <cbou@xxxxxxx>
+ * Copyright (c) 2004 Szabolcs Gyurko
+ * Copyright (c) 2003 Ian Molton <spyro@xxxxxxx>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ *
+ * All voltages, currents, charges, energies and temperatures in mV, mA,
+ * mAh, mWh and tenths of a Celsius degree unless otherwise stated. It's
+ * driver's job to convert its raw values to which this class operates. If
+ * for some reason driver can't afford this requirement, then it have to
+ * create its own attributes, plus additional "XYZ_units" for each of them.
+ */
+
+#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
+
+/*
+ * 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 can be NULL and will not appear in sysfs.
+ */
+
+enum battery_property {
+ BATTERY_PROP_STATUS = 0,
+ BATTERY_PROP_MIN_VOLTAGE,
+ BATTERY_PROP_MAX_VOLTAGE,
+ BATTERY_PROP_VOLTAGE,
+ BATTERY_PROP_MIN_CURRENT,
+ BATTERY_PROP_MAX_CURRENT,
+ BATTERY_PROP_CURRENT,
+ BATTERY_PROP_DESIGN_CHARGE,
+ BATTERY_PROP_MIN_CHARGE,
+ BATTERY_PROP_MAX_CHARGE,
+ BATTERY_PROP_CHARGE,
+ BATTERY_PROP_DESIGN_ENERGY,
+ BATTERY_PROP_MIN_ENERGY,
+ BATTERY_PROP_MAX_ENERGY,
+ BATTERY_PROP_ENERGY,
+ BATTERY_PROP_TEMP,
+};
+
+struct battery {
+ struct device *dev;
+ char *name;
+ int *properties;
+ int 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 power_supplicant pst;
+
+#ifdef CONFIG_LEDS_TRIGGERS
+ 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 bot use it, but that's what implementing most drivers,
+ * should try reuse for consistency.
+ */
+
+struct battery_info {
+ char *name;
+ int min_voltage;
+ int max_voltage;
+ int min_current;
+ int max_current;
+ int design_charge;
+ int min_charge;
+ int max_charge;
+ int design_energy;
+ int min_energy;
+ int max_energy;
+ 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
--
1.5.0.5-dirty

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/