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
= power_supply_get_drvdata(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\n");
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
->desc
->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 const struct power_supply_desc tosa_bat_main_desc
= {
226 .name
= "main-battery",
227 .type
= POWER_SUPPLY_TYPE_BATTERY
,
228 .properties
= tosa_bat_main_props
,
229 .num_properties
= ARRAY_SIZE(tosa_bat_main_props
),
230 .get_property
= tosa_bat_get_property
,
231 .external_power_changed
= tosa_bat_external_power_changed
,
235 static const struct power_supply_desc tosa_bat_jacket_desc
= {
236 .name
= "jacket-battery",
237 .type
= POWER_SUPPLY_TYPE_BATTERY
,
238 .properties
= tosa_bat_main_props
,
239 .num_properties
= ARRAY_SIZE(tosa_bat_main_props
),
240 .get_property
= tosa_bat_get_property
,
241 .external_power_changed
= tosa_bat_external_power_changed
,
244 static const struct power_supply_desc tosa_bat_bu_desc
= {
245 .name
= "backup-battery",
246 .type
= POWER_SUPPLY_TYPE_BATTERY
,
247 .properties
= tosa_bat_bu_props
,
248 .num_properties
= ARRAY_SIZE(tosa_bat_bu_props
),
249 .get_property
= tosa_bat_get_property
,
250 .external_power_changed
= tosa_bat_external_power_changed
,
253 static struct tosa_bat tosa_bat_main
= {
254 .status
= POWER_SUPPLY_STATUS_DISCHARGING
,
258 .gpio_full
= TOSA_GPIO_BAT0_CRG
,
259 .gpio_charge_off
= TOSA_GPIO_CHARGE_OFF
,
261 .technology
= POWER_SUPPLY_TECHNOLOGY_LIPO
,
263 .gpio_bat
= TOSA_GPIO_BAT0_V_ON
,
264 .adc_bat
= WM97XX_AUX_ID3
,
265 .adc_bat_divider
= 414,
267 .bat_min
= 1551 * 1000000 / 414,
269 .gpio_temp
= TOSA_GPIO_BAT1_TH_ON
,
270 .adc_temp
= WM97XX_AUX_ID2
,
271 .adc_temp_divider
= 10000,
274 static struct tosa_bat tosa_bat_jacket
= {
275 .status
= POWER_SUPPLY_STATUS_DISCHARGING
,
279 .is_present
= tosa_jacket_bat_is_present
,
280 .gpio_full
= TOSA_GPIO_BAT1_CRG
,
281 .gpio_charge_off
= TOSA_GPIO_CHARGE_OFF_JC
,
283 .technology
= POWER_SUPPLY_TECHNOLOGY_LIPO
,
285 .gpio_bat
= TOSA_GPIO_BAT1_V_ON
,
286 .adc_bat
= WM97XX_AUX_ID3
,
287 .adc_bat_divider
= 414,
289 .bat_min
= 1551 * 1000000 / 414,
291 .gpio_temp
= TOSA_GPIO_BAT0_TH_ON
,
292 .adc_temp
= WM97XX_AUX_ID2
,
293 .adc_temp_divider
= 10000,
296 static struct tosa_bat tosa_bat_bu
= {
297 .status
= POWER_SUPPLY_STATUS_UNKNOWN
,
302 .gpio_charge_off
= -1,
304 .technology
= POWER_SUPPLY_TECHNOLOGY_LiMn
,
306 .gpio_bat
= TOSA_GPIO_BU_CHRG_ON
,
307 .adc_bat
= WM97XX_AUX_ID4
,
308 .adc_bat_divider
= 1266,
312 .adc_temp_divider
= -1,
315 static struct gpio tosa_bat_gpios
[] = {
316 { TOSA_GPIO_CHARGE_OFF
, GPIOF_OUT_INIT_HIGH
, "main charge off" },
317 { TOSA_GPIO_CHARGE_OFF_JC
, GPIOF_OUT_INIT_HIGH
, "jacket charge off" },
318 { TOSA_GPIO_BAT_SW_ON
, GPIOF_OUT_INIT_LOW
, "battery switch" },
319 { TOSA_GPIO_BAT0_V_ON
, GPIOF_OUT_INIT_LOW
, "main battery" },
320 { TOSA_GPIO_BAT1_V_ON
, GPIOF_OUT_INIT_LOW
, "jacket battery" },
321 { TOSA_GPIO_BAT1_TH_ON
, GPIOF_OUT_INIT_LOW
, "main battery temp" },
322 { TOSA_GPIO_BAT0_TH_ON
, GPIOF_OUT_INIT_LOW
, "jacket battery temp" },
323 { TOSA_GPIO_BU_CHRG_ON
, GPIOF_OUT_INIT_LOW
, "backup battery" },
324 { TOSA_GPIO_BAT0_CRG
, GPIOF_IN
, "main battery full" },
325 { TOSA_GPIO_BAT1_CRG
, GPIOF_IN
, "jacket battery full" },
326 { TOSA_GPIO_BAT0_LOW
, GPIOF_IN
, "main battery low" },
327 { TOSA_GPIO_BAT1_LOW
, GPIOF_IN
, "jacket battery low" },
328 { TOSA_GPIO_JACKET_DETECT
, GPIOF_IN
, "jacket detect" },
332 static int tosa_bat_suspend(struct platform_device
*dev
, pm_message_t state
)
334 /* flush all pending status updates */
335 flush_work(&bat_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 tosa_bat_probe(struct platform_device
*dev
)
353 struct power_supply_config main_psy_cfg
= {},
357 if (!machine_is_tosa())
360 ret
= gpio_request_array(tosa_bat_gpios
, ARRAY_SIZE(tosa_bat_gpios
));
364 mutex_init(&tosa_bat_main
.work_lock
);
365 mutex_init(&tosa_bat_jacket
.work_lock
);
367 INIT_WORK(&bat_work
, tosa_bat_work
);
369 main_psy_cfg
.drv_data
= &tosa_bat_main
;
370 tosa_bat_main
.psy
= power_supply_register(&dev
->dev
,
373 if (IS_ERR(tosa_bat_main
.psy
)) {
374 ret
= PTR_ERR(tosa_bat_main
.psy
);
375 goto err_psy_reg_main
;
378 jacket_psy_cfg
.drv_data
= &tosa_bat_jacket
;
379 tosa_bat_jacket
.psy
= power_supply_register(&dev
->dev
,
380 &tosa_bat_jacket_desc
,
382 if (IS_ERR(tosa_bat_jacket
.psy
)) {
383 ret
= PTR_ERR(tosa_bat_jacket
.psy
);
384 goto err_psy_reg_jacket
;
387 bu_psy_cfg
.drv_data
= &tosa_bat_bu
;
388 tosa_bat_bu
.psy
= power_supply_register(&dev
->dev
, &tosa_bat_bu_desc
,
390 if (IS_ERR(tosa_bat_bu
.psy
)) {
391 ret
= PTR_ERR(tosa_bat_bu
.psy
);
395 ret
= request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
),
397 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
398 "main full", &tosa_bat_main
);
402 ret
= request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
),
404 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
405 "jacket full", &tosa_bat_jacket
);
409 ret
= request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT
),
411 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
412 "jacket detect", &tosa_bat_jacket
);
414 schedule_work(&bat_work
);
418 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
), &tosa_bat_jacket
);
420 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
), &tosa_bat_main
);
422 power_supply_unregister(tosa_bat_bu
.psy
);
424 power_supply_unregister(tosa_bat_jacket
.psy
);
426 power_supply_unregister(tosa_bat_main
.psy
);
429 /* see comment in tosa_bat_remove */
430 cancel_work_sync(&bat_work
);
432 gpio_free_array(tosa_bat_gpios
, ARRAY_SIZE(tosa_bat_gpios
));
436 static int tosa_bat_remove(struct platform_device
*dev
)
438 free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT
), &tosa_bat_jacket
);
439 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
), &tosa_bat_jacket
);
440 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
), &tosa_bat_main
);
442 power_supply_unregister(tosa_bat_bu
.psy
);
443 power_supply_unregister(tosa_bat_jacket
.psy
);
444 power_supply_unregister(tosa_bat_main
.psy
);
447 * Now cancel the bat_work. We won't get any more schedules,
448 * since all sources (isr and external_power_changed) are
451 cancel_work_sync(&bat_work
);
452 gpio_free_array(tosa_bat_gpios
, ARRAY_SIZE(tosa_bat_gpios
));
456 static struct platform_driver tosa_bat_driver
= {
457 .driver
.name
= "wm97xx-battery",
458 .driver
.owner
= THIS_MODULE
,
459 .probe
= tosa_bat_probe
,
460 .remove
= tosa_bat_remove
,
461 .suspend
= tosa_bat_suspend
,
462 .resume
= tosa_bat_resume
,
465 module_platform_driver(tosa_bat_driver
);
467 MODULE_LICENSE("GPL");
468 MODULE_AUTHOR("Dmitry Baryshkov");
469 MODULE_DESCRIPTION("Tosa battery driver");
470 MODULE_ALIAS("platform:wm97xx-battery");