aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorVasily Khoruzhick <anarsoul@gmail.com>2010-07-17 06:57:03 -0400
committerAnton Vorontsov <cbouatmailru@gmail.com>2010-07-23 09:08:41 -0400
commit808be4b22f47886d2279852ada3d186fc20909bc (patch)
treefb6d2c1525d26c02f098eeedc523d7d55554e522 /drivers
parent6721081b6b911a067fe6ca3d6f5534c4a11a9e59 (diff)
Add s3c-adc-battery driver
s3c-adc-battery is driver for monitoring and charging battery on iPAQ H1930/H1940/RX1950. It depends on s3c-adc driver to get battery voltage and current. Signed-off-by: Vasily Khoruzhick <anarsoul@gmail.com> Signed-off-by: Anton Vorontsov <cbouatmailru@gmail.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/power/Kconfig6
-rw-r--r--drivers/power/Makefile1
-rw-r--r--drivers/power/s3c_adc_battery.c431
3 files changed, 438 insertions, 0 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 6eae8211197f..d1d3c7006eac 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -136,6 +136,12 @@ config BATTERY_Z2
136 help 136 help
137 Say Y to include support for the battery on the Zipit Z2. 137 Say Y to include support for the battery on the Zipit Z2.
138 138
139config BATTERY_S3C_ADC
140 tristate "Battery driver for Samsung ADC based monitoring"
141 depends on S3C_ADC
142 help
143 Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery
144
139config CHARGER_PCF50633 145config CHARGER_PCF50633
140 tristate "NXP PCF50633 MBC" 146 tristate "NXP PCF50633 MBC"
141 depends on MFD_PCF50633 147 depends on MFD_PCF50633
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 2b9a81d41b4d..705675ae9f7c 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -33,5 +33,6 @@ obj-$(CONFIG_BATTERY_BQ27x00) += bq27x00_battery.o
33obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o 33obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
34obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o 34obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
35obj-$(CONFIG_BATTERY_Z2) += z2_battery.o 35obj-$(CONFIG_BATTERY_Z2) += z2_battery.o
36obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o
36obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o 37obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
37obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o 38obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o
diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c
new file mode 100644
index 000000000000..fe16b482e912
--- /dev/null
+++ b/drivers/power/s3c_adc_battery.c
@@ -0,0 +1,431 @@
1/*
2 * iPAQ h1930/h1940/rx1950 battery controler driver
3 * Copyright (c) Vasily Khoruzhick
4 * Based on h1940_battery.c by Arnaud Patard
5 *
6 * This file is subject to the terms and conditions of the GNU General Public
7 * License. See the file COPYING in the main directory of this archive for
8 * more details.
9 *
10 */
11
12#include <linux/interrupt.h>
13#include <linux/platform_device.h>
14#include <linux/power_supply.h>
15#include <linux/leds.h>
16#include <linux/gpio.h>
17#include <linux/err.h>
18#include <linux/timer.h>
19#include <linux/jiffies.h>
20#include <linux/s3c_adc_battery.h>
21#include <linux/errno.h>
22#include <linux/init.h>
23
24#include <plat/adc.h>
25
26#define BAT_POLL_INTERVAL 10000 /* ms */
27#define JITTER_DELAY 500 /* ms */
28
29struct s3c_adc_bat {
30 struct power_supply psy;
31 struct s3c_adc_client *client;
32 struct s3c_adc_bat_pdata *pdata;
33 int volt_value;
34 int cur_value;
35 unsigned int timestamp;
36 int level;
37 int status;
38 int cable_plugged:1;
39};
40
41static struct delayed_work bat_work;
42
43static void s3c_adc_bat_ext_power_changed(struct power_supply *psy)
44{
45 schedule_delayed_work(&bat_work,
46 msecs_to_jiffies(JITTER_DELAY));
47}
48
49static enum power_supply_property s3c_adc_backup_bat_props[] = {
50 POWER_SUPPLY_PROP_VOLTAGE_NOW,
51 POWER_SUPPLY_PROP_VOLTAGE_MIN,
52 POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
53};
54
55static int s3c_adc_backup_bat_get_property(struct power_supply *psy,
56 enum power_supply_property psp,
57 union power_supply_propval *val)
58{
59 struct s3c_adc_bat *bat = container_of(psy, struct s3c_adc_bat, psy);
60
61 if (!bat) {
62 dev_err(psy->dev, "%s: no battery infos ?!\n", __func__);
63 return -EINVAL;
64 }
65
66 if (bat->volt_value < 0 ||
67 jiffies_to_msecs(jiffies - bat->timestamp) >
68 BAT_POLL_INTERVAL) {
69 bat->volt_value = s3c_adc_read(bat->client,
70 bat->pdata->backup_volt_channel);
71 bat->volt_value *= bat->pdata->backup_volt_mult;
72 bat->timestamp = jiffies;
73 }
74
75 switch (psp) {
76 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
77 val->intval = bat->volt_value;
78 return 0;
79 case POWER_SUPPLY_PROP_VOLTAGE_MIN:
80 val->intval = bat->pdata->backup_volt_min;
81 return 0;
82 case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
83 val->intval = bat->pdata->backup_volt_max;
84 return 0;
85 default:
86 return -EINVAL;
87 }
88}
89
90static struct s3c_adc_bat backup_bat = {
91 .psy = {
92 .name = "backup-battery",
93 .type = POWER_SUPPLY_TYPE_BATTERY,
94 .properties = s3c_adc_backup_bat_props,
95 .num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props),
96 .get_property = s3c_adc_backup_bat_get_property,
97 .use_for_apm = 1,
98 },
99};
100
101static enum power_supply_property s3c_adc_main_bat_props[] = {
102 POWER_SUPPLY_PROP_STATUS,
103 POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
104 POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
105 POWER_SUPPLY_PROP_CHARGE_NOW,
106 POWER_SUPPLY_PROP_VOLTAGE_NOW,
107 POWER_SUPPLY_PROP_CURRENT_NOW,
108};
109
110static int calc_full_volt(int volt_val, int cur_val, int impedance)
111{
112 return volt_val + cur_val * impedance / 1000;
113}
114
115static int s3c_adc_bat_get_property(struct power_supply *psy,
116 enum power_supply_property psp,
117 union power_supply_propval *val)
118{
119 struct s3c_adc_bat *bat = container_of(psy, struct s3c_adc_bat, psy);
120
121 int new_level;
122 int full_volt;
123 const struct s3c_adc_bat_thresh *lut = bat->pdata->lut_noac;
124 unsigned int lut_size = bat->pdata->lut_noac_cnt;
125
126 if (!bat) {
127 dev_err(psy->dev, "no battery infos ?!\n");
128 return -EINVAL;
129 }
130
131 if (bat->volt_value < 0 || bat->cur_value < 0 ||
132 jiffies_to_msecs(jiffies - bat->timestamp) >
133 BAT_POLL_INTERVAL) {
134 bat->volt_value = s3c_adc_read(bat->client,
135 bat->pdata->volt_channel) * bat->pdata->volt_mult;
136 bat->cur_value = s3c_adc_read(bat->client,
137 bat->pdata->current_channel) * bat->pdata->current_mult;
138 bat->timestamp = jiffies;
139 }
140
141 if (bat->cable_plugged &&
142 ((bat->pdata->gpio_charge_finished < 0) ||
143 !gpio_get_value(bat->pdata->gpio_charge_finished))) {
144 lut = bat->pdata->lut_acin;
145 lut_size = bat->pdata->lut_acin_cnt;
146 }
147
148 new_level = 100000;
149 full_volt = calc_full_volt((bat->volt_value / 1000),
150 (bat->cur_value / 1000), bat->pdata->internal_impedance);
151
152 if (full_volt < calc_full_volt(lut->volt, lut->cur,
153 bat->pdata->internal_impedance)) {
154 lut_size--;
155 while (lut_size--) {
156 int lut_volt1;
157 int lut_volt2;
158
159 lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur,
160 bat->pdata->internal_impedance);
161 lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur,
162 bat->pdata->internal_impedance);
163 if (full_volt < lut_volt1 && full_volt >= lut_volt2) {
164 new_level = (lut[1].level +
165 (lut[0].level - lut[1].level) *
166 (full_volt - lut_volt2) /
167 (lut_volt1 - lut_volt2)) * 1000;
168 break;
169 }
170 new_level = lut[1].level * 1000;
171 lut++;
172 }
173 }
174
175 bat->level = new_level;
176
177 switch (psp) {
178 case POWER_SUPPLY_PROP_STATUS:
179 if (bat->pdata->gpio_charge_finished < 0)
180 val->intval = bat->level == 100000 ?
181 POWER_SUPPLY_STATUS_FULL : bat->status;
182 else
183 val->intval = bat->status;
184 return 0;
185 case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
186 val->intval = 100000;
187 return 0;
188 case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN:
189 val->intval = 0;
190 return 0;
191 case POWER_SUPPLY_PROP_CHARGE_NOW:
192 val->intval = bat->level;
193 return 0;
194 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
195 val->intval = bat->volt_value;
196 return 0;
197 case POWER_SUPPLY_PROP_CURRENT_NOW:
198 val->intval = bat->cur_value;
199 return 0;
200 default:
201 return -EINVAL;
202 }
203}
204
205static struct s3c_adc_bat main_bat = {
206 .psy = {
207 .name = "main-battery",
208 .type = POWER_SUPPLY_TYPE_BATTERY,
209 .properties = s3c_adc_main_bat_props,
210 .num_properties = ARRAY_SIZE(s3c_adc_main_bat_props),
211 .get_property = s3c_adc_bat_get_property,
212 .external_power_changed = s3c_adc_bat_ext_power_changed,
213 .use_for_apm = 1,
214 },
215};
216
217static void s3c_adc_bat_work(struct work_struct *work)
218{
219 struct s3c_adc_bat *bat = &main_bat;
220 int is_charged;
221 int is_plugged;
222 static int was_plugged;
223
224 is_plugged = power_supply_am_i_supplied(&bat->psy);
225 bat->cable_plugged = is_plugged;
226 if (is_plugged != was_plugged) {
227 was_plugged = is_plugged;
228 if (is_plugged) {
229 if (bat->pdata->enable_charger)
230 bat->pdata->enable_charger();
231 bat->status = POWER_SUPPLY_STATUS_CHARGING;
232 } else {
233 if (bat->pdata->disable_charger)
234 bat->pdata->disable_charger();
235 bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
236 }
237 } else {
238 if ((bat->pdata->gpio_charge_finished >= 0) && is_plugged) {
239 is_charged = gpio_get_value(
240 main_bat.pdata->gpio_charge_finished);
241 if (is_charged) {
242 if (bat->pdata->disable_charger)
243 bat->pdata->disable_charger();
244 bat->status = POWER_SUPPLY_STATUS_FULL;
245 } else {
246 if (bat->pdata->enable_charger)
247 bat->pdata->enable_charger();
248 bat->status = POWER_SUPPLY_STATUS_CHARGING;
249 }
250 }
251 }
252
253 power_supply_changed(&bat->psy);
254}
255
256static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id)
257{
258 schedule_delayed_work(&bat_work,
259 msecs_to_jiffies(JITTER_DELAY));
260 return IRQ_HANDLED;
261}
262
263static int __init s3c_adc_bat_probe(struct platform_device *pdev)
264{
265 struct s3c_adc_client *client;
266 struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
267 int ret;
268
269 client = s3c_adc_register(pdev, NULL, NULL, 0);
270 if (IS_ERR(client)) {
271 dev_err(&pdev->dev, "cannot register adc\n");
272 return PTR_ERR(client);
273 }
274
275 platform_set_drvdata(pdev, client);
276
277 main_bat.client = client;
278 main_bat.pdata = pdata;
279 main_bat.volt_value = -1;
280 main_bat.cur_value = -1;
281 main_bat.cable_plugged = 0;
282 main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING;
283
284 ret = power_supply_register(&pdev->dev, &main_bat.psy);
285 if (ret)
286 goto err_reg_main;
287 if (pdata->backup_volt_mult) {
288 backup_bat.client = client;
289 backup_bat.pdata = pdev->dev.platform_data;
290 backup_bat.volt_value = -1;
291 ret = power_supply_register(&pdev->dev, &backup_bat.psy);
292 if (ret)
293 goto err_reg_backup;
294 }
295
296 INIT_DELAYED_WORK(&bat_work, s3c_adc_bat_work);
297
298 if (pdata->gpio_charge_finished >= 0) {
299 ret = gpio_request(pdata->gpio_charge_finished, "charged");
300 if (ret)
301 goto err_gpio;
302
303 ret = request_irq(gpio_to_irq(pdata->gpio_charge_finished),
304 s3c_adc_bat_charged,
305 IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
306 "battery charged", NULL);
307 if (ret)
308 goto err_irq;
309 }
310
311 if (pdata->init) {
312 ret = pdata->init();
313 if (ret)
314 goto err_platform;
315 }
316
317 dev_info(&pdev->dev, "successfully loaded\n");
318 device_init_wakeup(&pdev->dev, 1);
319
320 /* Schedule timer to check current status */
321 schedule_delayed_work(&bat_work,
322 msecs_to_jiffies(JITTER_DELAY));
323
324 return 0;
325
326err_platform:
327 if (pdata->gpio_charge_finished >= 0)
328 free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL);
329err_irq:
330 if (pdata->gpio_charge_finished >= 0)
331 gpio_free(pdata->gpio_charge_finished);
332err_gpio:
333 if (pdata->backup_volt_mult)
334 power_supply_unregister(&backup_bat.psy);
335err_reg_backup:
336 power_supply_unregister(&main_bat.psy);
337err_reg_main:
338 return ret;
339}
340
341static int s3c_adc_bat_remove(struct platform_device *pdev)
342{
343 struct s3c_adc_client *client = platform_get_drvdata(pdev);
344 struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
345
346 power_supply_unregister(&main_bat.psy);
347 if (pdata->backup_volt_mult)
348 power_supply_unregister(&backup_bat.psy);
349
350 s3c_adc_release(client);
351
352 if (pdata->gpio_charge_finished >= 0) {
353 free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL);
354 gpio_free(pdata->gpio_charge_finished);
355 }
356
357 cancel_delayed_work(&bat_work);
358
359 if (pdata->exit)
360 pdata->exit();
361
362 return 0;
363}
364
365#ifdef CONFIG_PM
366static int s3c_adc_bat_suspend(struct platform_device *pdev,
367 pm_message_t state)
368{
369 struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
370
371 if (pdata->gpio_charge_finished >= 0) {
372 if (device_may_wakeup(&pdev->dev))
373 enable_irq_wake(
374 gpio_to_irq(pdata->gpio_charge_finished));
375 else {
376 disable_irq(gpio_to_irq(pdata->gpio_charge_finished));
377 main_bat.pdata->disable_charger();
378 }
379 }
380
381 return 0;
382}
383
384static int s3c_adc_bat_resume(struct platform_device *pdev)
385{
386 struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
387
388 if (pdata->gpio_charge_finished >= 0) {
389 if (device_may_wakeup(&pdev->dev))
390 disable_irq_wake(
391 gpio_to_irq(pdata->gpio_charge_finished));
392 else
393 enable_irq(gpio_to_irq(pdata->gpio_charge_finished));
394 }
395
396 /* Schedule timer to check current status */
397 schedule_delayed_work(&bat_work,
398 msecs_to_jiffies(JITTER_DELAY));
399
400 return 0;
401}
402#else
403#define s3c_adc_battery_suspend NULL
404#define s3c_adc_battery_resume NULL
405#endif
406
407static struct platform_driver s3c_adc_bat_driver = {
408 .driver = {
409 .name = "s3c-adc-battery",
410 },
411 .probe = s3c_adc_bat_probe,
412 .remove = s3c_adc_bat_remove,
413 .suspend = s3c_adc_bat_suspend,
414 .resume = s3c_adc_bat_resume,
415};
416
417static int __init s3c_adc_bat_init(void)
418{
419 return platform_driver_register(&s3c_adc_bat_driver);
420}
421module_init(s3c_adc_bat_init);
422
423static void __exit s3c_adc_bat_exit(void)
424{
425 platform_driver_unregister(&s3c_adc_bat_driver);
426}
427module_exit(s3c_adc_bat_exit);
428
429MODULE_AUTHOR("Vasily Khoruzhick <anarsoul@gmail.com>");
430MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controler driver");
431MODULE_LICENSE("GPL");