Index: linux-2.6.15gum/drivers/char/sa1100-rtc.c =================================================================== --- /dev/null +++ linux-2.6.15gum/drivers/char/sa1100-rtc.c @@ -0,0 +1,297 @@ +/* + * Real Time Clock interface for Linux on StrongARM SA1100 + * + * Copyright (c) 2000 Nils Faerber + * + * Based on rtc.c by Paul Gortmaker + * Date/time conversion routines taken from arch/arm/kernel/time.c + * by Linus Torvalds and Russel King + * and the GNU C Library + * ( ... I love the GPL ... just take what you need! ;) + * + * 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. + * + * 1.00 2001-06-08 Nicolas Pitre + * - added periodic timer capability using OSMR1 + * - flag compatibility with other RTC chips + * - permission checks for ioctls + * - major cleanup, partial rewrite + * + * 0.03 2001-03-07 CIH + * - Modify the bug setups RTC clock. + * + * 0.02 2001-02-27 Nils Faerber + * - removed mktime(), added alarm irq clear + * + * 0.01 2000-10-01 Nils Faerber + * - initial release + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_ARCH_PXA +#include +#endif + +#include +#include +#include +#include + +#define TIMER_FREQ 3686400 + +#define RTC_DEF_DIVIDER 32768 - 1 +#define RTC_DEF_TRIM 0 + +/* Those are the bits from a classic RTC we want to mimic */ +#define RTC_IRQF 0x80 /* any of the following 3 is active */ +#define RTC_PF 0x40 +#define RTC_AF 0x20 +#define RTC_UF 0x10 + +static unsigned long rtc_freq = 1024; +static struct rtc_time rtc_alarm = { + .tm_year = 0, + .tm_mon = 0, + .tm_mday = 0, + .tm_hour = 0, + .tm_mon = 0, + .tm_sec = 0, +}; + +extern spinlock_t rtc_lock; + +static int rtc_update_alarm(struct rtc_time *alrm) +{ + struct rtc_time alarm_tm, now_tm; + unsigned long now, time; + int ret; + + printk("Updating alarm\n"); + do { + now = RCNR; + rtc_time_to_tm(now, &now_tm); + rtc_next_alarm_time(&alarm_tm, &now_tm, alrm); + ret = rtc_tm_to_time(&alarm_tm, &time); + if (ret != 0) + break; + + RTSR = RTSR & (RTSR_HZE|RTSR_ALE|RTSR_AL); + RTAR = time; + } while (now != RCNR); + printk("set RTAR to %lx, now is %lx\n", time, now); + + return ret; +} + +static irqreturn_t rtc_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + unsigned int rtsr; + unsigned long events = 0; + + spin_lock(&rtc_lock); + + rtsr = RTSR; + /* clear interrupt sources */ + RTSR = 0; + RTSR = (RTSR_AL|RTSR_HZ) & (rtsr >> 2); + + printk(KERN_CRIT "rtc_interrupt: rtsr = %x\n", rtsr); + + /* clear alarm interrupt if it has occurred */ + if (rtsr & RTSR_AL) { + printk(KERN_CRIT "ALARM INTRRUPT\n"); + rtsr &= ~RTSR_ALE; + } + RTSR = rtsr & (RTSR_ALE|RTSR_HZE); + + /* update irq data & counter */ + if (rtsr & RTSR_AL) + events |= (RTC_AF|RTC_IRQF); + if (rtsr & RTSR_HZ) + events |= (RTC_UF|RTC_IRQF); + + rtc_update(1, events); + + if (rtsr & RTSR_AL && rtc_periodic_alarm(&rtc_alarm)) + rtc_update_alarm(&rtc_alarm); + + spin_unlock(&rtc_lock); + + return IRQ_HANDLED; +} + + +static int sa1100_rtc_open(void) +{ + int ret; + + ret = request_irq(IRQ_RTC1Hz, rtc_interrupt, SA_INTERRUPT, "rtc 1Hz", NULL); + if (ret) { + printk(KERN_ERR "rtc: IRQ%d already in use.\n", IRQ_RTC1Hz); + goto fail_ui; + } + ret = request_irq(IRQ_RTCAlrm, rtc_interrupt, SA_INTERRUPT, "rtc Alrm", NULL); + if (ret) { + printk(KERN_ERR "rtc: IRQ%d already in use.\n", IRQ_RTCAlrm); + goto fail_ai; + } + return 0; + + fail_ai: + free_irq(IRQ_RTC1Hz, NULL); + fail_ui: + return ret; +} + +static void sa1100_rtc_release(void) +{ + spin_lock_irq (&rtc_lock); + RTSR = 0; + OIER &= ~OIER_E1; + OSSR = OSSR_M1; + spin_unlock_irq (&rtc_lock); + + free_irq(IRQ_RTCAlrm, NULL); + free_irq(IRQ_RTC1Hz, NULL); +} + +static int sa1100_rtc_ioctl(unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case RTC_AIE_OFF: + spin_lock_irq(&rtc_lock); + RTSR &= ~RTSR_ALE; + spin_unlock_irq(&rtc_lock); + return 0; + case RTC_AIE_ON: + spin_lock_irq(&rtc_lock); + RTSR |= RTSR_ALE; + spin_unlock_irq(&rtc_lock); + return 0; + case RTC_UIE_OFF: + spin_lock_irq(&rtc_lock); + RTSR &= ~RTSR_HZE; + spin_unlock_irq(&rtc_lock); + return 0; + case RTC_UIE_ON: + spin_lock_irq(&rtc_lock); + RTSR |= RTSR_HZE; + spin_unlock_irq(&rtc_lock); + return 0; + } + return -EINVAL; +} + +static void sa1100_rtc_read_time(struct rtc_time *tm) +{ + rtc_time_to_tm(RCNR, tm); +} + +static int sa1100_rtc_set_time(struct rtc_time *tm) +{ + unsigned long time; + int ret; + + ret = rtc_tm_to_time(tm, &time); + if (ret == 0) + RCNR = time; + return ret; +} + +static void sa1100_rtc_read_alarm(struct rtc_wkalrm *alrm) +{ + memcpy(&alrm->time, &rtc_alarm, sizeof(struct rtc_time)); + alrm->pending = RTSR & RTSR_AL ? 1 : 0; +} + +static int sa1100_rtc_set_alarm(struct rtc_wkalrm *alrm) +{ + int ret; + + printk("sa1100_rtc_set_alarm\n"); + + spin_lock_irq(&rtc_lock); + ret = rtc_update_alarm(&alrm->time); + if (ret == 0) { + memcpy(&rtc_alarm, &alrm->time, sizeof(struct rtc_time)); + + if (alrm->enabled) + enable_irq_wake(IRQ_RTCAlrm); + else + disable_irq_wake(IRQ_RTCAlrm); + } + spin_unlock_irq(&rtc_lock); + + return ret; +} + +static int sa1100_rtc_proc(char *buf) +{ + char *p = buf; + + p += sprintf(p, "trim/divider\t: 0x%08x\n", RTTR); + p += sprintf(p, "alarm_IRQ\t: %s\n", (RTSR & RTSR_ALE) ? "yes" : "no" ); + p += sprintf(p, "update_IRQ\t: %s\n", (RTSR & RTSR_HZE) ? "yes" : "no"); + p += sprintf(p, "periodic_IRQ\t: %s\n", (OIER & OIER_E1) ? "yes" : "no"); + p += sprintf(p, "periodic_freq\t: %ld\n", rtc_freq); + + return p - buf; +} + +static struct rtc_ops sa1100_rtc_ops = { + .owner = THIS_MODULE, + .open = sa1100_rtc_open, + .release = sa1100_rtc_release, + .ioctl = sa1100_rtc_ioctl, + + .read_time = sa1100_rtc_read_time, + .set_time = sa1100_rtc_set_time, + .read_alarm = sa1100_rtc_read_alarm, + .set_alarm = sa1100_rtc_set_alarm, + .proc = sa1100_rtc_proc, +}; + +static int __init rtc_init(void) +{ + /* + * According to the manual we should be able to let RTTR be zero + * and then a default diviser for a 32.768KHz clock is used. + * Apparently this doesn't work, at least for my SA1110 rev 5. + * If the clock divider is uninitialized then reset it to the + * default value to get the 1Hz clock. + */ + if (RTTR == 0) { + RTTR = RTC_DEF_DIVIDER + (RTC_DEF_TRIM << 16); + printk(KERN_WARNING "rtc: warning: initializing default clock divider/trim value\n"); + /* The current RTC value probably doesn't make sense either */ + RCNR = 0; + } + + register_rtc(&sa1100_rtc_ops); + + return 0; +} + +static void __exit rtc_exit(void) +{ + unregister_rtc(&sa1100_rtc_ops); +} + +module_init(rtc_init); +module_exit(rtc_exit); + +MODULE_AUTHOR("Nils Faerber "); +MODULE_DESCRIPTION("SA1100 Realtime Clock Driver (RTC)"); +MODULE_LICENSE("GPL"); /* so says the header */ Index: linux-2.6.15gum/drivers/char/Makefile =================================================================== --- linux-2.6.15gum.orig/drivers/char/Makefile +++ linux-2.6.15gum/drivers/char/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_RTC) += rtc.o obj-$(CONFIG_HPET) += hpet.o obj-$(CONFIG_GEN_RTC) += genrtc.o obj-$(CONFIG_EFI_RTC) += efirtc.o +obj-$(CONFIG_SA1100_RTC) += sa1100-rtc.o obj-$(CONFIG_SGI_DS1286) += ds1286.o obj-$(CONFIG_SGI_IP27_RTC) += ip27-rtc.o obj-$(CONFIG_DS1302) += ds1302.o Index: linux-2.6.15gum/drivers/char/Kconfig =================================================================== --- linux-2.6.15gum.orig/drivers/char/Kconfig +++ linux-2.6.15gum/drivers/char/Kconfig @@ -790,6 +790,10 @@ config COBALT_LCD This option enables support for the LCD display and buttons found on Cobalt systems through a misc device. +config SA1100_RTC + tristate "SA1100/PXA2xx Real Time Clock" + depends on ARCH_SA1100 || ARCH_PXA + config DTLK tristate "Double Talk PC internal speech card support" help