* rtcwakeup.c
2006-07-15 19:40 [patch 2.6.18-rc1-git] rtc-acpi, with wakeup support David Brownell
@ 2006-07-15 19:48 ` David Brownell
2006-07-24 19:44 ` [patch 2.6.18-rc1-git] rtc-acpi, with wakeup support David Brownell
2006-09-29 6:54 ` [patch 2.6.18-git] " David Brownell
2 siblings, 0 replies; 5+ messages in thread
From: David Brownell @ 2006-07-15 19:48 UTC (permalink / raw)
To: Linux Kernel list
> p.s. A followup message will include a userspace program which
> makes it easier to try the RTC wakeup mechanism.
For example, if your system actually supports RTC wakeup correctly:
rtcwake -t $(date -u -d 'tomorrow 6:30am' +'%s') -m mem
will set up the system to wake up tomorrow at 6:30am, then suspend-to-RAM by
writing "mem" to /sys/power/state.
Or for testing kernels, unattended scripts like this may help:
while true
do
echo "suspend-to-disk for 10 minutes starting $(date)"
rtcwake -s $((10 * 60)) -m disk
sleep 500
done
If there are many RTC utilities out there that aren't x86-specific, I didn't
happen to find them. Ergo this one.
- Dave
#include <stdio.h>
#include <getopt.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <linux/rtc.h>
// #define DEBUG
/*
* rtcwake -- enter a system sleep state until specified wakeup time.
*
* This uses cross-platform Linux interfaces to enter a system sleep state,
* and leave it no later than a specified time. It uses any RTC framework
* driver that supports standard driver model wakeup flags.
*
* This is normally used like the old "apmsleep" utility, to wake from a
* suspend state like ACPI S1 (standby) or S3 (suspend-to-RAM). Most
* platforms can implement those without analogues of BIOS, APM, or ACPI.
*
* On some systems, this can also be used like "nvram-wakeup", waking
* from states like ACPI S4 (suspend to disk). Not all systems have
* persistent media that are appropriate for such suspend modes.
*/
static char *progname;
#ifdef DEBUG
#define VERSION "1.0 dev (" __DATE__ " " __TIME__ ")"
#else
#define VERSION "0.8"
#endif
static unsigned verbose;
static int may_wakeup(const char *devname)
{
char buf[128], *s;
FILE *f;
snprintf(buf, sizeof buf, "/sys/class/rtc/%s/device/power/wakeup",
devname);
f = fopen(buf, "r");
if (!f) {
perror(buf);
return 0;
}
fgets(buf, sizeof buf, f);
fclose(f);
s = strchr(buf, '\n');
if (!s)
return 0;
*s = 0;
/* wakeup events could be disabled or not supported */
return strcmp(buf, "enabled") == 0;
}
/* all times should be in UTC */
static time_t sys_time;
static time_t rtc_time;
static int get_basetimes(int fd)
{
struct tm tm;
time_t offset;
struct rtc_time rtc;
/* record offset of mktime(), so we can reverse it */
memset(&tm, 0, sizeof tm);
tm.tm_year = 70;
offset = mktime(&tm);
/* read system and rtc clocks "at the same time"; both in UTC */
sys_time = time(0);
if (sys_time == (time_t)-1) {
perror("read system time");
return 0;
}
if (ioctl(fd, RTC_RD_TIME, &rtc) < 0) {
perror("read rtc time");
return 0;
}
/* convert rtc_time to normal arithmetic-friendly form */
tm.tm_sec = rtc.tm_sec;
tm.tm_min = rtc.tm_min;
tm.tm_hour = rtc.tm_hour;
tm.tm_mday = rtc.tm_mday;
tm.tm_mon = rtc.tm_mon;
tm.tm_year = rtc.tm_year;
tm.tm_wday = rtc.tm_wday;
tm.tm_yday = rtc.tm_yday;
tm.tm_isdst = rtc.tm_isdst;
if (verbose) {
printf("\toffset = %ld\n", offset);
printf("\tsystime = %s", asctime(gmtime(&sys_time)));
printf("\trtctime = %s", asctime(&tm));
}
rtc_time = mktime(&tm) - offset;
if (rtc_time == (time_t)-1) {
perror("convert rtc time");
return 0;
}
return 1;
}
static int setup_alarm(int fd, time_t *wakeup)
{
struct tm *tm;
struct rtc_wkalrm wake;
tm = gmtime(wakeup);
wake.time.tm_sec = tm->tm_sec;
wake.time.tm_min = tm->tm_min;
wake.time.tm_hour = tm->tm_hour;
wake.time.tm_mday = tm->tm_mday;
wake.time.tm_mon = tm->tm_mon;
wake.time.tm_year = tm->tm_year;
wake.time.tm_wday = tm->tm_wday;
wake.time.tm_yday = tm->tm_yday;
wake.time.tm_isdst = tm->tm_isdst;
/* many rtc alarms only support up to 24 hours from 'now' ... */
if ((rtc_time + 24 * 60 * 60) > *wakeup) {
if (ioctl(fd, RTC_ALM_SET, &wake.time) < 0) {
perror("set rtc alarm");
return 0;
}
if (ioctl(fd, RTC_AIE_ON, 0) < 0) {
perror("enable rtc alarm");
return 0;
}
/* ... so use the "more than 24 hours" request only if we must */
} else {
/* avoid an extra AIE_ON call */
wake.enabled = 1;
if (ioctl(fd, RTC_WKALM_SET, &wake) < 0) {
perror("set rtc wake alarm");
return 0;
}
}
return 1;
}
static void suspend_system(const char *suspend)
{
FILE *f = fopen("/sys/power/state", "w");
if (!f) {
perror("/sys/power/state");
return;
}
fprintf(f, "%s\n", suspend);
fflush(f);
/* this executes after wake from suspend */
fclose(f);
}
int main(int argc, char **argv)
{
static char *devname = "rtc0";
static unsigned seconds = 0;
static char *suspend = "standby";
int t;
int fd;
time_t alarm = 0;
progname = strrchr(argv[0], '/');
if (progname)
progname++;
else
progname = argv[0];
if (chdir("/dev/") < 0) {
perror("chdir /dev");
return 1;
}
while ((t = getopt(argc, argv, "d:m:s:t:Vv")) != EOF) {
switch (t) {
case 'd':
devname = optarg;
break;
/* what system power mode to use? for now handle only
* standardized mode names; eventually when systems define
* their own state names, parse /sys/power/state.
*
* "on" is used just to test the RTC alarm mechanism,
* bypassing all the wakeup-from-sleep infrastructure.
*/
case 'm':
if (strcmp(optarg, "standby") == 0
|| strcmp(optarg, "mem") == 0
|| strcmp(optarg, "disk") == 0
|| strcmp(optarg, "on") == 0
) {
suspend = optarg;
break;
}
printf("%s: unrecognized suspend state '%s'\n",
progname, optarg);
goto usage;
/* alarm time, seconds-to-sleep (relative) */
case 's':
t = atoi(optarg);
if (t < 0) {
printf("%s: illegal interval %s seconds\n",
progname, optarg);
goto usage;
}
seconds = t;
break;
/* alarm time, time_t (absolute, seconds since 1/1 1970 UTC) */
case 't':
t = atoi(optarg);
if (t < 0) {
printf("%s: illegal time_t value %s\n",
progname, optarg);
goto usage;
}
alarm = t;
break;
case 'v':
verbose++;
break;
case 'V':
printf("%s: version %s\n", progname, VERSION);
break;
default:
usage:
printf("usage: %s [options]"
"\n\t"
"-d rtc0|rtc1|...\t(select rtc)"
"\n\t"
"-m standby|mem|...\t(sleep mode)"
"\n\t"
"-s seconds\t\t(seconds to sleep)"
"\n\t"
"-t time_t\t\t(time to wake)"
"\n\t"
"-v\t\t\t(verbose messages)"
"\n\t"
"-V\t\t\t(show version)"
"\n",
progname);
return 1;
}
}
if (!alarm && !seconds) {
printf("%s: must provide wake time\n", progname);
goto usage;
}
/* this RTC must exist and (if we'll sleep) be wakeup-enabled */
fd = open(devname, O_RDONLY);
if (fd < 0) {
perror(devname);
return 1;
}
if (strcmp(suspend, "on") != 0 && !may_wakeup(devname)) {
printf("%s: %s not enabled for wakeup events\n",
progname, devname);
return 1;
}
/* relative or absolute alarm time, normalized to time_t */
if (!get_basetimes(fd))
return 1;
if (verbose)
printf("alarm %ld, sys_time %ld, rtc_time %ld, seconds %u\n",
alarm, sys_time, rtc_time, seconds);
if (alarm) {
if (alarm < sys_time) {
printf("%s: time doesn't go backward to %s",
progname, ctime(&alarm));
return 1;
}
alarm += sys_time - rtc_time;
} else
alarm = rtc_time + seconds + 1;
if (setup_alarm(fd, &alarm) < 0)
return 1;
printf("%s: wakeup from \"%s\" using %s at %s",
progname, suspend, devname,
ctime(&alarm));
fflush(stdout);
usleep(10 * 1000);
if (strcmp(suspend, "on") != 0)
suspend_system(suspend);
else {
unsigned long data;
(void) read(fd, &data, sizeof data);
}
if (ioctl(fd, RTC_AIE_OFF, 0) < 0)
perror("disable rtc alarm interrupt");
close(fd);
return 0;
}
^ permalink raw reply [flat|nested] 5+ messages in thread* [patch 2.6.18-git] rtc-acpi, with wakeup support
2006-07-15 19:40 [patch 2.6.18-rc1-git] rtc-acpi, with wakeup support David Brownell
2006-07-15 19:48 ` rtcwakeup.c David Brownell
2006-07-24 19:44 ` [patch 2.6.18-rc1-git] rtc-acpi, with wakeup support David Brownell
@ 2006-09-29 6:54 ` David Brownell
2 siblings, 0 replies; 5+ messages in thread
From: David Brownell @ 2006-09-29 6:54 UTC (permalink / raw)
To: Linux Kernel list; +Cc: Alessandro Zummo
[ NOT FOR MERGING -- just a patch refresh ]
This is a "new RTC framework" driver for the ACPI RTC. It's a standard
AT-compatible RTC, with ACPI extensions that are mostly invisible to the
core code:
- new registers, for wakeup intervals longer than a day (up to a year)
and a century register for Y2K/Y3K awareness;
- more nvram (not that this driver cares);
- system wakeup hooks, through ACPI (that is, southbridge hooks)
Advantages of this vs. drivers/char/rtc.c (use one _or_ the other) include:
- Driver binding now has "real driver model support":
* By preference, this uses PNPACPI;
* If that's not configured, it creates a platform_device (but that
should really live someplace else, maybe ACPI init code)
- /sys/devices/.../power/wakeup and normal rtc ioctls manage RTC
wakeups using platform-neutral API (also supported on some embedded
Linuxes) not that yucky /proc/acpi/alarm interface.
Currently unsupported features:
- changing periodic irq frequency (char/rtc.c allows that).
- using the optional "century" register (no Linux code does, yet).
Issues of note:
- Some tools like "hwclock" expect /dev/rtc not /dev/rtc0; patches
are available for util-linux-2.13-pre7 and busybox.
- ACPI wakeup doesn't always work: after hardware wakeup, the software
re-activation commonly wedges. Some systems are very usable after
timer wakeup from S3/STR or S4/swsusp, other are not. (This issue
is generic to ACPI, not specific to RTC.) Workaround by disabling
RTC wakeup via "echo disable > /sys/devices/.../wakeup".
Those issues are not caused by this code, just exposed by it.
A significant change from the previous version is that this one doesn't
require PNPACPI ... although the way it works without that is klugey,
the innards are now factored better. Eventually arch/*/ platform code
on PCs should probably create the platform device. ACPI would then be
able to just add the relevant extensions to platform_data, providing a
relatively clean solution to the enumeration problem, probably even one
that could work on non-ACPI hardware.
Index: g26/drivers/rtc/Kconfig
===================================================================
--- g26.orig/drivers/rtc/Kconfig 2006-09-28 10:58:54.000000000 -0700
+++ g26/drivers/rtc/Kconfig 2006-09-28 11:04:49.000000000 -0700
@@ -95,6 +95,21 @@ config RTC_INTF_DEV_UIE_EMUL
comment "RTC drivers"
depends on RTC_CLASS
+config RTC_DRV_ACPI
+ tristate "ACPI real time clock"
+ depends on RTC_CLASS && ACPI
+ help
+ Say "yes" here to get direct support for the real time clock
+ found in every ACPI-based PC.
+
+ This RTC can be used to wake the system from suspend-to-RAM or
+ standby sleep modes, and on some systems from suspend-to-disk.
+
+ This obsoletes the old /proc/acpi/alarm interface.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-acpi.
+
config RTC_DRV_X1205
tristate "Xicor/Intersil X1205"
depends on RTC_CLASS && I2C
@@ -221,11 +236,14 @@ config RTC_DRV_S3C
will be called rtc-s3c.
config RTC_DRV_M48T86
- tristate "ST M48T86/Dallas DS12887"
- depends on RTC_CLASS
+ tristate "ST M48T86, Dallas DS12887 and compatible"
+ depends on RTC_CLASS && !X86_PC
help
- If you say Y here you will get support for the
- ST M48T86 and Dallas DS12887 RTC chips.
+ If you say Y here you will get support for the ST M48T86, Dallas
+ DS12887, and compatible RTC chips. To software, these are all
+ but identical to the "cmos clock" used in x86 PCs, although this
+ driver does not obey the locking or ACPI integration rules
+ used on that architecture.
This driver can also be built as a module. If so, the module
will be called rtc-m48t86.
Index: g26/drivers/rtc/Makefile
===================================================================
--- g26.orig/drivers/rtc/Makefile 2006-09-28 10:58:54.000000000 -0700
+++ g26/drivers/rtc/Makefile 2006-09-28 11:04:49.000000000 -0700
@@ -15,6 +15,7 @@ obj-$(CONFIG_RTC_INTF_SYSFS) += rtc-sysf
obj-$(CONFIG_RTC_INTF_PROC) += rtc-proc.o
obj-$(CONFIG_RTC_INTF_DEV) += rtc-dev.o
+obj-$(CONFIG_RTC_DRV_ACPI) += rtc-acpi.o
obj-$(CONFIG_RTC_DRV_X1205) += rtc-x1205.o
obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o
Index: g26/drivers/rtc/rtc-acpi.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ g26/drivers/rtc/rtc-acpi.c 2006-09-28 11:04:49.000000000 -0700
@@ -0,0 +1,725 @@
+/*
+ * RTC driver for ACPI
+ *
+ * Copyright (C) 1996 Paul Gortmaker (drivers/char/rtc.c)
+ * Copyright (C) 2006 David Brownell (convert to new frameworks)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+/*
+ * The original "cmos clock" chip was an MC146818 chip, now obsolete,
+ * which defined the register interface now provided by all PCs and
+ * incorporated into ACPI. Modern PC chipsets integrate an MC146818
+ * clone in their southbridge, rather than using discrete chips like
+ * the DS12887 and M48T86.
+ *
+ * That register API is also used directly by various other drivers,
+ * (notably for integrated NVRAM) and utilities ... so **ALL** calls
+ * to CMOS_READ and CMOS_WRITE must be done with interrupts disabled,
+ * holding the global rtc_lock, to exclude those other drivers and
+ * utilities (though userspace may not cooperate).
+ *
+ * This driver leverages the RTC framework for architecture-neutral
+ * programming interfaces, PNPACPI (by preference) to integrate with
+ * the driver model, the driver model /sys/.../power/wakeup device
+ * attribute to manage ACPI's RTC wake event mechanism, and ACPI
+ * extensions for alarms expiring up to a year in the future.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/platform_device.h>
+#include <linux/mod_devicetable.h>
+
+#include <acpi/acpi.h>
+#include <asm/rtc.h>
+
+
+enum rtc_type {
+ RTC_UNKNOWN = 0,
+ RTC_AT,
+ RTC_PIIX4,
+ RTC_DALLAS,
+};
+
+struct cmos_rtc {
+ struct rtc_device *rtc;
+ struct device *dev;
+ int irq;
+ struct resource *iomem;
+ u8 acpi;
+ u8 day_alrm;
+ u8 mon_alrm;
+};
+
+#ifdef CONFIG_ACPI_SLEEP
+#define use_acpi_wake(cmos) ((cmos)->acpi)
+#else
+#define use_acpi_wake(cmos) 0
+#endif
+
+static const char driver_name[] = "rtc-acpi";
+
+/*----------------------------------------------------------------*/
+
+static int cmos_read_time(struct device *dev, struct rtc_time *t)
+{
+ /* REVISIT: if the clock has a "century" register, use
+ * that instead of the heuristic in get_rtc_time().
+ * That'll make Y3K compatility (year > 2070) easy!
+ */
+ get_rtc_time(t);
+ return 0;
+}
+
+static int cmos_set_time(struct device *dev, struct rtc_time *t)
+{
+ /* REVISIT: set the "century" register if available */
+ return set_rtc_time(t);
+}
+
+static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+ unsigned char rtc_control;
+
+ /* Basic alarms only support hour, minute, and seconds fields.
+ * Some also support day and month, for alarms up to a year in
+ * the future.
+ */
+ spin_lock_irq(&rtc_lock);
+ t->time.tm_sec = CMOS_READ(RTC_SECONDS_ALARM);
+ t->time.tm_min = CMOS_READ(RTC_MINUTES_ALARM);
+ t->time.tm_hour = CMOS_READ(RTC_HOURS_ALARM);
+
+ if (cmos->day_alrm) {
+ t->time.tm_mday = CMOS_READ(cmos->day_alrm);
+ if (!t->time.tm_mday || ((unsigned)t->time.tm_mday) > 0x31)
+ t->time.tm_mday = -1;
+ } else
+ t->time.tm_mday = -1;
+
+ if (cmos->mon_alrm) {
+ t->time.tm_mon = CMOS_READ(cmos->mon_alrm);
+ if (!t->time.tm_mon || ((unsigned)t->time.tm_mon) > 0x12)
+ t->time.tm_mon = -1;
+ } else
+ t->time.tm_mon = -1;
+
+ rtc_control = CMOS_READ(RTC_CONTROL);
+ spin_unlock_irq(&rtc_lock);
+
+ if (((unsigned)t->time.tm_sec) < 0x60)
+ BCD_TO_BIN(t->time.tm_sec);
+ else
+ t->time.tm_sec = -1;
+ if (((unsigned)t->time.tm_min) < 0x60)
+ BCD_TO_BIN(t->time.tm_min);
+ else
+ t->time.tm_min = -1;
+ if (((unsigned)t->time.tm_hour) < 0x24)
+ BCD_TO_BIN(t->time.tm_hour);
+ else
+ t->time.tm_hour = -1;
+
+ if (t->time.tm_mday != -1)
+ t->time.tm_mday = BCD2BIN(t->time.tm_mday);
+ if (t->time.tm_mon != -1)
+ t->time.tm_mon = BCD2BIN(t->time.tm_mon) - 1;
+ t->time.tm_year = -1;
+
+ t->pending = !!(rtc_control & RTC_AIE);
+ t->enabled = t->pending && device_may_wakeup(dev);
+
+ return 0;
+}
+
+static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+ unsigned char mon, mday, hrs, min, sec;
+ unsigned char rtc_control;
+
+ /* Writing 0xff means "don't care" or "match all". */
+
+ mon = t->time.tm_mon;
+ mon = (mon < 12) ? BIN2BCD(mon) : 0xff;
+ mon++;
+
+ mday = t->time.tm_mday;
+ mday = (mday >= 1 && mday <= 31) ? BIN2BCD(mday) : 0xff;
+
+ hrs = t->time.tm_hour;
+ hrs = (hrs < 24) ? BIN2BCD(hrs) : 0xff;
+
+ min = t->time.tm_min;
+ min = (min < 60) ? BIN2BCD(min) : 0xff;
+
+ sec = t->time.tm_sec;
+ sec = (sec < 60) ? BIN2BCD(sec) : 0xff;
+
+ /* scrub old acpi state */
+ if (use_acpi_wake(cmos)) {
+ acpi_disable_event(ACPI_EVENT_RTC, 0);
+ acpi_clear_event(ACPI_EVENT_RTC);
+ }
+
+ spin_lock_irq(&rtc_lock);
+
+ /* next rtc irq must not be from previous alarm setting */
+ rtc_control = CMOS_READ(RTC_CONTROL);
+ rtc_control &= ~RTC_AIE;
+ CMOS_WRITE(rtc_control, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+
+ /* update alarm */
+ CMOS_WRITE(hrs, RTC_HOURS_ALARM);
+ CMOS_WRITE(min, RTC_MINUTES_ALARM);
+ CMOS_WRITE(sec, RTC_SECONDS_ALARM);
+
+ /* the system may support an "enhanced" alarm */
+ if (cmos->day_alrm)
+ CMOS_WRITE(mday, cmos->day_alrm);
+ if (cmos->mon_alrm)
+ CMOS_WRITE(mon, cmos->mon_alrm);
+
+ if (t->enabled) {
+ rtc_control |= RTC_AIE;
+ CMOS_WRITE(rtc_control, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+ }
+
+ spin_unlock_irq(&rtc_lock);
+
+#ifdef DEBUG
+/* FOR DEBUG ... does this ACPI event mechanism behave?
+ * I've never observed it to work correctly, on any system...
+ * we'd prefer to call this with the lock held, avoiding a race.
+ */
+ if (use_acpi_wake(cmos)) {
+ acpi_enable_event(ACPI_EVENT_RTC, 0);
+ }
+#endif
+
+ return 0;
+}
+
+static int cmos_read_freq(struct device *dev)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+ int freq;
+ unsigned long flags;
+
+ spin_lock_irqsave(&rtc_lock, flags);
+ freq = CMOS_READ(RTC_FREQ_SELECT);
+ spin_unlock_irqrestore(&rtc_lock, flags);
+
+ /* only the oldest "cmos" RTCs used non-32K clock */
+ WARN_ON((freq & RTC_DIV_CTL) != RTC_REF_CLCK_32KHZ);
+
+ freq &= 0x0f;
+ if (freq)
+ freq = 1 << (16 - freq);
+
+ if (freq != cmos->rtc->irq_freq) {
+ pr_debug("%s: recorded irq_freq %d was wrong (not %d)\n",
+ cmos->rtc->class_dev.class_id,
+ cmos->rtc->irq_freq, freq);
+ cmos->rtc->irq_freq = freq;
+ }
+ return freq;
+}
+
+static int cmos_set_freq(struct device *dev, int freq)
+{
+ /* FIXME implement this ... and fix the rtc core to know
+ * how to map RTC_IRQP_SET into irq_set_freq(). Maybe even
+ * handle IRQP_READ using rtc->irq_freq.
+ */
+ return -ENOIOCTLCMD;
+}
+
+#if defined(CONFIG_RTC_INTF_DEV) || defined(CONFIG_RTC_INTF_DEV_MODULE)
+
+static int
+cmos_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+ unsigned char rtc_control;
+ unsigned long flags;
+
+ switch (cmd) {
+ case RTC_AIE_OFF:
+ case RTC_AIE_ON:
+ case RTC_UIE_OFF:
+ case RTC_UIE_ON:
+ case RTC_PIE_OFF:
+ case RTC_PIE_ON:
+ break;
+ case RTC_IRQP_READ: /* read periodic IRQ rate */
+ return put_user(cmos_read_freq(dev),
+ (unsigned long __user *)arg);
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ spin_lock_irqsave(&rtc_lock, flags);
+ rtc_control = CMOS_READ(RTC_CONTROL);
+ switch (cmd) {
+ case RTC_AIE_OFF: /* alarm off */
+ rtc_control &= ~RTC_AIE;
+ break;
+ case RTC_AIE_ON: /* alarm on */
+ rtc_control |= RTC_AIE;
+ break;
+ case RTC_UIE_OFF: /* update off */
+ rtc_control &= ~RTC_UIE;
+ break;
+ case RTC_UIE_ON: /* update on */
+ rtc_control |= RTC_UIE;
+ break;
+ case RTC_PIE_OFF: /* periodic off */
+ rtc_control &= ~RTC_PIE;
+ break;
+ case RTC_PIE_ON: /* periodic on */
+ rtc_control |= RTC_PIE;
+ break;
+ }
+ CMOS_WRITE(rtc_control, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+ spin_unlock_irqrestore(&rtc_lock, flags);
+ return 0;
+}
+
+#else
+#define cmos_rtc_ioctl NULL
+#endif
+
+#if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
+
+static int cmos_procfs(struct device *dev, struct seq_file *seq)
+{
+ unsigned char rtc_control, valid;
+
+ spin_lock_irq(&rtc_lock);
+ rtc_control = CMOS_READ(RTC_CONTROL);
+ valid = CMOS_READ(RTC_VALID);
+ spin_unlock_irq(&rtc_lock);
+
+ return seq_printf(seq,
+ "periodic_IRQ\t: %s\n"
+ "periodic_freq\t: %d\n"
+ "batt_status\t: %s\n",
+ (rtc_control & RTC_PIE) ? "yes" : "no",
+ cmos_read_freq(dev),
+ (valid & RTC_VRT) ? "okay" : "dead");
+}
+
+#else
+#define cmos_procfs NULL
+#endif
+
+static struct rtc_class_ops cmos_rtc_ops = {
+ .ioctl = cmos_rtc_ioctl,
+ .read_time = cmos_read_time,
+ .set_time = cmos_set_time,
+ .read_alarm = cmos_read_alarm,
+ .set_alarm = cmos_set_alarm,
+ .proc = cmos_procfs,
+ .irq_set_freq = cmos_set_freq,
+};
+
+/*----------------------------------------------------------------*/
+
+static struct cmos_rtc cmos_rtc;
+
+static irqreturn_t cmos_interrupt(int irq, void *p, struct pt_regs *r)
+{
+ u8 irqstat;
+
+ spin_lock (&rtc_lock);
+ irqstat = CMOS_READ(RTC_INTR_FLAGS);
+ spin_unlock (&rtc_lock);
+
+ if (irqstat) {
+ /* NOTE: irqstat may have e.g. RTC_PF set
+ * even when RTC_PIE is clear...
+ */
+ rtc_update_irq(p, 1, irqstat);
+ return IRQ_HANDLED;
+ } else
+ return IRQ_NONE;
+}
+
+#ifdef CONFIG_PM
+
+static u32 cmos_acpi_handler(void *p)
+{
+ acpi_clear_event(ACPI_EVENT_RTC);
+ acpi_disable_event(ACPI_EVENT_RTC, 0);
+
+ /* REVISIT doesn't seem like acpi events work ... */
+
+printk("%s\n", __FUNCTION__);
+
+ // rtc_update_irq(p, 1, RTC_IRQF | RTC_AF);
+
+ return ACPI_INTERRUPT_HANDLED;
+}
+
+#endif
+
+#ifdef CONFIG_PNPACPI
+#define is_pnpacpi() 1
+#else
+#define is_pnpacpi() 0
+#endif
+
+static int __devinit
+cmos_do_probe(struct device *dev, enum rtc_type type,
+ struct resource *ports, int rtc_irq)
+{
+ int retval = 0;
+ unsigned char rtc_control;
+ int rtcs4 = 0;
+ int century = 0;
+
+ /* there can be only one ... */
+ if (cmos_rtc.dev)
+ return -EBUSY;
+
+ if (!ports || rtc_irq < 0)
+ return -ENODEV;
+
+ cmos_rtc.irq = rtc_irq;
+ cmos_rtc.iomem = ports;
+
+ /* REVISIT evidently access to this acpi table is supposed to
+ * be locked, but it's unclear why that'd be needed except
+ * during _very_ early boot ...
+ */
+ if (acpi_gbl_FADT) {
+ /* REVISIT there are many partial-acpi boot modes;
+ * only set the acpi flag if this system is running
+ * in one of the modes that supports it.
+ */
+ cmos_rtc.acpi = 1;
+
+ cmos_rtc.mon_alrm = acpi_gbl_FADT->mon_alrm;
+ cmos_rtc.day_alrm = acpi_gbl_FADT->day_alrm;
+ rtcs4 = acpi_gbl_FADT->rtcs4;
+ century = acpi_gbl_FADT->century;
+ }
+
+ dev_info(dev, "%s RTC%s%s, 1 %s alarm\n",
+ ({ char *s; switch (type) {
+ case RTC_AT: s = "AT compatible"; break;
+ case RTC_PIIX4: s = "PIIX4"; break;
+ case RTC_DALLAS:s = "Dallas"; break;
+ default: s = "*unknown*"; break;
+ } s; }),
+ rtcs4 ? " (S4wake)" : "",
+ century ? " (y3k)" : "",
+ cmos_rtc.mon_alrm
+ ? "year"
+ : (cmos_rtc.day_alrm ? "month" : "day")
+ );
+
+ cmos_rtc.rtc = rtc_device_register(driver_name, dev,
+ &cmos_rtc_ops, THIS_MODULE);
+ if (IS_ERR(cmos_rtc.rtc))
+ return PTR_ERR(cmos_rtc.rtc);
+
+ cmos_rtc.dev = dev;
+ dev_set_drvdata(dev, &cmos_rtc);
+
+ retval = request_irq(rtc_irq, cmos_interrupt, IRQF_DISABLED,
+ cmos_rtc.rtc->class_dev.class_id,
+ &cmos_rtc.rtc->class_dev);
+ if (retval < 0) {
+ printk(KERN_ERR "%s: IRQ %d already in use\n",
+ cmos_rtc.rtc->class_dev.class_id,
+ rtc_irq);
+ goto cleanup0;
+ }
+
+ /* platform and pnp busses handle resources incompatibly... */
+ if (is_pnpacpi()) {
+ retval = request_resource(&ioport_resource, ports);
+ if (retval < 0) {
+ printk(KERN_ERR "%s: i/o registers already in use\n",
+ cmos_rtc.rtc->class_dev.class_id);
+ goto cleanup1;
+ }
+ }
+ rename_region(ports, cmos_rtc.rtc->class_dev.class_id);
+
+ spin_lock_irq(&rtc_lock);
+
+ /* force periodic irq to CMOS reset default of 1024Hz */
+ CMOS_WRITE(RTC_REF_CLCK_32KHZ | 0x06, RTC_FREQ_SELECT);
+ cmos_rtc.rtc->irq_freq = 1024;
+
+ /* force to 24 hour time mode, bcd, disable irqs */
+ rtc_control = CMOS_READ(RTC_CONTROL);
+ rtc_control |= RTC_24H;
+ rtc_control &= ~(RTC_PIE|RTC_AIE|RTC_UIE|RTC_DM_BINARY);
+ CMOS_WRITE(rtc_control, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+
+ spin_unlock_irq(&rtc_lock);
+
+ /* see acpi spec 4.7.2.4 ... it's always a wakeup source from
+ * S1/S2/S3 and often S4 (when RTC_S4 is set in FADT).
+ */
+ device_init_wakeup(dev, 1);
+
+ if (use_acpi_wake(&cmos_rtc)) {
+ acpi_install_fixed_event_handler(ACPI_EVENT_RTC,
+ cmos_acpi_handler, &cmos_rtc.rtc->class_dev);
+ acpi_disable_event(ACPI_EVENT_RTC, 0);
+ }
+
+ return 0;
+
+cleanup1:
+ free_irq(rtc_irq, &cmos_rtc.rtc->class_dev);
+cleanup0:
+ rtc_device_unregister(cmos_rtc.rtc);
+ return retval;
+}
+
+static void cmos_do_shutdown(void)
+{
+ unsigned char rtc_control;
+
+ spin_lock_irq(&rtc_lock);
+ rtc_control = CMOS_READ(RTC_CONTROL);
+ rtc_control &= ~(RTC_PIE|RTC_AIE|RTC_UIE);
+ CMOS_WRITE(rtc_control, RTC_CONTROL);
+ CMOS_READ(RTC_INTR_FLAGS);
+ spin_unlock_irq(&rtc_lock);
+}
+
+static void __devexit cmos_do_remove(struct device *dev)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+
+ cmos_do_shutdown();
+
+ if (use_acpi_wake(cmos)) {
+ acpi_disable_event(ACPI_EVENT_RTC, 0);
+ acpi_remove_fixed_event_handler(ACPI_EVENT_RTC,
+ cmos_acpi_handler);
+ }
+ device_init_wakeup(dev, 0);
+
+ if (dev->bus != &platform_bus_type)
+ release_resource(cmos->iomem);
+ rename_region(cmos->iomem, NULL);
+
+ free_irq(cmos->irq, &cmos_rtc.rtc->class_dev);
+
+ rtc_device_unregister(cmos_rtc.rtc);
+
+ cmos_rtc.dev = NULL;
+ dev_set_drvdata(dev, NULL);
+}
+
+#ifdef CONFIG_PM
+
+static int cmos_suspend(struct device *dev, pm_message_t mesg)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+ int do_wake = cmos->acpi && device_may_wakeup(dev);
+
+ /* force RTC_EN value during system sleep states */
+ if (use_acpi_wake(cmos)) {
+ if (do_wake)
+ acpi_enable_event(ACPI_EVENT_RTC, 0);
+ else
+ acpi_disable_event(ACPI_EVENT_RTC, 0);
+ }
+
+pr_debug("%s, EVENT_RTC %sabled\n", __FUNCTION__, do_wake ? "en" : "dis");
+
+ return 0;
+}
+
+static int cmos_resume(struct device *dev)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+
+ /* REVISIT: the mechanism to resync jiffies on resume
+ * should be portable between platforms ...
+ */
+
+pr_debug("%s\n", __FUNCTION__);
+
+ if (use_acpi_wake(cmos))
+ acpi_disable_event(ACPI_EVENT_RTC, 0);
+
+ return 0;
+}
+
+#else
+#define cmos_suspend NULL
+#define cmos_resume NULL
+#endif
+
+/*----------------------------------------------------------------*/
+
+/* we prefer that the device node be created by PNPACPI, but
+ * can cope without it by using a platform device (which should
+ * eventually have platform_data to hold extensions
+ */
+
+#ifdef CONFIG_PNPACPI
+
+#include <linux/pnp.h>
+
+static int __devinit
+cmos_pnp_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
+{
+ /* REVISIT paranoia argues for a shutdown notifier, since PNP
+ * drivers can't provide shutdown() methods to disable IRQs
+ */
+ return cmos_do_probe(&pnp->dev, id->driver_data,
+ &pnp->res.port_resource[0],
+ pnp->res.irq_resource[0].start);
+}
+
+static void __devexit cmos_pnp_remove(struct pnp_dev *pnp)
+{
+ cmos_do_remove(&pnp->dev);
+}
+
+#ifdef CONFIG_PM
+
+static int cmos_pnp_suspend(struct pnp_dev *pnp, pm_message_t mesg)
+{
+ return cmos_suspend(&pnp->dev, mesg);
+}
+
+static int cmos_pnp_resume(struct pnp_dev *pnp)
+{
+ return cmos_resume(&pnp->dev);
+}
+
+#else
+#define cmos_pnp_suspend NULL
+#define cmos_pnp_resume NULL
+#endif
+
+
+static const struct pnp_device_id rtc_ids[] = {
+ { .id = "PNP0b00", .driver_data = RTC_AT, },
+ { .id = "PNP0b01", .driver_data = RTC_PIIX4, },
+ { .id = "PNP0b02", .driver_data = RTC_DALLAS, },
+ { },
+};
+MODULE_DEVICE_TABLE(pnp, rtc_ids);
+
+static struct pnp_driver cmos_pnp_driver = {
+ .name = (char *) driver_name,
+ .id_table = rtc_ids,
+ .probe = cmos_pnp_probe,
+ .remove = __devexit_p(cmos_pnp_remove),
+ .suspend = cmos_pnp_suspend,
+ .resume = cmos_pnp_resume,
+};
+
+static int __init cmos_init(void)
+{
+ return pnp_register_driver(&cmos_pnp_driver);
+}
+module_init(cmos_init);
+
+static void __exit cmos_exit(void)
+{
+ pnp_unregister_driver(&cmos_pnp_driver);
+}
+module_exit(cmos_exit);
+
+#else /* no PNPACPI */
+
+/*----------------------------------------------------------------*/
+
+static int __devinit cmos_platform_probe(struct platform_device *pdev)
+{
+ return cmos_do_probe(&pdev->dev, RTC_AT,
+ platform_get_resource(pdev, IORESOURCE_IO, 0),
+ platform_get_irq(pdev, 0));
+}
+
+static int __devexit cmos_platform_remove(struct platform_device *pdev)
+{
+ cmos_do_remove(&pdev->dev);
+ return 0;
+}
+
+static void cmos_platform_shutdown(struct platform_device *pdev)
+{
+ cmos_do_shutdown();
+}
+
+static struct platform_driver cmos_platform_driver = {
+ .probe = cmos_platform_probe,
+ .remove = __devexit_p(cmos_platform_remove),
+ .shutdown = cmos_platform_shutdown,
+ .driver = {
+ .name = (char *) driver_name,
+ .suspend = cmos_suspend,
+ .resume = cmos_resume,
+ }
+};
+
+/* FIXME platform device registration belongs in arch/.../ code,
+ * and should never need to be reversed ... and for this driver,
+ * such registration should not assume ACPI!!
+ */
+struct resource rtc_platform_resources[] = { {
+ .flags = IORESOURCE_IO,
+ .start = 0x70,
+ .end = 0x71,
+}, {
+ .flags = IORESOURCE_IRQ,
+ .start = RTC_IRQ
+} };
+
+struct platform_device rtc_platform_dev = {
+ .name = (char *) driver_name,
+ .id = -1,
+ .resource = rtc_platform_resources,
+ .num_resources = ARRAY_SIZE(rtc_platform_resources),
+};
+
+static int __init cmos_init(void)
+{
+ (void) platform_device_register(&rtc_platform_dev);
+
+ return platform_driver_register(&cmos_platform_driver);
+}
+module_init(cmos_init);
+
+/*
+static void __exit cmos_exit(void)
+{
+ platform_driver_unregister(&cmos_platform_driver);
+
+ platform_device_unregister(&rtc_platform_dev);
+
+}
+module_exit(cmos_exit);
+*/
+
+
+#endif /* !PNPACPI */
+
+MODULE_DESCRIPTION("RTC driver for ACPI");
+MODULE_LICENSE("GPL");
Index: g26/drivers/acpi/sleep/proc.c
===================================================================
--- g26.orig/drivers/acpi/sleep/proc.c 2006-09-27 16:06:10.000000000 -0700
+++ g26/drivers/acpi/sleep/proc.c 2006-09-28 11:04:49.000000000 -0700
@@ -70,6 +70,14 @@ acpi_system_write_sleep(struct file *fil
}
#endif /* CONFIG_ACPI_SLEEP_PROC_SLEEP */
+#if defined(CONFIG_RTC_DRV_ACPI) || defined(CONFIG_RTC_DRV_ACPI_MODULE)
+/* use code that fits into standard Linux driver frameworks */
+#else
+#define HAVE_PRIVATE_ALARM
+#endif
+
+#ifdef HAVE_PRIVATE_ALARM
+
static int acpi_system_alarm_seq_show(struct seq_file *seq, void *offset)
{
u32 sec, min, hr;
@@ -339,6 +347,8 @@ acpi_system_write_alarm(struct file *fil
end:
return_VALUE(result ? result : count);
}
+#endif /* HAVE_PRIVATE_ALARM */
+
extern struct list_head acpi_wakeup_device_list;
extern spinlock_t acpi_device_lock;
@@ -452,6 +462,7 @@ static const struct file_operations acpi
};
#endif /* CONFIG_ACPI_SLEEP_PROC_SLEEP */
+#ifdef HAVE_PRIVATE_ALARM
static const struct file_operations acpi_system_alarm_fops = {
.open = acpi_system_alarm_open_fs,
.read = seq_read,
@@ -467,6 +478,7 @@ static u32 rtc_handler(void *context)
return ACPI_INTERRUPT_HANDLED;
}
+#endif /* HAVE_PRIVATE_ALARM */
static int acpi_sleep_proc_init(void)
{
@@ -484,6 +496,7 @@ static int acpi_sleep_proc_init(void)
entry->proc_fops = &acpi_system_sleep_fops;
#endif
+#ifdef HAVE_PRIVATE_ALARM
/* 'alarm' [R/W] */
entry =
create_proc_entry("alarm", S_IFREG | S_IRUGO | S_IWUSR,
@@ -491,6 +504,9 @@ static int acpi_sleep_proc_init(void)
if (entry)
entry->proc_fops = &acpi_system_alarm_fops;
+ acpi_install_fixed_event_handler(ACPI_EVENT_RTC, rtc_handler, NULL);
+#endif /* HAVE_PRIVATE_ALARM */
+
/* 'wakeup device' [R/W] */
entry =
create_proc_entry("wakeup", S_IFREG | S_IRUGO | S_IWUSR,
@@ -498,7 +514,6 @@ static int acpi_sleep_proc_init(void)
if (entry)
entry->proc_fops = &acpi_system_wakeup_device_fops;
- acpi_install_fixed_event_handler(ACPI_EVENT_RTC, rtc_handler, NULL);
return 0;
}
Index: g26/drivers/acpi/utilities/utglobal.c
===================================================================
--- g26.orig/drivers/acpi/utilities/utglobal.c 2006-09-27 16:06:10.000000000 -0700
+++ g26/drivers/acpi/utilities/utglobal.c 2006-09-28 11:04:49.000000000 -0700
@@ -840,5 +840,6 @@ void acpi_ut_init_globals(void)
return_VOID;
}
+ACPI_EXPORT_SYMBOL(acpi_gbl_FADT)
ACPI_EXPORT_SYMBOL(acpi_dbg_level)
ACPI_EXPORT_SYMBOL(acpi_dbg_layer)
^ permalink raw reply [flat|nested] 5+ messages in thread