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

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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

---
 drivers/Kconfig                |    2 +
 drivers/Makefile               |    1 +
 drivers/power/Kconfig          |   13 ++
 drivers/power/Makefile         |    1 +
 drivers/power/external_power.c |  318 ++++++++++++++++++++++++++++++++++++++++
 include/linux/external_power.h |   54 +++++++
 6 files changed, 389 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..21c25a4
--- /dev/null
+++ b/drivers/power/external_power.c
@@ -0,0 +1,318 @@
+/* 
+ * Linux kernel interface for external power suppliers/supplicants
+ *
+ * Copyright (c) 2007  Anton Vorontsov <[email protected]>
+ *
+ * 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;
+	}
+
+	#define power_supply_device_create_file(x)         \
+	ret = device_create_file(psy->dev, &dev_attr_##x); \
+	if (ret)                                           \
+		goto dev_create_file_failed;
+
+	power_supply_device_create_file(online);
+	power_supply_device_create_file(type);
+	power_supply_device_create_file(nominal_voltage);
+
+	dev_set_drvdata(psy->dev, psy);
+
+	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);
+dev_create_file_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 <[email protected]>");
diff --git a/include/linux/external_power.h b/include/linux/external_power.h
new file mode 100644
index 0000000..4c03e09
--- /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 <[email protected]>
+ *
+ * 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 [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux