diff options
author | David Brownell <david-b@pacbell.net> | 2007-05-08 03:33:42 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-05-08 14:15:18 -0400 |
commit | 7ca1d488ffe4817adaba61cc05b972782f7d3f91 (patch) | |
tree | 97fee4d2ddbc5be5265d99f5825e902f7a9262c1 /drivers | |
parent | cd9662094edf4173e87f0452e57e4eacc228f8ff (diff) |
rtc: suspend()/resume() restores system clock
RTC class suspend/resume support, re-initializing the system clock on resume
from the clock used to initialize it at boot time.
- The reinit-on-resume is hooked to the existing RTC_HCTOSYS config
option, on the grounds that a clock good enough for init must also
be good enough for re-init.
- Inlining a version of the code used by ARM, to save and restore the
delta between a selected RTC and the current system wall-clock time.
- Removes calls to that ARM code from AT91, OMAP1, and S3C RTCs. This
means that systems using those RTCs across suspend/resume will likely
want to change their kernel configs to enable RTC_HCTOSYS.
If HCTOSYS isn't using a second RTC (with battery?), this changes the
system's initial date from Jan 1970 to the epoch this hardware uses:
1998 for AT91, 2000 for OMAP1 (assuming no split power mode), etc.
This goes on top of the patch series removing "struct class_device" usage
from the RTC framework. That's all needed for class suspend()/resume().
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Acked-By: Alessandro Zummo <a.zummo@towertech.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/rtc/Kconfig | 24 | ||||
-rw-r--r-- | drivers/rtc/class.c | 74 | ||||
-rw-r--r-- | drivers/rtc/rtc-at91rm9200.c | 30 | ||||
-rw-r--r-- | drivers/rtc/rtc-omap.c | 17 | ||||
-rw-r--r-- | drivers/rtc/rtc-s3c.c | 22 |
5 files changed, 91 insertions, 76 deletions
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index ec33ee87735e..a53ef4d670cc 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig | |||
@@ -21,21 +21,31 @@ config RTC_CLASS | |||
21 | will be called rtc-class. | 21 | will be called rtc-class. |
22 | 22 | ||
23 | config RTC_HCTOSYS | 23 | config RTC_HCTOSYS |
24 | bool "Set system time from RTC on startup" | 24 | bool "Set system time from RTC on startup and resume" |
25 | depends on RTC_CLASS = y | 25 | depends on RTC_CLASS = y |
26 | default y | 26 | default y |
27 | help | 27 | help |
28 | If you say yes here, the system time will be set using | 28 | If you say yes here, the system time (wall clock) will be set using |
29 | the value read from the specified RTC device. This is useful | 29 | the value read from a specified RTC device. This is useful to avoid |
30 | in order to avoid unnecessary fsck runs. | 30 | unnecessary fsck runs at boot time, and to network better. |
31 | 31 | ||
32 | config RTC_HCTOSYS_DEVICE | 32 | config RTC_HCTOSYS_DEVICE |
33 | string "The RTC to read the time from" | 33 | string "RTC used to set the system time" |
34 | depends on RTC_HCTOSYS = y | 34 | depends on RTC_HCTOSYS = y |
35 | default "rtc0" | 35 | default "rtc0" |
36 | help | 36 | help |
37 | The RTC device that will be used as the source for | 37 | The RTC device that will be used to (re)initialize the system |
38 | the system time, usually rtc0. | 38 | clock, usually rtc0. Initialization is done when the system |
39 | starts up, and when it resumes from a low power state. | ||
40 | |||
41 | This clock should be battery-backed, so that it reads the correct | ||
42 | time when the system boots from a power-off state. Otherwise, your | ||
43 | system will need an external clock source (like an NTP server). | ||
44 | |||
45 | If the clock you specify here is not battery backed, it may still | ||
46 | be useful to reinitialize system time when resuming from system | ||
47 | sleep states. Do not specify an RTC here unless it stays powered | ||
48 | during all this system's supported sleep states. | ||
39 | 49 | ||
40 | config RTC_DEBUG | 50 | config RTC_DEBUG |
41 | bool "RTC debug support" | 51 | bool "RTC debug support" |
diff --git a/drivers/rtc/class.c b/drivers/rtc/class.c index d58d74cf570e..587d251be454 100644 --- a/drivers/rtc/class.c +++ b/drivers/rtc/class.c | |||
@@ -32,6 +32,78 @@ static void rtc_device_release(struct device *dev) | |||
32 | kfree(rtc); | 32 | kfree(rtc); |
33 | } | 33 | } |
34 | 34 | ||
35 | #if defined(CONFIG_PM) && defined(CONFIG_RTC_HCTOSYS_DEVICE) | ||
36 | |||
37 | /* | ||
38 | * On suspend(), measure the delta between one RTC and the | ||
39 | * system's wall clock; restore it on resume(). | ||
40 | */ | ||
41 | |||
42 | static struct timespec delta; | ||
43 | static time_t oldtime; | ||
44 | |||
45 | static int rtc_suspend(struct device *dev, pm_message_t mesg) | ||
46 | { | ||
47 | struct rtc_device *rtc = to_rtc_device(dev); | ||
48 | struct rtc_time tm; | ||
49 | |||
50 | if (strncmp(rtc->dev.bus_id, | ||
51 | CONFIG_RTC_HCTOSYS_DEVICE, | ||
52 | BUS_ID_SIZE) != 0) | ||
53 | return 0; | ||
54 | |||
55 | rtc_read_time(rtc, &tm); | ||
56 | rtc_tm_to_time(&tm, &oldtime); | ||
57 | |||
58 | /* RTC precision is 1 second; adjust delta for avg 1/2 sec err */ | ||
59 | set_normalized_timespec(&delta, | ||
60 | xtime.tv_sec - oldtime, | ||
61 | xtime.tv_nsec - (NSEC_PER_SEC >> 1)); | ||
62 | |||
63 | return 0; | ||
64 | } | ||
65 | |||
66 | static int rtc_resume(struct device *dev) | ||
67 | { | ||
68 | struct rtc_device *rtc = to_rtc_device(dev); | ||
69 | struct rtc_time tm; | ||
70 | time_t newtime; | ||
71 | struct timespec time; | ||
72 | |||
73 | if (strncmp(rtc->dev.bus_id, | ||
74 | CONFIG_RTC_HCTOSYS_DEVICE, | ||
75 | BUS_ID_SIZE) != 0) | ||
76 | return 0; | ||
77 | |||
78 | rtc_read_time(rtc, &tm); | ||
79 | if (rtc_valid_tm(&tm) != 0) { | ||
80 | pr_debug("%s: bogus resume time\n", rtc->dev.bus_id); | ||
81 | return 0; | ||
82 | } | ||
83 | rtc_tm_to_time(&tm, &newtime); | ||
84 | if (newtime <= oldtime) { | ||
85 | if (newtime < oldtime) | ||
86 | pr_debug("%s: time travel!\n", rtc->dev.bus_id); | ||
87 | return 0; | ||
88 | } | ||
89 | |||
90 | /* restore wall clock using delta against this RTC; | ||
91 | * adjust again for avg 1/2 second RTC sampling error | ||
92 | */ | ||
93 | set_normalized_timespec(&time, | ||
94 | newtime + delta.tv_sec, | ||
95 | (NSEC_PER_SEC >> 1) + delta.tv_nsec); | ||
96 | do_settimeofday(&time); | ||
97 | |||
98 | return 0; | ||
99 | } | ||
100 | |||
101 | #else | ||
102 | #define rtc_suspend NULL | ||
103 | #define rtc_resume NULL | ||
104 | #endif | ||
105 | |||
106 | |||
35 | /** | 107 | /** |
36 | * rtc_device_register - register w/ RTC class | 108 | * rtc_device_register - register w/ RTC class |
37 | * @dev: the device to register | 109 | * @dev: the device to register |
@@ -143,6 +215,8 @@ static int __init rtc_init(void) | |||
143 | printk(KERN_ERR "%s: couldn't create class\n", __FILE__); | 215 | printk(KERN_ERR "%s: couldn't create class\n", __FILE__); |
144 | return PTR_ERR(rtc_class); | 216 | return PTR_ERR(rtc_class); |
145 | } | 217 | } |
218 | rtc_class->suspend = rtc_suspend; | ||
219 | rtc_class->resume = rtc_resume; | ||
146 | rtc_dev_init(); | 220 | rtc_dev_init(); |
147 | rtc_sysfs_init(rtc_class); | 221 | rtc_sysfs_init(rtc_class); |
148 | return 0; | 222 | return 0; |
diff --git a/drivers/rtc/rtc-at91rm9200.c b/drivers/rtc/rtc-at91rm9200.c index 2f06b5f9fb7b..33795e5a5595 100644 --- a/drivers/rtc/rtc-at91rm9200.c +++ b/drivers/rtc/rtc-at91rm9200.c | |||
@@ -348,21 +348,10 @@ static int __exit at91_rtc_remove(struct platform_device *pdev) | |||
348 | 348 | ||
349 | /* AT91RM9200 RTC Power management control */ | 349 | /* AT91RM9200 RTC Power management control */ |
350 | 350 | ||
351 | static struct timespec at91_rtc_delta; | ||
352 | static u32 at91_rtc_imr; | 351 | static u32 at91_rtc_imr; |
353 | 352 | ||
354 | static int at91_rtc_suspend(struct platform_device *pdev, pm_message_t state) | 353 | static int at91_rtc_suspend(struct platform_device *pdev, pm_message_t state) |
355 | { | 354 | { |
356 | struct rtc_time tm; | ||
357 | struct timespec time; | ||
358 | |||
359 | time.tv_nsec = 0; | ||
360 | |||
361 | /* calculate time delta for suspend */ | ||
362 | at91_rtc_readtime(&pdev->dev, &tm); | ||
363 | rtc_tm_to_time(&tm, &time.tv_sec); | ||
364 | save_time_delta(&at91_rtc_delta, &time); | ||
365 | |||
366 | /* this IRQ is shared with DBGU and other hardware which isn't | 355 | /* this IRQ is shared with DBGU and other hardware which isn't |
367 | * necessarily doing PM like we are... | 356 | * necessarily doing PM like we are... |
368 | */ | 357 | */ |
@@ -374,36 +363,17 @@ static int at91_rtc_suspend(struct platform_device *pdev, pm_message_t state) | |||
374 | else | 363 | else |
375 | at91_sys_write(AT91_RTC_IDR, at91_rtc_imr); | 364 | at91_sys_write(AT91_RTC_IDR, at91_rtc_imr); |
376 | } | 365 | } |
377 | |||
378 | pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__, | ||
379 | 1900 + tm.tm_year, tm.tm_mon, tm.tm_mday, | ||
380 | tm.tm_hour, tm.tm_min, tm.tm_sec); | ||
381 | |||
382 | return 0; | 366 | return 0; |
383 | } | 367 | } |
384 | 368 | ||
385 | static int at91_rtc_resume(struct platform_device *pdev) | 369 | static int at91_rtc_resume(struct platform_device *pdev) |
386 | { | 370 | { |
387 | struct rtc_time tm; | ||
388 | struct timespec time; | ||
389 | |||
390 | time.tv_nsec = 0; | ||
391 | |||
392 | at91_rtc_readtime(&pdev->dev, &tm); | ||
393 | rtc_tm_to_time(&tm, &time.tv_sec); | ||
394 | restore_time_delta(&at91_rtc_delta, &time); | ||
395 | |||
396 | if (at91_rtc_imr) { | 371 | if (at91_rtc_imr) { |
397 | if (device_may_wakeup(&pdev->dev)) | 372 | if (device_may_wakeup(&pdev->dev)) |
398 | disable_irq_wake(AT91_ID_SYS); | 373 | disable_irq_wake(AT91_ID_SYS); |
399 | else | 374 | else |
400 | at91_sys_write(AT91_RTC_IER, at91_rtc_imr); | 375 | at91_sys_write(AT91_RTC_IER, at91_rtc_imr); |
401 | } | 376 | } |
402 | |||
403 | pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__, | ||
404 | 1900 + tm.tm_year, tm.tm_mon, tm.tm_mday, | ||
405 | tm.tm_hour, tm.tm_min, tm.tm_sec); | ||
406 | |||
407 | return 0; | 377 | return 0; |
408 | } | 378 | } |
409 | #else | 379 | #else |
diff --git a/drivers/rtc/rtc-omap.c b/drivers/rtc/rtc-omap.c index ded35fd13ba0..e6c7b0149f27 100644 --- a/drivers/rtc/rtc-omap.c +++ b/drivers/rtc/rtc-omap.c | |||
@@ -488,19 +488,10 @@ static int __devexit omap_rtc_remove(struct platform_device *pdev) | |||
488 | 488 | ||
489 | #ifdef CONFIG_PM | 489 | #ifdef CONFIG_PM |
490 | 490 | ||
491 | static struct timespec rtc_delta; | ||
492 | static u8 irqstat; | 491 | static u8 irqstat; |
493 | 492 | ||
494 | static int omap_rtc_suspend(struct platform_device *pdev, pm_message_t state) | 493 | static int omap_rtc_suspend(struct platform_device *pdev, pm_message_t state) |
495 | { | 494 | { |
496 | struct rtc_time rtc_tm; | ||
497 | struct timespec time; | ||
498 | |||
499 | time.tv_nsec = 0; | ||
500 | omap_rtc_read_time(NULL, &rtc_tm); | ||
501 | rtc_tm_to_time(&rtc_tm, &time.tv_sec); | ||
502 | |||
503 | save_time_delta(&rtc_delta, &time); | ||
504 | irqstat = rtc_read(OMAP_RTC_INTERRUPTS_REG); | 495 | irqstat = rtc_read(OMAP_RTC_INTERRUPTS_REG); |
505 | 496 | ||
506 | /* FIXME the RTC alarm is not currently acting as a wakeup event | 497 | /* FIXME the RTC alarm is not currently acting as a wakeup event |
@@ -517,14 +508,6 @@ static int omap_rtc_suspend(struct platform_device *pdev, pm_message_t state) | |||
517 | 508 | ||
518 | static int omap_rtc_resume(struct platform_device *pdev) | 509 | static int omap_rtc_resume(struct platform_device *pdev) |
519 | { | 510 | { |
520 | struct rtc_time rtc_tm; | ||
521 | struct timespec time; | ||
522 | |||
523 | time.tv_nsec = 0; | ||
524 | omap_rtc_read_time(NULL, &rtc_tm); | ||
525 | rtc_tm_to_time(&rtc_tm, &time.tv_sec); | ||
526 | |||
527 | restore_time_delta(&rtc_delta, &time); | ||
528 | if (device_may_wakeup(&pdev->dev)) | 511 | if (device_may_wakeup(&pdev->dev)) |
529 | disable_irq_wake(omap_rtc_alarm); | 512 | disable_irq_wake(omap_rtc_alarm); |
530 | else | 513 | else |
diff --git a/drivers/rtc/rtc-s3c.c b/drivers/rtc/rtc-s3c.c index 3617c970caaa..54b613053468 100644 --- a/drivers/rtc/rtc-s3c.c +++ b/drivers/rtc/rtc-s3c.c | |||
@@ -548,37 +548,15 @@ static int ticnt_save; | |||
548 | 548 | ||
549 | static int s3c_rtc_suspend(struct platform_device *pdev, pm_message_t state) | 549 | static int s3c_rtc_suspend(struct platform_device *pdev, pm_message_t state) |
550 | { | 550 | { |
551 | struct rtc_time tm; | ||
552 | struct timespec time; | ||
553 | |||
554 | time.tv_nsec = 0; | ||
555 | |||
556 | /* save TICNT for anyone using periodic interrupts */ | 551 | /* save TICNT for anyone using periodic interrupts */ |
557 | |||
558 | ticnt_save = readb(s3c_rtc_base + S3C2410_TICNT); | 552 | ticnt_save = readb(s3c_rtc_base + S3C2410_TICNT); |
559 | |||
560 | /* calculate time delta for suspend */ | ||
561 | |||
562 | s3c_rtc_gettime(&pdev->dev, &tm); | ||
563 | rtc_tm_to_time(&tm, &time.tv_sec); | ||
564 | save_time_delta(&s3c_rtc_delta, &time); | ||
565 | s3c_rtc_enable(pdev, 0); | 553 | s3c_rtc_enable(pdev, 0); |
566 | |||
567 | return 0; | 554 | return 0; |
568 | } | 555 | } |
569 | 556 | ||
570 | static int s3c_rtc_resume(struct platform_device *pdev) | 557 | static int s3c_rtc_resume(struct platform_device *pdev) |
571 | { | 558 | { |
572 | struct rtc_time tm; | ||
573 | struct timespec time; | ||
574 | |||
575 | time.tv_nsec = 0; | ||
576 | |||
577 | s3c_rtc_enable(pdev, 1); | 559 | s3c_rtc_enable(pdev, 1); |
578 | s3c_rtc_gettime(&pdev->dev, &tm); | ||
579 | rtc_tm_to_time(&tm, &time.tv_sec); | ||
580 | restore_time_delta(&s3c_rtc_delta, &time); | ||
581 | |||
582 | writeb(ticnt_save, s3c_rtc_base + S3C2410_TICNT); | 560 | writeb(ticnt_save, s3c_rtc_base + S3C2410_TICNT); |
583 | return 0; | 561 | return 0; |
584 | } | 562 | } |