Re: [PATCH 1/7] [RFC] External power framework

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


On Thu, Apr 12, 2007 at 03:24:46AM +0400, Anton Vorontsov wrote:
> External power framework - power supplies and power supplicants.
>
> Supplicants (batteries so far) may ask to notify they when power supply
> arrive/gone. This framework used by battery class (next patches).
>
> It's permitted for supply to be bound to several supplicants (think main
> and backup batteries).
>
> It's also permitted for supplicants to consume power from several
> external supplies (say AC and USB).
>
> Here is how it look like from userspace:
>
> # pwd
> /sys/class/power_supply
> # ls
> ac usb
> # cat ac/online usb/online
> 1
> 0

Cleaned up version based on comments from Randy Dunlap.

Subject: [PATCH] [take2] External power framework


Signed-off-by: Anton Vorontsov <cbou@xxxxxxx>
---
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/power/Kconfig | 13 ++
drivers/power/Makefile | 1 +
drivers/power/external_power.c | 326 ++++++++++++++++++++++++++++++++++++++++
include/linux/external_power.h | 54 +++++++
6 files changed, 397 insertions(+), 0 deletions(-)
create mode 100644 drivers/power/Kconfig
create mode 100644 drivers/power/Makefile
create mode 100644 drivers/power/external_power.c
create mode 100644 include/linux/external_power.h

diff --git a/drivers/Kconfig b/drivers/Kconfig
index 050323f..c546de3 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -54,6 +54,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 3a718f5..2bdaae7 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_I2O) += message/
obj-$(CONFIG_RTC_LIB) += rtc/
obj-$(CONFIG_I2C) += i2c/
obj-$(CONFIG_W1) += w1/
+obj-$(CONFIG_EXTERNAL_POWER) += 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 0000000..17349c1
--- /dev/null
+++ b/drivers/power/Kconfig
@@ -0,0 +1,13 @@
+
+menu "External power support"
+
+config EXTERNAL_POWER
+ tristate "External power kernel interface"
+ help
+ Say Y here to enable kernel external power detection interface,
+ like AC or USB. Information also will exported to userspace via
+ /sys/class/external_power/ directory.
+
+ This interface is mandatory for battery class support.
+
+endmenu
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
new file mode 100644
index 0000000..c303b45
--- /dev/null
+++ b/drivers/power/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_EXTERNAL_POWER) += external_power.o
diff --git a/drivers/power/external_power.c b/drivers/power/external_power.c
new file mode 100644
index 0000000..310ea4b
--- /dev/null
+++ b/drivers/power/external_power.c
@@ -0,0 +1,326 @@
+/*
+ * Linux kernel interface for external power suppliers/supplicants
+ *
+ * Copyright (c) 2007 Anton Vorontsov <cbou@xxxxxxx>
+ *
+ * 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/device.h>
+#include <linux/rwsem.h>
+#include <linux/external_power.h>
+
+static struct class *power_supply_class;
+
+static LIST_HEAD(supplicants);
+static struct rw_semaphore supplicants_sem;
+
+struct bound_supply {
+ struct power_supply *psy;
+ struct list_head node;
+};
+
+struct bound_supplicant {
+ struct power_supplicant *pst;
+ struct list_head node;
+};
+
+int power_supplicant_am_i_supplied(struct power_supplicant *pst)
+{
+ int ret = 0;
+ struct bound_supply *bpsy;
+
+ pr_debug("%s\n", __FUNCTION__);
+ down(&power_supply_class->sem);
+ list_for_each_entry(bpsy, &pst->bound_supplies, node) {
+ if (bpsy->psy->is_online(bpsy->psy)) {
+ ret = 1;
+ goto out;
+ }
+ }
+out:
+ up(&power_supply_class->sem);
+
+ return ret;
+}
+
+static void unbind_pst_from_psys(struct power_supplicant *pst)
+{
+ struct bound_supply *bpsy, *bpsy_tmp;
+ struct bound_supplicant *bpst, *bpst_tmp;
+
+ list_for_each_entry_safe(bpsy, bpsy_tmp, &pst->bound_supplies, node) {
+ list_for_each_entry_safe(bpst, bpst_tmp,
+ &bpsy->psy->bound_supplicants, node) {
+ if (bpst->pst == pst) {
+ list_del(&bpst->node);
+ kfree(bpst);
+ break;
+ }
+ }
+ list_del(&bpsy->node);
+ kfree(bpsy);
+ }
+
+ return;
+}
+
+static void unbind_psy_from_psts(struct power_supply *psy)
+{
+ struct bound_supply *bpsy, *bpsy_tmp;
+ struct bound_supplicant *bpst, *bpst_tmp;
+
+ list_for_each_entry_safe(bpst, bpst_tmp, &psy->bound_supplicants,
+ node) {
+ list_for_each_entry_safe(bpsy, bpsy_tmp,
+ &bpst->pst->bound_supplies, node) {
+ if (bpsy->psy == psy) {
+ list_del(&bpsy->node);
+ kfree(bpsy);
+ break;
+ }
+ }
+ list_del(&bpst->node);
+ kfree(bpst);
+ }
+
+ return;
+}
+
+static int bind_pst_to_psy(struct power_supplicant *pst,
+ struct power_supply *psy)
+{
+ struct bound_supplicant *bpst = kmalloc(sizeof(*bpst), GFP_KERNEL);
+ if (!bpst)
+ return -ENOMEM;
+ bpst->pst = pst;
+ list_add_tail(&bpst->node, &psy->bound_supplicants);
+ pr_debug("power: bound pst %s to psy %s\n", pst->name, psy->name);
+ return 0;
+}
+
+static int bind_psy_to_pst(struct power_supply *psy,
+ struct power_supplicant *pst)
+{
+ struct bound_supply *bpsy = kmalloc(sizeof(*bpsy), GFP_KERNEL);
+ if (!bpsy)
+ return -ENOMEM;
+ bpsy->psy = psy;
+ list_add_tail(&bpsy->node, &pst->bound_supplies);
+ pr_debug("power: bound psy %s to pst %s\n", psy->name, pst->name);
+ return 0;
+}
+
+int power_supplicant_register(struct power_supplicant *pst)
+{
+ int ret = 0;
+ size_t i;
+ struct device *dev;
+ struct power_supply *psy;
+
+ INIT_LIST_HEAD(&pst->bound_supplies);
+
+ down_write(&supplicants_sem);
+ down(&power_supply_class->sem);
+
+ list_for_each_entry(dev, &power_supply_class->devices, node) {
+ psy = dev_get_drvdata(dev);
+ for (i = 0; i < psy->num_supplicants; i++) {
+ if (!strcmp(pst->name, psy->supplied_to[i])) {
+ ret = bind_pst_to_psy(pst, psy);
+ if (ret)
+ goto binding_failed;
+ ret = bind_psy_to_pst(psy, pst);
+ if (ret)
+ goto binding_failed;
+ break;
+ }
+ }
+ }
+
+ list_add_tail(&pst->node, &supplicants);
+
+ goto succeed;
+
+binding_failed:
+ unbind_pst_from_psys(pst);
+succeed:
+ up(&power_supply_class->sem);
+ up_write(&supplicants_sem);
+
+ return ret;
+}
+
+void power_supplicant_unregister(struct power_supplicant *pst)
+{
+ down_write(&supplicants_sem);
+ list_del(&pst->node);
+ up_write(&supplicants_sem);
+
+ down(&power_supply_class->sem);
+ unbind_pst_from_psys(pst);
+ up(&power_supply_class->sem);
+ return;
+}
+
+void power_supply_changed(struct power_supply *psy)
+{
+ struct bound_supplicant *bpst;
+
+ pr_debug("%s\n", __FUNCTION__);
+
+ list_for_each_entry(bpst, &psy->bound_supplicants, node)
+ bpst->pst->power_supply_changed(bpst->pst, psy);
+
+ return;
+}
+
+static ssize_t power_supply_show_online(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", psy->is_online(psy));
+}
+
+static ssize_t power_supply_show_type(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ return sprintf(buf, "%s\n", psy->type ? psy->type : "unknown");
+}
+
+static ssize_t power_supply_show_nominal_voltage(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", psy->nominal_voltage);
+}
+
+static DEVICE_ATTR(online, 0444, power_supply_show_online, NULL);
+static DEVICE_ATTR(type, 0444, power_supply_show_type, NULL);
+static DEVICE_ATTR(nominal_voltage, 0444, power_supply_show_nominal_voltage,
+ NULL);
+
+int power_supply_register(struct device *parent,
+ struct power_supply *psy)
+{
+ int ret = 0;
+ struct power_supplicant *pst;
+ size_t i;
+
+ INIT_LIST_HEAD(&psy->bound_supplicants);
+
+ psy->dev = device_create(power_supply_class, parent, 0, "%s",
+ psy->name);
+ if (IS_ERR(psy->dev)) {
+ ret = PTR_ERR(psy->dev);
+ goto dev_create_failed;
+ }
+
+ dev_set_drvdata(psy->dev, psy);
+
+ ret = device_create_file(psy->dev, &dev_attr_online);
+ if (ret)
+ goto create_online_failed;
+
+ ret = device_create_file(psy->dev, &dev_attr_type);
+ if (ret)
+ goto create_type_failed;
+
+ ret = device_create_file(psy->dev, &dev_attr_nominal_voltage);
+ if (ret)
+ goto create_nominal_voltage_failed;
+
+ down_write(&supplicants_sem);
+ list_for_each_entry(pst, &supplicants, node) {
+ for (i = 0; i < psy->num_supplicants; i++) {
+ if (!strcmp(pst->name, psy->supplied_to[i])) {
+ ret = bind_psy_to_pst(psy, pst);
+ if (ret)
+ goto binding_failed;
+ ret = bind_pst_to_psy(pst, psy);
+ if (ret)
+ goto binding_failed;
+ break;
+ }
+ }
+ }
+ up_write(&supplicants_sem);
+
+ /* notify supplicants that supply registred */
+ power_supply_changed(psy);
+
+ goto success;
+
+binding_failed:
+ unbind_psy_from_psts(psy);
+ device_remove_file(psy->dev, &dev_attr_nominal_voltage);
+create_nominal_voltage_failed:
+ device_remove_file(psy->dev, &dev_attr_type);
+create_type_failed:
+ device_remove_file(psy->dev, &dev_attr_online);
+create_online_failed:
+ device_unregister(psy->dev);
+dev_create_failed:
+success:
+ return ret;
+}
+
+void power_supply_unregister(struct power_supply *psy)
+{
+ down_write(&supplicants_sem);
+ unbind_psy_from_psts(psy);
+ up_write(&supplicants_sem);
+
+ device_unregister(psy->dev);
+
+ return;
+}
+
+static int __init external_power_init(void)
+{
+ int ret = 0;
+
+ power_supply_class = class_create(THIS_MODULE, "power_supply");
+ if (IS_ERR(power_supply_class)) {
+ printk(KERN_ERR "external_power: failed to create "
+ "power_supply class\n");
+ ret = PTR_ERR(power_supply_class);
+ goto class_create_failed;
+ }
+
+ init_rwsem(&supplicants_sem);
+
+ goto success;
+
+class_create_failed:
+success:
+ return ret;
+}
+
+static void __exit external_power_exit(void)
+{
+ class_destroy(power_supply_class);
+ return;
+}
+
+EXPORT_SYMBOL_GPL(power_supplicant_am_i_supplied);
+EXPORT_SYMBOL_GPL(power_supplicant_register);
+EXPORT_SYMBOL_GPL(power_supplicant_unregister);
+
+EXPORT_SYMBOL_GPL(power_supply_changed);
+EXPORT_SYMBOL_GPL(power_supply_register);
+EXPORT_SYMBOL_GPL(power_supply_unregister);
+
+subsys_initcall(external_power_init);
+module_exit(external_power_exit);
+
+MODULE_DESCRIPTION("Linux kernel interface for external power");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anton Vorontsov <cbou@xxxxxxx>");
diff --git a/include/linux/external_power.h b/include/linux/external_power.h
new file mode 100644
index 0000000..f297fce
--- /dev/null
+++ b/include/linux/external_power.h
@@ -0,0 +1,54 @@
+/*
+ * Linux kernel interface for external power suppliers/supplicants
+ *
+ * Copyright (c) 2007 Anton Vorontsov <cbou@xxxxxxx>
+ *
+ * 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.
+ */
+
+#ifndef __EXTERNAL_POWER_H__
+#define __EXTERNAL_POWER_H__
+
+#include <linux/list.h>
+
+/* kernel interface for external power suppliers, like AC or USB */
+
+struct power_supply {
+ char *name;
+ char *type;
+ int nominal_voltage;
+ int (*is_online)(struct power_supply *psy);
+ char **supplied_to;
+ size_t num_supplicants;
+
+ /* private */
+ struct list_head bound_supplicants;
+ struct device *dev;
+};
+
+extern void power_supply_changed(struct power_supply *psy);
+extern int power_supply_register(struct device *parent,
+ struct power_supply *psy);
+extern void power_supply_unregister(struct power_supply *psy);
+
+/* kernel interface for external power supplicants (batteries so far) */
+
+struct power_supplicant {
+ char *name;
+ /* used to notify supplicant about external power arrival/outage,
+ * do not sleep there, this is called from irq, most probably */
+ void (*power_supply_changed)(struct power_supplicant *pst,
+ struct power_supply *psy);
+
+ /* private */
+ struct list_head bound_supplies;
+ struct list_head node;
+};
+
+extern int power_supplicant_am_i_supplied(struct power_supplicant *pst);
+extern int power_supplicant_register(struct power_supplicant *pst);
+extern void power_supplicant_unregister(struct power_supplicant *pst);
+
+#endif /* __EXTERNAL_POWER_H__ */
--
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/