1 // SPDX-License-Identifier: GPL-2.0-only
3 * Battery and Power Management code for the Sharp SL-6000x
5 * Copyright (c) 2005 Dirk Opfer
6 * Copyright (c) 2008 Dmitry Baryshkov
8 #include <linux/kernel.h>
9 #include <linux/module.h>
10 #include <linux/power_supply.h>
11 #include <linux/wm97xx.h>
12 #include <linux/delay.h>
13 #include <linux/spinlock.h>
14 #include <linux/interrupt.h>
15 #include <linux/gpio.h>
17 #include <asm/mach-types.h>
18 #include <mach/tosa.h>
20 static DEFINE_MUTEX(bat_lock
); /* protects gpio pins */
21 static struct work_struct bat_work
;
25 struct power_supply
*psy
;
28 struct mutex work_lock
; /* protects data */
30 bool (*is_present
)(struct tosa_bat
*bat
);
47 static struct tosa_bat tosa_bat_main
;
48 static struct tosa_bat tosa_bat_jacket
;
50 static unsigned long tosa_read_bat(struct tosa_bat
*bat
)
52 unsigned long value
= 0;
54 if (bat
->gpio_bat
< 0 || bat
->adc_bat
< 0)
57 mutex_lock(&bat_lock
);
58 gpio_set_value(bat
->gpio_bat
, 1);
60 value
= wm97xx_read_aux_adc(dev_get_drvdata(bat
->psy
->dev
.parent
),
62 gpio_set_value(bat
->gpio_bat
, 0);
63 mutex_unlock(&bat_lock
);
65 value
= value
* 1000000 / bat
->adc_bat_divider
;
70 static unsigned long tosa_read_temp(struct tosa_bat
*bat
)
72 unsigned long value
= 0;
74 if (bat
->gpio_temp
< 0 || bat
->adc_temp
< 0)
77 mutex_lock(&bat_lock
);
78 gpio_set_value(bat
->gpio_temp
, 1);
80 value
= wm97xx_read_aux_adc(dev_get_drvdata(bat
->psy
->dev
.parent
),
82 gpio_set_value(bat
->gpio_temp
, 0);
83 mutex_unlock(&bat_lock
);
85 value
= value
* 10000 / bat
->adc_temp_divider
;
90 static int tosa_bat_get_property(struct power_supply
*psy
,
91 enum power_supply_property psp
,
92 union power_supply_propval
*val
)
95 struct tosa_bat
*bat
= power_supply_get_drvdata(psy
);
97 if (bat
->is_present
&& !bat
->is_present(bat
)
98 && psp
!= POWER_SUPPLY_PROP_PRESENT
) {
103 case POWER_SUPPLY_PROP_STATUS
:
104 val
->intval
= bat
->status
;
106 case POWER_SUPPLY_PROP_TECHNOLOGY
:
107 val
->intval
= bat
->technology
;
109 case POWER_SUPPLY_PROP_VOLTAGE_NOW
:
110 val
->intval
= tosa_read_bat(bat
);
112 case POWER_SUPPLY_PROP_VOLTAGE_MAX
:
113 if (bat
->full_chrg
== -1)
114 val
->intval
= bat
->bat_max
;
116 val
->intval
= bat
->full_chrg
;
118 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN
:
119 val
->intval
= bat
->bat_max
;
121 case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN
:
122 val
->intval
= bat
->bat_min
;
124 case POWER_SUPPLY_PROP_TEMP
:
125 val
->intval
= tosa_read_temp(bat
);
127 case POWER_SUPPLY_PROP_PRESENT
:
128 val
->intval
= bat
->is_present
? bat
->is_present(bat
) : 1;
137 static bool tosa_jacket_bat_is_present(struct tosa_bat
*bat
)
139 return gpio_get_value(TOSA_GPIO_JACKET_DETECT
) == 0;
142 static void tosa_bat_external_power_changed(struct power_supply
*psy
)
144 schedule_work(&bat_work
);
147 static irqreturn_t
tosa_bat_gpio_isr(int irq
, void *data
)
149 pr_info("tosa_bat_gpio irq\n");
150 schedule_work(&bat_work
);
154 static void tosa_bat_update(struct tosa_bat
*bat
)
157 struct power_supply
*psy
= bat
->psy
;
159 mutex_lock(&bat
->work_lock
);
163 if (bat
->is_present
&& !bat
->is_present(bat
)) {
164 printk(KERN_NOTICE
"%s not present\n", psy
->desc
->name
);
165 bat
->status
= POWER_SUPPLY_STATUS_UNKNOWN
;
167 } else if (power_supply_am_i_supplied(psy
)) {
168 if (bat
->status
== POWER_SUPPLY_STATUS_DISCHARGING
) {
169 gpio_set_value(bat
->gpio_charge_off
, 0);
173 if (gpio_get_value(bat
->gpio_full
)) {
174 if (old
== POWER_SUPPLY_STATUS_CHARGING
||
175 bat
->full_chrg
== -1)
176 bat
->full_chrg
= tosa_read_bat(bat
);
178 gpio_set_value(bat
->gpio_charge_off
, 1);
179 bat
->status
= POWER_SUPPLY_STATUS_FULL
;
181 gpio_set_value(bat
->gpio_charge_off
, 0);
182 bat
->status
= POWER_SUPPLY_STATUS_CHARGING
;
185 gpio_set_value(bat
->gpio_charge_off
, 1);
186 bat
->status
= POWER_SUPPLY_STATUS_DISCHARGING
;
189 if (old
!= bat
->status
)
190 power_supply_changed(psy
);
192 mutex_unlock(&bat
->work_lock
);
195 static void tosa_bat_work(struct work_struct
*work
)
197 tosa_bat_update(&tosa_bat_main
);
198 tosa_bat_update(&tosa_bat_jacket
);
202 static enum power_supply_property tosa_bat_main_props
[] = {
203 POWER_SUPPLY_PROP_STATUS
,
204 POWER_SUPPLY_PROP_TECHNOLOGY
,
205 POWER_SUPPLY_PROP_VOLTAGE_NOW
,
206 POWER_SUPPLY_PROP_VOLTAGE_MAX
,
207 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN
,
208 POWER_SUPPLY_PROP_TEMP
,
209 POWER_SUPPLY_PROP_PRESENT
,
212 static enum power_supply_property tosa_bat_bu_props
[] = {
213 POWER_SUPPLY_PROP_STATUS
,
214 POWER_SUPPLY_PROP_TECHNOLOGY
,
215 POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN
,
216 POWER_SUPPLY_PROP_VOLTAGE_NOW
,
217 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN
,
218 POWER_SUPPLY_PROP_PRESENT
,
221 static const struct power_supply_desc tosa_bat_main_desc
= {
222 .name
= "main-battery",
223 .type
= POWER_SUPPLY_TYPE_BATTERY
,
224 .properties
= tosa_bat_main_props
,
225 .num_properties
= ARRAY_SIZE(tosa_bat_main_props
),
226 .get_property
= tosa_bat_get_property
,
227 .external_power_changed
= tosa_bat_external_power_changed
,
231 static const struct power_supply_desc tosa_bat_jacket_desc
= {
232 .name
= "jacket-battery",
233 .type
= POWER_SUPPLY_TYPE_BATTERY
,
234 .properties
= tosa_bat_main_props
,
235 .num_properties
= ARRAY_SIZE(tosa_bat_main_props
),
236 .get_property
= tosa_bat_get_property
,
237 .external_power_changed
= tosa_bat_external_power_changed
,
240 static const struct power_supply_desc tosa_bat_bu_desc
= {
241 .name
= "backup-battery",
242 .type
= POWER_SUPPLY_TYPE_BATTERY
,
243 .properties
= tosa_bat_bu_props
,
244 .num_properties
= ARRAY_SIZE(tosa_bat_bu_props
),
245 .get_property
= tosa_bat_get_property
,
246 .external_power_changed
= tosa_bat_external_power_changed
,
249 static struct tosa_bat tosa_bat_main
= {
250 .status
= POWER_SUPPLY_STATUS_DISCHARGING
,
254 .gpio_full
= TOSA_GPIO_BAT0_CRG
,
255 .gpio_charge_off
= TOSA_GPIO_CHARGE_OFF
,
257 .technology
= POWER_SUPPLY_TECHNOLOGY_LIPO
,
259 .gpio_bat
= TOSA_GPIO_BAT0_V_ON
,
260 .adc_bat
= WM97XX_AUX_ID3
,
261 .adc_bat_divider
= 414,
263 .bat_min
= 1551 * 1000000 / 414,
265 .gpio_temp
= TOSA_GPIO_BAT1_TH_ON
,
266 .adc_temp
= WM97XX_AUX_ID2
,
267 .adc_temp_divider
= 10000,
270 static struct tosa_bat tosa_bat_jacket
= {
271 .status
= POWER_SUPPLY_STATUS_DISCHARGING
,
275 .is_present
= tosa_jacket_bat_is_present
,
276 .gpio_full
= TOSA_GPIO_BAT1_CRG
,
277 .gpio_charge_off
= TOSA_GPIO_CHARGE_OFF_JC
,
279 .technology
= POWER_SUPPLY_TECHNOLOGY_LIPO
,
281 .gpio_bat
= TOSA_GPIO_BAT1_V_ON
,
282 .adc_bat
= WM97XX_AUX_ID3
,
283 .adc_bat_divider
= 414,
285 .bat_min
= 1551 * 1000000 / 414,
287 .gpio_temp
= TOSA_GPIO_BAT0_TH_ON
,
288 .adc_temp
= WM97XX_AUX_ID2
,
289 .adc_temp_divider
= 10000,
292 static struct tosa_bat tosa_bat_bu
= {
293 .status
= POWER_SUPPLY_STATUS_UNKNOWN
,
298 .gpio_charge_off
= -1,
300 .technology
= POWER_SUPPLY_TECHNOLOGY_LiMn
,
302 .gpio_bat
= TOSA_GPIO_BU_CHRG_ON
,
303 .adc_bat
= WM97XX_AUX_ID4
,
304 .adc_bat_divider
= 1266,
308 .adc_temp_divider
= -1,
311 static struct gpio tosa_bat_gpios
[] = {
312 { TOSA_GPIO_CHARGE_OFF
, GPIOF_OUT_INIT_HIGH
, "main charge off" },
313 { TOSA_GPIO_CHARGE_OFF_JC
, GPIOF_OUT_INIT_HIGH
, "jacket charge off" },
314 { TOSA_GPIO_BAT_SW_ON
, GPIOF_OUT_INIT_LOW
, "battery switch" },
315 { TOSA_GPIO_BAT0_V_ON
, GPIOF_OUT_INIT_LOW
, "main battery" },
316 { TOSA_GPIO_BAT1_V_ON
, GPIOF_OUT_INIT_LOW
, "jacket battery" },
317 { TOSA_GPIO_BAT1_TH_ON
, GPIOF_OUT_INIT_LOW
, "main battery temp" },
318 { TOSA_GPIO_BAT0_TH_ON
, GPIOF_OUT_INIT_LOW
, "jacket battery temp" },
319 { TOSA_GPIO_BU_CHRG_ON
, GPIOF_OUT_INIT_LOW
, "backup battery" },
320 { TOSA_GPIO_BAT0_CRG
, GPIOF_IN
, "main battery full" },
321 { TOSA_GPIO_BAT1_CRG
, GPIOF_IN
, "jacket battery full" },
322 { TOSA_GPIO_BAT0_LOW
, GPIOF_IN
, "main battery low" },
323 { TOSA_GPIO_BAT1_LOW
, GPIOF_IN
, "jacket battery low" },
324 { TOSA_GPIO_JACKET_DETECT
, GPIOF_IN
, "jacket detect" },
328 static int tosa_bat_suspend(struct platform_device
*dev
, pm_message_t state
)
330 /* flush all pending status updates */
331 flush_work(&bat_work
);
335 static int tosa_bat_resume(struct platform_device
*dev
)
337 /* things may have changed while we were away */
338 schedule_work(&bat_work
);
342 #define tosa_bat_suspend NULL
343 #define tosa_bat_resume NULL
346 static int tosa_bat_probe(struct platform_device
*dev
)
349 struct power_supply_config main_psy_cfg
= {},
353 if (!machine_is_tosa())
356 ret
= gpio_request_array(tosa_bat_gpios
, ARRAY_SIZE(tosa_bat_gpios
));
360 mutex_init(&tosa_bat_main
.work_lock
);
361 mutex_init(&tosa_bat_jacket
.work_lock
);
363 INIT_WORK(&bat_work
, tosa_bat_work
);
365 main_psy_cfg
.drv_data
= &tosa_bat_main
;
366 tosa_bat_main
.psy
= power_supply_register(&dev
->dev
,
369 if (IS_ERR(tosa_bat_main
.psy
)) {
370 ret
= PTR_ERR(tosa_bat_main
.psy
);
371 goto err_psy_reg_main
;
374 jacket_psy_cfg
.drv_data
= &tosa_bat_jacket
;
375 tosa_bat_jacket
.psy
= power_supply_register(&dev
->dev
,
376 &tosa_bat_jacket_desc
,
378 if (IS_ERR(tosa_bat_jacket
.psy
)) {
379 ret
= PTR_ERR(tosa_bat_jacket
.psy
);
380 goto err_psy_reg_jacket
;
383 bu_psy_cfg
.drv_data
= &tosa_bat_bu
;
384 tosa_bat_bu
.psy
= power_supply_register(&dev
->dev
, &tosa_bat_bu_desc
,
386 if (IS_ERR(tosa_bat_bu
.psy
)) {
387 ret
= PTR_ERR(tosa_bat_bu
.psy
);
391 ret
= request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
),
393 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
394 "main full", &tosa_bat_main
);
398 ret
= request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
),
400 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
401 "jacket full", &tosa_bat_jacket
);
405 ret
= request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT
),
407 IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING
,
408 "jacket detect", &tosa_bat_jacket
);
410 schedule_work(&bat_work
);
414 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
);
420 power_supply_unregister(tosa_bat_jacket
.psy
);
422 power_supply_unregister(tosa_bat_main
.psy
);
425 /* see comment in tosa_bat_remove */
426 cancel_work_sync(&bat_work
);
428 gpio_free_array(tosa_bat_gpios
, ARRAY_SIZE(tosa_bat_gpios
));
432 static int tosa_bat_remove(struct platform_device
*dev
)
434 free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT
), &tosa_bat_jacket
);
435 free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG
), &tosa_bat_jacket
);
436 free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG
), &tosa_bat_main
);
438 power_supply_unregister(tosa_bat_bu
.psy
);
439 power_supply_unregister(tosa_bat_jacket
.psy
);
440 power_supply_unregister(tosa_bat_main
.psy
);
443 * Now cancel the bat_work. We won't get any more schedules,
444 * since all sources (isr and external_power_changed) are
447 cancel_work_sync(&bat_work
);
448 gpio_free_array(tosa_bat_gpios
, ARRAY_SIZE(tosa_bat_gpios
));
452 static struct platform_driver tosa_bat_driver
= {
453 .driver
.name
= "wm97xx-battery",
454 .driver
.owner
= THIS_MODULE
,
455 .probe
= tosa_bat_probe
,
456 .remove
= tosa_bat_remove
,
457 .suspend
= tosa_bat_suspend
,
458 .resume
= tosa_bat_resume
,
461 module_platform_driver(tosa_bat_driver
);
463 MODULE_LICENSE("GPL");
464 MODULE_AUTHOR("Dmitry Baryshkov");
465 MODULE_DESCRIPTION("Tosa battery driver");
466 MODULE_ALIAS("platform:wm97xx-battery");