Pull extend-notify-die into release branch
[linux-2.6] / drivers / sbus / char / rtc.c
1 /* $Id: rtc.c,v 1.28 2001/10/08 22:19:51 davem Exp $
2  *
3  * Linux/SPARC Real Time Clock Driver
4  * Copyright (C) 1996 Thomas K. Dyas (tdyas@eden.rutgers.edu)
5  *
6  * This is a little driver that lets a user-level program access
7  * the SPARC Mostek real time clock chip. It is no use unless you
8  * use the modified clock utility.
9  *
10  * Get the modified clock utility from:
11  *   ftp://vger.kernel.org/pub/linux/Sparc/userland/clock.c
12  */
13
14 #include <linux/module.h>
15 #include <linux/types.h>
16 #include <linux/errno.h>
17 #include <linux/miscdevice.h>
18 #include <linux/slab.h>
19 #include <linux/fcntl.h>
20 #include <linux/poll.h>
21 #include <linux/init.h>
22 #include <linux/smp_lock.h>
23 #include <asm/io.h>
24 #include <asm/mostek.h>
25 #include <asm/system.h>
26 #include <asm/uaccess.h>
27 #include <asm/rtc.h>
28
29 static int rtc_busy = 0;
30
31 /* This is the structure layout used by drivers/char/rtc.c, we
32  * support that driver's ioctls so that things are less messy in
33  * userspace.
34  */
35 struct rtc_time_generic {
36         int tm_sec;
37         int tm_min;
38         int tm_hour;
39         int tm_mday;
40         int tm_mon;
41         int tm_year;
42         int tm_wday;
43         int tm_yday;
44         int tm_isdst;
45 };
46 #define RTC_AIE_ON      _IO('p', 0x01)  /* Alarm int. enable on         */
47 #define RTC_AIE_OFF     _IO('p', 0x02)  /* ... off                      */
48 #define RTC_UIE_ON      _IO('p', 0x03)  /* Update int. enable on        */
49 #define RTC_UIE_OFF     _IO('p', 0x04)  /* ... off                      */
50 #define RTC_PIE_ON      _IO('p', 0x05)  /* Periodic int. enable on      */
51 #define RTC_PIE_OFF     _IO('p', 0x06)  /* ... off                      */
52 #define RTC_WIE_ON      _IO('p', 0x0f)  /* Watchdog int. enable on      */
53 #define RTC_WIE_OFF     _IO('p', 0x10)  /* ... off                      */
54 #define RTC_RD_TIME     _IOR('p', 0x09, struct rtc_time_generic) /* Read RTC time   */
55 #define RTC_SET_TIME    _IOW('p', 0x0a, struct rtc_time_generic) /* Set RTC time    */
56 #define RTC_ALM_SET     _IOW('p', 0x07, struct rtc_time) /* Set alarm time  */
57 #define RTC_ALM_READ    _IOR('p', 0x08, struct rtc_time) /* Read alarm time */
58 #define RTC_IRQP_READ   _IOR('p', 0x0b, unsigned long)   /* Read IRQ rate   */
59 #define RTC_IRQP_SET    _IOW('p', 0x0c, unsigned long)   /* Set IRQ rate    */
60 #define RTC_EPOCH_READ  _IOR('p', 0x0d, unsigned long)   /* Read epoch      */
61 #define RTC_EPOCH_SET   _IOW('p', 0x0e, unsigned long)   /* Set epoch       */
62 #define RTC_WKALM_SET   _IOW('p', 0x0f, struct rtc_wkalrm)/* Set wakeup alarm*/
63 #define RTC_WKALM_RD    _IOR('p', 0x10, struct rtc_wkalrm)/* Get wakeup alarm*/
64 #define RTC_PLL_GET     _IOR('p', 0x11, struct rtc_pll_info)  /* Get PLL correction */
65 #define RTC_PLL_SET     _IOW('p', 0x12, struct rtc_pll_info)  /* Set PLL correction */
66
67 /* Retrieve the current date and time from the real time clock. */
68 static void get_rtc_time(struct rtc_time *t)
69 {
70         void __iomem *regs = mstk48t02_regs;
71         u8 tmp;
72
73         spin_lock_irq(&mostek_lock);
74
75         tmp = mostek_read(regs + MOSTEK_CREG);
76         tmp |= MSTK_CREG_READ;
77         mostek_write(regs + MOSTEK_CREG, tmp);
78
79         t->sec = MSTK_REG_SEC(regs);
80         t->min = MSTK_REG_MIN(regs);
81         t->hour = MSTK_REG_HOUR(regs);
82         t->dow = MSTK_REG_DOW(regs);
83         t->dom = MSTK_REG_DOM(regs);
84         t->month = MSTK_REG_MONTH(regs);
85         t->year = MSTK_CVT_YEAR( MSTK_REG_YEAR(regs) );
86
87         tmp = mostek_read(regs + MOSTEK_CREG);
88         tmp &= ~MSTK_CREG_READ;
89         mostek_write(regs + MOSTEK_CREG, tmp);
90
91         spin_unlock_irq(&mostek_lock);
92 }
93
94 /* Set the current date and time inthe real time clock. */
95 void set_rtc_time(struct rtc_time *t)
96 {
97         void __iomem *regs = mstk48t02_regs;
98         u8 tmp;
99
100         spin_lock_irq(&mostek_lock);
101
102         tmp = mostek_read(regs + MOSTEK_CREG);
103         tmp |= MSTK_CREG_WRITE;
104         mostek_write(regs + MOSTEK_CREG, tmp);
105
106         MSTK_SET_REG_SEC(regs,t->sec);
107         MSTK_SET_REG_MIN(regs,t->min);
108         MSTK_SET_REG_HOUR(regs,t->hour);
109         MSTK_SET_REG_DOW(regs,t->dow);
110         MSTK_SET_REG_DOM(regs,t->dom);
111         MSTK_SET_REG_MONTH(regs,t->month);
112         MSTK_SET_REG_YEAR(regs,t->year - MSTK_YEAR_ZERO);
113
114         tmp = mostek_read(regs + MOSTEK_CREG);
115         tmp &= ~MSTK_CREG_WRITE;
116         mostek_write(regs + MOSTEK_CREG, tmp);
117
118         spin_unlock_irq(&mostek_lock);
119 }
120
121 static int put_rtc_time_generic(void __user *argp, struct rtc_time *tm)
122 {
123         struct rtc_time_generic __user *utm = argp;
124
125         if (__put_user(tm->sec, &utm->tm_sec) ||
126             __put_user(tm->min, &utm->tm_min) ||
127             __put_user(tm->hour, &utm->tm_hour) ||
128             __put_user(tm->dom, &utm->tm_mday) ||
129             __put_user(tm->month, &utm->tm_mon) ||
130             __put_user(tm->year, &utm->tm_year) ||
131             __put_user(tm->dow, &utm->tm_wday) ||
132             __put_user(0, &utm->tm_yday) ||
133             __put_user(0, &utm->tm_isdst))
134                 return -EFAULT;
135
136         return 0;
137 }
138
139 static int get_rtc_time_generic(struct rtc_time *tm, void __user *argp)
140 {
141         struct rtc_time_generic __user *utm = argp;
142
143         if (__get_user(tm->sec, &utm->tm_sec) ||
144             __get_user(tm->min, &utm->tm_min) ||
145             __get_user(tm->hour, &utm->tm_hour) ||
146             __get_user(tm->dom, &utm->tm_mday) ||
147             __get_user(tm->month, &utm->tm_mon) ||
148             __get_user(tm->year, &utm->tm_year) ||
149             __get_user(tm->dow, &utm->tm_wday))
150                 return -EFAULT;
151
152         return 0;
153 }
154
155 static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
156         unsigned long arg)
157 {
158         struct rtc_time rtc_tm;
159         void __user *argp = (void __user *)arg;
160
161         switch (cmd) {
162         /* No interrupt support, return an error
163          * compatible with drivers/char/rtc.c
164          */
165         case RTC_AIE_OFF:
166         case RTC_AIE_ON:
167         case RTC_PIE_OFF:
168         case RTC_PIE_ON:
169         case RTC_UIE_OFF:
170         case RTC_UIE_ON:
171         case RTC_IRQP_READ:
172         case RTC_IRQP_SET:
173         case RTC_EPOCH_SET:
174         case RTC_EPOCH_READ:
175                 return -EINVAL;
176
177         case RTCGET:
178         case RTC_RD_TIME:
179                 memset(&rtc_tm, 0, sizeof(struct rtc_time));
180                 get_rtc_time(&rtc_tm);
181
182                 if (cmd == RTCGET) {
183                         if (copy_to_user(argp, &rtc_tm,
184                                          sizeof(struct rtc_time)))
185                                 return -EFAULT;
186                 } else if (put_rtc_time_generic(argp, &rtc_tm))
187                         return -EFAULT;
188
189                 return 0;
190
191
192         case RTCSET:
193         case RTC_SET_TIME:
194                 if (!capable(CAP_SYS_TIME))
195                         return -EPERM;
196
197                 if (cmd == RTCSET) {
198                         if (copy_from_user(&rtc_tm, argp,
199                                            sizeof(struct rtc_time)))
200                                 return -EFAULT;
201                 } else if (get_rtc_time_generic(&rtc_tm, argp))
202                         return -EFAULT;
203
204                 set_rtc_time(&rtc_tm);
205
206                 return 0;
207
208         default:
209                 return -EINVAL;
210         }
211 }
212
213 static long rtc_compat_ioctl(struct file *file, unsigned int cmd,
214         unsigned long arg)
215 {
216         int rval = -ENOIOCTLCMD;
217
218         switch (cmd) {
219         /*
220          * These two are specific to this driver, the generic rtc ioctls
221          * are hanlded elsewhere.
222          */
223         case RTCGET:
224         case RTCSET:
225                 lock_kernel();
226                 rval = rtc_ioctl(file->f_dentry->d_inode, file, cmd, arg);
227                 unlock_kernel();
228                 break;
229         }
230
231         return rval;
232 }
233
234 static int rtc_open(struct inode *inode, struct file *file)
235 {
236         int ret;
237
238         spin_lock_irq(&mostek_lock);
239         if (rtc_busy) {
240                 ret = -EBUSY;
241         } else {
242                 rtc_busy = 1;
243                 ret = 0;
244         }
245         spin_unlock_irq(&mostek_lock);
246
247         return ret;
248 }
249
250 static int rtc_release(struct inode *inode, struct file *file)
251 {
252         rtc_busy = 0;
253
254         return 0;
255 }
256
257 static struct file_operations rtc_fops = {
258         .owner =        THIS_MODULE,
259         .llseek =       no_llseek,
260         .ioctl =        rtc_ioctl,
261         .compat_ioctl = rtc_compat_ioctl,
262         .open =         rtc_open,
263         .release =      rtc_release,
264 };
265
266 static struct miscdevice rtc_dev = { RTC_MINOR, "rtc", &rtc_fops };
267
268 static int __init rtc_sun_init(void)
269 {
270         int error;
271
272         /* It is possible we are being driven by some other RTC chip
273          * and thus another RTC driver is handling things.
274          */
275         if (!mstk48t02_regs)
276                 return -ENODEV;
277
278         error = misc_register(&rtc_dev);
279         if (error) {
280                 printk(KERN_ERR "rtc: unable to get misc minor for Mostek\n");
281                 return error;
282         }
283         printk("rtc_sun_init: Registered Mostek RTC driver.\n");
284
285         return 0;
286 }
287
288 static void __exit rtc_sun_cleanup(void)
289 {
290         misc_deregister(&rtc_dev);
291 }
292
293 module_init(rtc_sun_init);
294 module_exit(rtc_sun_cleanup);
295 MODULE_LICENSE("GPL");