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,
310 static 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" },
327 static int tosa_bat_suspend(struct platform_device
*dev
, pm_message_t state
)
329 /* flush all pending status updates */
330 flush_work_sync(&bat_work
);
334 static int tosa_bat_resume(struct platform_device
*dev
)
336 /* things may have changed while we were away */
337 schedule_work(&bat_work
);
341 #define tosa_bat_suspend NULL
342 #define tosa_bat_resume NULL
345 static int __devinit
tosa_bat_probe(struct platform_device
*dev
)
349 if (!machine_is_tosa())
352 ret
= gpio_request_array(tosa_bat_gpios
, ARRAY_SIZE(tosa_bat_gpios
));
356 mutex_init(&tosa_bat_main
.work_lock
);
357 mutex_init(&tosa_bat_jacket
.work_lock
);
359 INIT_WORK(&bat_work
, tosa_bat_work
);
361 ret
= power_supply_register(&dev
->dev
, &tosa_bat_main
.psy
);
363 goto err_psy_reg_main
;
364 ret
= power_supply_register(&dev
->dev
, &tosa_bat_jacket
.psy
);
366 goto err_psy_reg_jacket
;
367 ret
= power_supply_register(&dev
->dev
, &tosa_bat_bu
.psy
);
371 ret
= request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
),
373 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
374 "main full", &tosa_bat_main
);
378 ret
= request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
),
380 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
381 "jacket full", &tosa_bat_jacket
);
385 ret
= request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT
),
387 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
388 "jacket detect", &tosa_bat_jacket
);
390 schedule_work(&bat_work
);
394 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
), &tosa_bat_jacket
);
396 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
), &tosa_bat_main
);
398 power_supply_unregister(&tosa_bat_bu
.psy
);
400 power_supply_unregister(&tosa_bat_jacket
.psy
);
402 power_supply_unregister(&tosa_bat_main
.psy
);
405 /* see comment in tosa_bat_remove */
406 cancel_work_sync(&bat_work
);
408 gpio_free_array(tosa_bat_gpios
, ARRAY_SIZE(tosa_bat_gpios
));
412 static int __devexit
tosa_bat_remove(struct platform_device
*dev
)
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
);
418 power_supply_unregister(&tosa_bat_bu
.psy
);
419 power_supply_unregister(&tosa_bat_jacket
.psy
);
420 power_supply_unregister(&tosa_bat_main
.psy
);
423 * Now cancel the bat_work. We won't get any more schedules,
424 * since all sources (isr and external_power_changed) are
427 cancel_work_sync(&bat_work
);
428 gpio_free_array(tosa_bat_gpios
, ARRAY_SIZE(tosa_bat_gpios
));
432 static 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
,
441 module_platform_driver(tosa_bat_driver
);
443 MODULE_LICENSE("GPL");
444 MODULE_AUTHOR("Dmitry Baryshkov");
445 MODULE_DESCRIPTION("Tosa battery driver");
446 MODULE_ALIAS("platform:wm97xx-battery");