| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 2 | *	Real Time Clock interface for Linux | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 3 | * | 
|  | 4 | *	Copyright (C) 1996 Paul Gortmaker | 
|  | 5 | * | 
|  | 6 | *	This driver allows use of the real time clock (built into | 
|  | 7 | *	nearly all computers) from user space. It exports the /dev/rtc | 
|  | 8 | *	interface supporting various ioctl() and also the | 
|  | 9 | *	/proc/driver/rtc pseudo-file for status information. | 
|  | 10 | * | 
|  | 11 | *	The ioctls can be used to set the interrupt behaviour and | 
|  | 12 | *	generation rate from the RTC via IRQ 8. Then the /dev/rtc | 
|  | 13 | *	interface can be used to make use of these timer interrupts, | 
|  | 14 | *	be they interval or alarm based. | 
|  | 15 | * | 
|  | 16 | *	The /dev/rtc interface will block on reads until an interrupt | 
|  | 17 | *	has been received. If a RTC interrupt has already happened, | 
|  | 18 | *	it will output an unsigned long and then block. The output value | 
|  | 19 | *	contains the interrupt status in the low byte and the number of | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 20 | *	interrupts since the last read in the remaining high bytes. The | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 21 | *	/dev/rtc interface can also be used with the select(2) call. | 
|  | 22 | * | 
|  | 23 | *	This program is free software; you can redistribute it and/or | 
|  | 24 | *	modify it under the terms of the GNU General Public License | 
|  | 25 | *	as published by the Free Software Foundation; either version | 
|  | 26 | *	2 of the License, or (at your option) any later version. | 
|  | 27 | * | 
|  | 28 | *	Based on other minimal char device drivers, like Alan's | 
|  | 29 | *	watchdog, Ted's random, etc. etc. | 
|  | 30 | * | 
|  | 31 | *	1.07	Paul Gortmaker. | 
|  | 32 | *	1.08	Miquel van Smoorenburg: disallow certain things on the | 
|  | 33 | *		DEC Alpha as the CMOS clock is also used for other things. | 
|  | 34 | *	1.09	Nikita Schmidt: epoch support and some Alpha cleanup. | 
|  | 35 | *	1.09a	Pete Zaitcev: Sun SPARC | 
|  | 36 | *	1.09b	Jeff Garzik: Modularize, init cleanup | 
|  | 37 | *	1.09c	Jeff Garzik: SMP cleanup | 
| Ralf Baechle | 12a0a70 | 2006-10-09 23:20:47 +0100 | [diff] [blame] | 38 | *	1.10	Paul Barton-Davis: add support for async I/O | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 39 | *	1.10a	Andrea Arcangeli: Alpha updates | 
|  | 40 | *	1.10b	Andrew Morton: SMP lock fix | 
|  | 41 | *	1.10c	Cesar Barros: SMP locking fixes and cleanup | 
|  | 42 | *	1.10d	Paul Gortmaker: delete paranoia check in rtc_exit | 
|  | 43 | *	1.10e	Maciej W. Rozycki: Handle DECstation's year weirdness. | 
| Ralf Baechle | 12a0a70 | 2006-10-09 23:20:47 +0100 | [diff] [blame] | 44 | *	1.11	Takashi Iwai: Kernel access functions | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 45 | *			      rtc_register/rtc_unregister/rtc_control | 
|  | 46 | *      1.11a   Daniele Bellucci: Audit create_proc_read_entry in rtc_init | 
|  | 47 | *	1.12	Venkatesh Pallipadi: Hooks for emulating rtc on HPET base-timer | 
|  | 48 | *		CONFIG_HPET_EMULATE_RTC | 
| Maciej W. Rozycki | 38e0e8c | 2006-07-10 04:45:30 -0700 | [diff] [blame] | 49 | *	1.12a	Maciej W. Rozycki: Handle memory-mapped chips properly. | 
| Alan Cox | b759958 | 2006-01-11 12:17:32 -0800 | [diff] [blame] | 50 | *	1.12ac	Alan Cox: Allow read access to the day of week register | 
| David John | 048cd58 | 2008-10-23 13:55:56 +0530 | [diff] [blame] | 51 | *	1.12b	David John: Remove calls to the BKL. | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 52 | */ | 
|  | 53 |  | 
| David John | 048cd58 | 2008-10-23 13:55:56 +0530 | [diff] [blame] | 54 | #define RTC_VERSION		"1.12b" | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 55 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 56 | /* | 
|  | 57 | *	Note that *all* calls to CMOS_READ and CMOS_WRITE are done with | 
|  | 58 | *	interrupts disabled. Due to the index-port/data-port (0x70/0x71) | 
|  | 59 | *	design of the RTC, we don't want two different things trying to | 
| Michael Witten | 5196d20 | 2012-01-24 22:48:04 +0000 | [diff] [blame] | 60 | *	get to it at once. (e.g. the periodic 11 min sync from | 
|  | 61 | *      kernel/time/ntp.c vs. this driver.) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 62 | */ | 
|  | 63 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 64 | #include <linux/interrupt.h> | 
|  | 65 | #include <linux/module.h> | 
|  | 66 | #include <linux/kernel.h> | 
|  | 67 | #include <linux/types.h> | 
|  | 68 | #include <linux/miscdevice.h> | 
|  | 69 | #include <linux/ioport.h> | 
|  | 70 | #include <linux/fcntl.h> | 
|  | 71 | #include <linux/mc146818rtc.h> | 
|  | 72 | #include <linux/init.h> | 
|  | 73 | #include <linux/poll.h> | 
|  | 74 | #include <linux/proc_fs.h> | 
|  | 75 | #include <linux/seq_file.h> | 
|  | 76 | #include <linux/spinlock.h> | 
| Ingo Molnar | 86ae13b | 2009-10-12 16:22:46 +0200 | [diff] [blame] | 77 | #include <linux/sched.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 78 | #include <linux/sysctl.h> | 
|  | 79 | #include <linux/wait.h> | 
|  | 80 | #include <linux/bcd.h> | 
| Luca Falavigna | 47f176f | 2005-06-28 20:44:42 -0700 | [diff] [blame] | 81 | #include <linux/delay.h> | 
| Alan Cox | 53f1b14 | 2008-07-23 21:30:32 -0700 | [diff] [blame] | 82 | #include <linux/uaccess.h> | 
| Christian Dietrich | a28ee47 | 2011-06-04 17:36:33 +0200 | [diff] [blame] | 83 | #include <linux/ratelimit.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 84 |  | 
|  | 85 | #include <asm/current.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 86 |  | 
| Chris Wright | 55f93af | 2007-07-21 17:10:09 +0200 | [diff] [blame] | 87 | #ifdef CONFIG_X86 | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 88 | #include <asm/hpet.h> | 
|  | 89 | #endif | 
|  | 90 |  | 
| David S. Miller | cdee99d | 2007-07-19 13:59:58 -0700 | [diff] [blame] | 91 | #ifdef CONFIG_SPARC32 | 
| David S. Miller | 7508132 | 2008-08-30 00:23:51 -0700 | [diff] [blame] | 92 | #include <linux/of.h> | 
|  | 93 | #include <linux/of_device.h> | 
|  | 94 | #include <asm/io.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 95 |  | 
|  | 96 | static unsigned long rtc_port; | 
| David S. Miller | 7508132 | 2008-08-30 00:23:51 -0700 | [diff] [blame] | 97 | static int rtc_irq; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 98 | #endif | 
|  | 99 |  | 
| David Brownell | 0f4d3fd | 2008-10-15 22:03:00 -0700 | [diff] [blame] | 100 | #ifdef	CONFIG_HPET_EMULATE_RTC | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 101 | #undef	RTC_IRQ | 
|  | 102 | #endif | 
|  | 103 |  | 
|  | 104 | #ifdef RTC_IRQ | 
|  | 105 | static int rtc_has_irq = 1; | 
|  | 106 | #endif | 
|  | 107 |  | 
|  | 108 | #ifndef CONFIG_HPET_EMULATE_RTC | 
|  | 109 | #define is_hpet_enabled()			0 | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 110 | #define hpet_set_alarm_time(hrs, min, sec)	0 | 
|  | 111 | #define hpet_set_periodic_freq(arg)		0 | 
|  | 112 | #define hpet_mask_rtc_irq_bit(arg)		0 | 
|  | 113 | #define hpet_set_rtc_irq_bit(arg)		0 | 
|  | 114 | #define hpet_rtc_timer_init()			do { } while (0) | 
|  | 115 | #define hpet_rtc_dropped_irq()			0 | 
| David Howells | 32fa458 | 2008-02-28 13:29:43 +0000 | [diff] [blame] | 116 | #define hpet_register_irq_handler(h)		({ 0; }) | 
|  | 117 | #define hpet_unregister_irq_handler(h)		({ 0; }) | 
| Andrew Morton | 533ffc2 | 2006-12-22 01:06:36 -0800 | [diff] [blame] | 118 | #ifdef RTC_IRQ | 
|  | 119 | static irqreturn_t hpet_rtc_interrupt(int irq, void *dev_id) | 
|  | 120 | { | 
|  | 121 | return 0; | 
|  | 122 | } | 
|  | 123 | #endif | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 124 | #endif | 
|  | 125 |  | 
|  | 126 | /* | 
|  | 127 | *	We sponge a minor off of the misc major. No need slurping | 
|  | 128 | *	up another valuable major dev number for this. If you add | 
|  | 129 | *	an ioctl, make sure you don't conflict with SPARC's RTC | 
|  | 130 | *	ioctls. | 
|  | 131 | */ | 
|  | 132 |  | 
|  | 133 | static struct fasync_struct *rtc_async_queue; | 
|  | 134 |  | 
|  | 135 | static DECLARE_WAIT_QUEUE_HEAD(rtc_wait); | 
|  | 136 |  | 
|  | 137 | #ifdef RTC_IRQ | 
| Jiri Slaby | 40565f1 | 2007-02-12 00:52:31 -0800 | [diff] [blame] | 138 | static void rtc_dropped_irq(unsigned long data); | 
|  | 139 |  | 
|  | 140 | static DEFINE_TIMER(rtc_irq_timer, rtc_dropped_irq, 0, 0); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 141 | #endif | 
|  | 142 |  | 
|  | 143 | static ssize_t rtc_read(struct file *file, char __user *buf, | 
|  | 144 | size_t count, loff_t *ppos); | 
|  | 145 |  | 
| Alan Cox | 53f1b14 | 2008-07-23 21:30:32 -0700 | [diff] [blame] | 146 | static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg); | 
| Adrian Bunk | 9580d85 | 2008-07-25 19:46:25 -0700 | [diff] [blame] | 147 | static void rtc_get_rtc_time(struct rtc_time *rtc_tm); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 148 |  | 
|  | 149 | #ifdef RTC_IRQ | 
|  | 150 | static unsigned int rtc_poll(struct file *file, poll_table *wait); | 
|  | 151 | #endif | 
|  | 152 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 153 | static void get_rtc_alm_time(struct rtc_time *alm_tm); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 154 | #ifdef RTC_IRQ | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 155 | static void set_rtc_irq_bit_locked(unsigned char bit); | 
|  | 156 | static void mask_rtc_irq_bit_locked(unsigned char bit); | 
|  | 157 |  | 
|  | 158 | static inline void set_rtc_irq_bit(unsigned char bit) | 
|  | 159 | { | 
|  | 160 | spin_lock_irq(&rtc_lock); | 
|  | 161 | set_rtc_irq_bit_locked(bit); | 
|  | 162 | spin_unlock_irq(&rtc_lock); | 
|  | 163 | } | 
|  | 164 |  | 
|  | 165 | static void mask_rtc_irq_bit(unsigned char bit) | 
|  | 166 | { | 
|  | 167 | spin_lock_irq(&rtc_lock); | 
|  | 168 | mask_rtc_irq_bit_locked(bit); | 
|  | 169 | spin_unlock_irq(&rtc_lock); | 
|  | 170 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 171 | #endif | 
|  | 172 |  | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 173 | #ifdef CONFIG_PROC_FS | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 174 | static int rtc_proc_open(struct inode *inode, struct file *file); | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 175 | #endif | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 176 |  | 
|  | 177 | /* | 
|  | 178 | *	Bits in rtc_status. (6 bits of room for future expansion) | 
|  | 179 | */ | 
|  | 180 |  | 
|  | 181 | #define RTC_IS_OPEN		0x01	/* means /dev/rtc is in use	*/ | 
|  | 182 | #define RTC_TIMER_ON		0x02	/* missed irq timer active	*/ | 
|  | 183 |  | 
|  | 184 | /* | 
|  | 185 | * rtc_status is never changed by rtc_interrupt, and ioctl/open/close is | 
| David John | 048cd58 | 2008-10-23 13:55:56 +0530 | [diff] [blame] | 186 | * protected by the spin lock rtc_lock. However, ioctl can still disable the | 
|  | 187 | * timer in rtc_status and then with del_timer after the interrupt has read | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 188 | * rtc_status but before mod_timer is called, which would then reenable the | 
|  | 189 | * timer (but you would need to have an awful timing before you'd trip on it) | 
|  | 190 | */ | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 191 | static unsigned long rtc_status;	/* bitmapped status byte.	*/ | 
|  | 192 | static unsigned long rtc_freq;		/* Current periodic IRQ rate	*/ | 
|  | 193 | static unsigned long rtc_irq_data;	/* our output to the world	*/ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 194 | static unsigned long rtc_max_user_freq = 64; /* > this, need CAP_SYS_RESOURCE */ | 
|  | 195 |  | 
|  | 196 | #ifdef RTC_IRQ | 
|  | 197 | /* | 
|  | 198 | * rtc_task_lock nests inside rtc_lock. | 
|  | 199 | */ | 
|  | 200 | static DEFINE_SPINLOCK(rtc_task_lock); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 201 | static rtc_task_t *rtc_callback; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 202 | #endif | 
|  | 203 |  | 
|  | 204 | /* | 
|  | 205 | *	If this driver ever becomes modularised, it will be really nice | 
|  | 206 | *	to make the epoch retain its value across module reload... | 
|  | 207 | */ | 
|  | 208 |  | 
|  | 209 | static unsigned long epoch = 1900;	/* year corresponding to 0x00	*/ | 
|  | 210 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 211 | static const unsigned char days_in_mo[] = | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 212 | {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; | 
|  | 213 |  | 
|  | 214 | /* | 
|  | 215 | * Returns true if a clock update is in progress | 
|  | 216 | */ | 
|  | 217 | static inline unsigned char rtc_is_updating(void) | 
|  | 218 | { | 
| Peter Zijlstra | 0b16f21 | 2006-09-25 16:24:23 -0700 | [diff] [blame] | 219 | unsigned long flags; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 220 | unsigned char uip; | 
|  | 221 |  | 
| Peter Zijlstra | 0b16f21 | 2006-09-25 16:24:23 -0700 | [diff] [blame] | 222 | spin_lock_irqsave(&rtc_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 223 | uip = (CMOS_READ(RTC_FREQ_SELECT) & RTC_UIP); | 
| Peter Zijlstra | 0b16f21 | 2006-09-25 16:24:23 -0700 | [diff] [blame] | 224 | spin_unlock_irqrestore(&rtc_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 225 | return uip; | 
|  | 226 | } | 
|  | 227 |  | 
|  | 228 | #ifdef RTC_IRQ | 
|  | 229 | /* | 
| Thomas Gleixner | 0f2ed4c | 2006-07-01 19:29:33 -0700 | [diff] [blame] | 230 | *	A very tiny interrupt handler. It runs with IRQF_DISABLED set, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 231 | *	but there is possibility of conflicting with the set_rtc_mmss() | 
|  | 232 | *	call (the rtc irq and the timer irq can easily run at the same | 
|  | 233 | *	time in two different CPUs). So we need to serialize | 
|  | 234 | *	accesses to the chip with the rtc_lock spinlock that each | 
|  | 235 | *	architecture should implement in the timer code. | 
|  | 236 | *	(See ./arch/XXXX/kernel/time.c for the set_rtc_mmss() function.) | 
|  | 237 | */ | 
|  | 238 |  | 
| Adrian Bunk | 9580d85 | 2008-07-25 19:46:25 -0700 | [diff] [blame] | 239 | static irqreturn_t rtc_interrupt(int irq, void *dev_id) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 240 | { | 
|  | 241 | /* | 
|  | 242 | *	Can be an alarm interrupt, update complete interrupt, | 
|  | 243 | *	or a periodic interrupt. We store the status in the | 
|  | 244 | *	low byte and the number of interrupts received since | 
|  | 245 | *	the last read in the remainder of rtc_irq_data. | 
|  | 246 | */ | 
|  | 247 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 248 | spin_lock(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 249 | rtc_irq_data += 0x100; | 
|  | 250 | rtc_irq_data &= ~0xff; | 
|  | 251 | if (is_hpet_enabled()) { | 
|  | 252 | /* | 
|  | 253 | * In this case it is HPET RTC interrupt handler | 
|  | 254 | * calling us, with the interrupt information | 
|  | 255 | * passed as arg1, instead of irq. | 
|  | 256 | */ | 
|  | 257 | rtc_irq_data |= (unsigned long)irq & 0xF0; | 
|  | 258 | } else { | 
|  | 259 | rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0); | 
|  | 260 | } | 
|  | 261 |  | 
|  | 262 | if (rtc_status & RTC_TIMER_ON) | 
|  | 263 | mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100); | 
|  | 264 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 265 | spin_unlock(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 266 |  | 
|  | 267 | /* Now do the rest of the actions */ | 
|  | 268 | spin_lock(&rtc_task_lock); | 
|  | 269 | if (rtc_callback) | 
|  | 270 | rtc_callback->func(rtc_callback->private_data); | 
|  | 271 | spin_unlock(&rtc_task_lock); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 272 | wake_up_interruptible(&rtc_wait); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 273 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 274 | kill_fasync(&rtc_async_queue, SIGIO, POLL_IN); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 275 |  | 
|  | 276 | return IRQ_HANDLED; | 
|  | 277 | } | 
|  | 278 | #endif | 
|  | 279 |  | 
|  | 280 | /* | 
|  | 281 | * sysctl-tuning infrastructure. | 
|  | 282 | */ | 
|  | 283 | static ctl_table rtc_table[] = { | 
|  | 284 | { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 285 | .procname	= "max-user-freq", | 
|  | 286 | .data		= &rtc_max_user_freq, | 
|  | 287 | .maxlen		= sizeof(int), | 
|  | 288 | .mode		= 0644, | 
| Eric W. Biederman | 6d45611 | 2009-11-16 03:11:48 -0800 | [diff] [blame] | 289 | .proc_handler	= proc_dointvec, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 290 | }, | 
| Eric W. Biederman | 894d249 | 2009-11-05 14:34:02 -0800 | [diff] [blame] | 291 | { } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 292 | }; | 
|  | 293 |  | 
|  | 294 | static ctl_table rtc_root[] = { | 
|  | 295 | { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 296 | .procname	= "rtc", | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 297 | .mode		= 0555, | 
|  | 298 | .child		= rtc_table, | 
|  | 299 | }, | 
| Eric W. Biederman | 894d249 | 2009-11-05 14:34:02 -0800 | [diff] [blame] | 300 | { } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 301 | }; | 
|  | 302 |  | 
|  | 303 | static ctl_table dev_root[] = { | 
|  | 304 | { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 305 | .procname	= "dev", | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 306 | .mode		= 0555, | 
|  | 307 | .child		= rtc_root, | 
|  | 308 | }, | 
| Eric W. Biederman | 894d249 | 2009-11-05 14:34:02 -0800 | [diff] [blame] | 309 | { } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 310 | }; | 
|  | 311 |  | 
|  | 312 | static struct ctl_table_header *sysctl_header; | 
|  | 313 |  | 
|  | 314 | static int __init init_sysctl(void) | 
|  | 315 | { | 
| Eric W. Biederman | 0b4d414 | 2007-02-14 00:34:09 -0800 | [diff] [blame] | 316 | sysctl_header = register_sysctl_table(dev_root); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 317 | return 0; | 
|  | 318 | } | 
|  | 319 |  | 
|  | 320 | static void __exit cleanup_sysctl(void) | 
|  | 321 | { | 
|  | 322 | unregister_sysctl_table(sysctl_header); | 
|  | 323 | } | 
|  | 324 |  | 
|  | 325 | /* | 
|  | 326 | *	Now all the various file operations that we export. | 
|  | 327 | */ | 
|  | 328 |  | 
|  | 329 | static ssize_t rtc_read(struct file *file, char __user *buf, | 
|  | 330 | size_t count, loff_t *ppos) | 
|  | 331 | { | 
|  | 332 | #ifndef RTC_IRQ | 
|  | 333 | return -EIO; | 
|  | 334 | #else | 
|  | 335 | DECLARE_WAITQUEUE(wait, current); | 
|  | 336 | unsigned long data; | 
|  | 337 | ssize_t retval; | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 338 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 339 | if (rtc_has_irq == 0) | 
|  | 340 | return -EIO; | 
|  | 341 |  | 
| Maciej W. Rozycki | 38e0e8c | 2006-07-10 04:45:30 -0700 | [diff] [blame] | 342 | /* | 
|  | 343 | * Historically this function used to assume that sizeof(unsigned long) | 
|  | 344 | * is the same in userspace and kernelspace.  This lead to problems | 
|  | 345 | * for configurations with multiple ABIs such a the MIPS o32 and 64 | 
|  | 346 | * ABIs supported on the same kernel.  So now we support read of both | 
|  | 347 | * 4 and 8 bytes and assume that's the sizeof(unsigned long) in the | 
|  | 348 | * userspace ABI. | 
|  | 349 | */ | 
|  | 350 | if (count != sizeof(unsigned int) && count !=  sizeof(unsigned long)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 351 | return -EINVAL; | 
|  | 352 |  | 
|  | 353 | add_wait_queue(&rtc_wait, &wait); | 
|  | 354 |  | 
|  | 355 | do { | 
|  | 356 | /* First make it right. Then make it fast. Putting this whole | 
|  | 357 | * block within the parentheses of a while would be too | 
|  | 358 | * confusing. And no, xchg() is not the answer. */ | 
|  | 359 |  | 
|  | 360 | __set_current_state(TASK_INTERRUPTIBLE); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 361 |  | 
|  | 362 | spin_lock_irq(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 363 | data = rtc_irq_data; | 
|  | 364 | rtc_irq_data = 0; | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 365 | spin_unlock_irq(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 366 |  | 
|  | 367 | if (data != 0) | 
|  | 368 | break; | 
|  | 369 |  | 
|  | 370 | if (file->f_flags & O_NONBLOCK) { | 
|  | 371 | retval = -EAGAIN; | 
|  | 372 | goto out; | 
|  | 373 | } | 
|  | 374 | if (signal_pending(current)) { | 
|  | 375 | retval = -ERESTARTSYS; | 
|  | 376 | goto out; | 
|  | 377 | } | 
|  | 378 | schedule(); | 
|  | 379 | } while (1); | 
|  | 380 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 381 | if (count == sizeof(unsigned int)) { | 
|  | 382 | retval = put_user(data, | 
|  | 383 | (unsigned int __user *)buf) ?: sizeof(int); | 
|  | 384 | } else { | 
|  | 385 | retval = put_user(data, | 
|  | 386 | (unsigned long __user *)buf) ?: sizeof(long); | 
|  | 387 | } | 
| Maciej W. Rozycki | 38e0e8c | 2006-07-10 04:45:30 -0700 | [diff] [blame] | 388 | if (!retval) | 
|  | 389 | retval = count; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 390 | out: | 
| Milind Arun Choudhary | cc0a8fb | 2007-05-08 00:30:52 -0700 | [diff] [blame] | 391 | __set_current_state(TASK_RUNNING); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 392 | remove_wait_queue(&rtc_wait, &wait); | 
|  | 393 |  | 
|  | 394 | return retval; | 
|  | 395 | #endif | 
|  | 396 | } | 
|  | 397 |  | 
|  | 398 | static int rtc_do_ioctl(unsigned int cmd, unsigned long arg, int kernel) | 
|  | 399 | { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 400 | struct rtc_time wtime; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 401 |  | 
|  | 402 | #ifdef RTC_IRQ | 
|  | 403 | if (rtc_has_irq == 0) { | 
|  | 404 | switch (cmd) { | 
|  | 405 | case RTC_AIE_OFF: | 
|  | 406 | case RTC_AIE_ON: | 
|  | 407 | case RTC_PIE_OFF: | 
|  | 408 | case RTC_PIE_ON: | 
|  | 409 | case RTC_UIE_OFF: | 
|  | 410 | case RTC_UIE_ON: | 
|  | 411 | case RTC_IRQP_READ: | 
|  | 412 | case RTC_IRQP_SET: | 
|  | 413 | return -EINVAL; | 
| Peter Senna Tschudin | f8885c2 | 2012-09-12 19:03:18 +0200 | [diff] [blame] | 414 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 415 | } | 
|  | 416 | #endif | 
|  | 417 |  | 
|  | 418 | switch (cmd) { | 
|  | 419 | #ifdef RTC_IRQ | 
|  | 420 | case RTC_AIE_OFF:	/* Mask alarm int. enab. bit	*/ | 
|  | 421 | { | 
|  | 422 | mask_rtc_irq_bit(RTC_AIE); | 
|  | 423 | return 0; | 
|  | 424 | } | 
|  | 425 | case RTC_AIE_ON:	/* Allow alarm interrupts.	*/ | 
|  | 426 | { | 
|  | 427 | set_rtc_irq_bit(RTC_AIE); | 
|  | 428 | return 0; | 
|  | 429 | } | 
|  | 430 | case RTC_PIE_OFF:	/* Mask periodic int. enab. bit	*/ | 
|  | 431 | { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 432 | /* can be called from isr via rtc_control() */ | 
|  | 433 | unsigned long flags; | 
|  | 434 |  | 
|  | 435 | spin_lock_irqsave(&rtc_lock, flags); | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 436 | mask_rtc_irq_bit_locked(RTC_PIE); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 437 | if (rtc_status & RTC_TIMER_ON) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 438 | rtc_status &= ~RTC_TIMER_ON; | 
|  | 439 | del_timer(&rtc_irq_timer); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 440 | } | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 441 | spin_unlock_irqrestore(&rtc_lock, flags); | 
|  | 442 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 443 | return 0; | 
|  | 444 | } | 
|  | 445 | case RTC_PIE_ON:	/* Allow periodic ints		*/ | 
|  | 446 | { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 447 | /* can be called from isr via rtc_control() */ | 
|  | 448 | unsigned long flags; | 
|  | 449 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 450 | /* | 
|  | 451 | * We don't really want Joe User enabling more | 
|  | 452 | * than 64Hz of interrupts on a multi-user machine. | 
|  | 453 | */ | 
|  | 454 | if (!kernel && (rtc_freq > rtc_max_user_freq) && | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 455 | (!capable(CAP_SYS_RESOURCE))) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 456 | return -EACCES; | 
|  | 457 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 458 | spin_lock_irqsave(&rtc_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 459 | if (!(rtc_status & RTC_TIMER_ON)) { | 
| Jiri Slaby | 40565f1 | 2007-02-12 00:52:31 -0800 | [diff] [blame] | 460 | mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + | 
|  | 461 | 2*HZ/100); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 462 | rtc_status |= RTC_TIMER_ON; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 463 | } | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 464 | set_rtc_irq_bit_locked(RTC_PIE); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 465 | spin_unlock_irqrestore(&rtc_lock, flags); | 
|  | 466 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 467 | return 0; | 
|  | 468 | } | 
|  | 469 | case RTC_UIE_OFF:	/* Mask ints from RTC updates.	*/ | 
|  | 470 | { | 
|  | 471 | mask_rtc_irq_bit(RTC_UIE); | 
|  | 472 | return 0; | 
|  | 473 | } | 
|  | 474 | case RTC_UIE_ON:	/* Allow ints for RTC updates.	*/ | 
|  | 475 | { | 
|  | 476 | set_rtc_irq_bit(RTC_UIE); | 
|  | 477 | return 0; | 
|  | 478 | } | 
|  | 479 | #endif | 
|  | 480 | case RTC_ALM_READ:	/* Read the present alarm time */ | 
|  | 481 | { | 
|  | 482 | /* | 
|  | 483 | * This returns a struct rtc_time. Reading >= 0xc0 | 
|  | 484 | * means "don't care" or "match all". Only the tm_hour, | 
|  | 485 | * tm_min, and tm_sec values are filled in. | 
|  | 486 | */ | 
|  | 487 | memset(&wtime, 0, sizeof(struct rtc_time)); | 
|  | 488 | get_rtc_alm_time(&wtime); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 489 | break; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 490 | } | 
|  | 491 | case RTC_ALM_SET:	/* Store a time into the alarm */ | 
|  | 492 | { | 
|  | 493 | /* | 
|  | 494 | * This expects a struct rtc_time. Writing 0xff means | 
|  | 495 | * "don't care" or "match all". Only the tm_hour, | 
|  | 496 | * tm_min and tm_sec are used. | 
|  | 497 | */ | 
|  | 498 | unsigned char hrs, min, sec; | 
|  | 499 | struct rtc_time alm_tm; | 
|  | 500 |  | 
|  | 501 | if (copy_from_user(&alm_tm, (struct rtc_time __user *)arg, | 
|  | 502 | sizeof(struct rtc_time))) | 
|  | 503 | return -EFAULT; | 
|  | 504 |  | 
|  | 505 | hrs = alm_tm.tm_hour; | 
|  | 506 | min = alm_tm.tm_min; | 
|  | 507 | sec = alm_tm.tm_sec; | 
|  | 508 |  | 
|  | 509 | spin_lock_irq(&rtc_lock); | 
|  | 510 | if (hpet_set_alarm_time(hrs, min, sec)) { | 
|  | 511 | /* | 
|  | 512 | * Fallthru and set alarm time in CMOS too, | 
|  | 513 | * so that we will get proper value in RTC_ALM_READ | 
|  | 514 | */ | 
|  | 515 | } | 
|  | 516 | if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) || | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 517 | RTC_ALWAYS_BCD) { | 
|  | 518 | if (sec < 60) | 
| Adrian Bunk | 357c6e6 | 2008-10-18 20:28:42 -0700 | [diff] [blame] | 519 | sec = bin2bcd(sec); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 520 | else | 
|  | 521 | sec = 0xff; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 522 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 523 | if (min < 60) | 
| Adrian Bunk | 357c6e6 | 2008-10-18 20:28:42 -0700 | [diff] [blame] | 524 | min = bin2bcd(min); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 525 | else | 
|  | 526 | min = 0xff; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 527 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 528 | if (hrs < 24) | 
| Adrian Bunk | 357c6e6 | 2008-10-18 20:28:42 -0700 | [diff] [blame] | 529 | hrs = bin2bcd(hrs); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 530 | else | 
|  | 531 | hrs = 0xff; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 532 | } | 
|  | 533 | CMOS_WRITE(hrs, RTC_HOURS_ALARM); | 
|  | 534 | CMOS_WRITE(min, RTC_MINUTES_ALARM); | 
|  | 535 | CMOS_WRITE(sec, RTC_SECONDS_ALARM); | 
|  | 536 | spin_unlock_irq(&rtc_lock); | 
|  | 537 |  | 
|  | 538 | return 0; | 
|  | 539 | } | 
|  | 540 | case RTC_RD_TIME:	/* Read the time/date from RTC	*/ | 
|  | 541 | { | 
|  | 542 | memset(&wtime, 0, sizeof(struct rtc_time)); | 
|  | 543 | rtc_get_rtc_time(&wtime); | 
|  | 544 | break; | 
|  | 545 | } | 
|  | 546 | case RTC_SET_TIME:	/* Set the RTC */ | 
|  | 547 | { | 
|  | 548 | struct rtc_time rtc_tm; | 
|  | 549 | unsigned char mon, day, hrs, min, sec, leap_yr; | 
|  | 550 | unsigned char save_control, save_freq_select; | 
|  | 551 | unsigned int yrs; | 
|  | 552 | #ifdef CONFIG_MACH_DECSTATION | 
|  | 553 | unsigned int real_yrs; | 
|  | 554 | #endif | 
|  | 555 |  | 
|  | 556 | if (!capable(CAP_SYS_TIME)) | 
|  | 557 | return -EACCES; | 
|  | 558 |  | 
|  | 559 | if (copy_from_user(&rtc_tm, (struct rtc_time __user *)arg, | 
|  | 560 | sizeof(struct rtc_time))) | 
|  | 561 | return -EFAULT; | 
|  | 562 |  | 
|  | 563 | yrs = rtc_tm.tm_year + 1900; | 
|  | 564 | mon = rtc_tm.tm_mon + 1;   /* tm_mon starts at zero */ | 
|  | 565 | day = rtc_tm.tm_mday; | 
|  | 566 | hrs = rtc_tm.tm_hour; | 
|  | 567 | min = rtc_tm.tm_min; | 
|  | 568 | sec = rtc_tm.tm_sec; | 
|  | 569 |  | 
|  | 570 | if (yrs < 1970) | 
|  | 571 | return -EINVAL; | 
|  | 572 |  | 
|  | 573 | leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400)); | 
|  | 574 |  | 
|  | 575 | if ((mon > 12) || (day == 0)) | 
|  | 576 | return -EINVAL; | 
|  | 577 |  | 
|  | 578 | if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr))) | 
|  | 579 | return -EINVAL; | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 580 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 581 | if ((hrs >= 24) || (min >= 60) || (sec >= 60)) | 
|  | 582 | return -EINVAL; | 
|  | 583 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 584 | yrs -= epoch; | 
|  | 585 | if (yrs > 255)		/* They are unsigned */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 586 | return -EINVAL; | 
|  | 587 |  | 
|  | 588 | spin_lock_irq(&rtc_lock); | 
|  | 589 | #ifdef CONFIG_MACH_DECSTATION | 
|  | 590 | real_yrs = yrs; | 
|  | 591 | yrs = 72; | 
|  | 592 |  | 
|  | 593 | /* | 
|  | 594 | * We want to keep the year set to 73 until March | 
|  | 595 | * for non-leap years, so that Feb, 29th is handled | 
|  | 596 | * correctly. | 
|  | 597 | */ | 
|  | 598 | if (!leap_yr && mon < 3) { | 
|  | 599 | real_yrs--; | 
|  | 600 | yrs = 73; | 
|  | 601 | } | 
|  | 602 | #endif | 
|  | 603 | /* These limits and adjustments are independent of | 
|  | 604 | * whether the chip is in binary mode or not. | 
|  | 605 | */ | 
|  | 606 | if (yrs > 169) { | 
|  | 607 | spin_unlock_irq(&rtc_lock); | 
|  | 608 | return -EINVAL; | 
|  | 609 | } | 
|  | 610 | if (yrs >= 100) | 
|  | 611 | yrs -= 100; | 
|  | 612 |  | 
|  | 613 | if (!(CMOS_READ(RTC_CONTROL) & RTC_DM_BINARY) | 
|  | 614 | || RTC_ALWAYS_BCD) { | 
| Adrian Bunk | 357c6e6 | 2008-10-18 20:28:42 -0700 | [diff] [blame] | 615 | sec = bin2bcd(sec); | 
|  | 616 | min = bin2bcd(min); | 
|  | 617 | hrs = bin2bcd(hrs); | 
|  | 618 | day = bin2bcd(day); | 
|  | 619 | mon = bin2bcd(mon); | 
|  | 620 | yrs = bin2bcd(yrs); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 621 | } | 
|  | 622 |  | 
|  | 623 | save_control = CMOS_READ(RTC_CONTROL); | 
|  | 624 | CMOS_WRITE((save_control|RTC_SET), RTC_CONTROL); | 
|  | 625 | save_freq_select = CMOS_READ(RTC_FREQ_SELECT); | 
|  | 626 | CMOS_WRITE((save_freq_select|RTC_DIV_RESET2), RTC_FREQ_SELECT); | 
|  | 627 |  | 
|  | 628 | #ifdef CONFIG_MACH_DECSTATION | 
|  | 629 | CMOS_WRITE(real_yrs, RTC_DEC_YEAR); | 
|  | 630 | #endif | 
|  | 631 | CMOS_WRITE(yrs, RTC_YEAR); | 
|  | 632 | CMOS_WRITE(mon, RTC_MONTH); | 
|  | 633 | CMOS_WRITE(day, RTC_DAY_OF_MONTH); | 
|  | 634 | CMOS_WRITE(hrs, RTC_HOURS); | 
|  | 635 | CMOS_WRITE(min, RTC_MINUTES); | 
|  | 636 | CMOS_WRITE(sec, RTC_SECONDS); | 
|  | 637 |  | 
|  | 638 | CMOS_WRITE(save_control, RTC_CONTROL); | 
|  | 639 | CMOS_WRITE(save_freq_select, RTC_FREQ_SELECT); | 
|  | 640 |  | 
|  | 641 | spin_unlock_irq(&rtc_lock); | 
|  | 642 | return 0; | 
|  | 643 | } | 
|  | 644 | #ifdef RTC_IRQ | 
|  | 645 | case RTC_IRQP_READ:	/* Read the periodic IRQ rate.	*/ | 
|  | 646 | { | 
|  | 647 | return put_user(rtc_freq, (unsigned long __user *)arg); | 
|  | 648 | } | 
|  | 649 | case RTC_IRQP_SET:	/* Set periodic IRQ rate.	*/ | 
|  | 650 | { | 
|  | 651 | int tmp = 0; | 
|  | 652 | unsigned char val; | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 653 | /* can be called from isr via rtc_control() */ | 
|  | 654 | unsigned long flags; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 655 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 656 | /* | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 657 | * The max we can do is 8192Hz. | 
|  | 658 | */ | 
|  | 659 | if ((arg < 2) || (arg > 8192)) | 
|  | 660 | return -EINVAL; | 
|  | 661 | /* | 
|  | 662 | * We don't really want Joe User generating more | 
|  | 663 | * than 64Hz of interrupts on a multi-user machine. | 
|  | 664 | */ | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 665 | if (!kernel && (arg > rtc_max_user_freq) && | 
|  | 666 | !capable(CAP_SYS_RESOURCE)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 667 | return -EACCES; | 
|  | 668 |  | 
|  | 669 | while (arg > (1<<tmp)) | 
|  | 670 | tmp++; | 
|  | 671 |  | 
|  | 672 | /* | 
|  | 673 | * Check that the input was really a power of 2. | 
|  | 674 | */ | 
|  | 675 | if (arg != (1<<tmp)) | 
|  | 676 | return -EINVAL; | 
|  | 677 |  | 
| Paul Gortmaker | 61ca9da | 2008-07-10 17:30:48 -0700 | [diff] [blame] | 678 | rtc_freq = arg; | 
|  | 679 |  | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 680 | spin_lock_irqsave(&rtc_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 681 | if (hpet_set_periodic_freq(arg)) { | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 682 | spin_unlock_irqrestore(&rtc_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 683 | return 0; | 
|  | 684 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 685 |  | 
|  | 686 | val = CMOS_READ(RTC_FREQ_SELECT) & 0xf0; | 
|  | 687 | val |= (16 - tmp); | 
|  | 688 | CMOS_WRITE(val, RTC_FREQ_SELECT); | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 689 | spin_unlock_irqrestore(&rtc_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 690 | return 0; | 
|  | 691 | } | 
|  | 692 | #endif | 
|  | 693 | case RTC_EPOCH_READ:	/* Read the epoch.	*/ | 
|  | 694 | { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 695 | return put_user(epoch, (unsigned long __user *)arg); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 696 | } | 
|  | 697 | case RTC_EPOCH_SET:	/* Set the epoch.	*/ | 
|  | 698 | { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 699 | /* | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 700 | * There were no RTC clocks before 1900. | 
|  | 701 | */ | 
|  | 702 | if (arg < 1900) | 
|  | 703 | return -EINVAL; | 
|  | 704 |  | 
|  | 705 | if (!capable(CAP_SYS_TIME)) | 
|  | 706 | return -EACCES; | 
|  | 707 |  | 
|  | 708 | epoch = arg; | 
|  | 709 | return 0; | 
|  | 710 | } | 
|  | 711 | default: | 
|  | 712 | return -ENOTTY; | 
|  | 713 | } | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 714 | return copy_to_user((void __user *)arg, | 
|  | 715 | &wtime, sizeof wtime) ? -EFAULT : 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 716 | } | 
|  | 717 |  | 
| Alan Cox | 53f1b14 | 2008-07-23 21:30:32 -0700 | [diff] [blame] | 718 | static long rtc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 719 | { | 
| Alan Cox | 53f1b14 | 2008-07-23 21:30:32 -0700 | [diff] [blame] | 720 | long ret; | 
| Alan Cox | 53f1b14 | 2008-07-23 21:30:32 -0700 | [diff] [blame] | 721 | ret = rtc_do_ioctl(cmd, arg, 0); | 
| Alan Cox | 53f1b14 | 2008-07-23 21:30:32 -0700 | [diff] [blame] | 722 | return ret; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 723 | } | 
|  | 724 |  | 
|  | 725 | /* | 
|  | 726 | *	We enforce only one user at a time here with the open/close. | 
|  | 727 | *	Also clear the previous interrupt data on an open, and clean | 
|  | 728 | *	up things on a close. | 
|  | 729 | */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 730 | static int rtc_open(struct inode *inode, struct file *file) | 
|  | 731 | { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 732 | spin_lock_irq(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 733 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 734 | if (rtc_status & RTC_IS_OPEN) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 735 | goto out_busy; | 
|  | 736 |  | 
|  | 737 | rtc_status |= RTC_IS_OPEN; | 
|  | 738 |  | 
|  | 739 | rtc_irq_data = 0; | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 740 | spin_unlock_irq(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 741 | return 0; | 
|  | 742 |  | 
|  | 743 | out_busy: | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 744 | spin_unlock_irq(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 745 | return -EBUSY; | 
|  | 746 | } | 
|  | 747 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 748 | static int rtc_fasync(int fd, struct file *filp, int on) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 749 | { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 750 | return fasync_helper(fd, filp, on, &rtc_async_queue); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 751 | } | 
|  | 752 |  | 
|  | 753 | static int rtc_release(struct inode *inode, struct file *file) | 
|  | 754 | { | 
|  | 755 | #ifdef RTC_IRQ | 
|  | 756 | unsigned char tmp; | 
|  | 757 |  | 
|  | 758 | if (rtc_has_irq == 0) | 
|  | 759 | goto no_irq; | 
|  | 760 |  | 
|  | 761 | /* | 
|  | 762 | * Turn off all interrupts once the device is no longer | 
|  | 763 | * in use, and clear the data. | 
|  | 764 | */ | 
|  | 765 |  | 
|  | 766 | spin_lock_irq(&rtc_lock); | 
|  | 767 | if (!hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE)) { | 
|  | 768 | tmp = CMOS_READ(RTC_CONTROL); | 
|  | 769 | tmp &=  ~RTC_PIE; | 
|  | 770 | tmp &=  ~RTC_AIE; | 
|  | 771 | tmp &=  ~RTC_UIE; | 
|  | 772 | CMOS_WRITE(tmp, RTC_CONTROL); | 
|  | 773 | CMOS_READ(RTC_INTR_FLAGS); | 
|  | 774 | } | 
|  | 775 | if (rtc_status & RTC_TIMER_ON) { | 
|  | 776 | rtc_status &= ~RTC_TIMER_ON; | 
|  | 777 | del_timer(&rtc_irq_timer); | 
|  | 778 | } | 
|  | 779 | spin_unlock_irq(&rtc_lock); | 
|  | 780 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 781 | no_irq: | 
|  | 782 | #endif | 
|  | 783 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 784 | spin_lock_irq(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 785 | rtc_irq_data = 0; | 
|  | 786 | rtc_status &= ~RTC_IS_OPEN; | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 787 | spin_unlock_irq(&rtc_lock); | 
|  | 788 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 789 | return 0; | 
|  | 790 | } | 
|  | 791 |  | 
|  | 792 | #ifdef RTC_IRQ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 793 | static unsigned int rtc_poll(struct file *file, poll_table *wait) | 
|  | 794 | { | 
|  | 795 | unsigned long l; | 
|  | 796 |  | 
|  | 797 | if (rtc_has_irq == 0) | 
|  | 798 | return 0; | 
|  | 799 |  | 
|  | 800 | poll_wait(file, &rtc_wait, wait); | 
|  | 801 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 802 | spin_lock_irq(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 803 | l = rtc_irq_data; | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 804 | spin_unlock_irq(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 805 |  | 
|  | 806 | if (l != 0) | 
|  | 807 | return POLLIN | POLLRDNORM; | 
|  | 808 | return 0; | 
|  | 809 | } | 
|  | 810 | #endif | 
|  | 811 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 812 | int rtc_register(rtc_task_t *task) | 
|  | 813 | { | 
|  | 814 | #ifndef RTC_IRQ | 
|  | 815 | return -EIO; | 
|  | 816 | #else | 
|  | 817 | if (task == NULL || task->func == NULL) | 
|  | 818 | return -EINVAL; | 
|  | 819 | spin_lock_irq(&rtc_lock); | 
|  | 820 | if (rtc_status & RTC_IS_OPEN) { | 
|  | 821 | spin_unlock_irq(&rtc_lock); | 
|  | 822 | return -EBUSY; | 
|  | 823 | } | 
|  | 824 | spin_lock(&rtc_task_lock); | 
|  | 825 | if (rtc_callback) { | 
|  | 826 | spin_unlock(&rtc_task_lock); | 
|  | 827 | spin_unlock_irq(&rtc_lock); | 
|  | 828 | return -EBUSY; | 
|  | 829 | } | 
|  | 830 | rtc_status |= RTC_IS_OPEN; | 
|  | 831 | rtc_callback = task; | 
|  | 832 | spin_unlock(&rtc_task_lock); | 
|  | 833 | spin_unlock_irq(&rtc_lock); | 
|  | 834 | return 0; | 
|  | 835 | #endif | 
|  | 836 | } | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 837 | EXPORT_SYMBOL(rtc_register); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 838 |  | 
|  | 839 | int rtc_unregister(rtc_task_t *task) | 
|  | 840 | { | 
|  | 841 | #ifndef RTC_IRQ | 
|  | 842 | return -EIO; | 
|  | 843 | #else | 
|  | 844 | unsigned char tmp; | 
|  | 845 |  | 
|  | 846 | spin_lock_irq(&rtc_lock); | 
|  | 847 | spin_lock(&rtc_task_lock); | 
|  | 848 | if (rtc_callback != task) { | 
|  | 849 | spin_unlock(&rtc_task_lock); | 
|  | 850 | spin_unlock_irq(&rtc_lock); | 
|  | 851 | return -ENXIO; | 
|  | 852 | } | 
|  | 853 | rtc_callback = NULL; | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 854 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 855 | /* disable controls */ | 
|  | 856 | if (!hpet_mask_rtc_irq_bit(RTC_PIE | RTC_AIE | RTC_UIE)) { | 
|  | 857 | tmp = CMOS_READ(RTC_CONTROL); | 
|  | 858 | tmp &= ~RTC_PIE; | 
|  | 859 | tmp &= ~RTC_AIE; | 
|  | 860 | tmp &= ~RTC_UIE; | 
|  | 861 | CMOS_WRITE(tmp, RTC_CONTROL); | 
|  | 862 | CMOS_READ(RTC_INTR_FLAGS); | 
|  | 863 | } | 
|  | 864 | if (rtc_status & RTC_TIMER_ON) { | 
|  | 865 | rtc_status &= ~RTC_TIMER_ON; | 
|  | 866 | del_timer(&rtc_irq_timer); | 
|  | 867 | } | 
|  | 868 | rtc_status &= ~RTC_IS_OPEN; | 
|  | 869 | spin_unlock(&rtc_task_lock); | 
|  | 870 | spin_unlock_irq(&rtc_lock); | 
|  | 871 | return 0; | 
|  | 872 | #endif | 
|  | 873 | } | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 874 | EXPORT_SYMBOL(rtc_unregister); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 875 |  | 
|  | 876 | int rtc_control(rtc_task_t *task, unsigned int cmd, unsigned long arg) | 
|  | 877 | { | 
|  | 878 | #ifndef RTC_IRQ | 
|  | 879 | return -EIO; | 
|  | 880 | #else | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 881 | unsigned long flags; | 
|  | 882 | if (cmd != RTC_PIE_ON && cmd != RTC_PIE_OFF && cmd != RTC_IRQP_SET) | 
|  | 883 | return -EINVAL; | 
|  | 884 | spin_lock_irqsave(&rtc_task_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 885 | if (rtc_callback != task) { | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 886 | spin_unlock_irqrestore(&rtc_task_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 887 | return -ENXIO; | 
|  | 888 | } | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 889 | spin_unlock_irqrestore(&rtc_task_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 890 | return rtc_do_ioctl(cmd, arg, 1); | 
|  | 891 | #endif | 
|  | 892 | } | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 893 | EXPORT_SYMBOL(rtc_control); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 894 |  | 
|  | 895 | /* | 
|  | 896 | *	The various file operations we support. | 
|  | 897 | */ | 
|  | 898 |  | 
| Arjan van de Ven | 62322d2 | 2006-07-03 00:24:21 -0700 | [diff] [blame] | 899 | static const struct file_operations rtc_fops = { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 900 | .owner		= THIS_MODULE, | 
|  | 901 | .llseek		= no_llseek, | 
|  | 902 | .read		= rtc_read, | 
|  | 903 | #ifdef RTC_IRQ | 
|  | 904 | .poll		= rtc_poll, | 
|  | 905 | #endif | 
| Alan Cox | 53f1b14 | 2008-07-23 21:30:32 -0700 | [diff] [blame] | 906 | .unlocked_ioctl	= rtc_ioctl, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 907 | .open		= rtc_open, | 
|  | 908 | .release	= rtc_release, | 
|  | 909 | .fasync		= rtc_fasync, | 
|  | 910 | }; | 
|  | 911 |  | 
|  | 912 | static struct miscdevice rtc_dev = { | 
|  | 913 | .minor		= RTC_MINOR, | 
|  | 914 | .name		= "rtc", | 
|  | 915 | .fops		= &rtc_fops, | 
|  | 916 | }; | 
|  | 917 |  | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 918 | #ifdef CONFIG_PROC_FS | 
| Arjan van de Ven | 62322d2 | 2006-07-03 00:24:21 -0700 | [diff] [blame] | 919 | static const struct file_operations rtc_proc_fops = { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 920 | .owner		= THIS_MODULE, | 
|  | 921 | .open		= rtc_proc_open, | 
|  | 922 | .read		= seq_read, | 
|  | 923 | .llseek		= seq_lseek, | 
|  | 924 | .release	= single_release, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 925 | }; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 926 | #endif | 
|  | 927 |  | 
| Bjorn Helgaas | 9626f1f1 | 2007-11-14 16:59:57 -0800 | [diff] [blame] | 928 | static resource_size_t rtc_size; | 
|  | 929 |  | 
|  | 930 | static struct resource * __init rtc_request_region(resource_size_t size) | 
|  | 931 | { | 
|  | 932 | struct resource *r; | 
|  | 933 |  | 
|  | 934 | if (RTC_IOMAPPED) | 
|  | 935 | r = request_region(RTC_PORT(0), size, "rtc"); | 
|  | 936 | else | 
|  | 937 | r = request_mem_region(RTC_PORT(0), size, "rtc"); | 
|  | 938 |  | 
|  | 939 | if (r) | 
|  | 940 | rtc_size = size; | 
|  | 941 |  | 
|  | 942 | return r; | 
|  | 943 | } | 
|  | 944 |  | 
| Bjorn Helgaas | 4c06be1 | 2007-11-14 16:59:56 -0800 | [diff] [blame] | 945 | static void rtc_release_region(void) | 
|  | 946 | { | 
|  | 947 | if (RTC_IOMAPPED) | 
| Bjorn Helgaas | 9626f1f1 | 2007-11-14 16:59:57 -0800 | [diff] [blame] | 948 | release_region(RTC_PORT(0), rtc_size); | 
| Bjorn Helgaas | 4c06be1 | 2007-11-14 16:59:56 -0800 | [diff] [blame] | 949 | else | 
| Bjorn Helgaas | 9626f1f1 | 2007-11-14 16:59:57 -0800 | [diff] [blame] | 950 | release_mem_region(RTC_PORT(0), rtc_size); | 
| Bjorn Helgaas | 4c06be1 | 2007-11-14 16:59:56 -0800 | [diff] [blame] | 951 | } | 
|  | 952 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 953 | static int __init rtc_init(void) | 
|  | 954 | { | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 955 | #ifdef CONFIG_PROC_FS | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 956 | struct proc_dir_entry *ent; | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 957 | #endif | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 958 | #if defined(__alpha__) || defined(__mips__) | 
|  | 959 | unsigned int year, ctrl; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 960 | char *guess = NULL; | 
|  | 961 | #endif | 
| David S. Miller | cdee99d | 2007-07-19 13:59:58 -0700 | [diff] [blame] | 962 | #ifdef CONFIG_SPARC32 | 
| David S. Miller | 7508132 | 2008-08-30 00:23:51 -0700 | [diff] [blame] | 963 | struct device_node *ebus_dp; | 
| Grant Likely | 2dc1158 | 2010-08-06 09:25:50 -0600 | [diff] [blame] | 964 | struct platform_device *op; | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 965 | #else | 
| Maciej W. Rozycki | 38e0e8c | 2006-07-10 04:45:30 -0700 | [diff] [blame] | 966 | void *r; | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 967 | #ifdef RTC_IRQ | 
|  | 968 | irq_handler_t rtc_int_handler_ptr; | 
|  | 969 | #endif | 
| Maciej W. Rozycki | 38e0e8c | 2006-07-10 04:45:30 -0700 | [diff] [blame] | 970 | #endif | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 971 |  | 
| David S. Miller | cdee99d | 2007-07-19 13:59:58 -0700 | [diff] [blame] | 972 | #ifdef CONFIG_SPARC32 | 
| David S. Miller | 7508132 | 2008-08-30 00:23:51 -0700 | [diff] [blame] | 973 | for_each_node_by_name(ebus_dp, "ebus") { | 
|  | 974 | struct device_node *dp; | 
|  | 975 | for (dp = ebus_dp; dp; dp = dp->sibling) { | 
|  | 976 | if (!strcmp(dp->name, "rtc")) { | 
|  | 977 | op = of_find_device_by_node(dp); | 
|  | 978 | if (op) { | 
|  | 979 | rtc_port = op->resource[0].start; | 
|  | 980 | rtc_irq = op->irqs[0]; | 
|  | 981 | goto found; | 
|  | 982 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 983 | } | 
|  | 984 | } | 
|  | 985 | } | 
| Jan Beulich | f3e92d3 | 2006-12-13 00:35:04 -0800 | [diff] [blame] | 986 | rtc_has_irq = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 987 | printk(KERN_ERR "rtc_init: no PC rtc found\n"); | 
|  | 988 | return -EIO; | 
|  | 989 |  | 
|  | 990 | found: | 
| David S. Miller | 7508132 | 2008-08-30 00:23:51 -0700 | [diff] [blame] | 991 | if (!rtc_irq) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 992 | rtc_has_irq = 0; | 
|  | 993 | goto no_irq; | 
|  | 994 | } | 
|  | 995 |  | 
|  | 996 | /* | 
|  | 997 | * XXX Interrupt pin #7 in Espresso is shared between RTC and | 
| David S. Miller | 53d0fc2 | 2005-09-05 23:33:05 -0700 | [diff] [blame] | 998 | * PCI Slot 2 INTA# (and some INTx# in Slot 1). | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 999 | */ | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1000 | if (request_irq(rtc_irq, rtc_interrupt, IRQF_SHARED, "rtc", | 
|  | 1001 | (void *)&rtc_port)) { | 
| Jan Beulich | f3e92d3 | 2006-12-13 00:35:04 -0800 | [diff] [blame] | 1002 | rtc_has_irq = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1003 | printk(KERN_ERR "rtc: cannot register IRQ %d\n", rtc_irq); | 
|  | 1004 | return -EIO; | 
|  | 1005 | } | 
|  | 1006 | no_irq: | 
|  | 1007 | #else | 
| Bjorn Helgaas | 9626f1f1 | 2007-11-14 16:59:57 -0800 | [diff] [blame] | 1008 | r = rtc_request_region(RTC_IO_EXTENT); | 
|  | 1009 |  | 
|  | 1010 | /* | 
|  | 1011 | * If we've already requested a smaller range (for example, because | 
|  | 1012 | * PNPBIOS or ACPI told us how the device is configured), the request | 
|  | 1013 | * above might fail because it's too big. | 
|  | 1014 | * | 
|  | 1015 | * If so, request just the range we actually use. | 
|  | 1016 | */ | 
|  | 1017 | if (!r) | 
|  | 1018 | r = rtc_request_region(RTC_IO_EXTENT_USED); | 
| Maciej W. Rozycki | 38e0e8c | 2006-07-10 04:45:30 -0700 | [diff] [blame] | 1019 | if (!r) { | 
| Jan Beulich | f3e92d3 | 2006-12-13 00:35:04 -0800 | [diff] [blame] | 1020 | #ifdef RTC_IRQ | 
|  | 1021 | rtc_has_irq = 0; | 
|  | 1022 | #endif | 
| Maciej W. Rozycki | 38e0e8c | 2006-07-10 04:45:30 -0700 | [diff] [blame] | 1023 | printk(KERN_ERR "rtc: I/O resource %lx is not free.\n", | 
|  | 1024 | (long)(RTC_PORT(0))); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1025 | return -EIO; | 
|  | 1026 | } | 
|  | 1027 |  | 
|  | 1028 | #ifdef RTC_IRQ | 
|  | 1029 | if (is_hpet_enabled()) { | 
| Bernhard Walle | f8f7648 | 2008-01-30 13:33:31 +0100 | [diff] [blame] | 1030 | int err; | 
|  | 1031 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1032 | rtc_int_handler_ptr = hpet_rtc_interrupt; | 
| Bernhard Walle | f8f7648 | 2008-01-30 13:33:31 +0100 | [diff] [blame] | 1033 | err = hpet_register_irq_handler(rtc_interrupt); | 
|  | 1034 | if (err != 0) { | 
|  | 1035 | printk(KERN_WARNING "hpet_register_irq_handler failed " | 
|  | 1036 | "in rtc_init()."); | 
|  | 1037 | return err; | 
|  | 1038 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1039 | } else { | 
|  | 1040 | rtc_int_handler_ptr = rtc_interrupt; | 
|  | 1041 | } | 
|  | 1042 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1043 | if (request_irq(RTC_IRQ, rtc_int_handler_ptr, IRQF_DISABLED, | 
|  | 1044 | "rtc", NULL)) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1045 | /* Yeah right, seeing as irq 8 doesn't even hit the bus. */ | 
| Jan Beulich | f3e92d3 | 2006-12-13 00:35:04 -0800 | [diff] [blame] | 1046 | rtc_has_irq = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1047 | printk(KERN_ERR "rtc: IRQ %d is not free.\n", RTC_IRQ); | 
| Bjorn Helgaas | 4c06be1 | 2007-11-14 16:59:56 -0800 | [diff] [blame] | 1048 | rtc_release_region(); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1049 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1050 | return -EIO; | 
|  | 1051 | } | 
|  | 1052 | hpet_rtc_timer_init(); | 
|  | 1053 |  | 
|  | 1054 | #endif | 
|  | 1055 |  | 
| David S. Miller | cdee99d | 2007-07-19 13:59:58 -0700 | [diff] [blame] | 1056 | #endif /* CONFIG_SPARC32 vs. others */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1057 |  | 
|  | 1058 | if (misc_register(&rtc_dev)) { | 
|  | 1059 | #ifdef RTC_IRQ | 
|  | 1060 | free_irq(RTC_IRQ, NULL); | 
| Bernhard Walle | f8f7648 | 2008-01-30 13:33:31 +0100 | [diff] [blame] | 1061 | hpet_unregister_irq_handler(rtc_interrupt); | 
| Jan Beulich | f3e92d3 | 2006-12-13 00:35:04 -0800 | [diff] [blame] | 1062 | rtc_has_irq = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1063 | #endif | 
| Bjorn Helgaas | 4c06be1 | 2007-11-14 16:59:56 -0800 | [diff] [blame] | 1064 | rtc_release_region(); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1065 | return -ENODEV; | 
|  | 1066 | } | 
|  | 1067 |  | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 1068 | #ifdef CONFIG_PROC_FS | 
| Denis V. Lunev | 1b50221 | 2008-04-29 01:02:34 -0700 | [diff] [blame] | 1069 | ent = proc_create("driver/rtc", 0, NULL, &rtc_proc_fops); | 
|  | 1070 | if (!ent) | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 1071 | printk(KERN_WARNING "rtc: Failed to register with procfs.\n"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1072 | #endif | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1073 |  | 
|  | 1074 | #if defined(__alpha__) || defined(__mips__) | 
|  | 1075 | rtc_freq = HZ; | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1076 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1077 | /* Each operating system on an Alpha uses its own epoch. | 
|  | 1078 | Let's try to guess which one we are using now. */ | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1079 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1080 | if (rtc_is_updating() != 0) | 
| Luca Falavigna | 47f176f | 2005-06-28 20:44:42 -0700 | [diff] [blame] | 1081 | msleep(20); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1082 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1083 | spin_lock_irq(&rtc_lock); | 
|  | 1084 | year = CMOS_READ(RTC_YEAR); | 
|  | 1085 | ctrl = CMOS_READ(RTC_CONTROL); | 
|  | 1086 | spin_unlock_irq(&rtc_lock); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1087 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1088 | if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) | 
| Adrian Bunk | 357c6e6 | 2008-10-18 20:28:42 -0700 | [diff] [blame] | 1089 | year = bcd2bin(year);       /* This should never happen... */ | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1090 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1091 | if (year < 20) { | 
|  | 1092 | epoch = 2000; | 
|  | 1093 | guess = "SRM (post-2000)"; | 
|  | 1094 | } else if (year >= 20 && year < 48) { | 
|  | 1095 | epoch = 1980; | 
|  | 1096 | guess = "ARC console"; | 
|  | 1097 | } else if (year >= 48 && year < 72) { | 
|  | 1098 | epoch = 1952; | 
|  | 1099 | guess = "Digital UNIX"; | 
|  | 1100 | #if defined(__mips__) | 
|  | 1101 | } else if (year >= 72 && year < 74) { | 
|  | 1102 | epoch = 2000; | 
|  | 1103 | guess = "Digital DECstation"; | 
|  | 1104 | #else | 
|  | 1105 | } else if (year >= 70) { | 
|  | 1106 | epoch = 1900; | 
|  | 1107 | guess = "Standard PC (1900)"; | 
|  | 1108 | #endif | 
|  | 1109 | } | 
|  | 1110 | if (guess) | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1111 | printk(KERN_INFO "rtc: %s epoch (%lu) detected\n", | 
|  | 1112 | guess, epoch); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1113 | #endif | 
|  | 1114 | #ifdef RTC_IRQ | 
|  | 1115 | if (rtc_has_irq == 0) | 
|  | 1116 | goto no_irq2; | 
|  | 1117 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1118 | spin_lock_irq(&rtc_lock); | 
|  | 1119 | rtc_freq = 1024; | 
|  | 1120 | if (!hpet_set_periodic_freq(rtc_freq)) { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1121 | /* | 
|  | 1122 | * Initialize periodic frequency to CMOS reset default, | 
|  | 1123 | * which is 1024Hz | 
|  | 1124 | */ | 
|  | 1125 | CMOS_WRITE(((CMOS_READ(RTC_FREQ_SELECT) & 0xF0) | 0x06), | 
|  | 1126 | RTC_FREQ_SELECT); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1127 | } | 
|  | 1128 | spin_unlock_irq(&rtc_lock); | 
|  | 1129 | no_irq2: | 
|  | 1130 | #endif | 
|  | 1131 |  | 
|  | 1132 | (void) init_sysctl(); | 
|  | 1133 |  | 
|  | 1134 | printk(KERN_INFO "Real Time Clock Driver v" RTC_VERSION "\n"); | 
|  | 1135 |  | 
|  | 1136 | return 0; | 
|  | 1137 | } | 
|  | 1138 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1139 | static void __exit rtc_exit(void) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1140 | { | 
|  | 1141 | cleanup_sysctl(); | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1142 | remove_proc_entry("driver/rtc", NULL); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1143 | misc_deregister(&rtc_dev); | 
|  | 1144 |  | 
| David S. Miller | cdee99d | 2007-07-19 13:59:58 -0700 | [diff] [blame] | 1145 | #ifdef CONFIG_SPARC32 | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1146 | if (rtc_has_irq) | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1147 | free_irq(rtc_irq, &rtc_port); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1148 | #else | 
| Bjorn Helgaas | 4c06be1 | 2007-11-14 16:59:56 -0800 | [diff] [blame] | 1149 | rtc_release_region(); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1150 | #ifdef RTC_IRQ | 
| Bernhard Walle | f8f7648 | 2008-01-30 13:33:31 +0100 | [diff] [blame] | 1151 | if (rtc_has_irq) { | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1152 | free_irq(RTC_IRQ, NULL); | 
| Bernhard Walle | f8f7648 | 2008-01-30 13:33:31 +0100 | [diff] [blame] | 1153 | hpet_unregister_irq_handler(hpet_rtc_interrupt); | 
|  | 1154 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1155 | #endif | 
| David S. Miller | cdee99d | 2007-07-19 13:59:58 -0700 | [diff] [blame] | 1156 | #endif /* CONFIG_SPARC32 */ | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1157 | } | 
|  | 1158 |  | 
|  | 1159 | module_init(rtc_init); | 
|  | 1160 | module_exit(rtc_exit); | 
|  | 1161 |  | 
|  | 1162 | #ifdef RTC_IRQ | 
|  | 1163 | /* | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1164 | *	At IRQ rates >= 4096Hz, an interrupt may get lost altogether. | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1165 | *	(usually during an IDE disk interrupt, with IRQ unmasking off) | 
|  | 1166 | *	Since the interrupt handler doesn't get called, the IRQ status | 
|  | 1167 | *	byte doesn't get read, and the RTC stops generating interrupts. | 
|  | 1168 | *	A timer is set, and will call this function if/when that happens. | 
|  | 1169 | *	To get it out of this stalled state, we just read the status. | 
|  | 1170 | *	At least a jiffy of interrupts (rtc_freq/HZ) will have been lost. | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1171 | *	(You *really* shouldn't be trying to use a non-realtime system | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1172 | *	for something that requires a steady > 1KHz signal anyways.) | 
|  | 1173 | */ | 
|  | 1174 |  | 
|  | 1175 | static void rtc_dropped_irq(unsigned long data) | 
|  | 1176 | { | 
|  | 1177 | unsigned long freq; | 
|  | 1178 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1179 | spin_lock_irq(&rtc_lock); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1180 |  | 
|  | 1181 | if (hpet_rtc_dropped_irq()) { | 
|  | 1182 | spin_unlock_irq(&rtc_lock); | 
|  | 1183 | return; | 
|  | 1184 | } | 
|  | 1185 |  | 
|  | 1186 | /* Just in case someone disabled the timer from behind our back... */ | 
|  | 1187 | if (rtc_status & RTC_TIMER_ON) | 
|  | 1188 | mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100); | 
|  | 1189 |  | 
|  | 1190 | rtc_irq_data += ((rtc_freq/HZ)<<8); | 
|  | 1191 | rtc_irq_data &= ~0xff; | 
|  | 1192 | rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);	/* restart */ | 
|  | 1193 |  | 
|  | 1194 | freq = rtc_freq; | 
|  | 1195 |  | 
|  | 1196 | spin_unlock_irq(&rtc_lock); | 
|  | 1197 |  | 
| Christian Dietrich | a28ee47 | 2011-06-04 17:36:33 +0200 | [diff] [blame] | 1198 | printk_ratelimited(KERN_WARNING "rtc: lost some interrupts at %ldHz.\n", | 
|  | 1199 | freq); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1200 |  | 
|  | 1201 | /* Now we have new data */ | 
|  | 1202 | wake_up_interruptible(&rtc_wait); | 
|  | 1203 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1204 | kill_fasync(&rtc_async_queue, SIGIO, POLL_IN); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1205 | } | 
|  | 1206 | #endif | 
|  | 1207 |  | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 1208 | #ifdef CONFIG_PROC_FS | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1209 | /* | 
|  | 1210 | *	Info exported via "/proc/driver/rtc". | 
|  | 1211 | */ | 
|  | 1212 |  | 
|  | 1213 | static int rtc_proc_show(struct seq_file *seq, void *v) | 
|  | 1214 | { | 
|  | 1215 | #define YN(bit) ((ctrl & bit) ? "yes" : "no") | 
|  | 1216 | #define NY(bit) ((ctrl & bit) ? "no" : "yes") | 
|  | 1217 | struct rtc_time tm; | 
|  | 1218 | unsigned char batt, ctrl; | 
|  | 1219 | unsigned long freq; | 
|  | 1220 |  | 
|  | 1221 | spin_lock_irq(&rtc_lock); | 
|  | 1222 | batt = CMOS_READ(RTC_VALID) & RTC_VRT; | 
|  | 1223 | ctrl = CMOS_READ(RTC_CONTROL); | 
|  | 1224 | freq = rtc_freq; | 
|  | 1225 | spin_unlock_irq(&rtc_lock); | 
|  | 1226 |  | 
|  | 1227 |  | 
|  | 1228 | rtc_get_rtc_time(&tm); | 
|  | 1229 |  | 
|  | 1230 | /* | 
|  | 1231 | * There is no way to tell if the luser has the RTC set for local | 
|  | 1232 | * time or for Universal Standard Time (GMT). Probably local though. | 
|  | 1233 | */ | 
|  | 1234 | seq_printf(seq, | 
|  | 1235 | "rtc_time\t: %02d:%02d:%02d\n" | 
|  | 1236 | "rtc_date\t: %04d-%02d-%02d\n" | 
|  | 1237 | "rtc_epoch\t: %04lu\n", | 
|  | 1238 | tm.tm_hour, tm.tm_min, tm.tm_sec, | 
|  | 1239 | tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, epoch); | 
|  | 1240 |  | 
|  | 1241 | get_rtc_alm_time(&tm); | 
|  | 1242 |  | 
|  | 1243 | /* | 
|  | 1244 | * We implicitly assume 24hr mode here. Alarm values >= 0xc0 will | 
|  | 1245 | * match any value for that particular field. Values that are | 
|  | 1246 | * greater than a valid time, but less than 0xc0 shouldn't appear. | 
|  | 1247 | */ | 
|  | 1248 | seq_puts(seq, "alarm\t\t: "); | 
|  | 1249 | if (tm.tm_hour <= 24) | 
|  | 1250 | seq_printf(seq, "%02d:", tm.tm_hour); | 
|  | 1251 | else | 
|  | 1252 | seq_puts(seq, "**:"); | 
|  | 1253 |  | 
|  | 1254 | if (tm.tm_min <= 59) | 
|  | 1255 | seq_printf(seq, "%02d:", tm.tm_min); | 
|  | 1256 | else | 
|  | 1257 | seq_puts(seq, "**:"); | 
|  | 1258 |  | 
|  | 1259 | if (tm.tm_sec <= 59) | 
|  | 1260 | seq_printf(seq, "%02d\n", tm.tm_sec); | 
|  | 1261 | else | 
|  | 1262 | seq_puts(seq, "**\n"); | 
|  | 1263 |  | 
|  | 1264 | seq_printf(seq, | 
|  | 1265 | "DST_enable\t: %s\n" | 
|  | 1266 | "BCD\t\t: %s\n" | 
|  | 1267 | "24hr\t\t: %s\n" | 
|  | 1268 | "square_wave\t: %s\n" | 
|  | 1269 | "alarm_IRQ\t: %s\n" | 
|  | 1270 | "update_IRQ\t: %s\n" | 
|  | 1271 | "periodic_IRQ\t: %s\n" | 
|  | 1272 | "periodic_freq\t: %ld\n" | 
|  | 1273 | "batt_status\t: %s\n", | 
|  | 1274 | YN(RTC_DST_EN), | 
|  | 1275 | NY(RTC_DM_BINARY), | 
|  | 1276 | YN(RTC_24H), | 
|  | 1277 | YN(RTC_SQWE), | 
|  | 1278 | YN(RTC_AIE), | 
|  | 1279 | YN(RTC_UIE), | 
|  | 1280 | YN(RTC_PIE), | 
|  | 1281 | freq, | 
|  | 1282 | batt ? "okay" : "dead"); | 
|  | 1283 |  | 
|  | 1284 | return  0; | 
|  | 1285 | #undef YN | 
|  | 1286 | #undef NY | 
|  | 1287 | } | 
|  | 1288 |  | 
|  | 1289 | static int rtc_proc_open(struct inode *inode, struct file *file) | 
|  | 1290 | { | 
|  | 1291 | return single_open(file, rtc_proc_show, NULL); | 
|  | 1292 | } | 
| Jan Beulich | 9cef779 | 2006-12-13 00:35:05 -0800 | [diff] [blame] | 1293 | #endif | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1294 |  | 
| Adrian Bunk | 9580d85 | 2008-07-25 19:46:25 -0700 | [diff] [blame] | 1295 | static void rtc_get_rtc_time(struct rtc_time *rtc_tm) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1296 | { | 
| Ingo Molnar | 0f74964 | 2006-07-12 09:03:10 -0700 | [diff] [blame] | 1297 | unsigned long uip_watchdog = jiffies, flags; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1298 | unsigned char ctrl; | 
|  | 1299 | #ifdef CONFIG_MACH_DECSTATION | 
|  | 1300 | unsigned int real_year; | 
|  | 1301 | #endif | 
|  | 1302 |  | 
|  | 1303 | /* | 
|  | 1304 | * read RTC once any update in progress is done. The update | 
| Luca Falavigna | 47f176f | 2005-06-28 20:44:42 -0700 | [diff] [blame] | 1305 | * can take just over 2ms. We wait 20ms. There is no need to | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1306 | * to poll-wait (up to 1s - eeccch) for the falling edge of RTC_UIP. | 
|  | 1307 | * If you need to know *exactly* when a second has started, enable | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1308 | * periodic update complete interrupts, (via ioctl) and then | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1309 | * immediately read /dev/rtc which will block until you get the IRQ. | 
|  | 1310 | * Once the read clears, read the RTC time (again via ioctl). Easy. | 
|  | 1311 | */ | 
|  | 1312 |  | 
| Julia Lawall | dca03a5 | 2008-04-28 02:11:59 -0700 | [diff] [blame] | 1313 | while (rtc_is_updating() != 0 && | 
|  | 1314 | time_before(jiffies, uip_watchdog + 2*HZ/100)) | 
| Petr Vandrovec | 403fe5a | 2005-08-05 15:50:07 +0200 | [diff] [blame] | 1315 | cpu_relax(); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1316 |  | 
|  | 1317 | /* | 
|  | 1318 | * Only the values that we read from the RTC are set. We leave | 
| Alan Cox | b759958 | 2006-01-11 12:17:32 -0800 | [diff] [blame] | 1319 | * tm_wday, tm_yday and tm_isdst untouched. Note that while the | 
|  | 1320 | * RTC has RTC_DAY_OF_WEEK, we should usually ignore it, as it is | 
|  | 1321 | * only updated by the RTC when initially set to a non-zero value. | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1322 | */ | 
| Ingo Molnar | 0f74964 | 2006-07-12 09:03:10 -0700 | [diff] [blame] | 1323 | spin_lock_irqsave(&rtc_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1324 | rtc_tm->tm_sec = CMOS_READ(RTC_SECONDS); | 
|  | 1325 | rtc_tm->tm_min = CMOS_READ(RTC_MINUTES); | 
|  | 1326 | rtc_tm->tm_hour = CMOS_READ(RTC_HOURS); | 
|  | 1327 | rtc_tm->tm_mday = CMOS_READ(RTC_DAY_OF_MONTH); | 
|  | 1328 | rtc_tm->tm_mon = CMOS_READ(RTC_MONTH); | 
|  | 1329 | rtc_tm->tm_year = CMOS_READ(RTC_YEAR); | 
| Alan Cox | b759958 | 2006-01-11 12:17:32 -0800 | [diff] [blame] | 1330 | /* Only set from 2.6.16 onwards */ | 
|  | 1331 | rtc_tm->tm_wday = CMOS_READ(RTC_DAY_OF_WEEK); | 
|  | 1332 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1333 | #ifdef CONFIG_MACH_DECSTATION | 
|  | 1334 | real_year = CMOS_READ(RTC_DEC_YEAR); | 
|  | 1335 | #endif | 
|  | 1336 | ctrl = CMOS_READ(RTC_CONTROL); | 
| Ingo Molnar | 0f74964 | 2006-07-12 09:03:10 -0700 | [diff] [blame] | 1337 | spin_unlock_irqrestore(&rtc_lock, flags); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1338 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1339 | if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { | 
| Adrian Bunk | 357c6e6 | 2008-10-18 20:28:42 -0700 | [diff] [blame] | 1340 | rtc_tm->tm_sec = bcd2bin(rtc_tm->tm_sec); | 
|  | 1341 | rtc_tm->tm_min = bcd2bin(rtc_tm->tm_min); | 
|  | 1342 | rtc_tm->tm_hour = bcd2bin(rtc_tm->tm_hour); | 
|  | 1343 | rtc_tm->tm_mday = bcd2bin(rtc_tm->tm_mday); | 
|  | 1344 | rtc_tm->tm_mon = bcd2bin(rtc_tm->tm_mon); | 
|  | 1345 | rtc_tm->tm_year = bcd2bin(rtc_tm->tm_year); | 
|  | 1346 | rtc_tm->tm_wday = bcd2bin(rtc_tm->tm_wday); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1347 | } | 
|  | 1348 |  | 
|  | 1349 | #ifdef CONFIG_MACH_DECSTATION | 
|  | 1350 | rtc_tm->tm_year += real_year - 72; | 
|  | 1351 | #endif | 
|  | 1352 |  | 
|  | 1353 | /* | 
|  | 1354 | * Account for differences between how the RTC uses the values | 
|  | 1355 | * and how they are defined in a struct rtc_time; | 
|  | 1356 | */ | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1357 | rtc_tm->tm_year += epoch - 1900; | 
|  | 1358 | if (rtc_tm->tm_year <= 69) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1359 | rtc_tm->tm_year += 100; | 
|  | 1360 |  | 
|  | 1361 | rtc_tm->tm_mon--; | 
|  | 1362 | } | 
|  | 1363 |  | 
|  | 1364 | static void get_rtc_alm_time(struct rtc_time *alm_tm) | 
|  | 1365 | { | 
|  | 1366 | unsigned char ctrl; | 
|  | 1367 |  | 
|  | 1368 | /* | 
|  | 1369 | * Only the values that we read from the RTC are set. That | 
|  | 1370 | * means only tm_hour, tm_min, and tm_sec. | 
|  | 1371 | */ | 
|  | 1372 | spin_lock_irq(&rtc_lock); | 
|  | 1373 | alm_tm->tm_sec = CMOS_READ(RTC_SECONDS_ALARM); | 
|  | 1374 | alm_tm->tm_min = CMOS_READ(RTC_MINUTES_ALARM); | 
|  | 1375 | alm_tm->tm_hour = CMOS_READ(RTC_HOURS_ALARM); | 
|  | 1376 | ctrl = CMOS_READ(RTC_CONTROL); | 
|  | 1377 | spin_unlock_irq(&rtc_lock); | 
|  | 1378 |  | 
| Ingo Molnar | 5fd1fe9 | 2008-01-30 13:31:09 +0100 | [diff] [blame] | 1379 | if (!(ctrl & RTC_DM_BINARY) || RTC_ALWAYS_BCD) { | 
| Adrian Bunk | 357c6e6 | 2008-10-18 20:28:42 -0700 | [diff] [blame] | 1380 | alm_tm->tm_sec = bcd2bin(alm_tm->tm_sec); | 
|  | 1381 | alm_tm->tm_min = bcd2bin(alm_tm->tm_min); | 
|  | 1382 | alm_tm->tm_hour = bcd2bin(alm_tm->tm_hour); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1383 | } | 
|  | 1384 | } | 
|  | 1385 |  | 
|  | 1386 | #ifdef RTC_IRQ | 
|  | 1387 | /* | 
|  | 1388 | * Used to disable/enable interrupts for any one of UIE, AIE, PIE. | 
|  | 1389 | * Rumour has it that if you frob the interrupt enable/disable | 
|  | 1390 | * bits in RTC_CONTROL, you should read RTC_INTR_FLAGS, to | 
|  | 1391 | * ensure you actually start getting interrupts. Probably for | 
|  | 1392 | * compatibility with older/broken chipset RTC implementations. | 
|  | 1393 | * We also clear out any old irq data after an ioctl() that | 
|  | 1394 | * meddles with the interrupt enable/disable bits. | 
|  | 1395 | */ | 
|  | 1396 |  | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 1397 | static void mask_rtc_irq_bit_locked(unsigned char bit) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1398 | { | 
|  | 1399 | unsigned char val; | 
|  | 1400 |  | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 1401 | if (hpet_mask_rtc_irq_bit(bit)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1402 | return; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1403 | val = CMOS_READ(RTC_CONTROL); | 
|  | 1404 | val &=  ~bit; | 
|  | 1405 | CMOS_WRITE(val, RTC_CONTROL); | 
|  | 1406 | CMOS_READ(RTC_INTR_FLAGS); | 
|  | 1407 |  | 
|  | 1408 | rtc_irq_data = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1409 | } | 
|  | 1410 |  | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 1411 | static void set_rtc_irq_bit_locked(unsigned char bit) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1412 | { | 
|  | 1413 | unsigned char val; | 
|  | 1414 |  | 
| Takashi Iwai | c334876 | 2005-11-07 11:14:57 +0100 | [diff] [blame] | 1415 | if (hpet_set_rtc_irq_bit(bit)) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1416 | return; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1417 | val = CMOS_READ(RTC_CONTROL); | 
|  | 1418 | val |= bit; | 
|  | 1419 | CMOS_WRITE(val, RTC_CONTROL); | 
|  | 1420 | CMOS_READ(RTC_INTR_FLAGS); | 
|  | 1421 |  | 
|  | 1422 | rtc_irq_data = 0; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1423 | } | 
|  | 1424 | #endif | 
|  | 1425 |  | 
|  | 1426 | MODULE_AUTHOR("Paul Gortmaker"); | 
|  | 1427 | MODULE_LICENSE("GPL"); | 
|  | 1428 | MODULE_ALIAS_MISCDEV(RTC_MINOR); |