aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/power/charger-manager.c
diff options
context:
space:
mode:
authorDonggeun Kim <dg77.kim@samsung.com>2011-12-27 04:47:48 -0500
committerAnton Vorontsov <cbouatmailru@gmail.com>2012-01-03 23:08:27 -0500
commit3bb3dbbd56ea39e5537db8f8041ea95d28f16a7f (patch)
treeb660ceed66ef404530109f9670b63640bd2be4b6 /drivers/power/charger-manager.c
parent00a159a5567232fbe1dd85bc611c55f53943b0fc (diff)
power_supply: Add initial Charger-Manager driver
Because battery health monitoring should be done even when suspended, it needs to wake up and suspend periodically. Thus, userspace battery monitoring may incur too much overhead; every device and task is woken up periodically. Charger Manager uses suspend-again to provide in-suspend monitoring. This patch allows to monitor battery health in-suspend state. Signed-off-by: Donggeun Kim <dg77.kim@samsung.com> Signed-off-by: MyungJoo Ham <myungjoo.ham@samsung.com> Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Signed-off-by: Anton Vorontsov <cbouatmailru@gmail.com>
Diffstat (limited to 'drivers/power/charger-manager.c')
-rw-r--r--drivers/power/charger-manager.c779
1 files changed, 779 insertions, 0 deletions
diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c
new file mode 100644
index 000000000000..727a259ea46c
--- /dev/null
+++ b/drivers/power/charger-manager.c
@@ -0,0 +1,779 @@
1/*
2 * Copyright (C) 2011 Samsung Electronics Co., Ltd.
3 * MyungJoo Ham <myungjoo.ham@samsung.com>
4 *
5 * This driver enables to monitor battery health and control charger
6 * during suspend-to-mem.
7 * Charger manager depends on other devices. register this later than
8 * the depending devices.
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#include <linux/io.h>
16#include <linux/module.h>
17#include <linux/irq.h>
18#include <linux/interrupt.h>
19#include <linux/rtc.h>
20#include <linux/slab.h>
21#include <linux/workqueue.h>
22#include <linux/platform_device.h>
23#include <linux/power/charger-manager.h>
24#include <linux/regulator/consumer.h>
25
26/*
27 * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
28 * delayed works so that we can run delayed works with CM_JIFFIES_SMALL
29 * without any delays.
30 */
31#define CM_JIFFIES_SMALL (2)
32
33/* If y is valid (> 0) and smaller than x, do x = y */
34#define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x))
35
36/*
37 * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking
38 * rtc alarm. It should be 2 or larger
39 */
40#define CM_RTC_SMALL (2)
41
42#define UEVENT_BUF_SIZE 32
43
44static LIST_HEAD(cm_list);
45static DEFINE_MUTEX(cm_list_mtx);
46
47/* About in-suspend (suspend-again) monitoring */
48static struct rtc_device *rtc_dev;
49/*
50 * Backup RTC alarm
51 * Save the wakeup alarm before entering suspend-to-RAM
52 */
53static struct rtc_wkalrm rtc_wkalarm_save;
54/* Backup RTC alarm time in terms of seconds since 01-01-1970 00:00:00 */
55static unsigned long rtc_wkalarm_save_time;
56static bool cm_suspended;
57static bool cm_rtc_set;
58static unsigned long cm_suspend_duration_ms;
59
60/* Global charger-manager description */
61static struct charger_global_desc *g_desc; /* init with setup_charger_manager */
62
63/**
64 * is_batt_present - See if the battery presents in place.
65 * @cm: the Charger Manager representing the battery.
66 */
67static bool is_batt_present(struct charger_manager *cm)
68{
69 union power_supply_propval val;
70 bool present = false;
71 int i, ret;
72
73 switch (cm->desc->battery_present) {
74 case CM_FUEL_GAUGE:
75 ret = cm->fuel_gauge->get_property(cm->fuel_gauge,
76 POWER_SUPPLY_PROP_PRESENT, &val);
77 if (ret == 0 && val.intval)
78 present = true;
79 break;
80 case CM_CHARGER_STAT:
81 for (i = 0; cm->charger_stat[i]; i++) {
82 ret = cm->charger_stat[i]->get_property(
83 cm->charger_stat[i],
84 POWER_SUPPLY_PROP_PRESENT, &val);
85 if (ret == 0 && val.intval) {
86 present = true;
87 break;
88 }
89 }
90 break;
91 }
92
93 return present;
94}
95
96/**
97 * is_ext_pwr_online - See if an external power source is attached to charge
98 * @cm: the Charger Manager representing the battery.
99 *
100 * Returns true if at least one of the chargers of the battery has an external
101 * power source attached to charge the battery regardless of whether it is
102 * actually charging or not.
103 */
104static bool is_ext_pwr_online(struct charger_manager *cm)
105{
106 union power_supply_propval val;
107 bool online = false;
108 int i, ret;
109
110 /* If at least one of them has one, it's yes. */
111 for (i = 0; cm->charger_stat[i]; i++) {
112 ret = cm->charger_stat[i]->get_property(
113 cm->charger_stat[i],
114 POWER_SUPPLY_PROP_ONLINE, &val);
115 if (ret == 0 && val.intval) {
116 online = true;
117 break;
118 }
119 }
120
121 return online;
122}
123
124/**
125 * is_charging - Returns true if the battery is being charged.
126 * @cm: the Charger Manager representing the battery.
127 */
128static bool is_charging(struct charger_manager *cm)
129{
130 int i, ret;
131 bool charging = false;
132 union power_supply_propval val;
133
134 /* If there is no battery, it cannot be charged */
135 if (!is_batt_present(cm))
136 return false;
137
138 /* If at least one of the charger is charging, return yes */
139 for (i = 0; cm->charger_stat[i]; i++) {
140 /* 1. The charger sholuld not be DISABLED */
141 if (cm->emergency_stop)
142 continue;
143 if (!cm->charger_enabled)
144 continue;
145
146 /* 2. The charger should be online (ext-power) */
147 ret = cm->charger_stat[i]->get_property(
148 cm->charger_stat[i],
149 POWER_SUPPLY_PROP_ONLINE, &val);
150 if (ret) {
151 dev_warn(cm->dev, "Cannot read ONLINE value from %s.\n",
152 cm->desc->psy_charger_stat[i]);
153 continue;
154 }
155 if (val.intval == 0)
156 continue;
157
158 /*
159 * 3. The charger should not be FULL, DISCHARGING,
160 * or NOT_CHARGING.
161 */
162 ret = cm->charger_stat[i]->get_property(
163 cm->charger_stat[i],
164 POWER_SUPPLY_PROP_STATUS, &val);
165 if (ret) {
166 dev_warn(cm->dev, "Cannot read STATUS value from %s.\n",
167 cm->desc->psy_charger_stat[i]);
168 continue;
169 }
170 if (val.intval == POWER_SUPPLY_STATUS_FULL ||
171 val.intval == POWER_SUPPLY_STATUS_DISCHARGING ||
172 val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING)
173 continue;
174
175 /* Then, this is charging. */
176 charging = true;
177 break;
178 }
179
180 return charging;
181}
182
183/**
184 * is_polling_required - Return true if need to continue polling for this CM.
185 * @cm: the Charger Manager representing the battery.
186 */
187static bool is_polling_required(struct charger_manager *cm)
188{
189 switch (cm->desc->polling_mode) {
190 case CM_POLL_DISABLE:
191 return false;
192 case CM_POLL_ALWAYS:
193 return true;
194 case CM_POLL_EXTERNAL_POWER_ONLY:
195 return is_ext_pwr_online(cm);
196 case CM_POLL_CHARGING_ONLY:
197 return is_charging(cm);
198 default:
199 dev_warn(cm->dev, "Incorrect polling_mode (%d)\n",
200 cm->desc->polling_mode);
201 }
202
203 return false;
204}
205
206/**
207 * try_charger_enable - Enable/Disable chargers altogether
208 * @cm: the Charger Manager representing the battery.
209 * @enable: true: enable / false: disable
210 *
211 * Note that Charger Manager keeps the charger enabled regardless whether
212 * the charger is charging or not (because battery is full or no external
213 * power source exists) except when CM needs to disable chargers forcibly
214 * bacause of emergency causes; when the battery is overheated or too cold.
215 */
216static int try_charger_enable(struct charger_manager *cm, bool enable)
217{
218 int err = 0, i;
219 struct charger_desc *desc = cm->desc;
220
221 /* Ignore if it's redundent command */
222 if (enable && cm->charger_enabled)
223 return 0;
224 if (!enable && !cm->charger_enabled)
225 return 0;
226
227 if (enable) {
228 if (cm->emergency_stop)
229 return -EAGAIN;
230 err = regulator_bulk_enable(desc->num_charger_regulators,
231 desc->charger_regulators);
232 } else {
233 /*
234 * Abnormal battery state - Stop charging forcibly,
235 * even if charger was enabled at the other places
236 */
237 err = regulator_bulk_disable(desc->num_charger_regulators,
238 desc->charger_regulators);
239
240 for (i = 0; i < desc->num_charger_regulators; i++) {
241 if (regulator_is_enabled(
242 desc->charger_regulators[i].consumer)) {
243 regulator_force_disable(
244 desc->charger_regulators[i].consumer);
245 dev_warn(cm->dev,
246 "Disable regulator(%s) forcibly.\n",
247 desc->charger_regulators[i].supply);
248 }
249 }
250 }
251
252 if (!err)
253 cm->charger_enabled = enable;
254
255 return err;
256}
257
258/**
259 * uevent_notify - Let users know something has changed.
260 * @cm: the Charger Manager representing the battery.
261 * @event: the event string.
262 *
263 * If @event is null, it implies that uevent_notify is called
264 * by resume function. When called in the resume function, cm_suspended
265 * should be already reset to false in order to let uevent_notify
266 * notify the recent event during the suspend to users. While
267 * suspended, uevent_notify does not notify users, but tracks
268 * events so that uevent_notify can notify users later after resumed.
269 */
270static void uevent_notify(struct charger_manager *cm, const char *event)
271{
272 static char env_str[UEVENT_BUF_SIZE + 1] = "";
273 static char env_str_save[UEVENT_BUF_SIZE + 1] = "";
274
275 if (cm_suspended) {
276 /* Nothing in suspended-event buffer */
277 if (env_str_save[0] == 0) {
278 if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
279 return; /* status not changed */
280 strncpy(env_str_save, event, UEVENT_BUF_SIZE);
281 return;
282 }
283
284 if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE))
285 return; /* Duplicated. */
286 else
287 strncpy(env_str_save, event, UEVENT_BUF_SIZE);
288
289 return;
290 }
291
292 if (event == NULL) {
293 /* No messages pending */
294 if (!env_str_save[0])
295 return;
296
297 strncpy(env_str, env_str_save, UEVENT_BUF_SIZE);
298 kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
299 env_str_save[0] = 0;
300
301 return;
302 }
303
304 /* status not changed */
305 if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
306 return;
307
308 /* save the status and notify the update */
309 strncpy(env_str, event, UEVENT_BUF_SIZE);
310 kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
311
312 dev_info(cm->dev, event);
313}
314
315/**
316 * _cm_monitor - Monitor the temperature and return true for exceptions.
317 * @cm: the Charger Manager representing the battery.
318 *
319 * Returns true if there is an event to notify for the battery.
320 * (True if the status of "emergency_stop" changes)
321 */
322static bool _cm_monitor(struct charger_manager *cm)
323{
324 struct charger_desc *desc = cm->desc;
325 int temp = desc->temperature_out_of_range(&cm->last_temp_mC);
326
327 dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n",
328 cm->last_temp_mC / 1000, cm->last_temp_mC % 1000);
329
330 /* It has been stopped or charging already */
331 if (!!temp == !!cm->emergency_stop)
332 return false;
333
334 if (temp) {
335 cm->emergency_stop = temp;
336 if (!try_charger_enable(cm, false)) {
337 if (temp > 0)
338 uevent_notify(cm, "OVERHEAT");
339 else
340 uevent_notify(cm, "COLD");
341 }
342 } else {
343 cm->emergency_stop = 0;
344 if (!try_charger_enable(cm, true))
345 uevent_notify(cm, "CHARGING");
346 }
347
348 return true;
349}
350
351/**
352 * cm_monitor - Monitor every battery.
353 *
354 * Returns true if there is an event to notify from any of the batteries.
355 * (True if the status of "emergency_stop" changes)
356 */
357static bool cm_monitor(void)
358{
359 bool stop = false;
360 struct charger_manager *cm;
361
362 mutex_lock(&cm_list_mtx);
363
364 list_for_each_entry(cm, &cm_list, entry)
365 stop = stop || _cm_monitor(cm);
366
367 mutex_unlock(&cm_list_mtx);
368
369 return stop;
370}
371
372/**
373 * cm_setup_timer - For in-suspend monitoring setup wakeup alarm
374 * for suspend_again.
375 *
376 * Returns true if the alarm is set for Charger Manager to use.
377 * Returns false if
378 * cm_setup_timer fails to set an alarm,
379 * cm_setup_timer does not need to set an alarm for Charger Manager,
380 * or an alarm previously configured is to be used.
381 */
382static bool cm_setup_timer(void)
383{
384 struct charger_manager *cm;
385 unsigned int wakeup_ms = UINT_MAX;
386 bool ret = false;
387
388 mutex_lock(&cm_list_mtx);
389
390 list_for_each_entry(cm, &cm_list, entry) {
391 /* Skip if polling is not required for this CM */
392 if (!is_polling_required(cm) && !cm->emergency_stop)
393 continue;
394 if (cm->desc->polling_interval_ms == 0)
395 continue;
396 CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms);
397 }
398
399 mutex_unlock(&cm_list_mtx);
400
401 if (wakeup_ms < UINT_MAX && wakeup_ms > 0) {
402 pr_info("Charger Manager wakeup timer: %u ms.\n", wakeup_ms);
403 if (rtc_dev) {
404 struct rtc_wkalrm tmp;
405 unsigned long time, now;
406 unsigned long add = DIV_ROUND_UP(wakeup_ms, 1000);
407
408 /*
409 * Set alarm with the polling interval (wakeup_ms)
410 * except when rtc_wkalarm_save comes first.
411 * However, the alarm time should be NOW +
412 * CM_RTC_SMALL or later.
413 */
414 tmp.enabled = 1;
415 rtc_read_time(rtc_dev, &tmp.time);
416 rtc_tm_to_time(&tmp.time, &now);
417 if (add < CM_RTC_SMALL)
418 add = CM_RTC_SMALL;
419 time = now + add;
420
421 ret = true;
422
423 if (rtc_wkalarm_save.enabled &&
424 rtc_wkalarm_save_time &&
425 rtc_wkalarm_save_time < time) {
426 if (rtc_wkalarm_save_time < now + CM_RTC_SMALL)
427 time = now + CM_RTC_SMALL;
428 else
429 time = rtc_wkalarm_save_time;
430
431 /* The timer is not appointed by CM */
432 ret = false;
433 }
434
435 pr_info("Waking up after %lu secs.\n",
436 time - now);
437
438 rtc_time_to_tm(time, &tmp.time);
439 rtc_set_alarm(rtc_dev, &tmp);
440 cm_suspend_duration_ms += wakeup_ms;
441 return ret;
442 }
443 }
444
445 if (rtc_dev)
446 rtc_set_alarm(rtc_dev, &rtc_wkalarm_save);
447 return false;
448}
449
450/**
451 * cm_suspend_again - Determine whether suspend again or not
452 *
453 * Returns true if the system should be suspended again
454 * Returns false if the system should be woken up
455 */
456bool cm_suspend_again(void)
457{
458 struct charger_manager *cm;
459 bool ret = false;
460
461 if (!g_desc || !g_desc->rtc_only_wakeup || !g_desc->rtc_only_wakeup() ||
462 !cm_rtc_set)
463 return false;
464
465 if (cm_monitor())
466 goto out;
467
468 ret = true;
469 mutex_lock(&cm_list_mtx);
470 list_for_each_entry(cm, &cm_list, entry) {
471 if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) ||
472 cm->status_save_batt != is_batt_present(cm))
473 ret = false;
474 }
475 mutex_unlock(&cm_list_mtx);
476
477 cm_rtc_set = cm_setup_timer();
478out:
479 /* It's about the time when the non-CM appointed timer goes off */
480 if (rtc_wkalarm_save.enabled) {
481 unsigned long now;
482 struct rtc_time tmp;
483
484 rtc_read_time(rtc_dev, &tmp);
485 rtc_tm_to_time(&tmp, &now);
486
487 if (rtc_wkalarm_save_time &&
488 now + CM_RTC_SMALL >= rtc_wkalarm_save_time)
489 return false;
490 }
491 return ret;
492}
493EXPORT_SYMBOL_GPL(cm_suspend_again);
494
495/**
496 * setup_charger_manager - initialize charger_global_desc data
497 * @gd: pointer to instance of charger_global_desc
498 */
499int setup_charger_manager(struct charger_global_desc *gd)
500{
501 if (!gd)
502 return -EINVAL;
503
504 if (rtc_dev)
505 rtc_class_close(rtc_dev);
506 rtc_dev = NULL;
507 g_desc = NULL;
508
509 if (!gd->rtc_only_wakeup) {
510 pr_err("The callback rtc_only_wakeup is not given.\n");
511 return -EINVAL;
512 }
513
514 if (gd->rtc_name) {
515 rtc_dev = rtc_class_open(gd->rtc_name);
516 if (IS_ERR_OR_NULL(rtc_dev)) {
517 rtc_dev = NULL;
518 /* Retry at probe. RTC may be not registered yet */
519 }
520 } else {
521 pr_warn("No wakeup timer is given for charger manager."
522 "In-suspend monitoring won't work.\n");
523 }
524
525 g_desc = gd;
526 return 0;
527}
528EXPORT_SYMBOL_GPL(setup_charger_manager);
529
530static int charger_manager_probe(struct platform_device *pdev)
531{
532 struct charger_desc *desc = dev_get_platdata(&pdev->dev);
533 struct charger_manager *cm;
534 int ret = 0, i = 0;
535
536 if (g_desc && !rtc_dev && g_desc->rtc_name) {
537 rtc_dev = rtc_class_open(g_desc->rtc_name);
538 if (IS_ERR_OR_NULL(rtc_dev)) {
539 rtc_dev = NULL;
540 dev_err(&pdev->dev, "Cannot get RTC %s.\n",
541 g_desc->rtc_name);
542 ret = -ENODEV;
543 goto err_alloc;
544 }
545 }
546
547 if (!desc) {
548 dev_err(&pdev->dev, "No platform data (desc) found.\n");
549 ret = -ENODEV;
550 goto err_alloc;
551 }
552
553 cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL);
554 if (!cm) {
555 dev_err(&pdev->dev, "Cannot allocate memory.\n");
556 ret = -ENOMEM;
557 goto err_alloc;
558 }
559
560 /* Basic Values. Unspecified are Null or 0 */
561 cm->dev = &pdev->dev;
562 cm->desc = kzalloc(sizeof(struct charger_desc), GFP_KERNEL);
563 if (!cm->desc) {
564 dev_err(&pdev->dev, "Cannot allocate memory.\n");
565 ret = -ENOMEM;
566 goto err_alloc_desc;
567 }
568 memcpy(cm->desc, desc, sizeof(struct charger_desc));
569 cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */
570
571 if (!desc->charger_regulators || desc->num_charger_regulators < 1) {
572 ret = -EINVAL;
573 dev_err(&pdev->dev, "charger_regulators undefined.\n");
574 goto err_no_charger;
575 }
576
577 if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) {
578 dev_err(&pdev->dev, "No power supply defined.\n");
579 ret = -EINVAL;
580 goto err_no_charger_stat;
581 }
582
583 /* Counting index only */
584 while (desc->psy_charger_stat[i])
585 i++;
586
587 cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1),
588 GFP_KERNEL);
589 if (!cm->charger_stat) {
590 ret = -ENOMEM;
591 goto err_no_charger_stat;
592 }
593
594 for (i = 0; desc->psy_charger_stat[i]; i++) {
595 cm->charger_stat[i] = power_supply_get_by_name(
596 desc->psy_charger_stat[i]);
597 if (!cm->charger_stat[i]) {
598 dev_err(&pdev->dev, "Cannot find power supply "
599 "\"%s\"\n",
600 desc->psy_charger_stat[i]);
601 ret = -ENODEV;
602 goto err_chg_stat;
603 }
604 }
605
606 cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
607 if (!cm->fuel_gauge) {
608 dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
609 desc->psy_fuel_gauge);
610 ret = -ENODEV;
611 goto err_chg_stat;
612 }
613
614 if (desc->polling_interval_ms == 0 ||
615 msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) {
616 dev_err(&pdev->dev, "polling_interval_ms is too small\n");
617 ret = -EINVAL;
618 goto err_chg_stat;
619 }
620
621 if (!desc->temperature_out_of_range) {
622 dev_err(&pdev->dev, "there is no temperature_out_of_range\n");
623 ret = -EINVAL;
624 goto err_chg_stat;
625 }
626
627 platform_set_drvdata(pdev, cm);
628
629 ret = regulator_bulk_get(&pdev->dev, desc->num_charger_regulators,
630 desc->charger_regulators);
631 if (ret) {
632 dev_err(&pdev->dev, "Cannot get charger regulators.\n");
633 goto err_chg_stat;
634 }
635
636 ret = try_charger_enable(cm, true);
637 if (ret) {
638 dev_err(&pdev->dev, "Cannot enable charger regulators\n");
639 goto err_chg_enable;
640 }
641
642 /* Add to the list */
643 mutex_lock(&cm_list_mtx);
644 list_add(&cm->entry, &cm_list);
645 mutex_unlock(&cm_list_mtx);
646
647 return 0;
648
649err_chg_enable:
650 if (desc->charger_regulators)
651 regulator_bulk_free(desc->num_charger_regulators,
652 desc->charger_regulators);
653err_chg_stat:
654 kfree(cm->charger_stat);
655err_no_charger_stat:
656err_no_charger:
657 kfree(cm->desc);
658err_alloc_desc:
659 kfree(cm);
660err_alloc:
661 return ret;
662}
663
664static int __devexit charger_manager_remove(struct platform_device *pdev)
665{
666 struct charger_manager *cm = platform_get_drvdata(pdev);
667 struct charger_desc *desc = cm->desc;
668
669 /* Remove from the list */
670 mutex_lock(&cm_list_mtx);
671 list_del(&cm->entry);
672 mutex_unlock(&cm_list_mtx);
673
674 if (desc->charger_regulators)
675 regulator_bulk_free(desc->num_charger_regulators,
676 desc->charger_regulators);
677 kfree(cm->charger_stat);
678 kfree(cm->desc);
679 kfree(cm);
680
681 return 0;
682}
683
684const struct platform_device_id charger_manager_id[] = {
685 { "charger-manager", 0 },
686 { },
687};
688
689static int cm_suspend_prepare(struct device *dev)
690{
691 struct platform_device *pdev = container_of(dev, struct platform_device,
692 dev);
693 struct charger_manager *cm = platform_get_drvdata(pdev);
694
695 if (!cm_suspended) {
696 if (rtc_dev) {
697 struct rtc_time tmp;
698 unsigned long now;
699
700 rtc_read_alarm(rtc_dev, &rtc_wkalarm_save);
701 rtc_read_time(rtc_dev, &tmp);
702
703 if (rtc_wkalarm_save.enabled) {
704 rtc_tm_to_time(&rtc_wkalarm_save.time,
705 &rtc_wkalarm_save_time);
706 rtc_tm_to_time(&tmp, &now);
707 if (now > rtc_wkalarm_save_time)
708 rtc_wkalarm_save_time = 0;
709 } else {
710 rtc_wkalarm_save_time = 0;
711 }
712 }
713 cm_suspended = true;
714 }
715
716 cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm);
717 cm->status_save_batt = is_batt_present(cm);
718
719 if (!cm_rtc_set) {
720 cm_suspend_duration_ms = 0;
721 cm_rtc_set = cm_setup_timer();
722 }
723
724 return 0;
725}
726
727static void cm_suspend_complete(struct device *dev)
728{
729 struct platform_device *pdev = container_of(dev, struct platform_device,
730 dev);
731 struct charger_manager *cm = platform_get_drvdata(pdev);
732
733 if (cm_suspended) {
734 if (rtc_dev) {
735 struct rtc_wkalrm tmp;
736
737 rtc_read_alarm(rtc_dev, &tmp);
738 rtc_wkalarm_save.pending = tmp.pending;
739 rtc_set_alarm(rtc_dev, &rtc_wkalarm_save);
740 }
741 cm_suspended = false;
742 cm_rtc_set = false;
743 }
744
745 uevent_notify(cm, NULL);
746}
747
748static const struct dev_pm_ops charger_manager_pm = {
749 .prepare = cm_suspend_prepare,
750 .complete = cm_suspend_complete,
751};
752
753static struct platform_driver charger_manager_driver = {
754 .driver = {
755 .name = "charger-manager",
756 .owner = THIS_MODULE,
757 .pm = &charger_manager_pm,
758 },
759 .probe = charger_manager_probe,
760 .remove = __devexit_p(charger_manager_remove),
761 .id_table = charger_manager_id,
762};
763
764static int __init charger_manager_init(void)
765{
766 return platform_driver_register(&charger_manager_driver);
767}
768late_initcall(charger_manager_init);
769
770static void __exit charger_manager_cleanup(void)
771{
772 platform_driver_unregister(&charger_manager_driver);
773}
774module_exit(charger_manager_cleanup);
775
776MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
777MODULE_DESCRIPTION("Charger Manager");
778MODULE_LICENSE("GPL");
779MODULE_ALIAS("charger-manager");