Root/drivers/power/tosa_battery.c

1/*
2 * Battery and Power Management code for the Sharp SL-6000x
3 *
4 * Copyright (c) 2005 Dirk Opfer
5 * Copyright (c) 2008 Dmitry Baryshkov
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 *
11 */
12#include <linux/kernel.h>
13#include <linux/module.h>
14#include <linux/power_supply.h>
15#include <linux/wm97xx.h>
16#include <linux/delay.h>
17#include <linux/spinlock.h>
18#include <linux/interrupt.h>
19#include <linux/gpio.h>
20
21#include <asm/mach-types.h>
22#include <mach/tosa.h>
23
24static DEFINE_MUTEX(bat_lock); /* protects gpio pins */
25static struct work_struct bat_work;
26
27struct tosa_bat {
28    int status;
29    struct power_supply psy;
30    int full_chrg;
31
32    struct mutex work_lock; /* protects data */
33
34    bool (*is_present)(struct tosa_bat *bat);
35    int gpio_full;
36    int gpio_charge_off;
37
38    int technology;
39
40    int gpio_bat;
41    int adc_bat;
42    int adc_bat_divider;
43    int bat_max;
44    int bat_min;
45
46    int gpio_temp;
47    int adc_temp;
48    int adc_temp_divider;
49};
50
51static struct tosa_bat tosa_bat_main;
52static struct tosa_bat tosa_bat_jacket;
53
54static unsigned long tosa_read_bat(struct tosa_bat *bat)
55{
56    unsigned long value = 0;
57
58    if (bat->gpio_bat < 0 || bat->adc_bat < 0)
59        return 0;
60
61    mutex_lock(&bat_lock);
62    gpio_set_value(bat->gpio_bat, 1);
63    msleep(5);
64    value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy.dev->parent),
65            bat->adc_bat);
66    gpio_set_value(bat->gpio_bat, 0);
67    mutex_unlock(&bat_lock);
68
69    value = value * 1000000 / bat->adc_bat_divider;
70
71    return value;
72}
73
74static unsigned long tosa_read_temp(struct tosa_bat *bat)
75{
76    unsigned long value = 0;
77
78    if (bat->gpio_temp < 0 || bat->adc_temp < 0)
79        return 0;
80
81    mutex_lock(&bat_lock);
82    gpio_set_value(bat->gpio_temp, 1);
83    msleep(5);
84    value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy.dev->parent),
85            bat->adc_temp);
86    gpio_set_value(bat->gpio_temp, 0);
87    mutex_unlock(&bat_lock);
88
89    value = value * 10000 / bat->adc_temp_divider;
90
91    return value;
92}
93
94static int tosa_bat_get_property(struct power_supply *psy,
95                enum power_supply_property psp,
96                union power_supply_propval *val)
97{
98    int ret = 0;
99    struct tosa_bat *bat = container_of(psy, struct tosa_bat, psy);
100
101    if (bat->is_present && !bat->is_present(bat)
102            && psp != POWER_SUPPLY_PROP_PRESENT) {
103        return -ENODEV;
104    }
105
106    switch (psp) {
107    case POWER_SUPPLY_PROP_STATUS:
108        val->intval = bat->status;
109        break;
110    case POWER_SUPPLY_PROP_TECHNOLOGY:
111        val->intval = bat->technology;
112        break;
113    case POWER_SUPPLY_PROP_VOLTAGE_NOW:
114        val->intval = tosa_read_bat(bat);
115        break;
116    case POWER_SUPPLY_PROP_VOLTAGE_MAX:
117        if (bat->full_chrg == -1)
118            val->intval = bat->bat_max;
119        else
120            val->intval = bat->full_chrg;
121        break;
122    case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
123        val->intval = bat->bat_max;
124        break;
125    case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
126        val->intval = bat->bat_min;
127        break;
128    case POWER_SUPPLY_PROP_TEMP:
129        val->intval = tosa_read_temp(bat);
130        break;
131    case POWER_SUPPLY_PROP_PRESENT:
132        val->intval = bat->is_present ? bat->is_present(bat) : 1;
133        break;
134    default:
135        ret = -EINVAL;
136        break;
137    }
138    return ret;
139}
140
141static bool tosa_jacket_bat_is_present(struct tosa_bat *bat)
142{
143    return gpio_get_value(TOSA_GPIO_JACKET_DETECT) == 0;
144}
145
146static void tosa_bat_external_power_changed(struct power_supply *psy)
147{
148    schedule_work(&bat_work);
149}
150
151static irqreturn_t tosa_bat_gpio_isr(int irq, void *data)
152{
153    pr_info("tosa_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq)));
154    schedule_work(&bat_work);
155    return IRQ_HANDLED;
156}
157
158static void tosa_bat_update(struct tosa_bat *bat)
159{
160    int old;
161    struct power_supply *psy = &bat->psy;
162
163    mutex_lock(&bat->work_lock);
164
165    old = bat->status;
166
167    if (bat->is_present && !bat->is_present(bat)) {
168        printk(KERN_NOTICE "%s not present\n", psy->name);
169        bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
170        bat->full_chrg = -1;
171    } else if (power_supply_am_i_supplied(psy)) {
172        if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) {
173            gpio_set_value(bat->gpio_charge_off, 0);
174            mdelay(15);
175        }
176
177        if (gpio_get_value(bat->gpio_full)) {
178            if (old == POWER_SUPPLY_STATUS_CHARGING ||
179                    bat->full_chrg == -1)
180                bat->full_chrg = tosa_read_bat(bat);
181
182            gpio_set_value(bat->gpio_charge_off, 1);
183            bat->status = POWER_SUPPLY_STATUS_FULL;
184        } else {
185            gpio_set_value(bat->gpio_charge_off, 0);
186            bat->status = POWER_SUPPLY_STATUS_CHARGING;
187        }
188    } else {
189        gpio_set_value(bat->gpio_charge_off, 1);
190        bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
191    }
192
193    if (old != bat->status)
194        power_supply_changed(psy);
195
196    mutex_unlock(&bat->work_lock);
197}
198
199static void tosa_bat_work(struct work_struct *work)
200{
201    tosa_bat_update(&tosa_bat_main);
202    tosa_bat_update(&tosa_bat_jacket);
203}
204
205
206static enum power_supply_property tosa_bat_main_props[] = {
207    POWER_SUPPLY_PROP_STATUS,
208    POWER_SUPPLY_PROP_TECHNOLOGY,
209    POWER_SUPPLY_PROP_VOLTAGE_NOW,
210    POWER_SUPPLY_PROP_VOLTAGE_MAX,
211    POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
212    POWER_SUPPLY_PROP_TEMP,
213    POWER_SUPPLY_PROP_PRESENT,
214};
215
216static enum power_supply_property tosa_bat_bu_props[] = {
217    POWER_SUPPLY_PROP_STATUS,
218    POWER_SUPPLY_PROP_TECHNOLOGY,
219    POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
220    POWER_SUPPLY_PROP_VOLTAGE_NOW,
221    POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
222    POWER_SUPPLY_PROP_PRESENT,
223};
224
225static struct tosa_bat tosa_bat_main = {
226    .status = POWER_SUPPLY_STATUS_DISCHARGING,
227    .full_chrg = -1,
228    .psy = {
229        .name = "main-battery",
230        .type = POWER_SUPPLY_TYPE_BATTERY,
231        .properties = tosa_bat_main_props,
232        .num_properties = ARRAY_SIZE(tosa_bat_main_props),
233        .get_property = tosa_bat_get_property,
234        .external_power_changed = tosa_bat_external_power_changed,
235        .use_for_apm = 1,
236    },
237
238    .gpio_full = TOSA_GPIO_BAT0_CRG,
239    .gpio_charge_off = TOSA_GPIO_CHARGE_OFF,
240
241    .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
242
243    .gpio_bat = TOSA_GPIO_BAT0_V_ON,
244    .adc_bat = WM97XX_AUX_ID3,
245    .adc_bat_divider = 414,
246    .bat_max = 4310000,
247    .bat_min = 1551 * 1000000 / 414,
248
249    .gpio_temp = TOSA_GPIO_BAT1_TH_ON,
250    .adc_temp = WM97XX_AUX_ID2,
251    .adc_temp_divider = 10000,
252};
253
254static struct tosa_bat tosa_bat_jacket = {
255    .status = POWER_SUPPLY_STATUS_DISCHARGING,
256    .full_chrg = -1,
257    .psy = {
258        .name = "jacket-battery",
259        .type = POWER_SUPPLY_TYPE_BATTERY,
260        .properties = tosa_bat_main_props,
261        .num_properties = ARRAY_SIZE(tosa_bat_main_props),
262        .get_property = tosa_bat_get_property,
263        .external_power_changed = tosa_bat_external_power_changed,
264    },
265
266    .is_present = tosa_jacket_bat_is_present,
267    .gpio_full = TOSA_GPIO_BAT1_CRG,
268    .gpio_charge_off = TOSA_GPIO_CHARGE_OFF_JC,
269
270    .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
271
272    .gpio_bat = TOSA_GPIO_BAT1_V_ON,
273    .adc_bat = WM97XX_AUX_ID3,
274    .adc_bat_divider = 414,
275    .bat_max = 4310000,
276    .bat_min = 1551 * 1000000 / 414,
277
278    .gpio_temp = TOSA_GPIO_BAT0_TH_ON,
279    .adc_temp = WM97XX_AUX_ID2,
280    .adc_temp_divider = 10000,
281};
282
283static struct tosa_bat tosa_bat_bu = {
284    .status = POWER_SUPPLY_STATUS_UNKNOWN,
285    .full_chrg = -1,
286
287    .psy = {
288        .name = "backup-battery",
289        .type = POWER_SUPPLY_TYPE_BATTERY,
290        .properties = tosa_bat_bu_props,
291        .num_properties = ARRAY_SIZE(tosa_bat_bu_props),
292        .get_property = tosa_bat_get_property,
293        .external_power_changed = tosa_bat_external_power_changed,
294    },
295
296    .gpio_full = -1,
297    .gpio_charge_off = -1,
298
299    .technology = POWER_SUPPLY_TECHNOLOGY_LiMn,
300
301    .gpio_bat = TOSA_GPIO_BU_CHRG_ON,
302    .adc_bat = WM97XX_AUX_ID4,
303    .adc_bat_divider = 1266,
304
305    .gpio_temp = -1,
306    .adc_temp = -1,
307    .adc_temp_divider = -1,
308};
309
310static struct gpio tosa_bat_gpios[] = {
311    { TOSA_GPIO_CHARGE_OFF, GPIOF_OUT_INIT_HIGH, "main charge off" },
312    { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" },
313    { TOSA_GPIO_BAT_SW_ON, GPIOF_OUT_INIT_LOW, "battery switch" },
314    { TOSA_GPIO_BAT0_V_ON, GPIOF_OUT_INIT_LOW, "main battery" },
315    { TOSA_GPIO_BAT1_V_ON, GPIOF_OUT_INIT_LOW, "jacket battery" },
316    { TOSA_GPIO_BAT1_TH_ON, GPIOF_OUT_INIT_LOW, "main battery temp" },
317    { TOSA_GPIO_BAT0_TH_ON, GPIOF_OUT_INIT_LOW, "jacket battery temp" },
318    { TOSA_GPIO_BU_CHRG_ON, GPIOF_OUT_INIT_LOW, "backup battery" },
319    { TOSA_GPIO_BAT0_CRG, GPIOF_IN, "main battery full" },
320    { TOSA_GPIO_BAT1_CRG, GPIOF_IN, "jacket battery full" },
321    { TOSA_GPIO_BAT0_LOW, GPIOF_IN, "main battery low" },
322    { TOSA_GPIO_BAT1_LOW, GPIOF_IN, "jacket battery low" },
323    { TOSA_GPIO_JACKET_DETECT, GPIOF_IN, "jacket detect" },
324};
325
326#ifdef CONFIG_PM
327static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state)
328{
329    /* flush all pending status updates */
330    flush_work_sync(&bat_work);
331    return 0;
332}
333
334static int tosa_bat_resume(struct platform_device *dev)
335{
336    /* things may have changed while we were away */
337    schedule_work(&bat_work);
338    return 0;
339}
340#else
341#define tosa_bat_suspend NULL
342#define tosa_bat_resume NULL
343#endif
344
345static int __devinit tosa_bat_probe(struct platform_device *dev)
346{
347    int ret;
348
349    if (!machine_is_tosa())
350        return -ENODEV;
351
352    ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
353    if (ret)
354        return ret;
355
356    mutex_init(&tosa_bat_main.work_lock);
357    mutex_init(&tosa_bat_jacket.work_lock);
358
359    INIT_WORK(&bat_work, tosa_bat_work);
360
361    ret = power_supply_register(&dev->dev, &tosa_bat_main.psy);
362    if (ret)
363        goto err_psy_reg_main;
364    ret = power_supply_register(&dev->dev, &tosa_bat_jacket.psy);
365    if (ret)
366        goto err_psy_reg_jacket;
367    ret = power_supply_register(&dev->dev, &tosa_bat_bu.psy);
368    if (ret)
369        goto err_psy_reg_bu;
370
371    ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG),
372                tosa_bat_gpio_isr,
373                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
374                "main full", &tosa_bat_main);
375    if (ret)
376        goto err_req_main;
377
378    ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG),
379                tosa_bat_gpio_isr,
380                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
381                "jacket full", &tosa_bat_jacket);
382    if (ret)
383        goto err_req_jacket;
384
385    ret = request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT),
386                tosa_bat_gpio_isr,
387                IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
388                "jacket detect", &tosa_bat_jacket);
389    if (!ret) {
390        schedule_work(&bat_work);
391        return 0;
392    }
393
394    free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket);
395err_req_jacket:
396    free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main);
397err_req_main:
398    power_supply_unregister(&tosa_bat_bu.psy);
399err_psy_reg_bu:
400    power_supply_unregister(&tosa_bat_jacket.psy);
401err_psy_reg_jacket:
402    power_supply_unregister(&tosa_bat_main.psy);
403err_psy_reg_main:
404
405    /* see comment in tosa_bat_remove */
406    cancel_work_sync(&bat_work);
407
408    gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
409    return ret;
410}
411
412static int __devexit tosa_bat_remove(struct platform_device *dev)
413{
414    free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket);
415    free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket);
416    free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main);
417
418    power_supply_unregister(&tosa_bat_bu.psy);
419    power_supply_unregister(&tosa_bat_jacket.psy);
420    power_supply_unregister(&tosa_bat_main.psy);
421
422    /*
423     * Now cancel the bat_work. We won't get any more schedules,
424     * since all sources (isr and external_power_changed) are
425     * unregistered now.
426     */
427    cancel_work_sync(&bat_work);
428    gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
429    return 0;
430}
431
432static struct platform_driver tosa_bat_driver = {
433    .driver.name = "wm97xx-battery",
434    .driver.owner = THIS_MODULE,
435    .probe = tosa_bat_probe,
436    .remove = __devexit_p(tosa_bat_remove),
437    .suspend = tosa_bat_suspend,
438    .resume = tosa_bat_resume,
439};
440
441module_platform_driver(tosa_bat_driver);
442
443MODULE_LICENSE("GPL");
444MODULE_AUTHOR("Dmitry Baryshkov");
445MODULE_DESCRIPTION("Tosa battery driver");
446MODULE_ALIAS("platform:wm97xx-battery");
447

Archive Download this file



interactive