Commit | Line | Data |
---|---|---|
4e9687d9 MV |
1 | /* |
2 | * linux/drivers/power/wm97xx_battery.c | |
3 | * | |
4 | * Battery measurement code for WM97xx | |
5 | * | |
6 | * based on tosa_battery.c | |
7 | * | |
8 | * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com> | |
9 | * | |
10 | * This program is free software; you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License version 2 as | |
12 | * published by the Free Software Foundation. | |
13 | * | |
14 | */ | |
15 | ||
16 | #include <linux/init.h> | |
17 | #include <linux/kernel.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/platform_device.h> | |
20 | #include <linux/power_supply.h> | |
21 | #include <linux/wm97xx.h> | |
22 | #include <linux/spinlock.h> | |
23 | #include <linux/interrupt.h> | |
24 | #include <linux/gpio.h> | |
7c87942a | 25 | #include <linux/irq.h> |
5a0e3ad6 | 26 | #include <linux/slab.h> |
4e9687d9 MV |
27 | |
28 | static DEFINE_MUTEX(bat_lock); | |
29 | static struct work_struct bat_work; | |
f8d94eb7 | 30 | static struct mutex work_lock; |
4e9687d9 | 31 | static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN; |
b8bdc1d0 | 32 | static struct wm97xx_batt_info *gpdata; |
4e9687d9 MV |
33 | static enum power_supply_property *prop; |
34 | ||
35 | static unsigned long wm97xx_read_bat(struct power_supply *bat_ps) | |
36 | { | |
b8bdc1d0 MV |
37 | struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; |
38 | struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; | |
39 | ||
38c7dc37 | 40 | return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent), |
4e9687d9 MV |
41 | pdata->batt_aux) * pdata->batt_mult / |
42 | pdata->batt_div; | |
43 | } | |
44 | ||
45 | static unsigned long wm97xx_read_temp(struct power_supply *bat_ps) | |
46 | { | |
b8bdc1d0 MV |
47 | struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; |
48 | struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; | |
49 | ||
38c7dc37 | 50 | return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev->parent), |
4e9687d9 MV |
51 | pdata->temp_aux) * pdata->temp_mult / |
52 | pdata->temp_div; | |
53 | } | |
54 | ||
55 | static int wm97xx_bat_get_property(struct power_supply *bat_ps, | |
56 | enum power_supply_property psp, | |
57 | union power_supply_propval *val) | |
58 | { | |
b8bdc1d0 MV |
59 | struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; |
60 | struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; | |
61 | ||
4e9687d9 MV |
62 | switch (psp) { |
63 | case POWER_SUPPLY_PROP_STATUS: | |
64 | val->intval = bat_status; | |
65 | break; | |
66 | case POWER_SUPPLY_PROP_TECHNOLOGY: | |
67 | val->intval = pdata->batt_tech; | |
68 | break; | |
69 | case POWER_SUPPLY_PROP_VOLTAGE_NOW: | |
70 | if (pdata->batt_aux >= 0) | |
71 | val->intval = wm97xx_read_bat(bat_ps); | |
72 | else | |
73 | return -EINVAL; | |
74 | break; | |
75 | case POWER_SUPPLY_PROP_TEMP: | |
76 | if (pdata->temp_aux >= 0) | |
77 | val->intval = wm97xx_read_temp(bat_ps); | |
78 | else | |
79 | return -EINVAL; | |
80 | break; | |
81 | case POWER_SUPPLY_PROP_VOLTAGE_MAX: | |
82 | if (pdata->max_voltage >= 0) | |
83 | val->intval = pdata->max_voltage; | |
84 | else | |
85 | return -EINVAL; | |
86 | break; | |
87 | case POWER_SUPPLY_PROP_VOLTAGE_MIN: | |
88 | if (pdata->min_voltage >= 0) | |
89 | val->intval = pdata->min_voltage; | |
90 | else | |
91 | return -EINVAL; | |
92 | break; | |
93 | case POWER_SUPPLY_PROP_PRESENT: | |
94 | val->intval = 1; | |
95 | break; | |
96 | default: | |
97 | return -EINVAL; | |
98 | } | |
99 | return 0; | |
100 | } | |
101 | ||
102 | static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps) | |
103 | { | |
104 | schedule_work(&bat_work); | |
105 | } | |
106 | ||
107 | static void wm97xx_bat_update(struct power_supply *bat_ps) | |
108 | { | |
109 | int old_status = bat_status; | |
b8bdc1d0 MV |
110 | struct wm97xx_pdata *wmdata = bat_ps->dev->parent->platform_data; |
111 | struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; | |
4e9687d9 MV |
112 | |
113 | mutex_lock(&work_lock); | |
114 | ||
115 | bat_status = (pdata->charge_gpio >= 0) ? | |
116 | (gpio_get_value(pdata->charge_gpio) ? | |
117 | POWER_SUPPLY_STATUS_DISCHARGING : | |
118 | POWER_SUPPLY_STATUS_CHARGING) : | |
119 | POWER_SUPPLY_STATUS_UNKNOWN; | |
120 | ||
121 | if (old_status != bat_status) { | |
122 | pr_debug("%s: %i -> %i\n", bat_ps->name, old_status, | |
123 | bat_status); | |
124 | power_supply_changed(bat_ps); | |
125 | } | |
126 | ||
127 | mutex_unlock(&work_lock); | |
128 | } | |
129 | ||
130 | static struct power_supply bat_ps = { | |
131 | .type = POWER_SUPPLY_TYPE_BATTERY, | |
132 | .get_property = wm97xx_bat_get_property, | |
133 | .external_power_changed = wm97xx_bat_external_power_changed, | |
134 | .use_for_apm = 1, | |
135 | }; | |
136 | ||
137 | static void wm97xx_bat_work(struct work_struct *work) | |
138 | { | |
139 | wm97xx_bat_update(&bat_ps); | |
140 | } | |
141 | ||
7c87942a MV |
142 | static irqreturn_t wm97xx_chrg_irq(int irq, void *data) |
143 | { | |
144 | schedule_work(&bat_work); | |
145 | return IRQ_HANDLED; | |
146 | } | |
147 | ||
4e9687d9 | 148 | #ifdef CONFIG_PM |
83a8af0d | 149 | static int wm97xx_bat_suspend(struct device *dev) |
4e9687d9 MV |
150 | { |
151 | flush_scheduled_work(); | |
152 | return 0; | |
153 | } | |
154 | ||
83a8af0d | 155 | static int wm97xx_bat_resume(struct device *dev) |
4e9687d9 MV |
156 | { |
157 | schedule_work(&bat_work); | |
158 | return 0; | |
159 | } | |
83a8af0d | 160 | |
47145210 | 161 | static const struct dev_pm_ops wm97xx_bat_pm_ops = { |
83a8af0d MV |
162 | .suspend = wm97xx_bat_suspend, |
163 | .resume = wm97xx_bat_resume, | |
164 | }; | |
4e9687d9 MV |
165 | #endif |
166 | ||
167 | static int __devinit wm97xx_bat_probe(struct platform_device *dev) | |
168 | { | |
169 | int ret = 0; | |
170 | int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ | |
171 | int i = 0; | |
b8bdc1d0 MV |
172 | struct wm97xx_pdata *wmdata = dev->dev.platform_data; |
173 | struct wm97xx_batt_pdata *pdata; | |
174 | ||
175 | if (gpdata) { | |
176 | dev_err(&dev->dev, "Do not pass platform_data through " | |
177 | "wm97xx_bat_set_pdata!\n"); | |
178 | return -EINVAL; | |
12b336a8 MB |
179 | } |
180 | ||
181 | if (!wmdata) { | |
182 | dev_err(&dev->dev, "No platform data supplied\n"); | |
183 | return -EINVAL; | |
184 | } | |
185 | ||
186 | pdata = wmdata->batt_pdata; | |
4e9687d9 MV |
187 | |
188 | if (dev->id != -1) | |
189 | return -EINVAL; | |
190 | ||
191 | mutex_init(&work_lock); | |
192 | ||
193 | if (!pdata) { | |
b8bdc1d0 | 194 | dev_err(&dev->dev, "No platform_data supplied\n"); |
4e9687d9 MV |
195 | return -EINVAL; |
196 | } | |
197 | ||
7c87942a | 198 | if (gpio_is_valid(pdata->charge_gpio)) { |
4e9687d9 MV |
199 | ret = gpio_request(pdata->charge_gpio, "BATT CHRG"); |
200 | if (ret) | |
201 | goto err; | |
202 | ret = gpio_direction_input(pdata->charge_gpio); | |
7c87942a MV |
203 | if (ret) |
204 | goto err2; | |
205 | ret = request_irq(gpio_to_irq(pdata->charge_gpio), | |
206 | wm97xx_chrg_irq, IRQF_DISABLED, | |
f8d94eb7 | 207 | "AC Detect", dev); |
4e9687d9 MV |
208 | if (ret) |
209 | goto err2; | |
210 | props++; /* POWER_SUPPLY_PROP_STATUS */ | |
211 | } | |
212 | ||
213 | if (pdata->batt_tech >= 0) | |
214 | props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */ | |
215 | if (pdata->temp_aux >= 0) | |
216 | props++; /* POWER_SUPPLY_PROP_TEMP */ | |
217 | if (pdata->batt_aux >= 0) | |
218 | props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */ | |
219 | if (pdata->max_voltage >= 0) | |
220 | props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */ | |
221 | if (pdata->min_voltage >= 0) | |
222 | props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ | |
223 | ||
224 | prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); | |
225 | if (!prop) | |
7c87942a | 226 | goto err3; |
4e9687d9 MV |
227 | |
228 | prop[i++] = POWER_SUPPLY_PROP_PRESENT; | |
229 | if (pdata->charge_gpio >= 0) | |
230 | prop[i++] = POWER_SUPPLY_PROP_STATUS; | |
231 | if (pdata->batt_tech >= 0) | |
232 | prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY; | |
233 | if (pdata->temp_aux >= 0) | |
234 | prop[i++] = POWER_SUPPLY_PROP_TEMP; | |
235 | if (pdata->batt_aux >= 0) | |
236 | prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW; | |
237 | if (pdata->max_voltage >= 0) | |
238 | prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX; | |
239 | if (pdata->min_voltage >= 0) | |
240 | prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN; | |
241 | ||
242 | INIT_WORK(&bat_work, wm97xx_bat_work); | |
243 | ||
244 | if (!pdata->batt_name) { | |
245 | dev_info(&dev->dev, "Please consider setting proper battery " | |
246 | "name in platform definition file, falling " | |
247 | "back to name \"wm97xx-batt\"\n"); | |
248 | bat_ps.name = "wm97xx-batt"; | |
249 | } else | |
250 | bat_ps.name = pdata->batt_name; | |
251 | ||
252 | bat_ps.properties = prop; | |
253 | bat_ps.num_properties = props; | |
254 | ||
255 | ret = power_supply_register(&dev->dev, &bat_ps); | |
256 | if (!ret) | |
257 | schedule_work(&bat_work); | |
258 | else | |
7c87942a | 259 | goto err4; |
4e9687d9 MV |
260 | |
261 | return 0; | |
7c87942a | 262 | err4: |
4e9687d9 | 263 | kfree(prop); |
7c87942a MV |
264 | err3: |
265 | if (gpio_is_valid(pdata->charge_gpio)) | |
266 | free_irq(gpio_to_irq(pdata->charge_gpio), dev); | |
4e9687d9 | 267 | err2: |
7c87942a MV |
268 | if (gpio_is_valid(pdata->charge_gpio)) |
269 | gpio_free(pdata->charge_gpio); | |
4e9687d9 MV |
270 | err: |
271 | return ret; | |
272 | } | |
273 | ||
274 | static int __devexit wm97xx_bat_remove(struct platform_device *dev) | |
275 | { | |
b8bdc1d0 MV |
276 | struct wm97xx_pdata *wmdata = dev->dev.platform_data; |
277 | struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata; | |
278 | ||
7c87942a MV |
279 | if (pdata && gpio_is_valid(pdata->charge_gpio)) { |
280 | free_irq(gpio_to_irq(pdata->charge_gpio), dev); | |
4e9687d9 | 281 | gpio_free(pdata->charge_gpio); |
7c87942a | 282 | } |
4e9687d9 MV |
283 | flush_scheduled_work(); |
284 | power_supply_unregister(&bat_ps); | |
285 | kfree(prop); | |
286 | return 0; | |
287 | } | |
288 | ||
289 | static struct platform_driver wm97xx_bat_driver = { | |
290 | .driver = { | |
291 | .name = "wm97xx-battery", | |
292 | .owner = THIS_MODULE, | |
83a8af0d MV |
293 | #ifdef CONFIG_PM |
294 | .pm = &wm97xx_bat_pm_ops, | |
295 | #endif | |
4e9687d9 MV |
296 | }, |
297 | .probe = wm97xx_bat_probe, | |
298 | .remove = __devexit_p(wm97xx_bat_remove), | |
4e9687d9 MV |
299 | }; |
300 | ||
301 | static int __init wm97xx_bat_init(void) | |
302 | { | |
303 | return platform_driver_register(&wm97xx_bat_driver); | |
304 | } | |
305 | ||
306 | static void __exit wm97xx_bat_exit(void) | |
307 | { | |
308 | platform_driver_unregister(&wm97xx_bat_driver); | |
309 | } | |
310 | ||
b8bdc1d0 | 311 | void wm97xx_bat_set_pdata(struct wm97xx_batt_info *data) |
4e9687d9 | 312 | { |
b8bdc1d0 | 313 | gpdata = data; |
4e9687d9 MV |
314 | } |
315 | EXPORT_SYMBOL_GPL(wm97xx_bat_set_pdata); | |
316 | ||
317 | module_init(wm97xx_bat_init); | |
318 | module_exit(wm97xx_bat_exit); | |
319 | ||
320 | MODULE_LICENSE("GPL"); | |
321 | MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>"); | |
322 | MODULE_DESCRIPTION("WM97xx battery driver"); |