Commit | Line | Data |
---|---|---|
0c86edc0 AZ |
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> | |
5a0e3ad6 | 18 | #include <linux/slab.h> |
6610e089 | 19 | #include <linux/workqueue.h> |
0c86edc0 | 20 | |
5726fb20 DB |
21 | #include "rtc-core.h" |
22 | ||
23 | ||
0c86edc0 AZ |
24 | static DEFINE_IDR(rtc_idr); |
25 | static DEFINE_MUTEX(idr_lock); | |
26 | struct class *rtc_class; | |
27 | ||
cd966209 | 28 | static void rtc_device_release(struct device *dev) |
0c86edc0 | 29 | { |
cd966209 | 30 | struct rtc_device *rtc = to_rtc_device(dev); |
0c86edc0 AZ |
31 | mutex_lock(&idr_lock); |
32 | idr_remove(&rtc_idr, rtc->id); | |
33 | mutex_unlock(&idr_lock); | |
34 | kfree(rtc); | |
35 | } | |
36 | ||
7ca1d488 DB |
37 | #if defined(CONFIG_PM) && defined(CONFIG_RTC_HCTOSYS_DEVICE) |
38 | ||
39 | /* | |
40 | * On suspend(), measure the delta between one RTC and the | |
41 | * system's wall clock; restore it on resume(). | |
42 | */ | |
43 | ||
3dcad5ff JS |
44 | static struct timespec old_rtc, old_system, old_delta; |
45 | ||
7ca1d488 DB |
46 | |
47 | static int rtc_suspend(struct device *dev, pm_message_t mesg) | |
48 | { | |
49 | struct rtc_device *rtc = to_rtc_device(dev); | |
50 | struct rtc_time tm; | |
3dcad5ff | 51 | struct timespec delta, delta_delta; |
d4afc76c | 52 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
7ca1d488 DB |
53 | return 0; |
54 | ||
3dcad5ff | 55 | /* snapshot the current RTC and system time at suspend*/ |
7ca1d488 | 56 | rtc_read_time(rtc, &tm); |
3dcad5ff JS |
57 | getnstimeofday(&old_system); |
58 | rtc_tm_to_time(&tm, &old_rtc.tv_sec); | |
59 | ||
60 | ||
61 | /* | |
62 | * To avoid drift caused by repeated suspend/resumes, | |
63 | * which each can add ~1 second drift error, | |
64 | * try to compensate so the difference in system time | |
65 | * and rtc time stays close to constant. | |
66 | */ | |
67 | delta = timespec_sub(old_system, old_rtc); | |
68 | delta_delta = timespec_sub(delta, old_delta); | |
69 | if (abs(delta_delta.tv_sec) >= 2) { | |
70 | /* | |
71 | * if delta_delta is too large, assume time correction | |
72 | * has occured and set old_delta to the current delta. | |
73 | */ | |
74 | old_delta = delta; | |
75 | } else { | |
76 | /* Otherwise try to adjust old_system to compensate */ | |
77 | old_system = timespec_sub(old_system, delta_delta); | |
78 | } | |
7ca1d488 | 79 | |
7ca1d488 DB |
80 | return 0; |
81 | } | |
82 | ||
83 | static int rtc_resume(struct device *dev) | |
84 | { | |
85 | struct rtc_device *rtc = to_rtc_device(dev); | |
86 | struct rtc_time tm; | |
3dcad5ff JS |
87 | struct timespec new_system, new_rtc; |
88 | struct timespec sleep_time; | |
7ca1d488 | 89 | |
d4afc76c | 90 | if (strcmp(dev_name(&rtc->dev), CONFIG_RTC_HCTOSYS_DEVICE) != 0) |
7ca1d488 DB |
91 | return 0; |
92 | ||
3dcad5ff JS |
93 | /* snapshot the current rtc and system time at resume */ |
94 | getnstimeofday(&new_system); | |
7ca1d488 DB |
95 | rtc_read_time(rtc, &tm); |
96 | if (rtc_valid_tm(&tm) != 0) { | |
d4afc76c | 97 | pr_debug("%s: bogus resume time\n", dev_name(&rtc->dev)); |
7ca1d488 DB |
98 | return 0; |
99 | } | |
3dcad5ff JS |
100 | rtc_tm_to_time(&tm, &new_rtc.tv_sec); |
101 | new_rtc.tv_nsec = 0; | |
102 | ||
103 | if (new_rtc.tv_sec <= old_rtc.tv_sec) { | |
104 | if (new_rtc.tv_sec < old_rtc.tv_sec) | |
d4afc76c | 105 | pr_debug("%s: time travel!\n", dev_name(&rtc->dev)); |
7ca1d488 DB |
106 | return 0; |
107 | } | |
108 | ||
3dcad5ff JS |
109 | /* calculate the RTC time delta (sleep time)*/ |
110 | sleep_time = timespec_sub(new_rtc, old_rtc); | |
111 | ||
112 | /* | |
113 | * Since these RTC suspend/resume handlers are not called | |
114 | * at the very end of suspend or the start of resume, | |
115 | * some run-time may pass on either sides of the sleep time | |
116 | * so subtract kernel run-time between rtc_suspend to rtc_resume | |
117 | * to keep things accurate. | |
118 | */ | |
119 | sleep_time = timespec_sub(sleep_time, | |
120 | timespec_sub(new_system, old_system)); | |
7ca1d488 | 121 | |
3dcad5ff | 122 | timekeeping_inject_sleeptime(&sleep_time); |
7ca1d488 DB |
123 | return 0; |
124 | } | |
125 | ||
126 | #else | |
127 | #define rtc_suspend NULL | |
128 | #define rtc_resume NULL | |
129 | #endif | |
130 | ||
131 | ||
0c86edc0 AZ |
132 | /** |
133 | * rtc_device_register - register w/ RTC class | |
134 | * @dev: the device to register | |
135 | * | |
136 | * rtc_device_unregister() must be called when the class device is no | |
137 | * longer needed. | |
138 | * | |
139 | * Returns the pointer to the new struct class device. | |
140 | */ | |
141 | struct rtc_device *rtc_device_register(const char *name, struct device *dev, | |
ff8371ac | 142 | const struct rtc_class_ops *ops, |
0c86edc0 AZ |
143 | struct module *owner) |
144 | { | |
145 | struct rtc_device *rtc; | |
f44f7f96 | 146 | struct rtc_wkalrm alrm; |
0c86edc0 AZ |
147 | int id, err; |
148 | ||
149 | if (idr_pre_get(&rtc_idr, GFP_KERNEL) == 0) { | |
150 | err = -ENOMEM; | |
151 | goto exit; | |
152 | } | |
153 | ||
154 | ||
155 | mutex_lock(&idr_lock); | |
156 | err = idr_get_new(&rtc_idr, NULL, &id); | |
157 | mutex_unlock(&idr_lock); | |
158 | ||
159 | if (err < 0) | |
160 | goto exit; | |
161 | ||
162 | id = id & MAX_ID_MASK; | |
163 | ||
164 | rtc = kzalloc(sizeof(struct rtc_device), GFP_KERNEL); | |
165 | if (rtc == NULL) { | |
166 | err = -ENOMEM; | |
167 | goto exit_idr; | |
168 | } | |
169 | ||
170 | rtc->id = id; | |
171 | rtc->ops = ops; | |
172 | rtc->owner = owner; | |
83a06bf5 | 173 | rtc->irq_freq = 1; |
110d693d | 174 | rtc->max_user_freq = 64; |
cd966209 DB |
175 | rtc->dev.parent = dev; |
176 | rtc->dev.class = rtc_class; | |
177 | rtc->dev.release = rtc_device_release; | |
0c86edc0 AZ |
178 | |
179 | mutex_init(&rtc->ops_lock); | |
180 | spin_lock_init(&rtc->irq_lock); | |
181 | spin_lock_init(&rtc->irq_task_lock); | |
d691eb90 | 182 | init_waitqueue_head(&rtc->irq_queue); |
0c86edc0 | 183 | |
6610e089 JS |
184 | /* Init timerqueue */ |
185 | timerqueue_init_head(&rtc->timerqueue); | |
96c8f06a | 186 | INIT_WORK(&rtc->irqwork, rtc_timer_do_work); |
6610e089 | 187 | /* Init aie timer */ |
96c8f06a | 188 | rtc_timer_init(&rtc->aie_timer, rtc_aie_update_irq, (void *)rtc); |
6610e089 | 189 | /* Init uie timer */ |
96c8f06a | 190 | rtc_timer_init(&rtc->uie_rtctimer, rtc_uie_update_irq, (void *)rtc); |
6610e089 JS |
191 | /* Init pie timer */ |
192 | hrtimer_init(&rtc->pie_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); | |
193 | rtc->pie_timer.function = rtc_pie_update_irq; | |
194 | rtc->pie_enabled = 0; | |
195 | ||
f44f7f96 JS |
196 | /* Check to see if there is an ALARM already set in hw */ |
197 | err = __rtc_read_alarm(rtc, &alrm); | |
198 | ||
199 | if (!err && !rtc_valid_tm(&alrm.time)) | |
f6d5b331 | 200 | rtc_initialize_alarm(rtc, &alrm); |
f44f7f96 | 201 | |
0c86edc0 | 202 | strlcpy(rtc->name, name, RTC_DEVICE_NAME_SIZE); |
d4afc76c | 203 | dev_set_name(&rtc->dev, "rtc%d", id); |
0c86edc0 | 204 | |
cb3a58d2 DB |
205 | rtc_dev_prepare(rtc); |
206 | ||
cd966209 | 207 | err = device_register(&rtc->dev); |
59cca865 VK |
208 | if (err) { |
209 | put_device(&rtc->dev); | |
0c86edc0 | 210 | goto exit_kfree; |
59cca865 | 211 | } |
0c86edc0 | 212 | |
5726fb20 | 213 | rtc_dev_add_device(rtc); |
446ecbd9 | 214 | rtc_sysfs_add_device(rtc); |
7d9f99ec | 215 | rtc_proc_add_device(rtc); |
5726fb20 | 216 | |
0c86edc0 | 217 | dev_info(dev, "rtc core: registered %s as %s\n", |
d4afc76c | 218 | rtc->name, dev_name(&rtc->dev)); |
0c86edc0 AZ |
219 | |
220 | return rtc; | |
221 | ||
222 | exit_kfree: | |
223 | kfree(rtc); | |
224 | ||
225 | exit_idr: | |
6ac12dfe | 226 | mutex_lock(&idr_lock); |
0c86edc0 | 227 | idr_remove(&rtc_idr, id); |
6ac12dfe | 228 | mutex_unlock(&idr_lock); |
0c86edc0 AZ |
229 | |
230 | exit: | |
d1d65b77 AZ |
231 | dev_err(dev, "rtc core: unable to register %s, err = %d\n", |
232 | name, err); | |
0c86edc0 AZ |
233 | return ERR_PTR(err); |
234 | } | |
235 | EXPORT_SYMBOL_GPL(rtc_device_register); | |
236 | ||
237 | ||
238 | /** | |
239 | * rtc_device_unregister - removes the previously registered RTC class device | |
240 | * | |
241 | * @rtc: the RTC class device to destroy | |
242 | */ | |
243 | void rtc_device_unregister(struct rtc_device *rtc) | |
244 | { | |
cd966209 | 245 | if (get_device(&rtc->dev) != NULL) { |
e109ebd1 DB |
246 | mutex_lock(&rtc->ops_lock); |
247 | /* remove innards of this RTC, then disable it, before | |
248 | * letting any rtc_class_open() users access it again | |
249 | */ | |
446ecbd9 | 250 | rtc_sysfs_del_device(rtc); |
5726fb20 | 251 | rtc_dev_del_device(rtc); |
7d9f99ec | 252 | rtc_proc_del_device(rtc); |
cd966209 | 253 | device_unregister(&rtc->dev); |
e109ebd1 DB |
254 | rtc->ops = NULL; |
255 | mutex_unlock(&rtc->ops_lock); | |
cd966209 | 256 | put_device(&rtc->dev); |
e109ebd1 | 257 | } |
0c86edc0 AZ |
258 | } |
259 | EXPORT_SYMBOL_GPL(rtc_device_unregister); | |
260 | ||
0c86edc0 AZ |
261 | static int __init rtc_init(void) |
262 | { | |
263 | rtc_class = class_create(THIS_MODULE, "rtc"); | |
264 | if (IS_ERR(rtc_class)) { | |
265 | printk(KERN_ERR "%s: couldn't create class\n", __FILE__); | |
266 | return PTR_ERR(rtc_class); | |
267 | } | |
7ca1d488 DB |
268 | rtc_class->suspend = rtc_suspend; |
269 | rtc_class->resume = rtc_resume; | |
5726fb20 | 270 | rtc_dev_init(); |
446ecbd9 | 271 | rtc_sysfs_init(rtc_class); |
0c86edc0 AZ |
272 | return 0; |
273 | } | |
274 | ||
275 | static void __exit rtc_exit(void) | |
276 | { | |
5726fb20 | 277 | rtc_dev_exit(); |
0c86edc0 | 278 | class_destroy(rtc_class); |
2a7a06a0 | 279 | idr_destroy(&rtc_idr); |
0c86edc0 AZ |
280 | } |
281 | ||
818a8674 | 282 | subsys_initcall(rtc_init); |
0c86edc0 AZ |
283 | module_exit(rtc_exit); |
284 | ||
818a8674 | 285 | MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); |
0c86edc0 AZ |
286 | MODULE_DESCRIPTION("RTC class support"); |
287 | MODULE_LICENSE("GPL"); |