diff options
author | Lars-Peter Clausen <lars@metafoo.de> | 2010-06-19 00:08:29 -0400 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2010-08-05 08:26:20 -0400 |
commit | f6a21388bd255773cc80d4423afb4c69d4daa173 (patch) | |
tree | 4d331d997e84ca319c935383040d6ac409d7ea14 /drivers/power | |
parent | 7f983ba93d449972d5f372f12c6ad32d86ef30b4 (diff) |
POWER: Add JZ4740 battery driver.
Add support for the battery voltage measurement part of the JZ4740 ADC unit.
Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Acked-by: Anton Vorontsov <cbouatmailru@gmail.com>
Cc: linux-mips@linux-mips.org
Cc: linux-kernel@vger.kernel.org
Patchwork: https://patchwork.linux-mips.org/patch/1416/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/Kconfig | 11 | ||||
-rw-r--r-- | drivers/power/Makefile | 1 | ||||
-rw-r--r-- | drivers/power/jz4740-battery.c | 445 |
3 files changed, 457 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 8e9ba177d817..1e5506be39b4 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig | |||
@@ -142,4 +142,15 @@ config CHARGER_PCF50633 | |||
142 | help | 142 | help |
143 | Say Y to include support for NXP PCF50633 Main Battery Charger. | 143 | Say Y to include support for NXP PCF50633 Main Battery Charger. |
144 | 144 | ||
145 | config BATTERY_JZ4740 | ||
146 | tristate "Ingenic JZ4740 battery" | ||
147 | depends on MACH_JZ4740 | ||
148 | depends on MFD_JZ4740_ADC | ||
149 | help | ||
150 | Say Y to enable support for the battery on Ingenic JZ4740 based | ||
151 | boards. | ||
152 | |||
153 | This driver can be build as a module. If so, the module will be | ||
154 | called jz4740-battery. | ||
155 | |||
145 | endif # POWER_SUPPLY | 156 | endif # POWER_SUPPLY |
diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 00050809a6c7..cf95009d9bcd 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile | |||
@@ -34,3 +34,4 @@ obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o | |||
34 | obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o | 34 | obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o |
35 | obj-$(CONFIG_BATTERY_Z2) += z2_battery.o | 35 | obj-$(CONFIG_BATTERY_Z2) += z2_battery.o |
36 | obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o | 36 | obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o |
37 | obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o | ||
diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c new file mode 100644 index 000000000000..20c4b952e9bd --- /dev/null +++ b/drivers/power/jz4740-battery.c | |||
@@ -0,0 +1,445 @@ | |||
1 | /* | ||
2 | * Battery measurement code for Ingenic JZ SOC. | ||
3 | * | ||
4 | * Copyright (C) 2009 Jiejing Zhang <kzjeef@gmail.com> | ||
5 | * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> | ||
6 | * | ||
7 | * based on tosa_battery.c | ||
8 | * | ||
9 | * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License version 2 as | ||
13 | * published by the Free Software Foundation. | ||
14 | * | ||
15 | */ | ||
16 | |||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/kernel.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/platform_device.h> | ||
21 | #include <linux/slab.h> | ||
22 | |||
23 | #include <linux/delay.h> | ||
24 | #include <linux/gpio.h> | ||
25 | #include <linux/mfd/core.h> | ||
26 | #include <linux/power_supply.h> | ||
27 | |||
28 | #include <linux/power/jz4740-battery.h> | ||
29 | #include <linux/jz4740-adc.h> | ||
30 | |||
31 | struct jz_battery { | ||
32 | struct jz_battery_platform_data *pdata; | ||
33 | struct platform_device *pdev; | ||
34 | |||
35 | struct resource *mem; | ||
36 | void __iomem *base; | ||
37 | |||
38 | int irq; | ||
39 | int charge_irq; | ||
40 | |||
41 | struct mfd_cell *cell; | ||
42 | |||
43 | int status; | ||
44 | long voltage; | ||
45 | |||
46 | struct completion read_completion; | ||
47 | |||
48 | struct power_supply battery; | ||
49 | struct delayed_work work; | ||
50 | }; | ||
51 | |||
52 | static inline struct jz_battery *psy_to_jz_battery(struct power_supply *psy) | ||
53 | { | ||
54 | return container_of(psy, struct jz_battery, battery); | ||
55 | } | ||
56 | |||
57 | static irqreturn_t jz_battery_irq_handler(int irq, void *devid) | ||
58 | { | ||
59 | struct jz_battery *battery = devid; | ||
60 | |||
61 | complete(&battery->read_completion); | ||
62 | return IRQ_HANDLED; | ||
63 | } | ||
64 | |||
65 | static long jz_battery_read_voltage(struct jz_battery *battery) | ||
66 | { | ||
67 | unsigned long t; | ||
68 | unsigned long val; | ||
69 | long voltage; | ||
70 | |||
71 | INIT_COMPLETION(battery->read_completion); | ||
72 | |||
73 | enable_irq(battery->irq); | ||
74 | battery->cell->enable(battery->pdev); | ||
75 | |||
76 | t = wait_for_completion_interruptible_timeout(&battery->read_completion, | ||
77 | HZ); | ||
78 | |||
79 | if (t > 0) { | ||
80 | val = readw(battery->base) & 0xfff; | ||
81 | |||
82 | if (battery->pdata->info.voltage_max_design <= 2500000) | ||
83 | val = (val * 78125UL) >> 7UL; | ||
84 | else | ||
85 | val = ((val * 924375UL) >> 9UL) + 33000; | ||
86 | voltage = (long)val; | ||
87 | } else { | ||
88 | voltage = t ? t : -ETIMEDOUT; | ||
89 | } | ||
90 | |||
91 | battery->cell->disable(battery->pdev); | ||
92 | disable_irq(battery->irq); | ||
93 | |||
94 | return voltage; | ||
95 | } | ||
96 | |||
97 | static int jz_battery_get_capacity(struct power_supply *psy) | ||
98 | { | ||
99 | struct jz_battery *jz_battery = psy_to_jz_battery(psy); | ||
100 | struct power_supply_info *info = &jz_battery->pdata->info; | ||
101 | long voltage; | ||
102 | int ret; | ||
103 | int voltage_span; | ||
104 | |||
105 | voltage = jz_battery_read_voltage(jz_battery); | ||
106 | |||
107 | if (voltage < 0) | ||
108 | return voltage; | ||
109 | |||
110 | voltage_span = info->voltage_max_design - info->voltage_min_design; | ||
111 | ret = ((voltage - info->voltage_min_design) * 100) / voltage_span; | ||
112 | |||
113 | if (ret > 100) | ||
114 | ret = 100; | ||
115 | else if (ret < 0) | ||
116 | ret = 0; | ||
117 | |||
118 | return ret; | ||
119 | } | ||
120 | |||
121 | static int jz_battery_get_property(struct power_supply *psy, | ||
122 | enum power_supply_property psp, union power_supply_propval *val) | ||
123 | { | ||
124 | struct jz_battery *jz_battery = psy_to_jz_battery(psy); | ||
125 | struct power_supply_info *info = &jz_battery->pdata->info; | ||
126 | long voltage; | ||
127 | |||
128 | switch (psp) { | ||
129 | case POWER_SUPPLY_PROP_STATUS: | ||
130 | val->intval = jz_battery->status; | ||
131 | break; | ||
132 | case POWER_SUPPLY_PROP_TECHNOLOGY: | ||
133 | val->intval = jz_battery->pdata->info.technology; | ||
134 | break; | ||
135 | case POWER_SUPPLY_PROP_HEALTH: | ||
136 | voltage = jz_battery_read_voltage(jz_battery); | ||
137 | if (voltage < info->voltage_min_design) | ||
138 | val->intval = POWER_SUPPLY_HEALTH_DEAD; | ||
139 | else | ||
140 | val->intval = POWER_SUPPLY_HEALTH_GOOD; | ||
141 | break; | ||
142 | case POWER_SUPPLY_PROP_CAPACITY: | ||
143 | val->intval = jz_battery_get_capacity(psy); | ||
144 | break; | ||
145 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | ||
146 | val->intval = jz_battery_read_voltage(jz_battery); | ||
147 | if (val->intval < 0) | ||
148 | return val->intval; | ||
149 | break; | ||
150 | case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: | ||
151 | val->intval = info->voltage_max_design; | ||
152 | break; | ||
153 | case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: | ||
154 | val->intval = info->voltage_min_design; | ||
155 | break; | ||
156 | case POWER_SUPPLY_PROP_PRESENT: | ||
157 | val->intval = 1; | ||
158 | break; | ||
159 | default: | ||
160 | return -EINVAL; | ||
161 | } | ||
162 | return 0; | ||
163 | } | ||
164 | |||
165 | static void jz_battery_external_power_changed(struct power_supply *psy) | ||
166 | { | ||
167 | struct jz_battery *jz_battery = psy_to_jz_battery(psy); | ||
168 | |||
169 | cancel_delayed_work(&jz_battery->work); | ||
170 | schedule_delayed_work(&jz_battery->work, 0); | ||
171 | } | ||
172 | |||
173 | static irqreturn_t jz_battery_charge_irq(int irq, void *data) | ||
174 | { | ||
175 | struct jz_battery *jz_battery = data; | ||
176 | |||
177 | cancel_delayed_work(&jz_battery->work); | ||
178 | schedule_delayed_work(&jz_battery->work, 0); | ||
179 | |||
180 | return IRQ_HANDLED; | ||
181 | } | ||
182 | |||
183 | static void jz_battery_update(struct jz_battery *jz_battery) | ||
184 | { | ||
185 | int status; | ||
186 | long voltage; | ||
187 | bool has_changed = false; | ||
188 | int is_charging; | ||
189 | |||
190 | if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { | ||
191 | is_charging = gpio_get_value(jz_battery->pdata->gpio_charge); | ||
192 | is_charging ^= jz_battery->pdata->gpio_charge_active_low; | ||
193 | if (is_charging) | ||
194 | status = POWER_SUPPLY_STATUS_CHARGING; | ||
195 | else | ||
196 | status = POWER_SUPPLY_STATUS_NOT_CHARGING; | ||
197 | |||
198 | if (status != jz_battery->status) { | ||
199 | jz_battery->status = status; | ||
200 | has_changed = true; | ||
201 | } | ||
202 | } | ||
203 | |||
204 | voltage = jz_battery_read_voltage(jz_battery); | ||
205 | if (abs(voltage - jz_battery->voltage) < 50000) { | ||
206 | jz_battery->voltage = voltage; | ||
207 | has_changed = true; | ||
208 | } | ||
209 | |||
210 | if (has_changed) | ||
211 | power_supply_changed(&jz_battery->battery); | ||
212 | } | ||
213 | |||
214 | static enum power_supply_property jz_battery_properties[] = { | ||
215 | POWER_SUPPLY_PROP_STATUS, | ||
216 | POWER_SUPPLY_PROP_TECHNOLOGY, | ||
217 | POWER_SUPPLY_PROP_HEALTH, | ||
218 | POWER_SUPPLY_PROP_CAPACITY, | ||
219 | POWER_SUPPLY_PROP_VOLTAGE_NOW, | ||
220 | POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, | ||
221 | POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, | ||
222 | POWER_SUPPLY_PROP_PRESENT, | ||
223 | }; | ||
224 | |||
225 | static void jz_battery_work(struct work_struct *work) | ||
226 | { | ||
227 | /* Too small interval will increase system workload */ | ||
228 | const int interval = HZ * 30; | ||
229 | struct jz_battery *jz_battery = container_of(work, struct jz_battery, | ||
230 | work.work); | ||
231 | |||
232 | jz_battery_update(jz_battery); | ||
233 | schedule_delayed_work(&jz_battery->work, interval); | ||
234 | } | ||
235 | |||
236 | static int __devinit jz_battery_probe(struct platform_device *pdev) | ||
237 | { | ||
238 | int ret = 0; | ||
239 | struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data; | ||
240 | struct jz_battery *jz_battery; | ||
241 | struct power_supply *battery; | ||
242 | |||
243 | jz_battery = kzalloc(sizeof(*jz_battery), GFP_KERNEL); | ||
244 | if (!jz_battery) { | ||
245 | dev_err(&pdev->dev, "Failed to allocate driver structure\n"); | ||
246 | return -ENOMEM; | ||
247 | } | ||
248 | |||
249 | jz_battery->cell = pdev->dev.platform_data; | ||
250 | |||
251 | jz_battery->irq = platform_get_irq(pdev, 0); | ||
252 | if (jz_battery->irq < 0) { | ||
253 | ret = jz_battery->irq; | ||
254 | dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); | ||
255 | goto err_free; | ||
256 | } | ||
257 | |||
258 | jz_battery->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
259 | if (!jz_battery->mem) { | ||
260 | ret = -ENOENT; | ||
261 | dev_err(&pdev->dev, "Failed to get platform mmio resource\n"); | ||
262 | goto err_free; | ||
263 | } | ||
264 | |||
265 | jz_battery->mem = request_mem_region(jz_battery->mem->start, | ||
266 | resource_size(jz_battery->mem), pdev->name); | ||
267 | if (!jz_battery->mem) { | ||
268 | ret = -EBUSY; | ||
269 | dev_err(&pdev->dev, "Failed to request mmio memory region\n"); | ||
270 | goto err_free; | ||
271 | } | ||
272 | |||
273 | jz_battery->base = ioremap_nocache(jz_battery->mem->start, | ||
274 | resource_size(jz_battery->mem)); | ||
275 | if (!jz_battery->base) { | ||
276 | ret = -EBUSY; | ||
277 | dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); | ||
278 | goto err_release_mem_region; | ||
279 | } | ||
280 | |||
281 | battery = &jz_battery->battery; | ||
282 | battery->name = pdata->info.name; | ||
283 | battery->type = POWER_SUPPLY_TYPE_BATTERY; | ||
284 | battery->properties = jz_battery_properties; | ||
285 | battery->num_properties = ARRAY_SIZE(jz_battery_properties); | ||
286 | battery->get_property = jz_battery_get_property; | ||
287 | battery->external_power_changed = jz_battery_external_power_changed; | ||
288 | battery->use_for_apm = 1; | ||
289 | |||
290 | jz_battery->pdata = pdata; | ||
291 | jz_battery->pdev = pdev; | ||
292 | |||
293 | init_completion(&jz_battery->read_completion); | ||
294 | |||
295 | INIT_DELAYED_WORK(&jz_battery->work, jz_battery_work); | ||
296 | |||
297 | ret = request_irq(jz_battery->irq, jz_battery_irq_handler, 0, pdev->name, | ||
298 | jz_battery); | ||
299 | if (ret) { | ||
300 | dev_err(&pdev->dev, "Failed to request irq %d\n", ret); | ||
301 | goto err_iounmap; | ||
302 | } | ||
303 | disable_irq(jz_battery->irq); | ||
304 | |||
305 | if (gpio_is_valid(pdata->gpio_charge)) { | ||
306 | ret = gpio_request(pdata->gpio_charge, dev_name(&pdev->dev)); | ||
307 | if (ret) { | ||
308 | dev_err(&pdev->dev, "charger state gpio request failed.\n"); | ||
309 | goto err_free_irq; | ||
310 | } | ||
311 | ret = gpio_direction_input(pdata->gpio_charge); | ||
312 | if (ret) { | ||
313 | dev_err(&pdev->dev, "charger state gpio set direction failed.\n"); | ||
314 | goto err_free_gpio; | ||
315 | } | ||
316 | |||
317 | jz_battery->charge_irq = gpio_to_irq(pdata->gpio_charge); | ||
318 | |||
319 | if (jz_battery->charge_irq >= 0) { | ||
320 | ret = request_irq(jz_battery->charge_irq, | ||
321 | jz_battery_charge_irq, | ||
322 | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, | ||
323 | dev_name(&pdev->dev), jz_battery); | ||
324 | if (ret) { | ||
325 | dev_err(&pdev->dev, "Failed to request charge irq: %d\n", ret); | ||
326 | goto err_free_gpio; | ||
327 | } | ||
328 | } | ||
329 | } else { | ||
330 | jz_battery->charge_irq = -1; | ||
331 | } | ||
332 | |||
333 | if (jz_battery->pdata->info.voltage_max_design <= 2500000) | ||
334 | jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, | ||
335 | JZ_ADC_CONFIG_BAT_MB); | ||
336 | else | ||
337 | jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, 0); | ||
338 | |||
339 | ret = power_supply_register(&pdev->dev, &jz_battery->battery); | ||
340 | if (ret) { | ||
341 | dev_err(&pdev->dev, "power supply battery register failed.\n"); | ||
342 | goto err_free_charge_irq; | ||
343 | } | ||
344 | |||
345 | platform_set_drvdata(pdev, jz_battery); | ||
346 | schedule_delayed_work(&jz_battery->work, 0); | ||
347 | |||
348 | return 0; | ||
349 | |||
350 | err_free_charge_irq: | ||
351 | if (jz_battery->charge_irq >= 0) | ||
352 | free_irq(jz_battery->charge_irq, jz_battery); | ||
353 | err_free_gpio: | ||
354 | if (gpio_is_valid(pdata->gpio_charge)) | ||
355 | gpio_free(jz_battery->pdata->gpio_charge); | ||
356 | err_free_irq: | ||
357 | free_irq(jz_battery->irq, jz_battery); | ||
358 | err_iounmap: | ||
359 | platform_set_drvdata(pdev, NULL); | ||
360 | iounmap(jz_battery->base); | ||
361 | err_release_mem_region: | ||
362 | release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem)); | ||
363 | err_free: | ||
364 | kfree(jz_battery); | ||
365 | return ret; | ||
366 | } | ||
367 | |||
368 | static int __devexit jz_battery_remove(struct platform_device *pdev) | ||
369 | { | ||
370 | struct jz_battery *jz_battery = platform_get_drvdata(pdev); | ||
371 | |||
372 | cancel_delayed_work_sync(&jz_battery->work); | ||
373 | |||
374 | if (gpio_is_valid(jz_battery->pdata->gpio_charge)) { | ||
375 | if (jz_battery->charge_irq >= 0) | ||
376 | free_irq(jz_battery->charge_irq, jz_battery); | ||
377 | gpio_free(jz_battery->pdata->gpio_charge); | ||
378 | } | ||
379 | |||
380 | power_supply_unregister(&jz_battery->battery); | ||
381 | |||
382 | free_irq(jz_battery->irq, jz_battery); | ||
383 | |||
384 | iounmap(jz_battery->base); | ||
385 | release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem)); | ||
386 | |||
387 | return 0; | ||
388 | } | ||
389 | |||
390 | #ifdef CONFIG_PM | ||
391 | static int jz_battery_suspend(struct device *dev) | ||
392 | { | ||
393 | struct jz_battery *jz_battery = dev_get_drvdata(dev); | ||
394 | |||
395 | cancel_delayed_work_sync(&jz_battery->work); | ||
396 | jz_battery->status = POWER_SUPPLY_STATUS_UNKNOWN; | ||
397 | |||
398 | return 0; | ||
399 | } | ||
400 | |||
401 | static int jz_battery_resume(struct device *dev) | ||
402 | { | ||
403 | struct jz_battery *jz_battery = dev_get_drvdata(dev); | ||
404 | |||
405 | schedule_delayed_work(&jz_battery->work, 0); | ||
406 | |||
407 | return 0; | ||
408 | } | ||
409 | |||
410 | static const struct dev_pm_ops jz_battery_pm_ops = { | ||
411 | .suspend = jz_battery_suspend, | ||
412 | .resume = jz_battery_resume, | ||
413 | }; | ||
414 | |||
415 | #define JZ_BATTERY_PM_OPS (&jz_battery_pm_ops) | ||
416 | #else | ||
417 | #define JZ_BATTERY_PM_OPS NULL | ||
418 | #endif | ||
419 | |||
420 | static struct platform_driver jz_battery_driver = { | ||
421 | .probe = jz_battery_probe, | ||
422 | .remove = __devexit_p(jz_battery_remove), | ||
423 | .driver = { | ||
424 | .name = "jz4740-battery", | ||
425 | .owner = THIS_MODULE, | ||
426 | .pm = JZ_BATTERY_PM_OPS, | ||
427 | }, | ||
428 | }; | ||
429 | |||
430 | static int __init jz_battery_init(void) | ||
431 | { | ||
432 | return platform_driver_register(&jz_battery_driver); | ||
433 | } | ||
434 | module_init(jz_battery_init); | ||
435 | |||
436 | static void __exit jz_battery_exit(void) | ||
437 | { | ||
438 | platform_driver_unregister(&jz_battery_driver); | ||
439 | } | ||
440 | module_exit(jz_battery_exit); | ||
441 | |||
442 | MODULE_ALIAS("platform:jz4740-battery"); | ||
443 | MODULE_LICENSE("GPL"); | ||
444 | MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); | ||
445 | MODULE_DESCRIPTION("JZ4740 SoC battery driver"); | ||