linux/drivers/power/pda_power.c
<<
>>
Prefs
   1/*
   2 * Common power driver for PDAs and phones with one or two external
   3 * power supplies (AC/USB) connected to main and backup batteries,
   4 * and optional builtin charger.
   5 *
   6 * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2 as
  10 * published by the Free Software Foundation.
  11 */
  12
  13#include <linux/module.h>
  14#include <linux/platform_device.h>
  15#include <linux/interrupt.h>
  16#include <linux/power_supply.h>
  17#include <linux/pda_power.h>
  18#include <linux/timer.h>
  19#include <linux/jiffies.h>
  20
  21static inline unsigned int get_irq_flags(struct resource *res)
  22{
  23        unsigned int flags = IRQF_DISABLED | IRQF_SHARED;
  24
  25        flags |= res->flags & IRQF_TRIGGER_MASK;
  26
  27        return flags;
  28}
  29
  30static struct device *dev;
  31static struct pda_power_pdata *pdata;
  32static struct resource *ac_irq, *usb_irq;
  33static struct timer_list charger_timer;
  34static struct timer_list supply_timer;
  35
  36static int pda_power_get_property(struct power_supply *psy,
  37                                  enum power_supply_property psp,
  38                                  union power_supply_propval *val)
  39{
  40        switch (psp) {
  41        case POWER_SUPPLY_PROP_ONLINE:
  42                if (psy->type == POWER_SUPPLY_TYPE_MAINS)
  43                        val->intval = pdata->is_ac_online ?
  44                                      pdata->is_ac_online() : 0;
  45                else
  46                        val->intval = pdata->is_usb_online ?
  47                                      pdata->is_usb_online() : 0;
  48                break;
  49        default:
  50                return -EINVAL;
  51        }
  52        return 0;
  53}
  54
  55static enum power_supply_property pda_power_props[] = {
  56        POWER_SUPPLY_PROP_ONLINE,
  57};
  58
  59static char *pda_power_supplied_to[] = {
  60        "main-battery",
  61        "backup-battery",
  62};
  63
  64static struct power_supply pda_power_supplies[] = {
  65        {
  66                .name = "ac",
  67                .type = POWER_SUPPLY_TYPE_MAINS,
  68                .supplied_to = pda_power_supplied_to,
  69                .num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
  70                .properties = pda_power_props,
  71                .num_properties = ARRAY_SIZE(pda_power_props),
  72                .get_property = pda_power_get_property,
  73        },
  74        {
  75                .name = "usb",
  76                .type = POWER_SUPPLY_TYPE_USB,
  77                .supplied_to = pda_power_supplied_to,
  78                .num_supplicants = ARRAY_SIZE(pda_power_supplied_to),
  79                .properties = pda_power_props,
  80                .num_properties = ARRAY_SIZE(pda_power_props),
  81                .get_property = pda_power_get_property,
  82        },
  83};
  84
  85static void update_charger(void)
  86{
  87        if (!pdata->set_charge)
  88                return;
  89
  90        if (pdata->is_ac_online && pdata->is_ac_online()) {
  91                dev_dbg(dev, "charger on (AC)\n");
  92                pdata->set_charge(PDA_POWER_CHARGE_AC);
  93        } else if (pdata->is_usb_online && pdata->is_usb_online()) {
  94                dev_dbg(dev, "charger on (USB)\n");
  95                pdata->set_charge(PDA_POWER_CHARGE_USB);
  96        } else {
  97                dev_dbg(dev, "charger off\n");
  98                pdata->set_charge(0);
  99        }
 100}
 101
 102static void supply_timer_func(unsigned long power_supply_ptr)
 103{
 104        void *power_supply = (void *)power_supply_ptr;
 105
 106        power_supply_changed(power_supply);
 107}
 108
 109static void charger_timer_func(unsigned long power_supply_ptr)
 110{
 111        update_charger();
 112
 113        /* Okay, charger set. Now wait a bit before notifying supplicants,
 114         * charge power should stabilize. */
 115        supply_timer.data = power_supply_ptr;
 116        mod_timer(&supply_timer,
 117                  jiffies + msecs_to_jiffies(pdata->wait_for_charger));
 118}
 119
 120static irqreturn_t power_changed_isr(int irq, void *power_supply)
 121{
 122        /* Wait a bit before reading ac/usb line status and setting charger,
 123         * because ac/usb status readings may lag from irq. */
 124        charger_timer.data = (unsigned long)power_supply;
 125        mod_timer(&charger_timer,
 126                  jiffies + msecs_to_jiffies(pdata->wait_for_status));
 127        return IRQ_HANDLED;
 128}
 129
 130static int pda_power_probe(struct platform_device *pdev)
 131{
 132        int ret = 0;
 133
 134        dev = &pdev->dev;
 135
 136        if (pdev->id != -1) {
 137                dev_err(dev, "it's meaningless to register several "
 138                        "pda_powers; use id = -1\n");
 139                ret = -EINVAL;
 140                goto wrongid;
 141        }
 142
 143        pdata = pdev->dev.platform_data;
 144
 145        update_charger();
 146
 147        if (!pdata->wait_for_status)
 148                pdata->wait_for_status = 500;
 149
 150        if (!pdata->wait_for_charger)
 151                pdata->wait_for_charger = 500;
 152
 153        setup_timer(&charger_timer, charger_timer_func, 0);
 154        setup_timer(&supply_timer, supply_timer_func, 0);
 155
 156        ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac");
 157        usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb");
 158        if (!ac_irq && !usb_irq) {
 159                dev_err(dev, "no ac/usb irq specified\n");
 160                ret = -ENODEV;
 161                goto noirqs;
 162        }
 163
 164        if (pdata->supplied_to) {
 165                pda_power_supplies[0].supplied_to = pdata->supplied_to;
 166                pda_power_supplies[1].supplied_to = pdata->supplied_to;
 167                pda_power_supplies[0].num_supplicants = pdata->num_supplicants;
 168                pda_power_supplies[1].num_supplicants = pdata->num_supplicants;
 169        }
 170
 171        ret = power_supply_register(&pdev->dev, &pda_power_supplies[0]);
 172        if (ret) {
 173                dev_err(dev, "failed to register %s power supply\n",
 174                        pda_power_supplies[0].name);
 175                goto supply0_failed;
 176        }
 177
 178        ret = power_supply_register(&pdev->dev, &pda_power_supplies[1]);
 179        if (ret) {
 180                dev_err(dev, "failed to register %s power supply\n",
 181                        pda_power_supplies[1].name);
 182                goto supply1_failed;
 183        }
 184
 185        if (ac_irq) {
 186                ret = request_irq(ac_irq->start, power_changed_isr,
 187                                  get_irq_flags(ac_irq), ac_irq->name,
 188                                  &pda_power_supplies[0]);
 189                if (ret) {
 190                        dev_err(dev, "request ac irq failed\n");
 191                        goto ac_irq_failed;
 192                }
 193        }
 194
 195        if (usb_irq) {
 196                ret = request_irq(usb_irq->start, power_changed_isr,
 197                                  get_irq_flags(usb_irq), usb_irq->name,
 198                                  &pda_power_supplies[1]);
 199                if (ret) {
 200                        dev_err(dev, "request usb irq failed\n");
 201                        goto usb_irq_failed;
 202                }
 203        }
 204
 205        goto success;
 206
 207usb_irq_failed:
 208        if (ac_irq)
 209                free_irq(ac_irq->start, &pda_power_supplies[0]);
 210ac_irq_failed:
 211        power_supply_unregister(&pda_power_supplies[1]);
 212supply1_failed:
 213        power_supply_unregister(&pda_power_supplies[0]);
 214supply0_failed:
 215noirqs:
 216wrongid:
 217success:
 218        return ret;
 219}
 220
 221static int pda_power_remove(struct platform_device *pdev)
 222{
 223        if (usb_irq)
 224                free_irq(usb_irq->start, &pda_power_supplies[1]);
 225        if (ac_irq)
 226                free_irq(ac_irq->start, &pda_power_supplies[0]);
 227        del_timer_sync(&charger_timer);
 228        del_timer_sync(&supply_timer);
 229        power_supply_unregister(&pda_power_supplies[1]);
 230        power_supply_unregister(&pda_power_supplies[0]);
 231        return 0;
 232}
 233
 234static struct platform_driver pda_power_pdrv = {
 235        .driver = {
 236                .name = "pda-power",
 237        },
 238        .probe = pda_power_probe,
 239        .remove = pda_power_remove,
 240};
 241
 242static int __init pda_power_init(void)
 243{
 244        return platform_driver_register(&pda_power_pdrv);
 245}
 246
 247static void __exit pda_power_exit(void)
 248{
 249        platform_driver_unregister(&pda_power_pdrv);
 250}
 251
 252module_init(pda_power_init);
 253module_exit(pda_power_exit);
 254MODULE_LICENSE("GPL");
 255MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>");
 256