diff options
author | Alessandro Zummo <a.zummo@towertech.it> | 2006-03-27 04:16:41 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-03-27 11:44:51 -0500 |
commit | e824290e5dcfaf2120da587b16d10dfdff8d5d3e (patch) | |
tree | 1cb9b7c49191082d28e7791217a841b3da1f3964 /drivers/rtc/rtc-dev.c | |
parent | 728a294787b780130d8eb237518d4cac0afe760c (diff) |
[PATCH] RTC subsystem: dev interface
Add the dev interface to the RTC subsystem.
Each RTC will be available under /dev/rtcX . A symlink from /dev/rtc0 to
/dev/rtc cab be obtained with the following udev rule:
KERNEL=="rtc0", SYMLINK+="rtc"
Signed-off-by: Alessandro Zummo <a.zummo@towertech.it>
Acked-by: Greg Kroah-Hartman <gregkh@suse.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'drivers/rtc/rtc-dev.c')
-rw-r--r-- | drivers/rtc/rtc-dev.c | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-dev.c b/drivers/rtc/rtc-dev.c new file mode 100644 index 000000000000..b1e3e6179e56 --- /dev/null +++ b/drivers/rtc/rtc-dev.c | |||
@@ -0,0 +1,382 @@ | |||
1 | /* | ||
2 | * RTC subsystem, dev interface | ||
3 | * | ||
4 | * Copyright (C) 2005 Tower Technologies | ||
5 | * Author: Alessandro Zummo <a.zummo@towertech.it> | ||
6 | * | ||
7 | * based on arch/arm/common/rtctime.c | ||
8 | * | ||
9 | * This program is free software; you can redistribute it and/or modify | ||
10 | * it under the terms of the GNU General Public License version 2 as | ||
11 | * published by the Free Software Foundation. | ||
12 | */ | ||
13 | |||
14 | #include <linux/module.h> | ||
15 | #include <linux/rtc.h> | ||
16 | |||
17 | static struct class *rtc_dev_class; | ||
18 | static dev_t rtc_devt; | ||
19 | |||
20 | #define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */ | ||
21 | |||
22 | static int rtc_dev_open(struct inode *inode, struct file *file) | ||
23 | { | ||
24 | int err; | ||
25 | struct rtc_device *rtc = container_of(inode->i_cdev, | ||
26 | struct rtc_device, char_dev); | ||
27 | struct rtc_class_ops *ops = rtc->ops; | ||
28 | |||
29 | /* We keep the lock as long as the device is in use | ||
30 | * and return immediately if busy | ||
31 | */ | ||
32 | if (!(mutex_trylock(&rtc->char_lock))) | ||
33 | return -EBUSY; | ||
34 | |||
35 | file->private_data = &rtc->class_dev; | ||
36 | |||
37 | err = ops->open ? ops->open(rtc->class_dev.dev) : 0; | ||
38 | if (err == 0) { | ||
39 | spin_lock_irq(&rtc->irq_lock); | ||
40 | rtc->irq_data = 0; | ||
41 | spin_unlock_irq(&rtc->irq_lock); | ||
42 | |||
43 | return 0; | ||
44 | } | ||
45 | |||
46 | /* something has gone wrong, release the lock */ | ||
47 | mutex_unlock(&rtc->char_lock); | ||
48 | return err; | ||
49 | } | ||
50 | |||
51 | |||
52 | static ssize_t | ||
53 | rtc_dev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) | ||
54 | { | ||
55 | struct rtc_device *rtc = to_rtc_device(file->private_data); | ||
56 | |||
57 | DECLARE_WAITQUEUE(wait, current); | ||
58 | unsigned long data; | ||
59 | ssize_t ret; | ||
60 | |||
61 | if (count < sizeof(unsigned long)) | ||
62 | return -EINVAL; | ||
63 | |||
64 | add_wait_queue(&rtc->irq_queue, &wait); | ||
65 | do { | ||
66 | __set_current_state(TASK_INTERRUPTIBLE); | ||
67 | |||
68 | spin_lock_irq(&rtc->irq_lock); | ||
69 | data = rtc->irq_data; | ||
70 | rtc->irq_data = 0; | ||
71 | spin_unlock_irq(&rtc->irq_lock); | ||
72 | |||
73 | if (data != 0) { | ||
74 | ret = 0; | ||
75 | break; | ||
76 | } | ||
77 | if (file->f_flags & O_NONBLOCK) { | ||
78 | ret = -EAGAIN; | ||
79 | break; | ||
80 | } | ||
81 | if (signal_pending(current)) { | ||
82 | ret = -ERESTARTSYS; | ||
83 | break; | ||
84 | } | ||
85 | schedule(); | ||
86 | } while (1); | ||
87 | set_current_state(TASK_RUNNING); | ||
88 | remove_wait_queue(&rtc->irq_queue, &wait); | ||
89 | |||
90 | if (ret == 0) { | ||
91 | /* Check for any data updates */ | ||
92 | if (rtc->ops->read_callback) | ||
93 | data = rtc->ops->read_callback(rtc->class_dev.dev, data); | ||
94 | |||
95 | ret = put_user(data, (unsigned long __user *)buf); | ||
96 | if (ret == 0) | ||
97 | ret = sizeof(unsigned long); | ||
98 | } | ||
99 | return ret; | ||
100 | } | ||
101 | |||
102 | static unsigned int rtc_dev_poll(struct file *file, poll_table *wait) | ||
103 | { | ||
104 | struct rtc_device *rtc = to_rtc_device(file->private_data); | ||
105 | unsigned long data; | ||
106 | |||
107 | poll_wait(file, &rtc->irq_queue, wait); | ||
108 | |||
109 | data = rtc->irq_data; | ||
110 | |||
111 | return (data != 0) ? (POLLIN | POLLRDNORM) : 0; | ||
112 | } | ||
113 | |||
114 | static int rtc_dev_ioctl(struct inode *inode, struct file *file, | ||
115 | unsigned int cmd, unsigned long arg) | ||
116 | { | ||
117 | int err = 0; | ||
118 | struct class_device *class_dev = file->private_data; | ||
119 | struct rtc_device *rtc = to_rtc_device(class_dev); | ||
120 | struct rtc_class_ops *ops = rtc->ops; | ||
121 | struct rtc_time tm; | ||
122 | struct rtc_wkalrm alarm; | ||
123 | void __user *uarg = (void __user *) arg; | ||
124 | |||
125 | /* avoid conflicting IRQ users */ | ||
126 | if (cmd == RTC_PIE_ON || cmd == RTC_PIE_OFF || cmd == RTC_IRQP_SET) { | ||
127 | spin_lock(&rtc->irq_task_lock); | ||
128 | if (rtc->irq_task) | ||
129 | err = -EBUSY; | ||
130 | spin_unlock(&rtc->irq_task_lock); | ||
131 | |||
132 | if (err < 0) | ||
133 | return err; | ||
134 | } | ||
135 | |||
136 | /* try the driver's ioctl interface */ | ||
137 | if (ops->ioctl) { | ||
138 | err = ops->ioctl(class_dev->dev, cmd, arg); | ||
139 | if (err != -EINVAL) | ||
140 | return err; | ||
141 | } | ||
142 | |||
143 | /* if the driver does not provide the ioctl interface | ||
144 | * or if that particular ioctl was not implemented | ||
145 | * (-EINVAL), we will try to emulate here. | ||
146 | */ | ||
147 | |||
148 | switch (cmd) { | ||
149 | case RTC_ALM_READ: | ||
150 | err = rtc_read_alarm(class_dev, &alarm); | ||
151 | if (err < 0) | ||
152 | return err; | ||
153 | |||
154 | if (copy_to_user(uarg, &alarm.time, sizeof(tm))) | ||
155 | return -EFAULT; | ||
156 | break; | ||
157 | |||
158 | case RTC_ALM_SET: | ||
159 | if (copy_from_user(&alarm.time, uarg, sizeof(tm))) | ||
160 | return -EFAULT; | ||
161 | |||
162 | alarm.enabled = 0; | ||
163 | alarm.pending = 0; | ||
164 | alarm.time.tm_mday = -1; | ||
165 | alarm.time.tm_mon = -1; | ||
166 | alarm.time.tm_year = -1; | ||
167 | alarm.time.tm_wday = -1; | ||
168 | alarm.time.tm_yday = -1; | ||
169 | alarm.time.tm_isdst = -1; | ||
170 | err = rtc_set_alarm(class_dev, &alarm); | ||
171 | break; | ||
172 | |||
173 | case RTC_RD_TIME: | ||
174 | err = rtc_read_time(class_dev, &tm); | ||
175 | if (err < 0) | ||
176 | return err; | ||
177 | |||
178 | if (copy_to_user(uarg, &tm, sizeof(tm))) | ||
179 | return -EFAULT; | ||
180 | break; | ||
181 | |||
182 | case RTC_SET_TIME: | ||
183 | if (!capable(CAP_SYS_TIME)) | ||
184 | return -EACCES; | ||
185 | |||
186 | if (copy_from_user(&tm, uarg, sizeof(tm))) | ||
187 | return -EFAULT; | ||
188 | |||
189 | err = rtc_set_time(class_dev, &tm); | ||
190 | break; | ||
191 | #if 0 | ||
192 | case RTC_EPOCH_SET: | ||
193 | #ifndef rtc_epoch | ||
194 | /* | ||
195 | * There were no RTC clocks before 1900. | ||
196 | */ | ||
197 | if (arg < 1900) { | ||
198 | err = -EINVAL; | ||
199 | break; | ||
200 | } | ||
201 | if (!capable(CAP_SYS_TIME)) { | ||
202 | err = -EACCES; | ||
203 | break; | ||
204 | } | ||
205 | rtc_epoch = arg; | ||
206 | err = 0; | ||
207 | #endif | ||
208 | break; | ||
209 | |||
210 | case RTC_EPOCH_READ: | ||
211 | err = put_user(rtc_epoch, (unsigned long __user *)uarg); | ||
212 | break; | ||
213 | #endif | ||
214 | case RTC_WKALM_SET: | ||
215 | if (copy_from_user(&alarm, uarg, sizeof(alarm))) | ||
216 | return -EFAULT; | ||
217 | |||
218 | err = rtc_set_alarm(class_dev, &alarm); | ||
219 | break; | ||
220 | |||
221 | case RTC_WKALM_RD: | ||
222 | err = rtc_read_alarm(class_dev, &alarm); | ||
223 | if (err < 0) | ||
224 | return err; | ||
225 | |||
226 | if (copy_to_user(uarg, &alarm, sizeof(alarm))) | ||
227 | return -EFAULT; | ||
228 | break; | ||
229 | |||
230 | default: | ||
231 | err = -EINVAL; | ||
232 | break; | ||
233 | } | ||
234 | |||
235 | return err; | ||
236 | } | ||
237 | |||
238 | static int rtc_dev_release(struct inode *inode, struct file *file) | ||
239 | { | ||
240 | struct rtc_device *rtc = to_rtc_device(file->private_data); | ||
241 | |||
242 | if (rtc->ops->release) | ||
243 | rtc->ops->release(rtc->class_dev.dev); | ||
244 | |||
245 | mutex_unlock(&rtc->char_lock); | ||
246 | return 0; | ||
247 | } | ||
248 | |||
249 | static int rtc_dev_fasync(int fd, struct file *file, int on) | ||
250 | { | ||
251 | struct rtc_device *rtc = to_rtc_device(file->private_data); | ||
252 | return fasync_helper(fd, file, on, &rtc->async_queue); | ||
253 | } | ||
254 | |||
255 | static struct file_operations rtc_dev_fops = { | ||
256 | .owner = THIS_MODULE, | ||
257 | .llseek = no_llseek, | ||
258 | .read = rtc_dev_read, | ||
259 | .poll = rtc_dev_poll, | ||
260 | .ioctl = rtc_dev_ioctl, | ||
261 | .open = rtc_dev_open, | ||
262 | .release = rtc_dev_release, | ||
263 | .fasync = rtc_dev_fasync, | ||
264 | }; | ||
265 | |||
266 | /* insertion/removal hooks */ | ||
267 | |||
268 | static int rtc_dev_add_device(struct class_device *class_dev, | ||
269 | struct class_interface *class_intf) | ||
270 | { | ||
271 | int err = 0; | ||
272 | struct rtc_device *rtc = to_rtc_device(class_dev); | ||
273 | |||
274 | if (rtc->id >= RTC_DEV_MAX) { | ||
275 | dev_err(class_dev->dev, "too many RTCs\n"); | ||
276 | return -EINVAL; | ||
277 | } | ||
278 | |||
279 | mutex_init(&rtc->char_lock); | ||
280 | spin_lock_init(&rtc->irq_lock); | ||
281 | init_waitqueue_head(&rtc->irq_queue); | ||
282 | |||
283 | cdev_init(&rtc->char_dev, &rtc_dev_fops); | ||
284 | rtc->char_dev.owner = rtc->owner; | ||
285 | |||
286 | if (cdev_add(&rtc->char_dev, MKDEV(MAJOR(rtc_devt), rtc->id), 1)) { | ||
287 | cdev_del(&rtc->char_dev); | ||
288 | dev_err(class_dev->dev, | ||
289 | "failed to add char device %d:%d\n", | ||
290 | MAJOR(rtc_devt), rtc->id); | ||
291 | return -ENODEV; | ||
292 | } | ||
293 | |||
294 | rtc->rtc_dev = class_device_create(rtc_dev_class, NULL, | ||
295 | MKDEV(MAJOR(rtc_devt), rtc->id), | ||
296 | class_dev->dev, "rtc%d", rtc->id); | ||
297 | if (IS_ERR(rtc->rtc_dev)) { | ||
298 | dev_err(class_dev->dev, "cannot create rtc_dev device\n"); | ||
299 | err = PTR_ERR(rtc->rtc_dev); | ||
300 | goto err_cdev_del; | ||
301 | } | ||
302 | |||
303 | dev_info(class_dev->dev, "rtc intf: dev (%d:%d)\n", | ||
304 | MAJOR(rtc->rtc_dev->devt), | ||
305 | MINOR(rtc->rtc_dev->devt)); | ||
306 | |||
307 | return 0; | ||
308 | |||
309 | err_cdev_del: | ||
310 | |||
311 | cdev_del(&rtc->char_dev); | ||
312 | return err; | ||
313 | } | ||
314 | |||
315 | static void rtc_dev_remove_device(struct class_device *class_dev, | ||
316 | struct class_interface *class_intf) | ||
317 | { | ||
318 | struct rtc_device *rtc = to_rtc_device(class_dev); | ||
319 | |||
320 | if (rtc->rtc_dev) { | ||
321 | dev_dbg(class_dev->dev, "removing char %d:%d\n", | ||
322 | MAJOR(rtc->rtc_dev->devt), | ||
323 | MINOR(rtc->rtc_dev->devt)); | ||
324 | |||
325 | class_device_unregister(rtc->rtc_dev); | ||
326 | cdev_del(&rtc->char_dev); | ||
327 | } | ||
328 | } | ||
329 | |||
330 | /* interface registration */ | ||
331 | |||
332 | static struct class_interface rtc_dev_interface = { | ||
333 | .add = &rtc_dev_add_device, | ||
334 | .remove = &rtc_dev_remove_device, | ||
335 | }; | ||
336 | |||
337 | static int __init rtc_dev_init(void) | ||
338 | { | ||
339 | int err; | ||
340 | |||
341 | rtc_dev_class = class_create(THIS_MODULE, "rtc-dev"); | ||
342 | if (IS_ERR(rtc_dev_class)) | ||
343 | return PTR_ERR(rtc_dev_class); | ||
344 | |||
345 | err = alloc_chrdev_region(&rtc_devt, 0, RTC_DEV_MAX, "rtc"); | ||
346 | if (err < 0) { | ||
347 | printk(KERN_ERR "%s: failed to allocate char dev region\n", | ||
348 | __FILE__); | ||
349 | goto err_destroy_class; | ||
350 | } | ||
351 | |||
352 | err = rtc_interface_register(&rtc_dev_interface); | ||
353 | if (err < 0) { | ||
354 | printk(KERN_ERR "%s: failed to register the interface\n", | ||
355 | __FILE__); | ||
356 | goto err_unregister_chrdev; | ||
357 | } | ||
358 | |||
359 | return 0; | ||
360 | |||
361 | err_unregister_chrdev: | ||
362 | unregister_chrdev_region(rtc_devt, RTC_DEV_MAX); | ||
363 | |||
364 | err_destroy_class: | ||
365 | class_destroy(rtc_dev_class); | ||
366 | |||
367 | return err; | ||
368 | } | ||
369 | |||
370 | static void __exit rtc_dev_exit(void) | ||
371 | { | ||
372 | class_interface_unregister(&rtc_dev_interface); | ||
373 | class_destroy(rtc_dev_class); | ||
374 | unregister_chrdev_region(rtc_devt, RTC_DEV_MAX); | ||
375 | } | ||
376 | |||
377 | module_init(rtc_dev_init); | ||
378 | module_exit(rtc_dev_exit); | ||
379 | |||
380 | MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); | ||
381 | MODULE_DESCRIPTION("RTC class dev interface"); | ||
382 | MODULE_LICENSE("GPL"); | ||