Commit 0c86edc0d4970649f39748c4ce4f2895f728468f
Committed by
Linus Torvalds
1 parent
4079c39aaa
Exists in
master
and in
39 other branches
[PATCH] RTC subsystem: class
Add the basic RTC subsystem infrastructure to the kernel. rtc/class.c - registration facilities for RTC drivers rtc/interface.c - kernel/rtc interface functions rtc/hctosys.c - snippet of code that copies hw clock to sw clock at bootup, if configured to do so. 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>
Showing 8 changed files with 633 additions and 5 deletions Side-by-side Diff
CREDITS
... | ... | @@ -3741,10 +3741,11 @@ |
3741 | 3741 | D: Miscellaneous kernel fixes |
3742 | 3742 | |
3743 | 3743 | N: Alessandro Zummo |
3744 | -E: azummo@ita.flashnet.it | |
3745 | -W: http://freepage.logicom.it/azummo/ | |
3744 | +E: a.zummo@towertech.it | |
3746 | 3745 | D: CMI8330 support is sb_card.c |
3747 | 3746 | D: ISAPnP fixes in sb_card.c |
3747 | +D: ZyXEL omni.net lcd plus driver | |
3748 | +D: RTC subsystem | |
3748 | 3749 | S: Italy |
3749 | 3750 | |
3750 | 3751 | N: Marc Zyngier |
MAINTAINERS
... | ... | @@ -2233,6 +2233,12 @@ |
2233 | 2233 | L: linux-kernel@vger.kernel.org |
2234 | 2234 | S: Maintained |
2235 | 2235 | |
2236 | +REAL TIME CLOCK (RTC) SUBSYSTEM | |
2237 | +P: Alessandro Zummo | |
2238 | +M: a.zummo@towertech.it | |
2239 | +L: linux-kernel@vger.kernel.org | |
2240 | +S: Maintained | |
2241 | + | |
2236 | 2242 | REISERFS FILE SYSTEM |
2237 | 2243 | P: Hans Reiser |
2238 | 2244 | M: reiserfs-dev@namesys.com |
drivers/rtc/Kconfig
1 | -# | |
1 | +\# | |
2 | 2 | # RTC class/drivers configuration |
3 | 3 | # |
4 | 4 | |
5 | +menu "Real Time Clock" | |
6 | + | |
5 | 7 | config RTC_LIB |
6 | 8 | tristate |
9 | + | |
10 | +config RTC_CLASS | |
11 | + tristate "RTC class" | |
12 | + depends on EXPERIMENTAL | |
13 | + default n | |
14 | + select RTC_LIB | |
15 | + help | |
16 | + Generic RTC class support. If you say yes here, you will | |
17 | + be allowed to plug one or more RTCs to your system. You will | |
18 | + probably want to enable one of more of the interfaces below. | |
19 | + | |
20 | + This driver can also be built as a module. If so, the module | |
21 | + will be called rtc-class. | |
22 | + | |
23 | +config RTC_HCTOSYS | |
24 | + bool "Set system time from RTC on startup" | |
25 | + depends on RTC_CLASS = y | |
26 | + default y | |
27 | + help | |
28 | + If you say yes here, the system time will be set using | |
29 | + the value read from the specified RTC device. This is useful | |
30 | + in order to avoid unnecessary fschk runs. | |
31 | + | |
32 | +config RTC_HCTOSYS_DEVICE | |
33 | + string "The RTC to read the time from" | |
34 | + depends on RTC_HCTOSYS = y | |
35 | + default "rtc0" | |
36 | + help | |
37 | + The RTC device that will be used as the source for | |
38 | + the system time, usually rtc0. | |
39 | + | |
40 | +comment "RTC interfaces" | |
41 | + depends on RTC_CLASS | |
42 | + | |
43 | +comment "RTC drivers" | |
44 | + depends on RTC_CLASS | |
45 | + | |
46 | +endmenu |
drivers/rtc/Makefile
drivers/rtc/class.c
1 | +/* | |
2 | + * RTC subsystem, base class | |
3 | + * | |
4 | + * Copyright (C) 2005 Tower Technologies | |
5 | + * Author: Alessandro Zummo <a.zummo@towertech.it> | |
6 | + * | |
7 | + * class skeleton from drivers/hwmon/hwmon.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 | +#include <linux/kdev_t.h> | |
17 | +#include <linux/idr.h> | |
18 | + | |
19 | +static DEFINE_IDR(rtc_idr); | |
20 | +static DEFINE_MUTEX(idr_lock); | |
21 | +struct class *rtc_class; | |
22 | + | |
23 | +static void rtc_device_release(struct class_device *class_dev) | |
24 | +{ | |
25 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
26 | + mutex_lock(&idr_lock); | |
27 | + idr_remove(&rtc_idr, rtc->id); | |
28 | + mutex_unlock(&idr_lock); | |
29 | + kfree(rtc); | |
30 | +} | |
31 | + | |
32 | +/** | |
33 | + * rtc_device_register - register w/ RTC class | |
34 | + * @dev: the device to register | |
35 | + * | |
36 | + * rtc_device_unregister() must be called when the class device is no | |
37 | + * longer needed. | |
38 | + * | |
39 | + * Returns the pointer to the new struct class device. | |
40 | + */ | |
41 | +struct rtc_device *rtc_device_register(const char *name, struct device *dev, | |
42 | + struct rtc_class_ops *ops, | |
43 | + struct module *owner) | |
44 | +{ | |
45 | + struct rtc_device *rtc; | |
46 | + int id, err; | |
47 | + | |
48 | + if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) { | |
49 | + err = -ENOMEM; | |
50 | + goto exit; | |
51 | + } | |
52 | + | |
53 | + | |
54 | + mutex_lock(&idr_lock); | |
55 | + err = idr_get_new(&rtc_idr, NULL, &id); | |
56 | + mutex_unlock(&idr_lock); | |
57 | + | |
58 | + if (err < 0) | |
59 | + goto exit; | |
60 | + | |
61 | + id = id & MAX_ID_MASK; | |
62 | + | |
63 | + rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL); | |
64 | + if (rtc == NULL) { | |
65 | + err = -ENOMEM; | |
66 | + goto exit_idr; | |
67 | + } | |
68 | + | |
69 | + rtc->id = id; | |
70 | + rtc->ops = ops; | |
71 | + rtc->owner = owner; | |
72 | + rtc->class_dev.dev = dev; | |
73 | + rtc->class_dev.class = rtc_class; | |
74 | + rtc->class_dev.release = rtc_device_release; | |
75 | + | |
76 | + mutex_init(&rtc->ops_lock); | |
77 | + spin_lock_init(&rtc->irq_lock); | |
78 | + spin_lock_init(&rtc->irq_task_lock); | |
79 | + | |
80 | + strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE); | |
81 | + snprintf(rtc->class_dev.class_id, BUS_ID_SIZE, "rtc%d", id); | |
82 | + | |
83 | + err = class_device_register(&rtc->class_dev); | |
84 | + if (err) | |
85 | + goto exit_kfree; | |
86 | + | |
87 | + dev_info(dev, "rtc core: registered %s as %s\n", | |
88 | + rtc->name, rtc->class_dev.class_id); | |
89 | + | |
90 | + return rtc; | |
91 | + | |
92 | +exit_kfree: | |
93 | + kfree(rtc); | |
94 | + | |
95 | +exit_idr: | |
96 | + idr_remove(&rtc_idr, id); | |
97 | + | |
98 | +exit: | |
99 | + return ERR_PTR(err); | |
100 | +} | |
101 | +EXPORT_SYMBOL_GPL(rtc_device_register); | |
102 | + | |
103 | + | |
104 | +/** | |
105 | + * rtc_device_unregister - removes the previously registered RTC class device | |
106 | + * | |
107 | + * @rtc: the RTC class device to destroy | |
108 | + */ | |
109 | +void rtc_device_unregister(struct rtc_device *rtc) | |
110 | +{ | |
111 | + mutex_lock(&rtc->ops_lock); | |
112 | + rtc->ops = NULL; | |
113 | + mutex_unlock(&rtc->ops_lock); | |
114 | + class_device_unregister(&rtc->class_dev); | |
115 | +} | |
116 | +EXPORT_SYMBOL_GPL(rtc_device_unregister); | |
117 | + | |
118 | +int rtc_interface_register(struct class_interface *intf) | |
119 | +{ | |
120 | + intf->class = rtc_class; | |
121 | + return class_interface_register(intf); | |
122 | +} | |
123 | +EXPORT_SYMBOL_GPL(rtc_interface_register); | |
124 | + | |
125 | +static int __init rtc_init(void) | |
126 | +{ | |
127 | + rtc_class = class_create(THIS_MODULE, "rtc"); | |
128 | + if (IS_ERR(rtc_class)) { | |
129 | + printk(KERN_ERR "%s: couldn't create class\n", __FILE__); | |
130 | + return PTR_ERR(rtc_class); | |
131 | + } | |
132 | + return 0; | |
133 | +} | |
134 | + | |
135 | +static void __exit rtc_exit(void) | |
136 | +{ | |
137 | + class_destroy(rtc_class); | |
138 | +} | |
139 | + | |
140 | +module_init(rtc_init); | |
141 | +module_exit(rtc_exit); | |
142 | + | |
143 | +MODULE_AUTHOR("Alessandro Zummo <a.zummo@towerteh.it>"); | |
144 | +MODULE_DESCRIPTION("RTC class support"); | |
145 | +MODULE_LICENSE("GPL"); |
drivers/rtc/hctosys.c
1 | +/* | |
2 | + * RTC subsystem, initialize system time on startup | |
3 | + * | |
4 | + * Copyright (C) 2005 Tower Technologies | |
5 | + * Author: Alessandro Zummo <a.zummo@towertech.it> | |
6 | + * | |
7 | + * This program is free software; you can redistribute it and/or modify | |
8 | + * it under the terms of the GNU General Public License version 2 as | |
9 | + * published by the Free Software Foundation. | |
10 | +*/ | |
11 | + | |
12 | +#include <linux/rtc.h> | |
13 | + | |
14 | +/* IMPORTANT: the RTC only stores whole seconds. It is arbitrary | |
15 | + * whether it stores the most close value or the value with partial | |
16 | + * seconds truncated. However, it is important that we use it to store | |
17 | + * the truncated value. This is because otherwise it is necessary, | |
18 | + * in an rtc sync function, to read both xtime.tv_sec and | |
19 | + * xtime.tv_nsec. On some processors (i.e. ARM), an atomic read | |
20 | + * of >32bits is not possible. So storing the most close value would | |
21 | + * slow down the sync API. So here we have the truncated value and | |
22 | + * the best guess is to add 0.5s. | |
23 | + */ | |
24 | + | |
25 | +static int __init rtc_hctosys(void) | |
26 | +{ | |
27 | + int err; | |
28 | + struct rtc_time tm; | |
29 | + struct class_device *class_dev = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); | |
30 | + | |
31 | + if (class_dev == NULL) { | |
32 | + printk("%s: unable to open rtc device (%s)\n", | |
33 | + __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); | |
34 | + return -ENODEV; | |
35 | + } | |
36 | + | |
37 | + err = rtc_read_time(class_dev, &tm); | |
38 | + if (err == 0) { | |
39 | + err = rtc_valid_tm(&tm); | |
40 | + if (err == 0) { | |
41 | + struct timespec tv; | |
42 | + | |
43 | + tv.tv_nsec = NSEC_PER_SEC >> 1; | |
44 | + | |
45 | + rtc_tm_to_time(&tm, &tv.tv_sec); | |
46 | + | |
47 | + do_settimeofday(&tv); | |
48 | + | |
49 | + dev_info(class_dev->dev, | |
50 | + "setting the system clock to " | |
51 | + "%d-%02d-%02d %02d:%02d:%02d (%u)\n", | |
52 | + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, | |
53 | + tm.tm_hour, tm.tm_min, tm.tm_sec, | |
54 | + (unsigned int) tv.tv_sec); | |
55 | + } | |
56 | + else | |
57 | + dev_err(class_dev->dev, | |
58 | + "hctosys: invalid date/time\n"); | |
59 | + } | |
60 | + else | |
61 | + dev_err(class_dev->dev, | |
62 | + "hctosys: unable to read the hardware clock\n"); | |
63 | + | |
64 | + rtc_class_close(class_dev); | |
65 | + | |
66 | + return 0; | |
67 | +} | |
68 | + | |
69 | +late_initcall(rtc_hctosys); |
drivers/rtc/interface.c
1 | +/* | |
2 | + * RTC subsystem, interface functions | |
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/rtc.h> | |
15 | + | |
16 | +int rtc_read_time(struct class_device *class_dev, struct rtc_time *tm) | |
17 | +{ | |
18 | + int err; | |
19 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
20 | + | |
21 | + err = mutex_lock_interruptible(&rtc->ops_lock); | |
22 | + if (err) | |
23 | + return -EBUSY; | |
24 | + | |
25 | + if (!rtc->ops) | |
26 | + err = -ENODEV; | |
27 | + else if (!rtc->ops->read_time) | |
28 | + err = -EINVAL; | |
29 | + else { | |
30 | + memset(tm, 0, sizeof(struct rtc_time)); | |
31 | + err = rtc->ops->read_time(class_dev->dev, tm); | |
32 | + } | |
33 | + | |
34 | + mutex_unlock(&rtc->ops_lock); | |
35 | + return err; | |
36 | +} | |
37 | +EXPORT_SYMBOL_GPL(rtc_read_time); | |
38 | + | |
39 | +int rtc_set_time(struct class_device *class_dev, struct rtc_time *tm) | |
40 | +{ | |
41 | + int err; | |
42 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
43 | + | |
44 | + err = rtc_valid_tm(tm); | |
45 | + if (err != 0) | |
46 | + return err; | |
47 | + | |
48 | + err = mutex_lock_interruptible(&rtc->ops_lock); | |
49 | + if (err) | |
50 | + return -EBUSY; | |
51 | + | |
52 | + if (!rtc->ops) | |
53 | + err = -ENODEV; | |
54 | + else if (!rtc->ops->set_time) | |
55 | + err = -EINVAL; | |
56 | + else | |
57 | + err = rtc->ops->set_time(class_dev->dev, tm); | |
58 | + | |
59 | + mutex_unlock(&rtc->ops_lock); | |
60 | + return err; | |
61 | +} | |
62 | +EXPORT_SYMBOL_GPL(rtc_set_time); | |
63 | + | |
64 | +int rtc_set_mmss(struct class_device *class_dev, unsigned long secs) | |
65 | +{ | |
66 | + int err; | |
67 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
68 | + | |
69 | + err = mutex_lock_interruptible(&rtc->ops_lock); | |
70 | + if (err) | |
71 | + return -EBUSY; | |
72 | + | |
73 | + if (!rtc->ops) | |
74 | + err = -ENODEV; | |
75 | + else if (rtc->ops->set_mmss) | |
76 | + err = rtc->ops->set_mmss(class_dev->dev, secs); | |
77 | + else if (rtc->ops->read_time && rtc->ops->set_time) { | |
78 | + struct rtc_time new, old; | |
79 | + | |
80 | + err = rtc->ops->read_time(class_dev->dev, &old); | |
81 | + if (err == 0) { | |
82 | + rtc_time_to_tm(secs, &new); | |
83 | + | |
84 | + /* | |
85 | + * avoid writing when we're going to change the day of | |
86 | + * the month. We will retry in the next minute. This | |
87 | + * basically means that if the RTC must not drift | |
88 | + * by more than 1 minute in 11 minutes. | |
89 | + */ | |
90 | + if (!((old.tm_hour == 23 && old.tm_min == 59) || | |
91 | + (new.tm_hour == 23 && new.tm_min == 59))) | |
92 | + err = rtc->ops->set_time(class_dev->dev, &new); | |
93 | + } | |
94 | + } | |
95 | + else | |
96 | + err = -EINVAL; | |
97 | + | |
98 | + mutex_unlock(&rtc->ops_lock); | |
99 | + | |
100 | + return err; | |
101 | +} | |
102 | +EXPORT_SYMBOL_GPL(rtc_set_mmss); | |
103 | + | |
104 | +int rtc_read_alarm(struct class_device *class_dev, struct rtc_wkalrm *alarm) | |
105 | +{ | |
106 | + int err; | |
107 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
108 | + | |
109 | + err = mutex_lock_interruptible(&rtc->ops_lock); | |
110 | + if (err) | |
111 | + return -EBUSY; | |
112 | + | |
113 | + if (rtc->ops == NULL) | |
114 | + err = -ENODEV; | |
115 | + else if (!rtc->ops->read_alarm) | |
116 | + err = -EINVAL; | |
117 | + else { | |
118 | + memset(alarm, 0, sizeof(struct rtc_wkalrm)); | |
119 | + err = rtc->ops->read_alarm(class_dev->dev, alarm); | |
120 | + } | |
121 | + | |
122 | + mutex_unlock(&rtc->ops_lock); | |
123 | + return err; | |
124 | +} | |
125 | +EXPORT_SYMBOL_GPL(rtc_read_alarm); | |
126 | + | |
127 | +int rtc_set_alarm(struct class_device *class_dev, struct rtc_wkalrm *alarm) | |
128 | +{ | |
129 | + int err; | |
130 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
131 | + | |
132 | + err = mutex_lock_interruptible(&rtc->ops_lock); | |
133 | + if (err) | |
134 | + return -EBUSY; | |
135 | + | |
136 | + if (!rtc->ops) | |
137 | + err = -ENODEV; | |
138 | + else if (!rtc->ops->set_alarm) | |
139 | + err = -EINVAL; | |
140 | + else | |
141 | + err = rtc->ops->set_alarm(class_dev->dev, alarm); | |
142 | + | |
143 | + mutex_unlock(&rtc->ops_lock); | |
144 | + return err; | |
145 | +} | |
146 | +EXPORT_SYMBOL_GPL(rtc_set_alarm); | |
147 | + | |
148 | +void rtc_update_irq(struct class_device *class_dev, | |
149 | + unsigned long num, unsigned long events) | |
150 | +{ | |
151 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
152 | + | |
153 | + spin_lock(&rtc->irq_lock); | |
154 | + rtc->irq_data = (rtc->irq_data + (num << 8)) | events; | |
155 | + spin_unlock(&rtc->irq_lock); | |
156 | + | |
157 | + spin_lock(&rtc->irq_task_lock); | |
158 | + if (rtc->irq_task) | |
159 | + rtc->irq_task->func(rtc->irq_task->private_data); | |
160 | + spin_unlock(&rtc->irq_task_lock); | |
161 | + | |
162 | + wake_up_interruptible(&rtc->irq_queue); | |
163 | + kill_fasync(&rtc->async_queue, SIGIO, POLL_IN); | |
164 | +} | |
165 | +EXPORT_SYMBOL_GPL(rtc_update_irq); | |
166 | + | |
167 | +struct class_device *rtc_class_open(char *name) | |
168 | +{ | |
169 | + struct class_device *class_dev = NULL, | |
170 | + *class_dev_tmp; | |
171 | + | |
172 | + down(&rtc_class->sem); | |
173 | + list_for_each_entry(class_dev_tmp, &rtc_class->children, node) { | |
174 | + if (strncmp(class_dev_tmp->class_id, name, BUS_ID_SIZE) == 0) { | |
175 | + class_dev = class_dev_tmp; | |
176 | + break; | |
177 | + } | |
178 | + } | |
179 | + | |
180 | + if (class_dev) { | |
181 | + if (!try_module_get(to_rtc_device(class_dev)->owner)) | |
182 | + class_dev = NULL; | |
183 | + } | |
184 | + up(&rtc_class->sem); | |
185 | + | |
186 | + return class_dev; | |
187 | +} | |
188 | +EXPORT_SYMBOL_GPL(rtc_class_open); | |
189 | + | |
190 | +void rtc_class_close(struct class_device *class_dev) | |
191 | +{ | |
192 | + module_put(to_rtc_device(class_dev)->owner); | |
193 | +} | |
194 | +EXPORT_SYMBOL_GPL(rtc_class_close); | |
195 | + | |
196 | +int rtc_irq_register(struct class_device *class_dev, struct rtc_task *task) | |
197 | +{ | |
198 | + int retval = -EBUSY; | |
199 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
200 | + | |
201 | + if (task == NULL || task->func == NULL) | |
202 | + return -EINVAL; | |
203 | + | |
204 | + spin_lock(&rtc->irq_task_lock); | |
205 | + if (rtc->irq_task == NULL) { | |
206 | + rtc->irq_task = task; | |
207 | + retval = 0; | |
208 | + } | |
209 | + spin_unlock(&rtc->irq_task_lock); | |
210 | + | |
211 | + return retval; | |
212 | +} | |
213 | +EXPORT_SYMBOL_GPL(rtc_irq_register); | |
214 | + | |
215 | +void rtc_irq_unregister(struct class_device *class_dev, struct rtc_task *task) | |
216 | +{ | |
217 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
218 | + | |
219 | + spin_lock(&rtc->irq_task_lock); | |
220 | + if (rtc->irq_task == task) | |
221 | + rtc->irq_task = NULL; | |
222 | + spin_unlock(&rtc->irq_task_lock); | |
223 | +} | |
224 | +EXPORT_SYMBOL_GPL(rtc_irq_unregister); | |
225 | + | |
226 | +int rtc_irq_set_state(struct class_device *class_dev, struct rtc_task *task, int enabled) | |
227 | +{ | |
228 | + int err = 0; | |
229 | + unsigned long flags; | |
230 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
231 | + | |
232 | + spin_lock_irqsave(&rtc->irq_task_lock, flags); | |
233 | + if (rtc->irq_task != task) | |
234 | + err = -ENXIO; | |
235 | + spin_unlock_irqrestore(&rtc->irq_task_lock, flags); | |
236 | + | |
237 | + if (err == 0) | |
238 | + err = rtc->ops->irq_set_state(class_dev->dev, enabled); | |
239 | + | |
240 | + return err; | |
241 | +} | |
242 | +EXPORT_SYMBOL_GPL(rtc_irq_set_state); | |
243 | + | |
244 | +int rtc_irq_set_freq(struct class_device *class_dev, struct rtc_task *task, int freq) | |
245 | +{ | |
246 | + int err = 0, tmp = 0; | |
247 | + unsigned long flags; | |
248 | + struct rtc_device *rtc = to_rtc_device(class_dev); | |
249 | + | |
250 | + /* allowed range is 2-8192 */ | |
251 | + if (freq < 2 || freq > 8192) | |
252 | + return -EINVAL; | |
253 | +/* | |
254 | + FIXME: this does not belong here, will move where appropriate | |
255 | + at a later stage. It cannot hurt right now, trust me :) | |
256 | + if ((freq > rtc_max_user_freq) && (!capable(CAP_SYS_RESOURCE))) | |
257 | + return -EACCES; | |
258 | +*/ | |
259 | + /* check if freq is a power of 2 */ | |
260 | + while (freq > (1 << tmp)) | |
261 | + tmp++; | |
262 | + | |
263 | + if (freq != (1 << tmp)) | |
264 | + return -EINVAL; | |
265 | + | |
266 | + spin_lock_irqsave(&rtc->irq_task_lock, flags); | |
267 | + if (rtc->irq_task != task) | |
268 | + err = -ENXIO; | |
269 | + spin_unlock_irqrestore(&rtc->irq_task_lock, flags); | |
270 | + | |
271 | + if (err == 0) { | |
272 | + err = rtc->ops->irq_set_freq(class_dev->dev, freq); | |
273 | + if (err == 0) | |
274 | + rtc->irq_freq = freq; | |
275 | + } | |
276 | + return err; | |
277 | +} |
include/linux/rtc.h
... | ... | @@ -91,6 +91,12 @@ |
91 | 91 | #define RTC_PLL_GET _IOR('p', 0x11, struct rtc_pll_info) /* Get PLL correction */ |
92 | 92 | #define RTC_PLL_SET _IOW('p', 0x12, struct rtc_pll_info) /* Set PLL correction */ |
93 | 93 | |
94 | +/* interrupt flags */ | |
95 | +#define RTC_IRQF 0x80 /* any of the following is active */ | |
96 | +#define RTC_PF 0x40 | |
97 | +#define RTC_AF 0x20 | |
98 | +#define RTC_UF 0x10 | |
99 | + | |
94 | 100 | #ifdef __KERNEL__ |
95 | 101 | |
96 | 102 | #include <linux/interrupt.h> |
... | ... | @@ -99,6 +105,87 @@ |
99 | 105 | extern int rtc_valid_tm(struct rtc_time *tm); |
100 | 106 | extern int rtc_tm_to_time(struct rtc_time *tm, unsigned long *time); |
101 | 107 | extern void rtc_time_to_tm(unsigned long time, struct rtc_time *tm); |
108 | + | |
109 | +#include <linux/device.h> | |
110 | +#include <linux/seq_file.h> | |
111 | +#include <linux/cdev.h> | |
112 | +#include <linux/poll.h> | |
113 | +#include <linux/mutex.h> | |
114 | + | |
115 | +extern struct class *rtc_class; | |
116 | + | |
117 | +struct rtc_class_ops { | |
118 | + int (*open)(struct device *); | |
119 | + void (*release)(struct device *); | |
120 | + int (*ioctl)(struct device *, unsigned int, unsigned long); | |
121 | + int (*read_time)(struct device *, struct rtc_time *); | |
122 | + int (*set_time)(struct device *, struct rtc_time *); | |
123 | + int (*read_alarm)(struct device *, struct rtc_wkalrm *); | |
124 | + int (*set_alarm)(struct device *, struct rtc_wkalrm *); | |
125 | + int (*proc)(struct device *, struct seq_file *); | |
126 | + int (*set_mmss)(struct device *, unsigned long secs); | |
127 | + int (*irq_set_state)(struct device *, int enabled); | |
128 | + int (*irq_set_freq)(struct device *, int freq); | |
129 | + int (*read_callback)(struct device *, int data); | |
130 | +}; | |
131 | + | |
132 | +#define RTC_DEVICE_NAME_SIZE 20 | |
133 | +struct rtc_task; | |
134 | + | |
135 | +struct rtc_device | |
136 | +{ | |
137 | + struct class_device class_dev; | |
138 | + struct module *owner; | |
139 | + | |
140 | + int id; | |
141 | + char name[RTC_DEVICE_NAME_SIZE]; | |
142 | + | |
143 | + struct rtc_class_ops *ops; | |
144 | + struct mutex ops_lock; | |
145 | + | |
146 | + struct class_device *rtc_dev; | |
147 | + struct cdev char_dev; | |
148 | + struct mutex char_lock; | |
149 | + | |
150 | + unsigned long irq_data; | |
151 | + spinlock_t irq_lock; | |
152 | + wait_queue_head_t irq_queue; | |
153 | + struct fasync_struct *async_queue; | |
154 | + | |
155 | + struct rtc_task *irq_task; | |
156 | + spinlock_t irq_task_lock; | |
157 | + int irq_freq; | |
158 | +}; | |
159 | +#define to_rtc_device(d) container_of(d, struct rtc_device, class_dev) | |
160 | + | |
161 | +extern struct rtc_device *rtc_device_register(const char *name, | |
162 | + struct device *dev, | |
163 | + struct rtc_class_ops *ops, | |
164 | + struct module *owner); | |
165 | +extern void rtc_device_unregister(struct rtc_device *rdev); | |
166 | +extern int rtc_interface_register(struct class_interface *intf); | |
167 | + | |
168 | +extern int rtc_read_time(struct class_device *class_dev, struct rtc_time *tm); | |
169 | +extern int rtc_set_time(struct class_device *class_dev, struct rtc_time *tm); | |
170 | +extern int rtc_set_mmss(struct class_device *class_dev, unsigned long secs); | |
171 | +extern int rtc_read_alarm(struct class_device *class_dev, | |
172 | + struct rtc_wkalrm *alrm); | |
173 | +extern int rtc_set_alarm(struct class_device *class_dev, | |
174 | + struct rtc_wkalrm *alrm); | |
175 | +extern void rtc_update_irq(struct class_device *class_dev, | |
176 | + unsigned long num, unsigned long events); | |
177 | + | |
178 | +extern struct class_device *rtc_class_open(char *name); | |
179 | +extern void rtc_class_close(struct class_device *class_dev); | |
180 | + | |
181 | +extern int rtc_irq_register(struct class_device *class_dev, | |
182 | + struct rtc_task *task); | |
183 | +extern void rtc_irq_unregister(struct class_device *class_dev, | |
184 | + struct rtc_task *task); | |
185 | +extern int rtc_irq_set_state(struct class_device *class_dev, | |
186 | + struct rtc_task *task, int enabled); | |
187 | +extern int rtc_irq_set_freq(struct class_device *class_dev, | |
188 | + struct rtc_task *task, int freq); | |
102 | 189 | |
103 | 190 | typedef struct rtc_task { |
104 | 191 | void (*func)(void *private_data); |