Skip to content

Commit

Permalink
rtc: suspend()/resume() restores system clock
Browse files Browse the repository at this point in the history
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 <[email protected]>
Acked-by: Greg Kroah-Hartman <[email protected]>
Acked-By: Alessandro Zummo <[email protected]>
Signed-off-by: Andrew Morton <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
  • Loading branch information
David Brownell authored and Linus Torvalds committed May 8, 2007
1 parent cd96620 commit 7ca1d48
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 76 deletions.
24 changes: 17 additions & 7 deletions drivers/rtc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,31 @@ config RTC_CLASS
will be called rtc-class.

config RTC_HCTOSYS
bool "Set system time from RTC on startup"
bool "Set system time from RTC on startup and resume"
depends on RTC_CLASS = y
default y
help
If you say yes here, the system time will be set using
the value read from the specified RTC device. This is useful
in order to avoid unnecessary fsck runs.
If you say yes here, the system time (wall clock) will be set using
the value read from a specified RTC device. This is useful to avoid
unnecessary fsck runs at boot time, and to network better.

config RTC_HCTOSYS_DEVICE
string "The RTC to read the time from"
string "RTC used to set the system time"
depends on RTC_HCTOSYS = y
default "rtc0"
help
The RTC device that will be used as the source for
the system time, usually rtc0.
The RTC device that will be used to (re)initialize the system
clock, usually rtc0. Initialization is done when the system
starts up, and when it resumes from a low power state.

This clock should be battery-backed, so that it reads the correct
time when the system boots from a power-off state. Otherwise, your
system will need an external clock source (like an NTP server).

If the clock you specify here is not battery backed, it may still
be useful to reinitialize system time when resuming from system
sleep states. Do not specify an RTC here unless it stays powered
during all this system's supported sleep states.

config RTC_DEBUG
bool "RTC debug support"
Expand Down
74 changes: 74 additions & 0 deletions drivers/rtc/class.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,78 @@ static void rtc_device_release(struct device *dev)
kfree(rtc);
}

#if defined(CONFIG_PM) && defined(CONFIG_RTC_HCTOSYS_DEVICE)

/*
* On suspend(), measure the delta between one RTC and the
* system's wall clock; restore it on resume().
*/

static struct timespec delta;
static time_t oldtime;

static int rtc_suspend(struct device *dev, pm_message_t mesg)
{
struct rtc_device *rtc = to_rtc_device(dev);
struct rtc_time tm;

if (strncmp(rtc->dev.bus_id,
CONFIG_RTC_HCTOSYS_DEVICE,
BUS_ID_SIZE) != 0)
return 0;

rtc_read_time(rtc, &tm);
rtc_tm_to_time(&tm, &oldtime);

/* RTC precision is 1 second; adjust delta for avg 1/2 sec err */
set_normalized_timespec(&delta,
xtime.tv_sec - oldtime,
xtime.tv_nsec - (NSEC_PER_SEC >> 1));

return 0;
}

static int rtc_resume(struct device *dev)
{
struct rtc_device *rtc = to_rtc_device(dev);
struct rtc_time tm;
time_t newtime;
struct timespec time;

if (strncmp(rtc->dev.bus_id,
CONFIG_RTC_HCTOSYS_DEVICE,
BUS_ID_SIZE) != 0)
return 0;

rtc_read_time(rtc, &tm);
if (rtc_valid_tm(&tm) != 0) {
pr_debug("%s: bogus resume time\n", rtc->dev.bus_id);
return 0;
}
rtc_tm_to_time(&tm, &newtime);
if (newtime <= oldtime) {
if (newtime < oldtime)
pr_debug("%s: time travel!\n", rtc->dev.bus_id);
return 0;
}

/* restore wall clock using delta against this RTC;
* adjust again for avg 1/2 second RTC sampling error
*/
set_normalized_timespec(&time,
newtime + delta.tv_sec,
(NSEC_PER_SEC >> 1) + delta.tv_nsec);
do_settimeofday(&time);

return 0;
}

#else
#define rtc_suspend NULL
#define rtc_resume NULL
#endif


/**
* rtc_device_register - register w/ RTC class
* @dev: the device to register
Expand Down Expand Up @@ -143,6 +215,8 @@ static int __init rtc_init(void)
printk(KERN_ERR "%s: couldn't create class\n", __FILE__);
return PTR_ERR(rtc_class);
}
rtc_class->suspend = rtc_suspend;
rtc_class->resume = rtc_resume;
rtc_dev_init();
rtc_sysfs_init(rtc_class);
return 0;
Expand Down
30 changes: 0 additions & 30 deletions drivers/rtc/rtc-at91rm9200.c
Original file line number Diff line number Diff line change
Expand Up @@ -348,21 +348,10 @@ static int __exit at91_rtc_remove(struct platform_device *pdev)

/* AT91RM9200 RTC Power management control */

static struct timespec at91_rtc_delta;
static u32 at91_rtc_imr;

static int at91_rtc_suspend(struct platform_device *pdev, pm_message_t state)
{
struct rtc_time tm;
struct timespec time;

time.tv_nsec = 0;

/* calculate time delta for suspend */
at91_rtc_readtime(&pdev->dev, &tm);
rtc_tm_to_time(&tm, &time.tv_sec);
save_time_delta(&at91_rtc_delta, &time);

/* this IRQ is shared with DBGU and other hardware which isn't
* necessarily doing PM like we are...
*/
Expand All @@ -374,36 +363,17 @@ static int at91_rtc_suspend(struct platform_device *pdev, pm_message_t state)
else
at91_sys_write(AT91_RTC_IDR, at91_rtc_imr);
}

pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
1900 + tm.tm_year, tm.tm_mon, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);

return 0;
}

static int at91_rtc_resume(struct platform_device *pdev)
{
struct rtc_time tm;
struct timespec time;

time.tv_nsec = 0;

at91_rtc_readtime(&pdev->dev, &tm);
rtc_tm_to_time(&tm, &time.tv_sec);
restore_time_delta(&at91_rtc_delta, &time);

if (at91_rtc_imr) {
if (device_may_wakeup(&pdev->dev))
disable_irq_wake(AT91_ID_SYS);
else
at91_sys_write(AT91_RTC_IER, at91_rtc_imr);
}

pr_debug("%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __FUNCTION__,
1900 + tm.tm_year, tm.tm_mon, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);

return 0;
}
#else
Expand Down
17 changes: 0 additions & 17 deletions drivers/rtc/rtc-omap.c
Original file line number Diff line number Diff line change
Expand Up @@ -488,19 +488,10 @@ static int __devexit omap_rtc_remove(struct platform_device *pdev)

#ifdef CONFIG_PM

static struct timespec rtc_delta;
static u8 irqstat;

static int omap_rtc_suspend(struct platform_device *pdev, pm_message_t state)
{
struct rtc_time rtc_tm;
struct timespec time;

time.tv_nsec = 0;
omap_rtc_read_time(NULL, &rtc_tm);
rtc_tm_to_time(&rtc_tm, &time.tv_sec);

save_time_delta(&rtc_delta, &time);
irqstat = rtc_read(OMAP_RTC_INTERRUPTS_REG);

/* FIXME the RTC alarm is not currently acting as a wakeup event
Expand All @@ -517,14 +508,6 @@ static int omap_rtc_suspend(struct platform_device *pdev, pm_message_t state)

static int omap_rtc_resume(struct platform_device *pdev)
{
struct rtc_time rtc_tm;
struct timespec time;

time.tv_nsec = 0;
omap_rtc_read_time(NULL, &rtc_tm);
rtc_tm_to_time(&rtc_tm, &time.tv_sec);

restore_time_delta(&rtc_delta, &time);
if (device_may_wakeup(&pdev->dev))
disable_irq_wake(omap_rtc_alarm);
else
Expand Down
22 changes: 0 additions & 22 deletions drivers/rtc/rtc-s3c.c
Original file line number Diff line number Diff line change
Expand Up @@ -548,37 +548,15 @@ static int ticnt_save;

static int s3c_rtc_suspend(struct platform_device *pdev, pm_message_t state)
{
struct rtc_time tm;
struct timespec time;

time.tv_nsec = 0;

/* save TICNT for anyone using periodic interrupts */

ticnt_save = readb(s3c_rtc_base + S3C2410_TICNT);

/* calculate time delta for suspend */

s3c_rtc_gettime(&pdev->dev, &tm);
rtc_tm_to_time(&tm, &time.tv_sec);
save_time_delta(&s3c_rtc_delta, &time);
s3c_rtc_enable(pdev, 0);

return 0;
}

static int s3c_rtc_resume(struct platform_device *pdev)
{
struct rtc_time tm;
struct timespec time;

time.tv_nsec = 0;

s3c_rtc_enable(pdev, 1);
s3c_rtc_gettime(&pdev->dev, &tm);
rtc_tm_to_time(&tm, &time.tv_sec);
restore_time_delta(&s3c_rtc_delta, &time);

writeb(ticnt_save, s3c_rtc_base + S3C2410_TICNT);
return 0;
}
Expand Down

0 comments on commit 7ca1d48

Please sign in to comment.