1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Battery and Power Management code for the Sharp SL-5x00 |
4 | * |
5 | * Copyright (C) 2009 Thomas Kunze |
6 | * |
7 | * based on tosa_battery.c |
8 | */ |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> |
11 | #include <linux/power_supply.h> |
12 | #include <linux/delay.h> |
13 | #include <linux/spinlock.h> |
14 | #include <linux/interrupt.h> |
15 | #include <linux/gpio/driver.h> |
16 | #include <linux/gpio/machine.h> |
17 | #include <linux/gpio/consumer.h> |
18 | #include <linux/mfd/ucb1x00.h> |
19 | |
20 | #include <asm/mach/sharpsl_param.h> |
21 | #include <asm/mach-types.h> |
22 | #include <mach/collie.h> |
23 | |
24 | static DEFINE_MUTEX(bat_lock); /* protects gpio pins */ |
25 | static struct work_struct bat_work; |
26 | static struct ucb1x00 *ucb; |
27 | |
28 | struct collie_bat { |
29 | int status; |
30 | struct power_supply *psy; |
31 | int full_chrg; |
32 | |
33 | struct mutex work_lock; /* protects data */ |
34 | |
35 | bool (*is_present)(struct collie_bat *bat); |
36 | struct gpio_desc *gpio_full; |
37 | struct gpio_desc *gpio_charge_on; |
38 | |
39 | int technology; |
40 | |
41 | struct gpio_desc *gpio_bat; |
42 | int adc_bat; |
43 | int adc_bat_divider; |
44 | int bat_max; |
45 | int bat_min; |
46 | |
47 | struct gpio_desc *gpio_temp; |
48 | int adc_temp; |
49 | int adc_temp_divider; |
50 | }; |
51 | |
52 | static struct collie_bat collie_bat_main; |
53 | |
54 | static unsigned long collie_read_bat(struct collie_bat *bat) |
55 | { |
56 | unsigned long value = 0; |
57 | |
58 | if (!bat->gpio_bat || bat->adc_bat < 0) |
59 | return 0; |
60 | mutex_lock(&bat_lock); |
61 | gpiod_set_value(desc: bat->gpio_bat, value: 1); |
62 | msleep(msecs: 5); |
63 | ucb1x00_adc_enable(ucb); |
64 | value = ucb1x00_adc_read(ucb, adc_channel: bat->adc_bat, UCB_SYNC); |
65 | ucb1x00_adc_disable(ucb); |
66 | gpiod_set_value(desc: bat->gpio_bat, value: 0); |
67 | mutex_unlock(lock: &bat_lock); |
68 | value = value * 1000000 / bat->adc_bat_divider; |
69 | |
70 | return value; |
71 | } |
72 | |
73 | static unsigned long collie_read_temp(struct collie_bat *bat) |
74 | { |
75 | unsigned long value = 0; |
76 | if (!bat->gpio_temp || bat->adc_temp < 0) |
77 | return 0; |
78 | |
79 | mutex_lock(&bat_lock); |
80 | gpiod_set_value(desc: bat->gpio_temp, value: 1); |
81 | msleep(msecs: 5); |
82 | ucb1x00_adc_enable(ucb); |
83 | value = ucb1x00_adc_read(ucb, adc_channel: bat->adc_temp, UCB_SYNC); |
84 | ucb1x00_adc_disable(ucb); |
85 | gpiod_set_value(desc: bat->gpio_temp, value: 0); |
86 | mutex_unlock(lock: &bat_lock); |
87 | |
88 | value = value * 10000 / bat->adc_temp_divider; |
89 | |
90 | return value; |
91 | } |
92 | |
93 | static int collie_bat_get_property(struct power_supply *psy, |
94 | enum power_supply_property psp, |
95 | union power_supply_propval *val) |
96 | { |
97 | int ret = 0; |
98 | struct collie_bat *bat = power_supply_get_drvdata(psy); |
99 | |
100 | if (bat->is_present && !bat->is_present(bat) |
101 | && psp != POWER_SUPPLY_PROP_PRESENT) { |
102 | return -ENODEV; |
103 | } |
104 | |
105 | switch (psp) { |
106 | case POWER_SUPPLY_PROP_STATUS: |
107 | val->intval = bat->status; |
108 | break; |
109 | case POWER_SUPPLY_PROP_TECHNOLOGY: |
110 | val->intval = bat->technology; |
111 | break; |
112 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: |
113 | val->intval = collie_read_bat(bat); |
114 | break; |
115 | case POWER_SUPPLY_PROP_VOLTAGE_MAX: |
116 | if (bat->full_chrg == -1) |
117 | val->intval = bat->bat_max; |
118 | else |
119 | val->intval = bat->full_chrg; |
120 | break; |
121 | case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: |
122 | val->intval = bat->bat_max; |
123 | break; |
124 | case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: |
125 | val->intval = bat->bat_min; |
126 | break; |
127 | case POWER_SUPPLY_PROP_TEMP: |
128 | val->intval = collie_read_temp(bat); |
129 | break; |
130 | case POWER_SUPPLY_PROP_PRESENT: |
131 | val->intval = bat->is_present ? bat->is_present(bat) : 1; |
132 | break; |
133 | default: |
134 | ret = -EINVAL; |
135 | break; |
136 | } |
137 | return ret; |
138 | } |
139 | |
140 | static void collie_bat_external_power_changed(struct power_supply *psy) |
141 | { |
142 | schedule_work(work: &bat_work); |
143 | } |
144 | |
145 | static irqreturn_t collie_bat_gpio_isr(int irq, void *data) |
146 | { |
147 | pr_info("collie_bat_gpio irq\n" ); |
148 | schedule_work(work: &bat_work); |
149 | return IRQ_HANDLED; |
150 | } |
151 | |
152 | static void collie_bat_update(struct collie_bat *bat) |
153 | { |
154 | int old; |
155 | struct power_supply *psy = bat->psy; |
156 | |
157 | mutex_lock(&bat->work_lock); |
158 | |
159 | old = bat->status; |
160 | |
161 | if (bat->is_present && !bat->is_present(bat)) { |
162 | printk(KERN_NOTICE "%s not present\n" , psy->desc->name); |
163 | bat->status = POWER_SUPPLY_STATUS_UNKNOWN; |
164 | bat->full_chrg = -1; |
165 | } else if (power_supply_am_i_supplied(psy)) { |
166 | if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) { |
167 | gpiod_set_value(desc: bat->gpio_charge_on, value: 1); |
168 | mdelay(15); |
169 | } |
170 | |
171 | if (gpiod_get_value(desc: bat->gpio_full)) { |
172 | if (old == POWER_SUPPLY_STATUS_CHARGING || |
173 | bat->full_chrg == -1) |
174 | bat->full_chrg = collie_read_bat(bat); |
175 | |
176 | gpiod_set_value(desc: bat->gpio_charge_on, value: 0); |
177 | bat->status = POWER_SUPPLY_STATUS_FULL; |
178 | } else { |
179 | gpiod_set_value(desc: bat->gpio_charge_on, value: 1); |
180 | bat->status = POWER_SUPPLY_STATUS_CHARGING; |
181 | } |
182 | } else { |
183 | gpiod_set_value(desc: bat->gpio_charge_on, value: 0); |
184 | bat->status = POWER_SUPPLY_STATUS_DISCHARGING; |
185 | } |
186 | |
187 | if (old != bat->status) |
188 | power_supply_changed(psy); |
189 | |
190 | mutex_unlock(lock: &bat->work_lock); |
191 | } |
192 | |
193 | static void collie_bat_work(struct work_struct *work) |
194 | { |
195 | collie_bat_update(bat: &collie_bat_main); |
196 | } |
197 | |
198 | |
199 | static enum power_supply_property collie_bat_main_props[] = { |
200 | POWER_SUPPLY_PROP_STATUS, |
201 | POWER_SUPPLY_PROP_TECHNOLOGY, |
202 | POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, |
203 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
204 | POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, |
205 | POWER_SUPPLY_PROP_VOLTAGE_MAX, |
206 | POWER_SUPPLY_PROP_PRESENT, |
207 | POWER_SUPPLY_PROP_TEMP, |
208 | }; |
209 | |
210 | static enum power_supply_property collie_bat_bu_props[] = { |
211 | POWER_SUPPLY_PROP_STATUS, |
212 | POWER_SUPPLY_PROP_TECHNOLOGY, |
213 | POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, |
214 | POWER_SUPPLY_PROP_VOLTAGE_NOW, |
215 | POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, |
216 | POWER_SUPPLY_PROP_VOLTAGE_MAX, |
217 | POWER_SUPPLY_PROP_PRESENT, |
218 | }; |
219 | |
220 | static const struct power_supply_desc collie_bat_main_desc = { |
221 | .name = "main-battery" , |
222 | .type = POWER_SUPPLY_TYPE_BATTERY, |
223 | .properties = collie_bat_main_props, |
224 | .num_properties = ARRAY_SIZE(collie_bat_main_props), |
225 | .get_property = collie_bat_get_property, |
226 | .external_power_changed = collie_bat_external_power_changed, |
227 | .use_for_apm = 1, |
228 | }; |
229 | |
230 | static struct collie_bat collie_bat_main = { |
231 | .status = POWER_SUPPLY_STATUS_DISCHARGING, |
232 | .full_chrg = -1, |
233 | .psy = NULL, |
234 | |
235 | .gpio_full = NULL, |
236 | .gpio_charge_on = NULL, |
237 | |
238 | .technology = POWER_SUPPLY_TECHNOLOGY_LIPO, |
239 | |
240 | .gpio_bat = NULL, |
241 | .adc_bat = UCB_ADC_INP_AD1, |
242 | .adc_bat_divider = 155, |
243 | .bat_max = 4310000, |
244 | .bat_min = 1551 * 1000000 / 414, |
245 | |
246 | .gpio_temp = NULL, |
247 | .adc_temp = UCB_ADC_INP_AD0, |
248 | .adc_temp_divider = 10000, |
249 | }; |
250 | |
251 | static const struct power_supply_desc collie_bat_bu_desc = { |
252 | .name = "backup-battery" , |
253 | .type = POWER_SUPPLY_TYPE_BATTERY, |
254 | .properties = collie_bat_bu_props, |
255 | .num_properties = ARRAY_SIZE(collie_bat_bu_props), |
256 | .get_property = collie_bat_get_property, |
257 | .external_power_changed = collie_bat_external_power_changed, |
258 | }; |
259 | |
260 | static struct collie_bat collie_bat_bu = { |
261 | .status = POWER_SUPPLY_STATUS_UNKNOWN, |
262 | .full_chrg = -1, |
263 | .psy = NULL, |
264 | |
265 | .gpio_full = NULL, |
266 | .gpio_charge_on = NULL, |
267 | |
268 | .technology = POWER_SUPPLY_TECHNOLOGY_LiMn, |
269 | |
270 | .gpio_bat = NULL, |
271 | .adc_bat = UCB_ADC_INP_AD1, |
272 | .adc_bat_divider = 155, |
273 | .bat_max = 3000000, |
274 | .bat_min = 1900000, |
275 | |
276 | .gpio_temp = NULL, |
277 | .adc_temp = -1, |
278 | .adc_temp_divider = -1, |
279 | }; |
280 | |
281 | /* Obtained but unused GPIO */ |
282 | static struct gpio_desc *collie_mbat_low; |
283 | |
284 | #ifdef CONFIG_PM |
285 | static int wakeup_enabled; |
286 | |
287 | static int collie_bat_suspend(struct ucb1x00_dev *dev) |
288 | { |
289 | /* flush all pending status updates */ |
290 | flush_work(work: &bat_work); |
291 | |
292 | if (device_may_wakeup(dev: &dev->ucb->dev) && |
293 | collie_bat_main.status == POWER_SUPPLY_STATUS_CHARGING) |
294 | wakeup_enabled = !enable_irq_wake(irq: gpiod_to_irq(desc: collie_bat_main.gpio_full)); |
295 | else |
296 | wakeup_enabled = 0; |
297 | |
298 | return 0; |
299 | } |
300 | |
301 | static int collie_bat_resume(struct ucb1x00_dev *dev) |
302 | { |
303 | if (wakeup_enabled) |
304 | disable_irq_wake(irq: gpiod_to_irq(desc: collie_bat_main.gpio_full)); |
305 | |
306 | /* things may have changed while we were away */ |
307 | schedule_work(work: &bat_work); |
308 | return 0; |
309 | } |
310 | #else |
311 | #define collie_bat_suspend NULL |
312 | #define collie_bat_resume NULL |
313 | #endif |
314 | |
315 | static int collie_bat_probe(struct ucb1x00_dev *dev) |
316 | { |
317 | int ret; |
318 | struct power_supply_config psy_main_cfg = {}, psy_bu_cfg = {}; |
319 | struct gpio_chip *gc = &dev->ucb->gpio; |
320 | |
321 | if (!machine_is_collie()) |
322 | return -ENODEV; |
323 | |
324 | ucb = dev->ucb; |
325 | |
326 | /* Obtain all the main battery GPIOs */ |
327 | collie_bat_main.gpio_full = gpiod_get(dev: &dev->ucb->dev, |
328 | con_id: "main battery full" , |
329 | flags: GPIOD_IN); |
330 | if (IS_ERR(ptr: collie_bat_main.gpio_full)) |
331 | return PTR_ERR(ptr: collie_bat_main.gpio_full); |
332 | |
333 | collie_mbat_low = gpiod_get(dev: &dev->ucb->dev, |
334 | con_id: "main battery low" , |
335 | flags: GPIOD_IN); |
336 | if (IS_ERR(ptr: collie_mbat_low)) { |
337 | ret = PTR_ERR(ptr: collie_mbat_low); |
338 | goto err_put_gpio_full; |
339 | } |
340 | |
341 | collie_bat_main.gpio_charge_on = gpiod_get(dev: &dev->ucb->dev, |
342 | con_id: "main charge on" , |
343 | flags: GPIOD_OUT_LOW); |
344 | if (IS_ERR(ptr: collie_bat_main.gpio_charge_on)) { |
345 | ret = PTR_ERR(ptr: collie_bat_main.gpio_charge_on); |
346 | goto err_put_mbat_low; |
347 | } |
348 | |
349 | /* COLLIE_GPIO_MBAT_ON = GPIO 7 on the UCB (TC35143) */ |
350 | collie_bat_main.gpio_bat = gpiochip_request_own_desc(gc, |
351 | hwnum: 7, |
352 | label: "main battery" , |
353 | lflags: GPIO_ACTIVE_HIGH, |
354 | dflags: GPIOD_OUT_LOW); |
355 | if (IS_ERR(ptr: collie_bat_main.gpio_bat)) { |
356 | ret = PTR_ERR(ptr: collie_bat_main.gpio_bat); |
357 | goto err_put_gpio_charge_on; |
358 | } |
359 | |
360 | /* COLLIE_GPIO_TMP_ON = GPIO 9 on the UCB (TC35143) */ |
361 | collie_bat_main.gpio_temp = gpiochip_request_own_desc(gc, |
362 | hwnum: 9, |
363 | label: "main battery temp" , |
364 | lflags: GPIO_ACTIVE_HIGH, |
365 | dflags: GPIOD_OUT_LOW); |
366 | if (IS_ERR(ptr: collie_bat_main.gpio_temp)) { |
367 | ret = PTR_ERR(ptr: collie_bat_main.gpio_temp); |
368 | goto err_free_gpio_bat; |
369 | } |
370 | |
371 | /* |
372 | * Obtain the backup battery COLLIE_GPIO_BBAT_ON which is |
373 | * GPIO 8 on the UCB (TC35143) |
374 | */ |
375 | collie_bat_bu.gpio_bat = gpiochip_request_own_desc(gc, |
376 | hwnum: 8, |
377 | label: "backup battery" , |
378 | lflags: GPIO_ACTIVE_HIGH, |
379 | dflags: GPIOD_OUT_LOW); |
380 | if (IS_ERR(ptr: collie_bat_bu.gpio_bat)) { |
381 | ret = PTR_ERR(ptr: collie_bat_bu.gpio_bat); |
382 | goto err_free_gpio_temp; |
383 | } |
384 | |
385 | mutex_init(&collie_bat_main.work_lock); |
386 | |
387 | INIT_WORK(&bat_work, collie_bat_work); |
388 | |
389 | psy_main_cfg.drv_data = &collie_bat_main; |
390 | collie_bat_main.psy = power_supply_register(parent: &dev->ucb->dev, |
391 | desc: &collie_bat_main_desc, |
392 | cfg: &psy_main_cfg); |
393 | if (IS_ERR(ptr: collie_bat_main.psy)) { |
394 | ret = PTR_ERR(ptr: collie_bat_main.psy); |
395 | goto err_psy_reg_main; |
396 | } |
397 | |
398 | psy_bu_cfg.drv_data = &collie_bat_bu; |
399 | collie_bat_bu.psy = power_supply_register(parent: &dev->ucb->dev, |
400 | desc: &collie_bat_bu_desc, |
401 | cfg: &psy_bu_cfg); |
402 | if (IS_ERR(ptr: collie_bat_bu.psy)) { |
403 | ret = PTR_ERR(ptr: collie_bat_bu.psy); |
404 | goto err_psy_reg_bu; |
405 | } |
406 | |
407 | ret = request_irq(irq: gpiod_to_irq(desc: collie_bat_main.gpio_full), |
408 | handler: collie_bat_gpio_isr, |
409 | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, |
410 | name: "main full" , dev: &collie_bat_main); |
411 | if (ret) |
412 | goto err_irq; |
413 | |
414 | device_init_wakeup(dev: &ucb->dev, enable: 1); |
415 | schedule_work(work: &bat_work); |
416 | |
417 | return 0; |
418 | |
419 | err_irq: |
420 | power_supply_unregister(psy: collie_bat_bu.psy); |
421 | err_psy_reg_bu: |
422 | power_supply_unregister(psy: collie_bat_main.psy); |
423 | err_psy_reg_main: |
424 | /* see comment in collie_bat_remove */ |
425 | cancel_work_sync(work: &bat_work); |
426 | gpiochip_free_own_desc(desc: collie_bat_bu.gpio_bat); |
427 | err_free_gpio_temp: |
428 | gpiochip_free_own_desc(desc: collie_bat_main.gpio_temp); |
429 | err_free_gpio_bat: |
430 | gpiochip_free_own_desc(desc: collie_bat_main.gpio_bat); |
431 | err_put_gpio_charge_on: |
432 | gpiod_put(desc: collie_bat_main.gpio_charge_on); |
433 | err_put_mbat_low: |
434 | gpiod_put(desc: collie_mbat_low); |
435 | err_put_gpio_full: |
436 | gpiod_put(desc: collie_bat_main.gpio_full); |
437 | |
438 | return ret; |
439 | } |
440 | |
441 | static void collie_bat_remove(struct ucb1x00_dev *dev) |
442 | { |
443 | free_irq(gpiod_to_irq(desc: collie_bat_main.gpio_full), &collie_bat_main); |
444 | power_supply_unregister(psy: collie_bat_bu.psy); |
445 | power_supply_unregister(psy: collie_bat_main.psy); |
446 | |
447 | /* These are obtained from the machine */ |
448 | gpiod_put(desc: collie_bat_main.gpio_full); |
449 | gpiod_put(desc: collie_mbat_low); |
450 | gpiod_put(desc: collie_bat_main.gpio_charge_on); |
451 | /* These are directly from the UCB so let's free them */ |
452 | gpiochip_free_own_desc(desc: collie_bat_main.gpio_bat); |
453 | gpiochip_free_own_desc(desc: collie_bat_main.gpio_temp); |
454 | gpiochip_free_own_desc(desc: collie_bat_bu.gpio_bat); |
455 | /* |
456 | * Now cancel the bat_work. We won't get any more schedules, |
457 | * since all sources (isr and external_power_changed) are |
458 | * unregistered now. |
459 | */ |
460 | cancel_work_sync(work: &bat_work); |
461 | } |
462 | |
463 | static struct ucb1x00_driver collie_bat_driver = { |
464 | .add = collie_bat_probe, |
465 | .remove = collie_bat_remove, |
466 | .suspend = collie_bat_suspend, |
467 | .resume = collie_bat_resume, |
468 | }; |
469 | |
470 | static int __init collie_bat_init(void) |
471 | { |
472 | return ucb1x00_register_driver(&collie_bat_driver); |
473 | } |
474 | |
475 | static void __exit collie_bat_exit(void) |
476 | { |
477 | ucb1x00_unregister_driver(&collie_bat_driver); |
478 | } |
479 | |
480 | module_init(collie_bat_init); |
481 | module_exit(collie_bat_exit); |
482 | |
483 | MODULE_LICENSE("GPL" ); |
484 | MODULE_AUTHOR("Thomas Kunze" ); |
485 | MODULE_DESCRIPTION("Collie battery driver" ); |
486 | |