On Thu, Apr 12, 2007 at 03:24:56AM +0400, Anton Vorontsov wrote:
> This driver used to stop code/logic duplication through different
> machines we porting at handhelds.org. pda_power register machs' power
> supplies, and will take care about notifying batteries about power
> changes through external power interface.
>
> This driver should be suitable for almost every Linux gadget today.
Changes:
- implement timer, it's used for two purposes:
1) on some machines you can't read is_{ac,usb}_online() values just
after interrupt. Should wait a bit to read reliable values.
2) irq debouncing
- cleanups
Subject: [PATCH] [take2] pda_power driver
Signed-off-by: Anton Vorontsov <[email protected]>
---
drivers/power/Kconfig | 8 ++
drivers/power/Makefile | 1 +
drivers/power/pda_power.c | 231 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/pda_power.h | 25 +++++
4 files changed, 265 insertions(+), 0 deletions(-)
create mode 100644 drivers/power/pda_power.c
create mode 100644 include/linux/pda_power.h
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 17349c1..b87779e 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -10,4 +10,12 @@ config EXTERNAL_POWER
This interface is mandatory for battery class support.
+config PDA_POWER
+ tristate "Generic PDA/phone power driver"
+ depends on EXTERNAL_POWER
+ 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.
+
endmenu
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index c303b45..6f084e7 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -1 +1,2 @@
obj-$(CONFIG_EXTERNAL_POWER) += external_power.o
+obj-$(CONFIG_PDA_POWER) += pda_power.o
diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c
new file mode 100644
index 0000000..ae90bd7
--- /dev/null
+++ b/drivers/power/pda_power.c
@@ -0,0 +1,231 @@
+/*
+ * Common 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.
+ *
+ * Copyright 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/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/external_power.h>
+#include <linux/pda_power.h>
+#include <linux/timer.h>
+
+/*
+ * include/linux/ioport.h does not provide flags for generic IRQ trigger
+ * types. So, we're using "ISA PnP IRQ specific bits", and converting them.
+ */
+static unsigned int get_irq_flags(struct resource *res)
+{
+ unsigned int flags = IRQF_DISABLED;
+
+ if (res->flags & IORESOURCE_IRQ_HIGHEDGE)
+ flags |= IRQF_TRIGGER_RISING;
+ if (res->flags & IORESOURCE_IRQ_LOWEDGE)
+ flags |= IRQF_TRIGGER_FALLING;
+ if (res->flags & IORESOURCE_IRQ_HIGHLEVEL)
+ flags |= IRQF_TRIGGER_HIGH;
+ if (res->flags & IORESOURCE_IRQ_LOWLEVEL)
+ flags |= IRQF_TRIGGER_LOW;
+ if (res->flags & IORESOURCE_IRQ_SHAREABLE)
+ flags |= IRQF_SHARED;
+
+ return flags;
+}
+
+static struct resource *ac_irq, *usb_irq;
+static struct pda_power_pdata *pdata;
+static struct timer_list isr_timer;
+
+static int pda_power_is_ac_online(struct power_supply *psy)
+{
+ return pdata->is_ac_online ? pdata->is_ac_online() : 0;
+}
+
+static int pda_power_is_usb_online(struct power_supply *psy)
+{
+ return pdata->is_usb_online ? pdata->is_usb_online() : 0;
+}
+
+static char *pda_power_supplied_to[] = {
+ "main-battery",
+ "backup-battery",
+};
+
+static struct power_supply pda_power_supplies[] = {
+ {
+ .name = "ac",
+ .type = "ac",
+ .supplied_to = pda_power_supplied_to,
+ .num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
+ .is_online = pda_power_is_ac_online,
+ },
+ {
+ .name = "usb",
+ .type = "dc",
+ .supplied_to = pda_power_supplied_to,
+ .num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
+ .is_online = pda_power_is_usb_online,
+ },
+};
+
+static void update_charger(void)
+{
+ if (!pdata->set_charge)
+ return;
+
+ if (pdata->is_ac_online && pdata->is_ac_online()) {
+ pr_debug("pda_power: charger on (AC)\n");
+ pdata->set_charge(PDA_POWER_CHARGE_AC);
+ }
+ else if (pdata->is_usb_online && pdata->is_usb_online()) {
+ pr_debug("pda_power: charger on (USB)\n");
+ pdata->set_charge(PDA_POWER_CHARGE_USB);
+ }
+ else {
+ pr_debug("pda_power: charger off\n");
+ pdata->set_charge(0);
+ }
+
+ return;
+}
+
+static void isr_timer_func(unsigned long irq)
+{
+ if (ac_irq && irq == ac_irq->start) {
+ power_supply_changed(&pda_power_supplies[0]);
+ }
+ else if (usb_irq && irq == usb_irq->start) {
+ power_supply_changed(&pda_power_supplies[1]);
+ }
+ else {
+ printk(KERN_WARNING "pda_power: spurious irq %li", irq);
+ return;
+ }
+
+ update_charger();
+
+ return;
+}
+
+static irqreturn_t power_changed_isr(int irq, void *unused)
+{
+ isr_timer.data = irq;
+ mod_timer(&isr_timer, jiffies + HZ);
+ return IRQ_HANDLED;
+}
+
+static int pda_power_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+
+ if (pdev->id != -1) {
+ printk(KERN_WARNING "pda_power: it's meaningless to "
+ "register several pda_powers, use id = -1\n");
+ ret = -EINVAL;
+ goto wrongid;
+ }
+
+ pdata = pdev->dev.platform_data;
+
+ setup_timer(&isr_timer, isr_timer_func, 0);
+
+ ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac");
+ usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb");
+ if (!ac_irq && !usb_irq) {
+ printk(KERN_ERR "pda_power: no ac/usb irq specified\n");
+ ret = -ENODEV;
+ goto noirqs;
+ }
+
+ ret = power_supply_register(&pdev->dev, &pda_power_supplies[0]);
+ if (ret) {
+ printk(KERN_ERR "pda_power: failed to register %s power "
+ "supply\n", pda_power_supplies[0].name);
+ goto supply0_failed;
+ }
+
+ ret = power_supply_register(&pdev->dev, &pda_power_supplies[1]);
+ if (ret) {
+ printk(KERN_ERR "pda_power: failed to register %s power "
+ "supply\n", pda_power_supplies[1].name);
+ goto supply1_failed;
+ }
+
+ if (ac_irq) {
+ ret = request_irq(ac_irq->start, power_changed_isr,
+ get_irq_flags(ac_irq), ac_irq->name, NULL);
+ if (ret) {
+ printk(KERN_ERR "pda_power: request ac irq failed\n");
+ goto ac_irq_failed;
+ }
+ }
+
+ if (usb_irq) {
+ ret = request_irq(usb_irq->start, power_changed_isr,
+ get_irq_flags(ac_irq), usb_irq->name, NULL);
+ if (ret) {
+ printk(KERN_ERR "pda_power: request usb irq failed\n");
+ goto usb_irq_failed;
+ }
+ }
+
+ update_charger();
+
+ goto success;
+
+usb_irq_failed:
+ if (ac_irq)
+ free_irq(ac_irq->start, NULL);
+ac_irq_failed:
+ power_supply_unregister(&pda_power_supplies[1]);
+supply1_failed:
+ power_supply_unregister(&pda_power_supplies[0]);
+supply0_failed:
+noirqs:
+wrongid:
+success:
+ return ret;
+}
+
+static int pda_power_remove(struct platform_device *pdev)
+{
+ if (usb_irq)
+ free_irq(usb_irq->start, NULL);
+ if (ac_irq)
+ free_irq(ac_irq->start, NULL);
+ del_timer_sync(&isr_timer);
+ power_supply_unregister(&pda_power_supplies[1]);
+ power_supply_unregister(&pda_power_supplies[0]);
+ return 0;
+}
+
+static struct platform_driver pda_power_pdrv = {
+ .driver = {
+ .name = "pda-power",
+ },
+ .probe = pda_power_probe,
+ .remove = pda_power_remove,
+};
+
+static int __init pda_power_init(void)
+{
+ return platform_driver_register(&pda_power_pdrv);
+}
+
+static void __exit pda_power_exit(void)
+{
+ platform_driver_unregister(&pda_power_pdrv);
+ return;
+}
+
+module_init(pda_power_init);
+module_exit(pda_power_exit);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anton Vorontsov <[email protected]>");
diff --git a/include/linux/pda_power.h b/include/linux/pda_power.h
new file mode 100644
index 0000000..0d414a8
--- /dev/null
+++ b/include/linux/pda_power.h
@@ -0,0 +1,25 @@
+/*
+ * Common 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.
+ *
+ * Copyright 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 __PDA_POWER_H__
+#define __PDA_POWER_H__
+
+#define PDA_POWER_CHARGE_AC (1 << 0)
+#define PDA_POWER_CHARGE_USB (1 << 1)
+
+struct pda_power_pdata {
+ int (*is_ac_online)(void);
+ int (*is_usb_online)(void);
+ void (*set_charge)(int flags);
+};
+
+#endif /* __PDA_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]