2 * Battery and Power Management code for the Sharp SL-6000x
4 * Copyright (c) 2005 Dirk Opfer
5 * Copyright (c) 2008 Dmitry Baryshkov
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.
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>
21 #include <asm/mach-types.h>
22 #include <mach/tosa.h>
24 static DEFINE_MUTEX(bat_lock
); /* protects gpio pins */
25 static struct work_struct bat_work
;
29 struct power_supply psy
;
32 struct mutex work_lock
; /* protects data */
34 bool (*is_present
)(struct tosa_bat
*bat
);
51 static struct tosa_bat tosa_bat_main
;
52 static struct tosa_bat tosa_bat_jacket
;
54 static unsigned long tosa_read_bat(struct tosa_bat
*bat
)
56 unsigned long value
= 0;
58 if (bat
->gpio_bat
< 0 || bat
->adc_bat
< 0)
61 mutex_lock(&bat_lock
);
62 gpio_set_value(bat
->gpio_bat
, 1);
64 value
= wm97xx_read_aux_adc(dev_get_drvdata(bat
->psy
.dev
->parent
),
66 gpio_set_value(bat
->gpio_bat
, 0);
67 mutex_unlock(&bat_lock
);
69 value
= value
* 1000000 / bat
->adc_bat_divider
;
74 static unsigned long tosa_read_temp(struct tosa_bat
*bat
)
76 unsigned long value
= 0;
78 if (bat
->gpio_temp
< 0 || bat
->adc_temp
< 0)
81 mutex_lock(&bat_lock
);
82 gpio_set_value(bat
->gpio_temp
, 1);
84 value
= wm97xx_read_aux_adc(dev_get_drvdata(bat
->psy
.dev
->parent
),
86 gpio_set_value(bat
->gpio_temp
, 0);
87 mutex_unlock(&bat_lock
);
89 value
= value
* 10000 / bat
->adc_temp_divider
;
94 static int tosa_bat_get_property(struct power_supply
*psy
,
95 enum power_supply_property psp
,
96 union power_supply_propval
*val
)
99 struct tosa_bat
*bat
= container_of(psy
, struct tosa_bat
, psy
);
101 if (bat
->is_present
&& !bat
->is_present(bat
)
102 && psp
!= POWER_SUPPLY_PROP_PRESENT
) {
107 case POWER_SUPPLY_PROP_STATUS
:
108 val
->intval
= bat
->status
;
110 case POWER_SUPPLY_PROP_TECHNOLOGY
:
111 val
->intval
= bat
->technology
;
113 case POWER_SUPPLY_PROP_VOLTAGE_NOW
:
114 val
->intval
= tosa_read_bat(bat
);
116 case POWER_SUPPLY_PROP_VOLTAGE_MAX
:
117 if (bat
->full_chrg
== -1)
118 val
->intval
= bat
->bat_max
;
120 val
->intval
= bat
->full_chrg
;
122 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN
:
123 val
->intval
= bat
->bat_max
;
125 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN
:
126 val
->intval
= bat
->bat_min
;
128 case POWER_SUPPLY_PROP_TEMP
:
129 val
->intval
= tosa_read_temp(bat
);
131 case POWER_SUPPLY_PROP_PRESENT
:
132 val
->intval
= bat
->is_present
? bat
->is_present(bat
) : 1;
141 static bool tosa_jacket_bat_is_present(struct tosa_bat
*bat
)
143 return gpio_get_value(TOSA_GPIO_JACKET_DETECT
) == 0;
146 static void tosa_bat_external_power_changed(struct power_supply
*psy
)
148 schedule_work(&bat_work
);
151 static irqreturn_t
tosa_bat_gpio_isr(int irq
, void *data
)
153 pr_info("tosa_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq
)));
154 schedule_work(&bat_work
);
158 static void tosa_bat_update(struct tosa_bat
*bat
)
161 struct power_supply
*psy
= &bat
->psy
;
163 mutex_lock(&bat
->work_lock
);
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
;
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);
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
);
182 gpio_set_value(bat
->gpio_charge_off
, 1);
183 bat
->status
= POWER_SUPPLY_STATUS_FULL
;
185 gpio_set_value(bat
->gpio_charge_off
, 0);
186 bat
->status
= POWER_SUPPLY_STATUS_CHARGING
;
189 gpio_set_value(bat
->gpio_charge_off
, 1);
190 bat
->status
= POWER_SUPPLY_STATUS_DISCHARGING
;
193 if (old
!= bat
->status
)
194 power_supply_changed(psy
);
196 mutex_unlock(&bat
->work_lock
);
199 static void tosa_bat_work(struct work_struct
*work
)
201 tosa_bat_update(&tosa_bat_main
);
202 tosa_bat_update(&tosa_bat_jacket
);
206 static 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
,
216 static 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
,
225 static struct tosa_bat tosa_bat_main
= {
226 .status
= POWER_SUPPLY_STATUS_DISCHARGING
,
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
,
238 .gpio_full
= TOSA_GPIO_BAT0_CRG
,
239 .gpio_charge_off
= TOSA_GPIO_CHARGE_OFF
,
241 .technology
= POWER_SUPPLY_TECHNOLOGY_LIPO
,
243 .gpio_bat
= TOSA_GPIO_BAT0_V_ON
,
244 .adc_bat
= WM97XX_AUX_ID3
,
245 .adc_bat_divider
= 414,
247 .bat_min
= 1551 * 1000000 / 414,
249 .gpio_temp
= TOSA_GPIO_BAT1_TH_ON
,
250 .adc_temp
= WM97XX_AUX_ID2
,
251 .adc_temp_divider
= 10000,
254 static struct tosa_bat tosa_bat_jacket
= {
255 .status
= POWER_SUPPLY_STATUS_DISCHARGING
,
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
,
266 .is_present
= tosa_jacket_bat_is_present
,
267 .gpio_full
= TOSA_GPIO_BAT1_CRG
,
268 .gpio_charge_off
= TOSA_GPIO_CHARGE_OFF_JC
,
270 .technology
= POWER_SUPPLY_TECHNOLOGY_LIPO
,
272 .gpio_bat
= TOSA_GPIO_BAT1_V_ON
,
273 .adc_bat
= WM97XX_AUX_ID3
,
274 .adc_bat_divider
= 414,
276 .bat_min
= 1551 * 1000000 / 414,
278 .gpio_temp
= TOSA_GPIO_BAT0_TH_ON
,
279 .adc_temp
= WM97XX_AUX_ID2
,
280 .adc_temp_divider
= 10000,
283 static struct tosa_bat tosa_bat_bu
= {
284 .status
= POWER_SUPPLY_STATUS_UNKNOWN
,
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
,
297 .gpio_charge_off
= -1,
299 .technology
= POWER_SUPPLY_TECHNOLOGY_LiMn
,
301 .gpio_bat
= TOSA_GPIO_BU_CHRG_ON
,
302 .adc_bat
= WM97XX_AUX_ID4
,
303 .adc_bat_divider
= 1266,
307 .adc_temp_divider
= -1,
316 { TOSA_GPIO_CHARGE_OFF
, "main charge off", 1, 1 },
317 { TOSA_GPIO_CHARGE_OFF_JC
, "jacket charge off", 1, 1 },
318 { TOSA_GPIO_BAT_SW_ON
, "battery switch", 1, 0 },
319 { TOSA_GPIO_BAT0_V_ON
, "main battery", 1, 0 },
320 { TOSA_GPIO_BAT1_V_ON
, "jacket battery", 1, 0 },
321 { TOSA_GPIO_BAT1_TH_ON
, "main battery temp", 1, 0 },
322 { TOSA_GPIO_BAT0_TH_ON
, "jacket battery temp", 1, 0 },
323 { TOSA_GPIO_BU_CHRG_ON
, "backup battery", 1, 0 },
324 { TOSA_GPIO_BAT0_CRG
, "main battery full", 0, 0 },
325 { TOSA_GPIO_BAT1_CRG
, "jacket battery full", 0, 0 },
326 { TOSA_GPIO_BAT0_LOW
, "main battery low", 0, 0 },
327 { TOSA_GPIO_BAT1_LOW
, "jacket battery low", 0, 0 },
328 { TOSA_GPIO_JACKET_DETECT
, "jacket detect", 0, 0 },
332 static int tosa_bat_suspend(struct platform_device
*dev
, pm_message_t state
)
334 /* flush all pending status updates */
335 flush_scheduled_work();
339 static int tosa_bat_resume(struct platform_device
*dev
)
341 /* things may have changed while we were away */
342 schedule_work(&bat_work
);
346 #define tosa_bat_suspend NULL
347 #define tosa_bat_resume NULL
350 static int __devinit
tosa_bat_probe(struct platform_device
*dev
)
355 if (!machine_is_tosa())
358 for (i
= 0; i
< ARRAY_SIZE(gpios
); i
++) {
359 ret
= gpio_request(gpios
[i
].gpio
, gpios
[i
].name
);
366 ret
= gpio_direction_output(gpios
[i
].gpio
,
369 ret
= gpio_direction_input(gpios
[i
].gpio
);
375 mutex_init(&tosa_bat_main
.work_lock
);
376 mutex_init(&tosa_bat_jacket
.work_lock
);
378 INIT_WORK(&bat_work
, tosa_bat_work
);
380 ret
= power_supply_register(&dev
->dev
, &tosa_bat_main
.psy
);
382 goto err_psy_reg_main
;
383 ret
= power_supply_register(&dev
->dev
, &tosa_bat_jacket
.psy
);
385 goto err_psy_reg_jacket
;
386 ret
= power_supply_register(&dev
->dev
, &tosa_bat_bu
.psy
);
390 ret
= request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
),
392 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
393 "main full", &tosa_bat_main
);
397 ret
= request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
),
399 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
400 "jacket full", &tosa_bat_jacket
);
404 ret
= request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT
),
406 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
407 "jacket detect", &tosa_bat_jacket
);
409 schedule_work(&bat_work
);
413 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
), &tosa_bat_jacket
);
415 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
), &tosa_bat_main
);
417 power_supply_unregister(&tosa_bat_bu
.psy
);
419 power_supply_unregister(&tosa_bat_jacket
.psy
);
421 power_supply_unregister(&tosa_bat_main
.psy
);
424 /* see comment in tosa_bat_remove */
425 flush_scheduled_work();
430 gpio_free(gpios
[i
].gpio
);
435 static int __devexit
tosa_bat_remove(struct platform_device
*dev
)
439 free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT
), &tosa_bat_jacket
);
440 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
), &tosa_bat_jacket
);
441 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
), &tosa_bat_main
);
443 power_supply_unregister(&tosa_bat_bu
.psy
);
444 power_supply_unregister(&tosa_bat_jacket
.psy
);
445 power_supply_unregister(&tosa_bat_main
.psy
);
448 * now flush all pending work.
449 * we won't get any more schedules, since all
450 * sources (isr and external_power_changed)
451 * are unregistered now.
453 flush_scheduled_work();
455 for (i
= ARRAY_SIZE(gpios
) - 1; i
>= 0; i
--)
456 gpio_free(gpios
[i
].gpio
);
461 static struct platform_driver tosa_bat_driver
= {
462 .driver
.name
= "wm97xx-battery",
463 .driver
.owner
= THIS_MODULE
,
464 .probe
= tosa_bat_probe
,
465 .remove
= __devexit_p(tosa_bat_remove
),
466 .suspend
= tosa_bat_suspend
,
467 .resume
= tosa_bat_resume
,
470 static int __init
tosa_bat_init(void)
472 return platform_driver_register(&tosa_bat_driver
);
475 static void __exit
tosa_bat_exit(void)
477 platform_driver_unregister(&tosa_bat_driver
);
480 module_init(tosa_bat_init
);
481 module_exit(tosa_bat_exit
);
483 MODULE_LICENSE("GPL");
484 MODULE_AUTHOR("Dmitry Baryshkov");
485 MODULE_DESCRIPTION("Tosa battery driver");
486 MODULE_ALIAS("platform:wm97xx-battery");