Here are more RTC framework updates, basically for the 2.6.20 queue:
- /proc/driver/rtc update ... display the 'struct rtc_wkalrm' status
bits more sensibly (though the EFI "irq pending" flag is nonsense
with an OS running)
- rtc-sa1100 update ... wasn't reporting "alarm enabled", and its
extra procfs info duplicated information already found there
- X86_PC updates ... create an rtc_cmos platform device when PNPACPI
isn't available do make one through PNP. (Non-PC platforms can do
similar things if they have a "cmos" RTC.)
- Export ACPI RTC extensions through platform_data to the PNP device
or the platform device, as appropriate.
- New "rtc-cmos" driver, for the RTC on most PCs. For most folk this
seems like it should be able to replace drivers/char/rtc.c ...
- Newish "rtc-omap" driver, for the RTC on OMAP1 processors. No point
in having this just live in the OMAP tree.
Folk wanting to try "rtc-cmos" will likely be wanting to tweak their
/dev/rtc node to become a symlink to /dev/rtc0, at least until the
new version of util-linux comes out (with updated "hwclock" knowing
about the new RTC class devices).
- Dave
Fix two minor botches in the procfs dumping of RTC alarm status:
- Stop confusing "alarm enabled" with "wakeup enabled".
- Don't display bogus "irq pending/un-acked" status; those are the rather
pointless semantics EFI assigned to this (for a no-IRQs environment).
The main RTC that seems confused about this is the sa1100 one, which
doesn't actually report whether it enabled the alarm.
Signed-off-by: David Brownell <[email protected]>
Index: g26/drivers/rtc/rtc-proc.c
===================================================================
--- g26.orig/drivers/rtc/rtc-proc.c 2006-11-20 09:35:39.000000000 -0800
+++ g26/drivers/rtc/rtc-proc.c 2006-11-20 09:36:23.000000000 -0800
@@ -66,9 +66,11 @@ static int rtc_proc_show(struct seq_file
else
seq_printf(seq, "**\n");
seq_printf(seq, "alrm_wakeup\t: %s\n",
+ device_may_wakeup(class_dev->dev)
+ ? "yes" : "no");
+ seq_printf(seq, "alrm_enabled\t: %s\n",
alrm.enabled ? "yes" : "no");
- seq_printf(seq, "alrm_pending\t: %s\n",
- alrm.pending ? "yes" : "no");
+ /* alrm.pending ("irq un-acked") is useless ... */
}
seq_printf(seq, "24hr\t\t: yes\n");
Update X86_PC platforms (i386, x86_64) to create an "rtc_cmos" platform device
when PNPACPI won't be creating a corresponding PNP node for us. There may be
other platform devices that should get corresponding treatment; it might help
get rid of more legacy ISA probing logic.
Signed-off-by: David Brownell <[email protected]>
Index: g26/arch/x86_64/kernel/setup.c
===================================================================
--- g26.orig/arch/x86_64/kernel/setup.c 2006-11-20 10:03:06.000000000 -0800
+++ g26/arch/x86_64/kernel/setup.c 2006-11-20 10:10:43.000000000 -0800
@@ -1212,22 +1212,58 @@ struct seq_operations cpuinfo_op = {
.show = show_cpuinfo,
};
-#if defined(CONFIG_INPUT_PCSPKR) || defined(CONFIG_INPUT_PCSPKR_MODULE)
+
+#ifdef CONFIG_X86_PC
+
#include <linux/platform_device.h>
-static __init int add_pcspkr(void)
-{
- struct platform_device *pd;
- int ret;
+#include <asm/mc146818rtc.h>
+
+#if defined(CONFIG_INPUT_PCSPKR) || defined(CONFIG_INPUT_PCSPKR_MODULE)
+static struct platform_device pcspkr_dev = {
+ .name = "pcspkr",
+ .id = -1,
+};
+#endif /* PCSPKR */
- pd = platform_device_alloc("pcspkr", -1);
- if (!pd)
- return -ENOMEM;
-
- ret = platform_device_add(pd);
- if (ret)
- platform_device_put(pd);
+#ifndef CONFIG_PNPACPI
+struct resource rtc_platform_resources[] = { {
+ .flags = IORESOURCE_IO,
+ .start = RTC_PORT(0),
+ .end = RTC_PORT(1),
+}, {
+ .flags = IORESOURCE_IRQ,
+ .start = RTC_IRQ
+} };
- return ret;
-}
-device_initcall(add_pcspkr);
+struct platform_device rtc_dev = {
+ .name = "rtc_cmos",
+ .id = -1,
+ .resource = rtc_platform_resources,
+ .num_resources = ARRAY_SIZE(rtc_platform_resources),
+};
+#endif /* !PNPACPI */
+
+static struct platform_device *x86_pc_devs[] __initdata = {
+#if defined(CONFIG_INPUT_PCSPKR) || defined(CONFIG_INPUT_PCSPKR_MODULE)
+ &pcspkr_dev,
+#endif
+#ifndef CONFIG_PNPACPI
+ &rtc_dev,
+#endif
+};
+
+static __init int add_devices(void)
+{
+#ifndef CONFIG_PNPACPI
+ /* On most motherboards starting with ATX (1995+),
+ * RTC alarms can wake the system
+ */
+ device_init_wakeup(&rtc_dev.dev, 1);
#endif
+
+ return platform_add_devices(x86_pc_devs, ARRAY_SIZE(x86_pc_devs));
+}
+arch_initcall(add_devices);
+
+#endif /* X86_PC */
+
Index: g26/arch/i386/kernel/setup.c
===================================================================
--- g26.orig/arch/i386/kernel/setup.c 2006-11-20 10:03:06.000000000 -0800
+++ g26/arch/i386/kernel/setup.c 2006-11-20 10:10:43.000000000 -0800
@@ -1481,22 +1481,56 @@ void __init setup_arch(char **cmdline_p)
tsc_init();
}
-static __init int add_pcspkr(void)
-{
- struct platform_device *pd;
- int ret;
- pd = platform_device_alloc("pcspkr", -1);
- if (!pd)
- return -ENOMEM;
-
- ret = platform_device_add(pd);
- if (ret)
- platform_device_put(pd);
+#ifdef CONFIG_X86_PC
+
+#include <linux/platform_device.h>
+#include <asm/mc146818rtc.h>
+
+static struct platform_device pcspkr_dev = {
+ .name = "pcspkr",
+ .id = -1,
+};
+
+#ifndef CONFIG_PNPACPI
+struct resource rtc_platform_resources[] = { {
+ .flags = IORESOURCE_IO,
+ .start = RTC_PORT(0),
+ .end = RTC_PORT(1),
+}, {
+ .flags = IORESOURCE_IRQ,
+ .start = RTC_IRQ
+} };
+
+struct platform_device rtc_dev = {
+ .name = "rtc_cmos",
+ .id = -1,
+ .resource = rtc_platform_resources,
+ .num_resources = ARRAY_SIZE(rtc_platform_resources),
+};
+#endif /* !PNPACPI */
- return ret;
+static struct platform_device *x86_pc_devs[] __initdata = {
+ &pcspkr_dev,
+#ifndef CONFIG_PNPACPI
+ &rtc_dev,
+#endif
+};
+
+static __init int add_devices(void)
+{
+#ifndef CONFIG_PNPACPI
+ /* On most motherboards starting with ATX (1995+),
+ * RTC alarms can wake the system
+ */
+ device_init_wakeup(&rtc_dev.dev, 1);
+#endif
+
+ return platform_add_devices(x86_pc_devs, ARRAY_SIZE(x86_pc_devs));
}
-device_initcall(add_pcspkr);
+arch_initcall(add_devices);
+
+#endif /* X86_PC */
/*
* Local Variables:
This is a "new RTC framework" driver for the "CMOS" RTCs which are standard
on PCs and some other platforms. That's an MC146818, or compatibles like
the ones in most south bridges, the M48T86 and DS12887, etc. Advantages of
this vs. drivers/char/rtc.c (use one _or_ the other) include:
- This leverages both the new RTC framework and the driver model;
both PNPACPI and platform device modes are supported.
- It supports ACPI extensions like longer alarms.
- Likewise, system wakeup events use "real driver model support", with
policy control via sysfs "wakeup" attributes and and using normal rtc
ioctls to manage wakeup.
- Obsoletes and removes the acpi-only /proc/acpi/alarm interface.
Disadvantages include no testing on non-x86 systems, or with HPET; and
issues associated with newness of this code and the relatively limited
use of the RTC framework (i.e. primarily on embedded Linuxes, not PCs).
Significant changes since last version: handles periodic irqs, given
the rtc core patch for RTC_IRQP_{GET,SET}; renamed as rtc-cmos; better
platform device support; cares less about ACPI; passes "rtctest".
Issues of note:
- Some tools like "hwclock" expect /dev/rtc not /dev/rtc0; upcoming
util-linux and busybox versions should resolve this.
- ACPI wakeup doesn't always work ... this is not specific to RTC,
but it's worth remembering. (Or fixing, please!, if you can.)
- The ALSA sound/core/rtctimer.c doesn't understand the new RTC API,
or the fact that not every RTC can generate 1K/sec IRQs.
This version seems generally usable, though it may need tweaks yet.
Signed-off-by: David Brownell <[email protected]>
Index: g26/drivers/rtc/Kconfig
===================================================================
--- g26.orig/drivers/rtc/Kconfig 2006-11-20 10:03:06.000000000 -0800
+++ g26/drivers/rtc/Kconfig 2006-11-20 10:10:47.000000000 -0800
@@ -95,6 +95,25 @@ config RTC_INTF_DEV_UIE_EMUL
comment "RTC drivers"
depends on RTC_CLASS
+config RTC_DRV_CMOS
+ tristate "CMOS real time clock"
+ depends on RTC_CLASS && (X86_PC || ACPI)
+ help
+ Say "yes" here to get direct support for the real time clock
+ found in every PC or ACPI-based system, and some others.
+ Specifically the original MC146818, or compatibles like those
+ in PC south bridges, the DS12887 or M48T86, and so on.
+
+ This RTC can be used to wake most systems from suspend-to-RAM or
+ standby sleep modes, and on some systems from suspend-to-disk.
+ (Any PC predating the 1995 introduction of ATX power supplies
+ probably can't be woken up by its RTC.)
+
+ 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-cmos.
+
config RTC_DRV_X1205
tristate "Xicor/Intersil X1205"
depends on RTC_CLASS && I2C
Index: g26/drivers/rtc/Makefile
===================================================================
--- g26.orig/drivers/rtc/Makefile 2006-11-20 10:03:06.000000000 -0800
+++ g26/drivers/rtc/Makefile 2006-11-20 10:10:47.000000000 -0800
@@ -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_CMOS) += rtc-cmos.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-cmos.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ g26/drivers/rtc/rtc-cmos.c 2006-11-20 10:10:47.000000000 -0800
@@ -0,0 +1,738 @@
+/*
+ * RTC class driver for "CMOS RTC": PCs, ACPI, etc
+ *
+ * Copyright (C) 1996 Paul Gortmaker (drivers/char/rtc.c)
+ * Copyright (C) 2006 David Brownell (convert to new framework)
+ *
+ * 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.
+ * That defined the register interface now provided by all PCs, some
+ * non-PC systems, and incorporated into ACPI. Modern PC chipsets
+ * integrate an MC146818 clone in their southbridge, and boards use
+ * that instead of discrete clones like the DS12887 or M48T86. There
+ * are also clones that connect using the LPC bus.
+ *
+ * That register API is also used directly by various other drivers,
+ * (notably for integrated NVRAM), infrastructure (x86 has code to
+ * bypass the RTC framework, directly reading the RTC during boot
+ * and updating minutes/seconds for systems using NTP synch) and
+ * utilities (like userspace 'hwclock', if no /dev node exists).
+ *
+ * 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 on correctly configured systems.
+ */
+#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>
+
+
+struct cmos_rtc {
+ struct rtc_device *rtc;
+ struct device *dev;
+ int irq;
+ struct resource *iomem;
+ u8 acpi;
+
+ /* newer hardware extends the original register set */
+ u8 day_alrm;
+ u8 mon_alrm;
+ u8 century;
+};
+
+#ifdef CONFIG_ACPI_SLEEP
+#define use_acpi_wake(cmos) ((cmos)->acpi)
+#else
+#define use_acpi_wake(cmos) 0
+#endif
+
+/* both platform and pnp busses use negative numbers for invalid irqs */
+#define is_valid_irq(n) ((n) >= 0)
+
+static const char driver_name[] = "rtc_cmos";
+
+/*----------------------------------------------------------------*/
+
+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
+ *
+ * NOTE: this ignores the issue whereby updating the seconds
+ * takes effect exactly 500ms after we write the register.
+ * (Also queueing and other delays before we get this far.)
+ */
+ 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;
+
+ if (!is_valid_irq(cmos->irq))
+ return -EIO;
+
+ /* Basic alarms only support hour, minute, and seconds fields.
+ * Some also support day and month, for alarms up to a year in
+ * the future.
+ */
+ t->time.tm_mday = -1;
+ t->time.tm_mon = -1;
+
+ 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)
+ t->time.tm_mday = -1;
+
+ if (cmos->mon_alrm) {
+ t->time.tm_mon = CMOS_READ(cmos->mon_alrm);
+ if (!t->time.tm_mon)
+ t->time.tm_mon = -1;
+ }
+ }
+
+ rtc_control = CMOS_READ(RTC_CONTROL);
+ spin_unlock_irq(&rtc_lock);
+
+ /* REVISIT this assumes PC style usage: always BCD */
+
+ if (((unsigned)t->time.tm_sec) < 0x60)
+ t->time.tm_sec = BCD2BIN(t->time.tm_sec);
+ else
+ t->time.tm_sec = -1;
+ if (((unsigned)t->time.tm_min) < 0x60)
+ t->time.tm_min = BCD2BIN(t->time.tm_min);
+ else
+ t->time.tm_min = -1;
+ if (((unsigned)t->time.tm_hour) < 0x24)
+ t->time.tm_hour = BCD2BIN(t->time.tm_hour);
+ else
+ t->time.tm_hour = -1;
+
+ if (cmos->day_alrm) {
+ if (((unsigned)t->time.tm_mday) <= 0x31)
+ t->time.tm_mday = BCD2BIN(t->time.tm_mday);
+ else
+ t->time.tm_mday = -1;
+ if (cmos->mon_alrm) {
+ if (((unsigned)t->time.tm_mon) <= 0x12)
+ t->time.tm_mon = BCD2BIN(t->time.tm_mon) - 1;
+ else
+ t->time.tm_mon = -1;
+ }
+ }
+ t->time.tm_year = -1;
+
+ t->enabled = !!(rtc_control & RTC_AIE);
+ t->pending = 0;
+
+ 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, rtc_intr;
+
+ if (!is_valid_irq(cmos->irq))
+ return -EIO;
+
+ /* REVISIT this assumes PC style usage: always BCD */
+
+ /* 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);
+ rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
+ if (rtc_intr)
+ rtc_update_irq(&cmos->rtc->class_dev, 1, rtc_intr);
+
+ /* 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);
+ rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
+ if (rtc_intr)
+ rtc_update_irq(&cmos->rtc->class_dev, 1, rtc_intr);
+ }
+
+ spin_unlock_irq(&rtc_lock);
+
+#ifdef DEBUG
+/* REVISIT ... 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_set_freq(struct device *dev, int freq)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+ int f;
+ unsigned long flags;
+
+ if (!is_valid_irq(cmos->irq))
+ return -ENXIO;
+
+ /* 0 = no irqs; 1 = 2^15 Hz ... 15 = 2^0 Hz */
+ f = ffs(freq);
+ if (f != 0) {
+ if (f-- > 16 || freq != (1 << f))
+ return -EINVAL;
+ f = 16 - f;
+ }
+
+ spin_lock_irqsave(&rtc_lock, flags);
+ CMOS_WRITE(RTC_REF_CLCK_32KHZ | f, RTC_FREQ_SELECT);
+ spin_unlock_irqrestore(&rtc_lock, flags);
+
+ return 0;
+}
+
+#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)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+ unsigned char rtc_control, rtc_intr;
+ 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:
+ if (!is_valid_irq(cmos->irq))
+ return -EINVAL;
+ break;
+ 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);
+ rtc_intr = CMOS_READ(RTC_INTR_FLAGS);
+ if (rtc_intr)
+ rtc_update_irq(&cmos->rtc->class_dev, 1, rtc_intr);
+ 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)
+{
+ struct cmos_rtc *cmos = dev_get_drvdata(dev);
+ 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"
+ "update_IRQ\t: %s\n"
+ // "square_wave\t: %s\n"
+ // "BCD\t\t: %s\n"
+ "DST_enable\t: %s\n"
+ "periodic_freq\t: %d\n"
+ "batt_status\t: %s\n",
+ (rtc_control & RTC_PIE) ? "yes" : "no",
+ (rtc_control & RTC_UIE) ? "yes" : "no",
+ // (rtc_control & RTC_SQWE) ? "yes" : "no",
+ // (rtc_control & RTC_DM_BINARY) ? "no" : "yes",
+ (rtc_control & RTC_DST_EN) ? "yes" : "no",
+ cmos->rtc->irq_freq,
+ (valid & RTC_VRT) ? "okay" : "dead");
+}
+
+#else
+#define cmos_procfs NULL
+#endif
+
+static const 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)
+{
+ 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__);
+
+ // call this with irqs blocked:
+ // 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, struct resource *ports, int rtc_irq)
+{
+ struct cmos_rtc_board_info *info = dev->platform_data;
+ int retval = 0;
+ unsigned char rtc_control;
+ int wake_std = 0;
+
+ /* there can be only one ... */
+ if (cmos_rtc.dev)
+ return -EBUSY;
+
+ if (!ports)
+ return -ENODEV;
+
+ cmos_rtc.irq = rtc_irq;
+ cmos_rtc.iomem = ports;
+
+ if (info) {
+ /* REVISIT this driver should not care about ACPI.
+ * The ACPI alarms never seem to fire, anyway...
+ */
+ cmos_rtc.acpi = 1;
+
+ cmos_rtc.day_alrm = info->rtc_day_alarm;
+ cmos_rtc.mon_alrm = info->rtc_mon_alarm;
+ cmos_rtc.century = info->rtc_century;
+ wake_std = info->wake_from_std;
+ }
+
+ 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);
+
+ /* platform and pnp busses handle resources incompatibly.
+ *
+ * REVISIT for non-x86 systems we may need to handle io memory
+ * resources: ioremap them, and request_mem_region().
+ */
+ if (is_pnpacpi()) {
+ retval = request_resource(&ioport_resource, ports);
+ if (retval < 0) {
+ dev_dbg(dev, "i/o registers already in use\n");
+ goto cleanup0;
+ }
+ }
+ rename_region(ports, cmos_rtc.rtc->class_dev.class_id);
+
+ spin_lock_irq(&rtc_lock);
+
+ /* force periodic irq to CMOS reset default of 1024Hz;
+ *
+ * REVISIT it's been reported that at least one x86_64 ALI mobo
+ * doesn't use 32KHz here ... for portability we might need to
+ * do something about other clock frequencies.
+ */
+ CMOS_WRITE(RTC_REF_CLCK_32KHZ | 0x06, RTC_FREQ_SELECT);
+ cmos_rtc.rtc->irq_freq = 1024;
+
+ /* disable irqs.
+ *
+ * NOTE after changing RTC_xIE bits we always read INTR_FLAGS;
+ * allegedly some older rtcs need that to handle irqs properly
+ */
+ 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);
+
+ /* FIXME teach the alarm code how to handle binary mode;
+ * <asm-generic/rtc.h> doesn't know 12-hour mode either.
+ */
+ if (!(rtc_control & RTC_24H) || (rtc_control & (RTC_DM_BINARY))) {
+ dev_dbg(dev, "only 24-hr BCD mode supported\n");
+ retval = -ENXIO;
+ goto cleanup1;
+ }
+
+ if (is_valid_irq(rtc_irq))
+ retval = request_irq(rtc_irq, cmos_interrupt, IRQF_DISABLED,
+ cmos_rtc.rtc->class_dev.class_id,
+ &cmos_rtc.rtc->class_dev);
+ if (retval < 0) {
+ dev_dbg(dev, "IRQ %d is already in use\n", rtc_irq);
+ goto cleanup1;
+ }
+
+ pr_info("%s: %s, %s alarm%s%s\n",
+ cmos_rtc.rtc->class_dev.class_id,
+ dev->driver->name,
+ is_valid_irq(rtc_irq)
+ ? (cmos_rtc.mon_alrm
+ ? "year"
+ : (cmos_rtc.day_alrm
+ ? "month" : "day"))
+ : "no",
+ wake_std ? ", wake from STD" : "",
+ cmos_rtc.century ? ", y3k" : ""
+ );
+
+ 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:
+ rename_region(ports, NULL);
+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);
+ }
+
+ if (dev->bus != &platform_bus_type)
+ release_resource(cmos->iomem);
+ rename_region(cmos->iomem, NULL);
+
+ if (is_valid_irq(cmos->irq))
+ 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
+
+/*----------------------------------------------------------------*/
+
+/* On ACPI systems, the device node may be created as either a PNP
+ * device or a platform_device. In either case the FADT data should
+ * have been transferred to us through platform_data.
+ */
+
+#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.
+ * Or better yet, fix PNP to allow those methods...
+ */
+ return cmos_do_probe(&pnp->dev,
+ &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", },
+ { .id = "PNP0b01", },
+ { .id = "PNP0b02", },
+ { },
+};
+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 */
+
+/*----------------------------------------------------------------*/
+
+/* Platform setup should have set up an RTC device, when PNPACPI is
+ * unavailable ... including non-PC and non-ACPI platforms.
+ */
+
+static int __devinit cmos_platform_probe(struct platform_device *pdev)
+{
+ return cmos_do_probe(&pdev->dev,
+ 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,
+ }
+};
+
+static int __init cmos_init(void)
+{
+ return platform_driver_register(&cmos_platform_driver);
+}
+module_init(cmos_init);
+
+static void __exit cmos_exit(void)
+{
+ platform_driver_unregister(&cmos_platform_driver);
+}
+module_exit(cmos_exit);
+
+
+#endif /* !PNPACPI */
+
+MODULE_DESCRIPTION("'CMOS' RTC driver: PCs, ACPI, etc");
+MODULE_LICENSE("GPL");
Index: g26/drivers/acpi/sleep/proc.c
===================================================================
--- g26.orig/drivers/acpi/sleep/proc.c 2006-11-20 10:03:06.000000000 -0800
+++ g26/drivers/acpi/sleep/proc.c 2006-11-20 10:10:47.000000000 -0800
@@ -70,6 +70,14 @@ acpi_system_write_sleep(struct file *fil
}
#endif /* CONFIG_ACPI_SLEEP_PROC_SLEEP */
+#if defined(CONFIG_RTC_DRV_CMOS) || defined(CONFIG_RTC_DRV_CMOS_MODULE)
+/* use code that fits into standard Linux driver frameworks */
+#else
+#define HAVE_ACPI_LEGACY_ALARM
+#endif
+
+#ifdef HAVE_ACPI_LEGACY_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_ACPI_LEGACY_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_ACPI_LEGACY_ALARM
static const struct file_operations acpi_system_alarm_fops = {
.open = acpi_system_alarm_open_fs,
.read = seq_read,
@@ -467,8 +478,9 @@ static u32 rtc_handler(void *context)
return ACPI_INTERRUPT_HANDLED;
}
+#endif /* HAVE_ACPI_LEGACY_ALARM */
-static int acpi_sleep_proc_init(void)
+static int __init acpi_sleep_proc_init(void)
{
struct proc_dir_entry *entry = NULL;
@@ -484,6 +496,7 @@ static int acpi_sleep_proc_init(void)
entry->proc_fops = &acpi_system_sleep_fops;
#endif
+#ifdef HAVE_ACPI_LEGACY_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_ACPI_LEGACY_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;
}
Minor updates to rtc-sa1100: report whether the alarm is enabled, remove
duplicate procfs reporting of that factoid, and stick a FIXME at a place
where alarms should be enabled (but aren't).
Signed-off-by: David Brownell <[email protected]>
Index: g26/drivers/rtc/rtc-sa1100.c
===================================================================
--- g26.orig/drivers/rtc/rtc-sa1100.c 2006-11-20 09:35:19.000000000 -0800
+++ g26/drivers/rtc/rtc-sa1100.c 2006-11-20 09:36:24.000000000 -0800
@@ -263,8 +263,12 @@ static int sa1100_rtc_set_time(struct de
static int sa1100_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
+ u32 rtsr;
+
memcpy(&alrm->time, &rtc_alarm, sizeof(struct rtc_time));
- alrm->pending = RTSR & RTSR_AL ? 1 : 0;
+ rtsr = RTSR;
+ alrm->pending = (rtsr & RTSR_AL) ? 1 : 0;
+ alrm->enabled = (rtsr & RTSR_ALE) ? 1 : 0;
return 0;
}
@@ -277,6 +281,7 @@ static int sa1100_rtc_set_alarm(struct d
if (ret == 0) {
memcpy(&rtc_alarm, &alrm->time, sizeof(struct rtc_time));
+ /* FIXME 'enabled' should update RTSR_ALE instead */
if (alrm->enabled)
enable_irq_wake(IRQ_RTCAlrm);
else
@@ -290,8 +295,6 @@ static int sa1100_rtc_set_alarm(struct d
static int sa1100_rtc_proc(struct device *dev, struct seq_file *seq)
{
seq_printf(seq, "trim/divider\t: 0x%08lx\n", RTTR);
- seq_printf(seq, "alarm_IRQ\t: %s\n",
- (RTSR & RTSR_ALE) ? "yes" : "no" );
seq_printf(seq, "update_IRQ\t: %s\n",
(RTSR & RTSR_HZE) ? "yes" : "no");
seq_printf(seq, "periodic_IRQ\t: %s\n",
Define platform_data for the MC146818 based RTCs, currently just holding
extensions standardized by ACPI (but available on some ACPI-neutral clones).
Update ACPI to export that RTC extension information through platform_data
to the PNP or platform bus device node.
Signed-off-by: David Brownell <[email protected]>
Index: g26/include/linux/mc146818rtc.h
===================================================================
--- g26.orig/include/linux/mc146818rtc.h 2006-11-20 10:03:06.000000000 -0800
+++ g26/include/linux/mc146818rtc.h 2006-11-20 10:10:45.000000000 -0800
@@ -18,6 +18,18 @@
#ifdef __KERNEL__
#include <linux/spinlock.h> /* spinlock_t */
extern spinlock_t rtc_lock; /* serialize CMOS RAM access */
+
+/* Some RTCs extend the mc146818 register set to support alarms of more
+ * than 24 hours in the future; or dates that include a century code.
+ * And sometimes the RTC alarm can wake the system from suspend-to-disk.
+ * This platform_data structure can pass this information to the driver.
+ */
+struct cmos_rtc_board_info {
+ u8 rtc_day_alarm; /* zero, or register index */
+ u8 rtc_mon_alarm; /* zero, or register index */
+ u8 rtc_century; /* zero, or register index */
+ u8 wake_from_std; /* true iff alarm wakes from STD */
+};
#endif
/**********************************************************************
Index: g26/drivers/acpi/glue.c
===================================================================
--- g26.orig/drivers/acpi/glue.c 2006-11-20 10:03:06.000000000 -0800
+++ g26/drivers/acpi/glue.c 2006-11-20 10:10:45.000000000 -0800
@@ -358,3 +358,107 @@ static int __init init_acpi_device_notif
}
arch_initcall(init_acpi_device_notify);
+
+
+#if defined(CONFIG_RTC_DRV_CMOS) || defined(CONFIG_RTC_DRV_CMOS_MODULE)
+
+/* Every ACPI platform has a mc146818 compatible "cmos rtc". We want
+ * to find its device node, so we can pass ACPI-specific config data
+ * to its driver. So we'll just probe for that device -- with a dummy
+ * driver, using the relevant bus -- before the RTC driver initializes.
+ */
+#include <linux/mc146818rtc.h>
+
+static struct cmos_rtc_board_info rtc_info;
+
+static struct device *rtc_dev __initdata;
+static char drvname[] __initdata = "rtc_cmos";
+
+
+#ifdef CONFIG_PNPACPI
+
+/* PNP devices are registered in a subsys_initcall();
+ * ACPI specifies the PNP IDs to use.
+ */
+#include <linux/pnp.h>
+
+static struct pnp_device_id rtc_ids[] __initdata = {
+ { .id = "PNP0b00", },
+ { .id = "PNP0b01", },
+ { .id = "PNP0b02", },
+ { },
+};
+
+static int __init
+rtc_pnp_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
+{
+ rtc_dev = &pnp->dev;
+ return -ENXIO;
+}
+
+static struct pnp_driver cmos_pnp_driver __initdata = {
+ .name = drvname,
+ .id_table = rtc_ids,
+ .probe = rtc_pnp_probe,
+};
+
+static struct device *__init get_rtc_dev(void)
+{
+ if (pnp_register_driver(&cmos_pnp_driver) == 0)
+ pnp_unregister_driver(&cmos_pnp_driver);
+ return rtc_dev;
+}
+
+#else
+
+/* We expect platforms to register an RTC device, conventionally at or
+ * near arch_initcall(). That should work even without ACPI. The driver
+ * name matters; it must match the driver.
+ */
+#include <linux/platform_device.h>
+
+static int __init rtc_platform_probe(struct platform_device *pdev)
+{
+ rtc_dev = &pdev->dev;
+ return -ENXIO;
+}
+
+static struct platform_driver rtc_platform_driver __initdata = {
+ .driver = {
+ .name = drvname,
+ },
+ .probe = rtc_platform_probe,
+};
+
+static struct device *__init get_rtc_dev(void)
+{
+ if (platform_driver_register(&rtc_platform_driver) == 0)
+ platform_driver_unregister(&rtc_platform_driver);
+ return rtc_dev;
+}
+
+#endif
+
+static int __init acpi_rtc_init(void)
+{
+ struct device *dev = get_rtc_dev();
+
+ if (dev) {
+ rtc_info.rtc_day_alarm = acpi_gbl_FADT->day_alrm;
+ rtc_info.rtc_mon_alarm = acpi_gbl_FADT->mon_alrm;
+ rtc_info.rtc_century = acpi_gbl_FADT->century;
+
+ /* REVISIT iff revision > FADT2_REVISION_ID ? */
+ rtc_info.wake_from_std = acpi_gbl_FADT->rtcs4;
+
+ dev->platform_data = &rtc_info;
+
+ /* RTC always wakes from S1/S2/S3, and often S4/STD */
+ device_init_wakeup(dev, 1);
+ } else
+ pr_debug("ACPI: RTC unavailable?\n");
+ return 0;
+}
+fs_initcall(acpi_rtc_init);
+
+#endif
This creates a new RTC-framework driver for the RTC/calendar module found
in various OMAP1 chips. (OMAP2 and OMAP3 use external RTCs, like those in
TI's multifunction PM companion chips.) It's been in the Linux-OMAP tree
for several months now, and other trees before that, so it's quite stable.
The most notable issue is that the OMAP IRQ code doesn't yet support the
RTC IRQ as a wakeup event. Once that's fixed, a patch will be needed.
Signed-off-by: David Brownell <[email protected]>
Index: g26/drivers/rtc/Kconfig
===================================================================
--- g26.orig/drivers/rtc/Kconfig 2006-11-20 10:10:47.000000000 -0800
+++ g26/drivers/rtc/Kconfig 2006-11-20 10:10:49.000000000 -0800
@@ -182,6 +182,14 @@ config RTC_DRV_DS1742
This driver can also be built as a module. If so, the module
will be called rtc-ds1742.
+config RTC_DRV_OMAP
+ tristate "TI OMAP1"
+ depends on RTC_CLASS && ( \
+ ARCH_OMAP15XX || ARCH_OMAP16XX || ARCH_OMAP730 )
+ help
+ Say "yes" here to support the real time clock on TI OMAP1 chips.
+ This driver can also be built as a module called rtc-omap.
+
config RTC_DRV_PCF8563
tristate "Philips PCF8563/Epson RTC8564"
depends on RTC_CLASS && I2C
Index: g26/arch/arm/mach-omap1/devices.c
===================================================================
--- g26.orig/arch/arm/mach-omap1/devices.c 2006-11-20 10:03:05.000000000 -0800
+++ g26/arch/arm/mach-omap1/devices.c 2006-11-20 10:10:49.000000000 -0800
@@ -55,7 +55,7 @@ static inline void omap_init_irda(void)
/*-------------------------------------------------------------------------*/
-#if defined(CONFIG_OMAP_RTC) || defined(CONFIG_OMAP_RTC)
+#if defined(CONFIG_RTC_DRV_OMAP) || defined(CONFIG_RTC_DRV_OMAP_MODULE)
#define OMAP_RTC_BASE 0xfffb4800
Index: g26/drivers/rtc/Makefile
===================================================================
--- g26.orig/drivers/rtc/Makefile 2006-11-20 10:10:47.000000000 -0800
+++ g26/drivers/rtc/Makefile 2006-11-20 10:10:49.000000000 -0800
@@ -22,6 +22,7 @@ obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o
obj-$(CONFIG_RTC_DRV_DS1307) += rtc-ds1307.o
obj-$(CONFIG_RTC_DRV_DS1672) += rtc-ds1672.o
obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds1742.o
+obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o
obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
obj-$(CONFIG_RTC_DRV_RS5C372) += rtc-rs5c372.o
Index: g26/drivers/rtc/rtc-omap.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ g26/drivers/rtc/rtc-omap.c 2006-11-20 10:10:49.000000000 -0800
@@ -0,0 +1,572 @@
+/*
+ * TI OMAP1 Real Time Clock interface for Linux
+ *
+ * Copyright (C) 2003 MontaVista Software, Inc.
+ * Author: George G. Davis <[email protected]> or <[email protected]>
+ *
+ * Copyright (C) 2006 David Brownell (new RTC framework)
+ *
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/platform_device.h>
+
+#include <asm/io.h>
+#include <asm/mach/time.h>
+
+
+/* The OMAP1 RTC is a year/month/day/hours/minutes/seconds BCD clock
+ * with century-range alarm matching, driven by the 32kHz clock.
+ *
+ * The main user-visible ways it differs from PC RTCs are by omitting
+ * "don't care" alarm fields and sub-second periodic IRQs, and having
+ * an autoadjust mechanism to calibrate to the true oscillator rate.
+ *
+ * Board-specific wiring options include using split power mode with
+ * RTC_OFF_NOFF used as the reset signal (so the RTC won't be reset),
+ * and wiring RTC_WAKE_INT (so the RTC alarm can wake the system from
+ * low power modes). See the BOARD-SPECIFIC CUSTOMIZATION comment.
+ */
+
+#define OMAP_RTC_BASE 0xfffb4800
+
+/* RTC registers */
+#define OMAP_RTC_SECONDS_REG 0x00
+#define OMAP_RTC_MINUTES_REG 0x04
+#define OMAP_RTC_HOURS_REG 0x08
+#define OMAP_RTC_DAYS_REG 0x0C
+#define OMAP_RTC_MONTHS_REG 0x10
+#define OMAP_RTC_YEARS_REG 0x14
+#define OMAP_RTC_WEEKS_REG 0x18
+
+#define OMAP_RTC_ALARM_SECONDS_REG 0x20
+#define OMAP_RTC_ALARM_MINUTES_REG 0x24
+#define OMAP_RTC_ALARM_HOURS_REG 0x28
+#define OMAP_RTC_ALARM_DAYS_REG 0x2c
+#define OMAP_RTC_ALARM_MONTHS_REG 0x30
+#define OMAP_RTC_ALARM_YEARS_REG 0x34
+
+#define OMAP_RTC_CTRL_REG 0x40
+#define OMAP_RTC_STATUS_REG 0x44
+#define OMAP_RTC_INTERRUPTS_REG 0x48
+
+#define OMAP_RTC_COMP_LSB_REG 0x4c
+#define OMAP_RTC_COMP_MSB_REG 0x50
+#define OMAP_RTC_OSC_REG 0x54
+
+/* OMAP_RTC_CTRL_REG bit fields: */
+#define OMAP_RTC_CTRL_SPLIT (1<<7)
+#define OMAP_RTC_CTRL_DISABLE (1<<6)
+#define OMAP_RTC_CTRL_SET_32_COUNTER (1<<5)
+#define OMAP_RTC_CTRL_TEST (1<<4)
+#define OMAP_RTC_CTRL_MODE_12_24 (1<<3)
+#define OMAP_RTC_CTRL_AUTO_COMP (1<<2)
+#define OMAP_RTC_CTRL_ROUND_30S (1<<1)
+#define OMAP_RTC_CTRL_STOP (1<<0)
+
+/* OMAP_RTC_STATUS_REG bit fields: */
+#define OMAP_RTC_STATUS_POWER_UP (1<<7)
+#define OMAP_RTC_STATUS_ALARM (1<<6)
+#define OMAP_RTC_STATUS_1D_EVENT (1<<5)
+#define OMAP_RTC_STATUS_1H_EVENT (1<<4)
+#define OMAP_RTC_STATUS_1M_EVENT (1<<3)
+#define OMAP_RTC_STATUS_1S_EVENT (1<<2)
+#define OMAP_RTC_STATUS_RUN (1<<1)
+#define OMAP_RTC_STATUS_BUSY (1<<0)
+
+/* OMAP_RTC_INTERRUPTS_REG bit fields: */
+#define OMAP_RTC_INTERRUPTS_IT_ALARM (1<<3)
+#define OMAP_RTC_INTERRUPTS_IT_TIMER (1<<2)
+
+
+#define rtc_read(addr) omap_readb(OMAP_RTC_BASE + (addr))
+#define rtc_write(val, addr) omap_writeb(val, OMAP_RTC_BASE + (addr))
+
+
+/* platform_bus isn't hotpluggable, so for static linkage it'd be safe
+ * to get rid of probe() and remove() code ... too bad the driver struct
+ * remembers probe(), that's about 25% of the runtime footprint!!
+ */
+#ifndef MODULE
+#undef __devexit
+#undef __devexit_p
+#define __devexit __exit
+#define __devexit_p __exit_p
+#endif
+
+
+/* we rely on the rtc framework to handle locking (rtc->ops_lock),
+ * so the only other requirement is that register accesses which
+ * require BUSY to be clear are made with IRQs locally disabled
+ */
+static void rtc_wait_not_busy(void)
+{
+ int count = 0;
+ u8 status;
+
+ /* BUSY may stay active for 1/32768 second (~30 usec) */
+ for (count = 0; count < 50; count++) {
+ status = rtc_read(OMAP_RTC_STATUS_REG);
+ if ((status & (u8)OMAP_RTC_STATUS_BUSY) == 0)
+ break;
+ udelay(1);
+ }
+ /* now we have ~15 usec to read/write various registers */
+}
+
+static irqreturn_t rtc_irq(int irq, void *class_dev)
+{
+ unsigned long events = 0;
+ u8 irq_data;
+
+ irq_data = rtc_read(OMAP_RTC_STATUS_REG);
+
+ /* alarm irq? */
+ if (irq_data & OMAP_RTC_STATUS_ALARM) {
+ rtc_write(OMAP_RTC_STATUS_ALARM, OMAP_RTC_STATUS_REG);
+ events |= RTC_IRQF | RTC_AF;
+ }
+
+ /* 1/sec periodic/update irq? */
+ if (irq_data & OMAP_RTC_STATUS_1S_EVENT)
+ events |= RTC_IRQF | RTC_UF;
+
+ rtc_update_irq(class_dev, 1, events);
+
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_RTC_INTF_DEV
+
+static int
+omap_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+ u8 reg;
+
+ switch (cmd) {
+ case RTC_AIE_OFF:
+ case RTC_AIE_ON:
+ case RTC_UIE_OFF:
+ case RTC_UIE_ON:
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ local_irq_disable();
+ rtc_wait_not_busy();
+ reg = rtc_read(OMAP_RTC_INTERRUPTS_REG);
+ switch (cmd) {
+ /* AIE = Alarm Interrupt Enable */
+ case RTC_AIE_OFF:
+ reg &= ~OMAP_RTC_INTERRUPTS_IT_ALARM;
+ break;
+ case RTC_AIE_ON:
+ reg |= OMAP_RTC_INTERRUPTS_IT_ALARM;
+ break;
+ /* UIE = Update Interrupt Enable (1/second) */
+ case RTC_UIE_OFF:
+ reg &= ~OMAP_RTC_INTERRUPTS_IT_TIMER;
+ break;
+ case RTC_UIE_ON:
+ reg |= OMAP_RTC_INTERRUPTS_IT_TIMER;
+ break;
+ }
+ rtc_wait_not_busy();
+ rtc_write(reg, OMAP_RTC_INTERRUPTS_REG);
+ local_irq_enable();
+
+ return 0;
+}
+
+#else
+#define omap_rtc_ioctl NULL
+#endif
+
+/* this hardware doesn't support "don't care" alarm fields */
+static int tm2bcd(struct rtc_time *tm)
+{
+ if (rtc_valid_tm(tm) != 0)
+ return -EINVAL;
+
+ tm->tm_sec = BIN2BCD(tm->tm_sec);
+ tm->tm_min = BIN2BCD(tm->tm_min);
+ tm->tm_hour = BIN2BCD(tm->tm_hour);
+ tm->tm_mday = BIN2BCD(tm->tm_mday);
+
+ tm->tm_mon = BIN2BCD(tm->tm_mon + 1);
+
+ /* epoch == 1900 */
+ if (tm->tm_year < 100 || tm->tm_year > 199)
+ return -EINVAL;
+ tm->tm_year = BIN2BCD(tm->tm_year - 100);
+
+ return 0;
+}
+
+static void bcd2tm(struct rtc_time *tm)
+{
+ tm->tm_sec = BCD2BIN(tm->tm_sec);
+ tm->tm_min = BCD2BIN(tm->tm_min);
+ tm->tm_hour = BCD2BIN(tm->tm_hour);
+ tm->tm_mday = BCD2BIN(tm->tm_mday);
+ tm->tm_mon = BCD2BIN(tm->tm_mon) - 1;
+ /* epoch == 1900 */
+ tm->tm_year = BCD2BIN(tm->tm_year) + 100;
+}
+
+
+static int omap_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ /* we don't report wday/yday/isdst ... */
+ local_irq_disable();
+ rtc_wait_not_busy();
+
+ tm->tm_sec = rtc_read(OMAP_RTC_SECONDS_REG);
+ tm->tm_min = rtc_read(OMAP_RTC_MINUTES_REG);
+ tm->tm_hour = rtc_read(OMAP_RTC_HOURS_REG);
+ tm->tm_mday = rtc_read(OMAP_RTC_DAYS_REG);
+ tm->tm_mon = rtc_read(OMAP_RTC_MONTHS_REG);
+ tm->tm_year = rtc_read(OMAP_RTC_YEARS_REG);
+
+ local_irq_enable();
+
+ bcd2tm(tm);
+ return 0;
+}
+
+static int omap_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ if (tm2bcd(tm) < 0)
+ return -EINVAL;
+ local_irq_disable();
+ rtc_wait_not_busy();
+
+ rtc_write(tm->tm_year, OMAP_RTC_YEARS_REG);
+ rtc_write(tm->tm_mon, OMAP_RTC_MONTHS_REG);
+ rtc_write(tm->tm_mday, OMAP_RTC_DAYS_REG);
+ rtc_write(tm->tm_hour, OMAP_RTC_HOURS_REG);
+ rtc_write(tm->tm_min, OMAP_RTC_MINUTES_REG);
+ rtc_write(tm->tm_sec, OMAP_RTC_SECONDS_REG);
+
+ local_irq_enable();
+
+ return 0;
+}
+
+static int omap_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ local_irq_disable();
+ rtc_wait_not_busy();
+
+ alm->time.tm_sec = rtc_read(OMAP_RTC_ALARM_SECONDS_REG);
+ alm->time.tm_min = rtc_read(OMAP_RTC_ALARM_MINUTES_REG);
+ alm->time.tm_hour = rtc_read(OMAP_RTC_ALARM_HOURS_REG);
+ alm->time.tm_mday = rtc_read(OMAP_RTC_ALARM_DAYS_REG);
+ alm->time.tm_mon = rtc_read(OMAP_RTC_ALARM_MONTHS_REG);
+ alm->time.tm_year = rtc_read(OMAP_RTC_ALARM_YEARS_REG);
+
+ local_irq_enable();
+
+ bcd2tm(&alm->time);
+ alm->pending = !!(rtc_read(OMAP_RTC_INTERRUPTS_REG)
+ & OMAP_RTC_INTERRUPTS_IT_ALARM);
+ alm->enabled = alm->pending && device_may_wakeup(dev);
+
+ return 0;
+}
+
+static int omap_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ u8 reg;
+
+ /* Much userspace code uses RTC_ALM_SET, thus "don't care" for
+ * day/month/year specifies alarms up to 24 hours in the future.
+ * So we need to handle that ... but let's ignore the "don't care"
+ * values for hours/minutes/seconds.
+ */
+ if (alm->time.tm_mday <= 0
+ && alm->time.tm_mon < 0
+ && alm->time.tm_year < 0) {
+ struct rtc_time tm;
+ unsigned long now, then;
+
+ omap_rtc_read_time(dev, &tm);
+ rtc_tm_to_time(&tm, &now);
+
+ alm->time.tm_mday = tm.tm_mday;
+ alm->time.tm_mon = tm.tm_mon;
+ alm->time.tm_year = tm.tm_year;
+ rtc_tm_to_time(&alm->time, &then);
+
+ /* sometimes the alarm wraps into tomorrow */
+ if (then < now) {
+ rtc_time_to_tm(now + 24 * 60 * 60, &tm);
+ alm->time.tm_mday = tm.tm_mday;
+ alm->time.tm_mon = tm.tm_mon;
+ alm->time.tm_year = tm.tm_year;
+ }
+ }
+
+ if (tm2bcd(&alm->time) < 0)
+ return -EINVAL;
+
+ local_irq_disable();
+ rtc_wait_not_busy();
+
+ rtc_write(alm->time.tm_year, OMAP_RTC_ALARM_YEARS_REG);
+ rtc_write(alm->time.tm_mon, OMAP_RTC_ALARM_MONTHS_REG);
+ rtc_write(alm->time.tm_mday, OMAP_RTC_ALARM_DAYS_REG);
+ rtc_write(alm->time.tm_hour, OMAP_RTC_ALARM_HOURS_REG);
+ rtc_write(alm->time.tm_min, OMAP_RTC_ALARM_MINUTES_REG);
+ rtc_write(alm->time.tm_sec, OMAP_RTC_ALARM_SECONDS_REG);
+
+ reg = rtc_read(OMAP_RTC_INTERRUPTS_REG);
+ if (alm->enabled)
+ reg |= OMAP_RTC_INTERRUPTS_IT_ALARM;
+ else
+ reg &= ~OMAP_RTC_INTERRUPTS_IT_ALARM;
+ rtc_write(reg, OMAP_RTC_INTERRUPTS_REG);
+
+ local_irq_enable();
+
+ return 0;
+}
+
+static struct rtc_class_ops omap_rtc_ops = {
+ .ioctl = omap_rtc_ioctl,
+ .read_time = omap_rtc_read_time,
+ .set_time = omap_rtc_set_time,
+ .read_alarm = omap_rtc_read_alarm,
+ .set_alarm = omap_rtc_set_alarm,
+};
+
+static int omap_rtc_alarm;
+static int omap_rtc_timer;
+
+static int __devinit omap_rtc_probe(struct platform_device *pdev)
+{
+ struct resource *res, *mem;
+ struct rtc_device *rtc;
+ u8 reg, new_ctrl;
+
+ omap_rtc_timer = platform_get_irq(pdev, 0);
+ if (omap_rtc_timer <= 0) {
+ pr_debug("%s: no update irq?\n", pdev->name);
+ return -ENOENT;
+ }
+
+ omap_rtc_alarm = platform_get_irq(pdev, 1);
+ if (omap_rtc_alarm <= 0) {
+ pr_debug("%s: no alarm irq?\n", pdev->name);
+ return -ENOENT;
+ }
+
+ /* NOTE: using static mapping for RTC registers */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (res && res->start != OMAP_RTC_BASE) {
+ pr_debug("%s: RTC registers at %08x, expected %08x\n",
+ pdev->name, (unsigned) res->start, OMAP_RTC_BASE);
+ return -ENOENT;
+ }
+
+ if (res)
+ mem = request_mem_region(res->start,
+ res->end - res->start + 1,
+ pdev->name);
+ else
+ mem = NULL;
+ if (!mem) {
+ pr_debug("%s: RTC registers at %08x are not free\n",
+ pdev->name, OMAP_RTC_BASE);
+ return -EBUSY;
+ }
+
+ rtc = rtc_device_register(pdev->name, &pdev->dev,
+ &omap_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc)) {
+ pr_debug("%s: can't register RTC device, err %ld\n",
+ pdev->name, PTR_ERR(rtc));
+ goto fail;
+ }
+ platform_set_drvdata(pdev, rtc);
+ class_set_devdata(&rtc->class_dev, mem);
+
+ /* clear pending irqs, and set 1/second periodic,
+ * which we'll use instead of update irqs
+ */
+ rtc_write(0, OMAP_RTC_INTERRUPTS_REG);
+
+ /* clear old status */
+ reg = rtc_read(OMAP_RTC_STATUS_REG);
+ if (reg & (u8) OMAP_RTC_STATUS_POWER_UP) {
+ pr_info("%s: RTC power up reset detected\n",
+ pdev->name);
+ rtc_write(OMAP_RTC_STATUS_POWER_UP, OMAP_RTC_STATUS_REG);
+ }
+ if (reg & (u8) OMAP_RTC_STATUS_ALARM)
+ rtc_write(OMAP_RTC_STATUS_ALARM, OMAP_RTC_STATUS_REG);
+
+ /* handle periodic and alarm irqs */
+ if (request_irq(omap_rtc_timer, rtc_irq, SA_INTERRUPT,
+ rtc->class_dev.class_id, &rtc->class_dev)) {
+ pr_debug("%s: RTC timer interrupt IRQ%d already claimed\n",
+ pdev->name, omap_rtc_timer);
+ goto fail0;
+ }
+ if (request_irq(omap_rtc_alarm, rtc_irq, SA_INTERRUPT,
+ rtc->class_dev.class_id, &rtc->class_dev)) {
+ pr_debug("%s: RTC alarm interrupt IRQ%d already claimed\n",
+ pdev->name, omap_rtc_alarm);
+ goto fail1;
+ }
+
+ /* On boards with split power, RTC_ON_NOFF won't reset the RTC */
+ reg = rtc_read(OMAP_RTC_CTRL_REG);
+ if (reg & (u8) OMAP_RTC_CTRL_STOP)
+ pr_info("%s: already running\n", pdev->name);
+
+ /* force to 24 hour mode */
+ new_ctrl = reg & ~(OMAP_RTC_CTRL_SPLIT|OMAP_RTC_CTRL_AUTO_COMP);
+ new_ctrl |= OMAP_RTC_CTRL_STOP;
+
+ /* BOARD-SPECIFIC CUSTOMIZATION CAN GO HERE:
+ *
+ * - Boards wired so that RTC_WAKE_INT does something, and muxed
+ * right (W13_1610_RTC_WAKE_INT is the default after chip reset),
+ * should initialize the device wakeup flag appropriately.
+ *
+ * - Boards wired so RTC_ON_nOFF is used as the reset signal,
+ * rather than nPWRON_RESET, should forcibly enable split
+ * power mode. (Some chip errata report that RTC_CTRL_SPLIT
+ * is write-only, and always reads as zero...)
+ */
+ device_init_wakeup(&pdev->dev, 0);
+
+ if (new_ctrl & (u8) OMAP_RTC_CTRL_SPLIT)
+ pr_info("%s: split power mode\n", pdev->name);
+
+ if (reg != new_ctrl)
+ rtc_write(new_ctrl, OMAP_RTC_CTRL_REG);
+
+ return 0;
+
+fail1:
+ free_irq(omap_rtc_timer, NULL);
+fail0:
+ rtc_device_unregister(rtc);
+fail:
+ release_resource(mem);
+ return -EIO;
+}
+
+static int __devexit omap_rtc_remove(struct platform_device *pdev)
+{
+ struct rtc_device *rtc = platform_get_drvdata(pdev);;
+
+ device_init_wakeup(&pdev->dev, 0);
+
+ /* leave rtc running, but disable irqs */
+ rtc_write(0, OMAP_RTC_INTERRUPTS_REG);
+
+ free_irq(omap_rtc_timer, rtc);
+ free_irq(omap_rtc_alarm, rtc);
+
+ release_resource(class_get_devdata(&rtc->class_dev));
+ rtc_device_unregister(rtc);
+ return 0;
+}
+
+#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
+ * source, and in fact this enable() call is just saving a flag
+ * that's never used...
+ */
+ if (device_may_wakeup(&pdev->dev))
+ enable_irq_wake(omap_rtc_alarm);
+ else
+ rtc_write(0, OMAP_RTC_INTERRUPTS_REG);
+
+ return 0;
+}
+
+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
+ rtc_write(irqstat, OMAP_RTC_INTERRUPTS_REG);
+ return 0;
+}
+
+#else
+#define omap_rtc_suspend NULL
+#define omap_rtc_resume NULL
+#endif
+
+static void omap_rtc_shutdown(struct platform_device *pdev)
+{
+ rtc_write(0, OMAP_RTC_INTERRUPTS_REG);
+}
+
+MODULE_ALIAS("omap_rtc");
+static struct platform_driver omap_rtc_driver = {
+ .probe = omap_rtc_probe,
+ .remove = __devexit_p(omap_rtc_remove),
+ .suspend = omap_rtc_suspend,
+ .resume = omap_rtc_resume,
+ .shutdown = omap_rtc_shutdown,
+ .driver = {
+ .name = "omap_rtc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init rtc_init(void)
+{
+ return platform_driver_register(&omap_rtc_driver);
+}
+module_init(rtc_init);
+
+static void __exit rtc_exit(void)
+{
+ platform_driver_unregister(&omap_rtc_driver);
+}
+module_exit(rtc_exit);
+
+MODULE_AUTHOR("George G. Davis (and others)");
+MODULE_LICENSE("GPL");
On Mon, Nov 20, 2006 at 10:19:53AM -0800, David Brownell wrote:
> Minor updates to rtc-sa1100: report whether the alarm is enabled, remove
> duplicate procfs reporting of that factoid, and stick a FIXME at a place
> where alarms should be enabled (but aren't).
I think you're rather confused about alarms, but you're going to tell
me that it's me who is no doubt, so I'm not sure why I'm bothering to
write this mail.
The pre-rtc-lib user API was as follows:
- ALM_SET - sets the time of the alarm, does not enable or disable
current alarm settings
- AIE_ON - enables alarm interrupts
- AIE_OFF - disables alarm interrupts
- WKALM_SET - sets the wake up alarm, enables wake up alarm, indicates
whether wake up alarm is pending
So, alrm->enabled indicates _independently_ of the alarm interrupt whether
this should cause a wakeup, as per the SA1100 driver. Whether a wakeup
can occur with or without AIE_ON is implementation defined.
Now, if the new RTC library treats this differently, then /it's/ changing
the userspace API and is therefore buggy.
Note also that _lots_ of drivers don't support these new weird
device_set_wakeup_enable() and device_may_wakeup() calls - it seems
that there's an exercise for someone to go through and add a load of
device_set_wakeup_enable() before we go trying to use device_may_wakeup()
on those devices. I'm not presently sure what they're supposed to do
or achieve.
--
Russell King
Linux kernel 2.6 ARM Linux - http://www.arm.linux.org.uk/
maintainer of: 2.6 Serial core
On Mon, 20 Nov 2006 10:28:48 -0800
David Brownell <[email protected]> wrote:
> This creates a new RTC-framework driver for the RTC/calendar module found
> in various OMAP1 chips. (OMAP2 and OMAP3 use external RTCs, like those in
> TI's multifunction PM companion chips.) It's been in the Linux-OMAP tree
> for several months now, and other trees before that, so it's quite stable.
> The most notable issue is that the OMAP IRQ code doesn't yet support the
> RTC IRQ as a wakeup event. Once that's fixed, a patch will be needed.
>
> Signed-off-by: David Brownell <[email protected]>
Thanks David!
Acked-by: Alessandro Zummo <[email protected]>
--
Best regards,
Alessandro Zummo,
Tower Technologies - Turin, Italy
http://www.towertech.it
On Mon, 20 Nov 2006 10:17:19 -0800
David Brownell <[email protected]> wrote:
> Fix two minor botches in the procfs dumping of RTC alarm status:
>
> - Stop confusing "alarm enabled" with "wakeup enabled".
>
> - Don't display bogus "irq pending/un-acked" status; those are the rather
> pointless semantics EFI assigned to this (for a no-IRQs environment).
I wouldn't change that, the /proc interface to rtc is old
and should not be used anyhow. Here I'm trying to mimic
the behaviour of the original one.
sysfs provides a much better interface
(once we'll have all the attributes exported, of course :) )
I don't know if there's any user space tool relying on this.
If yes, then it should be fixed.
Any thoughts?
--
Best regards,
Alessandro Zummo,
Tower Technologies - Turin, Italy
http://www.towertech.it
On Monday 20 November 2006 2:48 pm, Russell King wrote:
> On Mon, Nov 20, 2006 at 10:19:53AM -0800, David Brownell wrote:
> > Minor updates to rtc-sa1100: report whether the alarm is enabled, remove
> > duplicate procfs reporting of that factoid, and stick a FIXME at a place
> > where alarms should be enabled (but aren't).
>
> I think you're rather confused about alarms, but you're going to tell
> me that it's me who is no doubt, so I'm not sure why I'm bothering to
> write this mail.
Because you think it likely that more than one person is confused,
and you're 100% certain that will continue unless someone like you
asks a question to help sort it out? :)
> The pre-rtc-lib user API was as follows:
There were actually several such APIs. Most tried to act like
the drivers/char/rtc.c driver (/dev/rtc on a PC), but of course
not all RTCs are at heart MC146818 clones.
ARM is as usual a rich source of counter-examples, with varying
capabilities; and it has its own RTC API, now being more or less
replaced by the generic framework. (I only see Integrator left
in terms of in-tree callers.)
> - ALM_SET - sets the time of the alarm, does not enable or disable
> current alarm settings
> - AIE_ON - enables alarm interrupts
> - AIE_OFF - disables alarm interrupts
Agreed; that's what the PC "rtc.c" did, and most drivers did a good
job of emulating those parts.
One thing that seemed to vary between drivers though: just what an
"alarm setting" is. A MC146818 supports only HH:MM:SS alarms, but
any of those fields can be wildcards. Lots of drivers implemented
that without the wildcard functionality. Some also handled DD-MON,
or DD-MON-YEAR specifiers.
I believe that for Linux today, that wildcard functionality is in
the "nonportable behavior" category.
> - WKALM_SET - sets the wake up alarm, enables wake up alarm, indicates
> whether wake up alarm is pending
Sort of. Semantics varied a lot.
Near as I can tell, WKALM_SET joined us with IA64 and the "efirtc.c"
driver (/dev/efirtc on IA64) which **DOES NOT** implement the other
calls above ... and implemented WKALM_SET by punting directly to EFI
firmware. Ditto its sibling WKALM_GET.
(The arch/arm/common/rtctime.c code seems to have come about four
years after the efirtc.c driver, and defined *different* semantics
for those calls ...)
Now, I happened to look up what EFI said about the semantics of those
firmware requests. Summarizing its manual, the two RTC calls are:
SET ... takes a YYYY-MON-DD HH:MM:SS date and an enable flag,
which affects "wakeup" (including power on)
GET ... returns the above, plus a "irq pending" flag, which is
cleared by calling SET with enable=false (and date=ignored)
The original definition of the WKLAM_* ioctls on Linux was operationally,
with respect to EFI firmware.
> So, alrm->enabled indicates _independently_ of the alarm interrupt whether
> this should cause a wakeup, as per the SA1100 driver.
Not according to the EFI docs. There is no other way to enable an alarm,
and there is no way to enable an alarm that doesn't do system wakeup.
They're joined at the hip, and there's no such notion as AIE_ON.
> Whether a wakeup
> can occur with or without AIE_ON is implementation defined.
See above ... a program expecting /dev/rtc0 and /dev/efirtc to act
the same would justifiably say it's a bug if WKALM_SET wasn't a
one-stop-shopping solution for setting and activating the alarm.
> Now, if the new RTC library treats this differently, then /it's/ changing
> the userspace API and is therefore buggy.
As shown above, it's not changing WKALM_SET ... at least in the way
you described, since AIE_ON has always been implicit there. It's
quite possible that folk implementing that call in the ARM framework
were not implementing those original semantics ("therefore buggy").
So it's arguable just how that call should be implemented. The
original definition seems clear, and that's what I've tried to use;
although that whole "pending" thing does not make sense to me if
there are real IRQs.
It seemed to me that the consensus about how to implement WKLALM_SET
(judging just by votes in drivers/rtc/*.c drivers) is: (a) it's a
combination of ALM_SET plus either AIE_ON or AIE_OFF, as with EFI;
(b) unlike ALM_SET, it supports alarms more than 24 hours in the
future; and (c) system wakeup, or lack thereof, is orthogonal to the
basic alarm functionality.
> Note also that _lots_ of drivers don't support these new weird
> device_set_wakeup_enable() and device_may_wakeup() calls - it seems
> that there's an exercise for someone to go through and add a load of
> device_set_wakeup_enable()
Wakeup is only relevant for PM-enabled platforms, and there aren't all
that many PM-enabled platforms in the first place. And among those,
there are darn few that support wakeup events sanely ... with PCs
solidly in the "does not support wakeup" category.
Initialization of wakeup-capable devices should device_init_wakeup().
Probably the best example of how to do that is in the at91rm9200 code,
where many drivers -- CF, MMC, USB host, USB peripheral, RTC, and
more -- are all wakeup-enabled.
> before we go trying to use device_may_wakeup()
> on those devices. I'm not presently sure what they're supposed to do
> or achieve.
Drivers should use device_may_wakeup() to decide whether they should
enable their wakeup capabilities. By default the answer is "yes" if
the hardware allows it. Userspace can change that, and would do so
for two basic reasons:
(1) It might not work correctly on a given system, even when it
looks to the kernel as if it should. Maybe some switch wore
out; I have a USB mouse that lies about being wakeup-capable.
(2) Drivers can usually save more power if they don't have to be
wakeup-capable. If disabling a few wakeup event sources were
to save 20 mA battery power, that could be very critical in
the effort to get another few hours out of a charge.
Plus of course just not wanting a particular thing to be a wakeup
event ... maybe inserting an MMC or CF card isn't such a big deal
on one system as it is on another.
- Dave
On Monday 20 November 2006 3:13 pm, Alessandro Zummo wrote:
> On Mon, 20 Nov 2006 10:17:19 -0800
> David Brownell <[email protected]> wrote:
>
> > Fix two minor botches in the procfs dumping of RTC alarm status:
> >
> > - Stop confusing "alarm enabled" with "wakeup enabled".
> >
> > - Don't display bogus "irq pending/un-acked" status; those are the rather
> > pointless semantics EFI assigned to this (for a no-IRQs environment).
>
> I wouldn't change that, the /proc interface to rtc is old
> and should not be used anyhow. Here I'm trying to mimic
> the behaviour of the original one.
The "original" one never had such fields. Even the efirtc.c
code (which originated those flags) didn't call them that;
it used "Enabled" not "alrm_enabled", so at least this patch
moves closer to that "original" behavior.
> sysfs provides a much better interface
> (once we'll have all the attributes exported, of course :) )
That's an orthgonal issue. (Though one can argue that the
procfs file should be /proc/driver/rtc0 etc, one per RTC,
rather than /proc/driver/rtc...)
> I don't know if there's any user space tool relying on this.
There shouldn't be any code parsing /proc/driver/rtc ... if there
is such stuff, it's already got so many variants to cope with that
adding one that actually matches the rest of the system would be
a net simplification.
> If yes, then it should be fixed.
>
> Any thoughts?
The whole RTC framework is still labeled "experimental", and
AFAIK I'm the first person to audit the use of those flags.
Until it's no longer experimental, I have a hard time thinking
that backwards compatibility should prevent fixing such interface
bugs ... interface bugs are normally in the "fix ASAP" category,
since if you delay fixing them the costs grow exponentially.
- Dave
On Mon, 20 Nov 2006 10:28:48 -0800
David Brownell <[email protected]> wrote:
> This creates a new RTC-framework driver for the RTC/calendar module found
> in various OMAP1 chips. (OMAP2 and OMAP3 use external RTCs, like those in
> TI's multifunction PM companion chips.) It's been in the Linux-OMAP tree
> for several months now, and other trees before that, so it's quite stable.
> The most notable issue is that the OMAP IRQ code doesn't yet support the
> RTC IRQ as a wakeup event. Once that's fixed, a patch will be needed.
>
> ...
>
> +static int omap_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
> +{
> + u8 reg;
> +
> + /* Much userspace code uses RTC_ALM_SET, thus "don't care" for
> + * day/month/year specifies alarms up to 24 hours in the future.
> + * So we need to handle that ... but let's ignore the "don't care"
> + * values for hours/minutes/seconds.
> + */
> + if (alm->time.tm_mday <= 0
> + && alm->time.tm_mon < 0
> + && alm->time.tm_year < 0) {
> + struct rtc_time tm;
> + unsigned long now, then;
> +
> + omap_rtc_read_time(dev, &tm);
> + rtc_tm_to_time(&tm, &now);
> +
> + alm->time.tm_mday = tm.tm_mday;
> + alm->time.tm_mon = tm.tm_mon;
> + alm->time.tm_year = tm.tm_year;
> + rtc_tm_to_time(&alm->time, &then);
> +
> + /* sometimes the alarm wraps into tomorrow */
> + if (then < now) {
This isn't wraparound-safe. If you have then=0xffffffff and now=0x00000001.
Perhaps that can't happen.
> +MODULE_AUTHOR("George G. Davis (and others)");
Maybe some additional signoffs would be appropirate?
On Tuesday 21 November 2006 5:19 pm, Andrew Morton wrote:
> On Mon, 20 Nov 2006 10:28:48 -0800
> > + /* sometimes the alarm wraps into tomorrow */
> > + if (then < now) {
>
> This isn't wraparound-safe. If you have then=0xffffffff and now=0x00000001.
>
> Perhaps that can't happen.
Starting in 2037 or whenever, various things will be breaking...
Probably the RTC lib routines should use a time_t, and when that gets
changed to 64 bits then things like this will be fixed automagically.
Right now they use "unsigned long".
I suggest Alessandro handle those issues.
> > +MODULE_AUTHOR("George G. Davis (and others)");
>
> Maybe some additional signoffs would be appropirate?
I pinged the MontaVista emails from the original driver; maybe
they'll send signoffs.
- Dave
On Tue, 21 Nov 2006 18:15:42 -0800
David Brownell <[email protected]> wrote:
> On Tuesday 21 November 2006 5:19 pm, Andrew Morton wrote:
> > On Mon, 20 Nov 2006 10:28:48 -0800
>
> > > + /* sometimes the alarm wraps into tomorrow */
> > > + if (then < now) {
> >
> > This isn't wraparound-safe. If you have then=0xffffffff and now=0x00000001.
> >
> > Perhaps that can't happen.
>
> Starting in 2037 or whenever, various things will be breaking...
>
> Probably the RTC lib routines should use a time_t, and when that gets
> changed to 64 bits then things like this will be fixed automagically.
> Right now they use "unsigned long".
>
> I suggest Alessandro handle those issues.
>
We could simply (ab)use timer_after() here.
>
> > > +MODULE_AUTHOR("George G. Davis (and others)");
> >
> > Maybe some additional signoffs would be appropirate?
>
> I pinged the MontaVista emails from the original driver; maybe
> they'll send signoffs.
>
OK. Such signoffs are certainly not a DCO _requirement_. If the MV guys
are asleep, no big deal - we trust david-b's assertion.
On Tue, 21 Nov 2006 18:15:42 -0800
David Brownell <[email protected]> wrote:
> On Tuesday 21 November 2006 5:19 pm, Andrew Morton wrote:
> > On Mon, 20 Nov 2006 10:28:48 -0800
>
> > > + /* sometimes the alarm wraps into tomorrow */
> > > + if (then < now) {
> >
> > This isn't wraparound-safe. If you have then=0xffffffff and now=0x00000001.
> >
> > Perhaps that can't happen.
>
> Starting in 2037 or whenever, various things will be breaking...
>
> Probably the RTC lib routines should use a time_t, and when that gets
> changed to 64 bits then things like this will be fixed automagically.
> Right now they use "unsigned long".
>
> I suggest Alessandro handle those issues.
I'll make a note for switching to time_t before
2037 :)
--
Best regards,
Alessandro Zummo,
Tower Technologies - Turin, Italy
http://www.towertech.it
On Mon, 20 Nov 2006 18:47:57 -0800
David Brownell <[email protected]> wrote:
> > I wouldn't change that, the /proc interface to rtc is old
> > and should not be used anyhow. Here I'm trying to mimic
> > the behaviour of the original one.
>
> The "original" one never had such fields. Even the efirtc.c
> code (which originated those flags) didn't call them that;
> it used "Enabled" not "alrm_enabled", so at least this patch
> moves closer to that "original" behavior.
[..]
> > I don't know if there's any user space tool relying on this.
>
> There shouldn't be any code parsing /proc/driver/rtc ... if there
> is such stuff, it's already got so many variants to cope with that
> adding one that actually matches the rest of the system would be
> a net simplification.
> The whole RTC framework is still labeled "experimental", and
> AFAIK I'm the first person to audit the use of those flags.
>
> Until it's no longer experimental, I have a hard time thinking
> that backwards compatibility should prevent fixing such interface
> bugs ... interface bugs are normally in the "fix ASAP" category,
> since if you delay fixing them the costs grow exponentially.
given the experimental status, I'm inclined to remove the /proc
driver right now.
Any objection?
--
Best regards,
Alessandro Zummo,
Tower Technologies - Turin, Italy
http://www.towertech.it
On Wednesday 22 November 2006 12:37 pm, Alessandro Zummo wrote:
>
> given the experimental status, I'm inclined to remove the /proc
> driver right now.
I actually prefer the /proc/driver/rtc* file to the sysfs stuff.
Even though some people would call that "politically incorrect".
- Dave
On Tuesday 21 November 2006 6:28 pm, Andrew Morton wrote:
> > Starting in 2037 or whenever, various things will be breaking...
> >
> > Probably the RTC lib routines should use a time_t, and when that gets
> > changed to 64 bits then things like this will be fixed automagically.
> > Right now they use "unsigned long".
> >
> > I suggest Alessandro handle those issues.
>
> We could simply (ab)use timer_after() here.
We could indeed. But things will still break badly in 2037,
and the timer_after() thing is clearly an incomplete fix.
- Dave