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 | |
| 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>
| -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 | } |
