Commit | Line | Data |
---|---|---|
46b402a0 AG |
1 | /* |
2 | * pps_gen_parport.c -- kernel parallel port PPS signal generator | |
3 | * | |
4 | * | |
5 | * Copyright (C) 2009 Alexander Gordeev <lasaine@lvk.cs.msu.su> | |
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 as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
20 | */ | |
21 | ||
22 | ||
23 | /* | |
24 | * TODO: | |
25 | * fix issues when realtime clock is adjusted in a leap | |
26 | */ | |
27 | ||
28 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
29 | ||
30 | #include <linux/kernel.h> | |
31 | #include <linux/module.h> | |
32 | #include <linux/init.h> | |
33 | #include <linux/time.h> | |
34 | #include <linux/hrtimer.h> | |
35 | #include <linux/parport.h> | |
36 | ||
37 | #define DRVDESC "parallel port PPS signal generator" | |
38 | ||
39 | #define SIGNAL 0 | |
40 | #define NO_SIGNAL PARPORT_CONTROL_STROBE | |
41 | ||
42 | /* module parameters */ | |
43 | ||
44 | #define SEND_DELAY_MAX 100000 | |
45 | ||
46 | static unsigned int send_delay = 30000; | |
47 | MODULE_PARM_DESC(delay, | |
48 | "Delay between setting and dropping the signal (ns)"); | |
49 | module_param_named(delay, send_delay, uint, 0); | |
50 | ||
51 | ||
52 | #define SAFETY_INTERVAL 3000 /* set the hrtimer earlier for safety (ns) */ | |
53 | ||
54 | /* internal per port structure */ | |
55 | struct pps_generator_pp { | |
56 | struct pardevice *pardev; /* parport device */ | |
57 | struct hrtimer timer; | |
58 | long port_write_time; /* calibrated port write time (ns) */ | |
59 | }; | |
60 | ||
61 | static struct pps_generator_pp device = { | |
62 | .pardev = NULL, | |
63 | }; | |
64 | ||
65 | static int attached; | |
66 | ||
67 | /* calibrated time between a hrtimer event and the reaction */ | |
68 | static long hrtimer_error = SAFETY_INTERVAL; | |
69 | ||
70 | /* the kernel hrtimer event */ | |
71 | static enum hrtimer_restart hrtimer_event(struct hrtimer *timer) | |
72 | { | |
73 | struct timespec expire_time, ts1, ts2, ts3, dts; | |
74 | struct pps_generator_pp *dev; | |
75 | struct parport *port; | |
76 | long lim, delta; | |
77 | unsigned long flags; | |
78 | ||
563558b2 AG |
79 | /* We have to disable interrupts here. The idea is to prevent |
80 | * other interrupts on the same processor to introduce random | |
81 | * lags while polling the clock. getnstimeofday() takes <1us on | |
82 | * most machines while other interrupt handlers can take much | |
83 | * more potentially. | |
84 | * | |
85 | * NB: approx time with blocked interrupts = | |
86 | * send_delay + 3 * SAFETY_INTERVAL | |
87 | */ | |
46b402a0 AG |
88 | local_irq_save(flags); |
89 | ||
90 | /* first of all we get the time stamp... */ | |
91 | getnstimeofday(&ts1); | |
92 | expire_time = ktime_to_timespec(hrtimer_get_softexpires(timer)); | |
93 | dev = container_of(timer, struct pps_generator_pp, timer); | |
94 | lim = NSEC_PER_SEC - send_delay - dev->port_write_time; | |
95 | ||
96 | /* check if we are late */ | |
97 | if (expire_time.tv_sec != ts1.tv_sec || ts1.tv_nsec > lim) { | |
98 | local_irq_restore(flags); | |
99 | pr_err("we are late this time %ld.%09ld\n", | |
100 | ts1.tv_sec, ts1.tv_nsec); | |
101 | goto done; | |
102 | } | |
103 | ||
104 | /* busy loop until the time is right for an assert edge */ | |
105 | do { | |
106 | getnstimeofday(&ts2); | |
107 | } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim); | |
108 | ||
109 | /* set the signal */ | |
110 | port = dev->pardev->port; | |
111 | port->ops->write_control(port, SIGNAL); | |
112 | ||
113 | /* busy loop until the time is right for a clear edge */ | |
114 | lim = NSEC_PER_SEC - dev->port_write_time; | |
115 | do { | |
116 | getnstimeofday(&ts2); | |
117 | } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim); | |
118 | ||
119 | /* unset the signal */ | |
120 | port->ops->write_control(port, NO_SIGNAL); | |
121 | ||
122 | getnstimeofday(&ts3); | |
123 | ||
124 | local_irq_restore(flags); | |
125 | ||
126 | /* update calibrated port write time */ | |
127 | dts = timespec_sub(ts3, ts2); | |
128 | dev->port_write_time = | |
129 | (dev->port_write_time + timespec_to_ns(&dts)) >> 1; | |
130 | ||
131 | done: | |
132 | /* update calibrated hrtimer error */ | |
133 | dts = timespec_sub(ts1, expire_time); | |
134 | delta = timespec_to_ns(&dts); | |
135 | /* If the new error value is bigger then the old, use the new | |
136 | * value, if not then slowly move towards the new value. This | |
137 | * way it should be safe in bad conditions and efficient in | |
138 | * good conditions. | |
139 | */ | |
140 | if (delta >= hrtimer_error) | |
141 | hrtimer_error = delta; | |
142 | else | |
143 | hrtimer_error = (3 * hrtimer_error + delta) >> 2; | |
144 | ||
145 | /* update the hrtimer expire time */ | |
146 | hrtimer_set_expires(timer, | |
147 | ktime_set(expire_time.tv_sec + 1, | |
148 | NSEC_PER_SEC - (send_delay + | |
149 | dev->port_write_time + SAFETY_INTERVAL + | |
150 | 2 * hrtimer_error))); | |
151 | ||
152 | return HRTIMER_RESTART; | |
153 | } | |
154 | ||
155 | /* calibrate port write time */ | |
156 | #define PORT_NTESTS_SHIFT 5 | |
157 | static void calibrate_port(struct pps_generator_pp *dev) | |
158 | { | |
159 | struct parport *port = dev->pardev->port; | |
160 | int i; | |
161 | long acc = 0; | |
162 | ||
163 | for (i = 0; i < (1 << PORT_NTESTS_SHIFT); i++) { | |
164 | struct timespec a, b; | |
165 | unsigned long irq_flags; | |
166 | ||
167 | local_irq_save(irq_flags); | |
168 | getnstimeofday(&a); | |
169 | port->ops->write_control(port, NO_SIGNAL); | |
170 | getnstimeofday(&b); | |
171 | local_irq_restore(irq_flags); | |
172 | ||
173 | b = timespec_sub(b, a); | |
174 | acc += timespec_to_ns(&b); | |
175 | } | |
176 | ||
177 | dev->port_write_time = acc >> PORT_NTESTS_SHIFT; | |
178 | pr_info("port write takes %ldns\n", dev->port_write_time); | |
179 | } | |
180 | ||
181 | static inline ktime_t next_intr_time(struct pps_generator_pp *dev) | |
182 | { | |
183 | struct timespec ts; | |
184 | ||
185 | getnstimeofday(&ts); | |
186 | ||
187 | return ktime_set(ts.tv_sec + | |
188 | ((ts.tv_nsec > 990 * NSEC_PER_MSEC) ? 1 : 0), | |
189 | NSEC_PER_SEC - (send_delay + | |
190 | dev->port_write_time + 3 * SAFETY_INTERVAL)); | |
191 | } | |
192 | ||
193 | static void parport_attach(struct parport *port) | |
194 | { | |
195 | if (attached) { | |
196 | /* we already have a port */ | |
197 | return; | |
198 | } | |
199 | ||
200 | device.pardev = parport_register_device(port, KBUILD_MODNAME, | |
4f542e3d | 201 | NULL, NULL, NULL, PARPORT_FLAG_EXCL, &device); |
46b402a0 AG |
202 | if (!device.pardev) { |
203 | pr_err("couldn't register with %s\n", port->name); | |
204 | return; | |
205 | } | |
206 | ||
207 | if (parport_claim_or_block(device.pardev) < 0) { | |
208 | pr_err("couldn't claim %s\n", port->name); | |
209 | goto err_unregister_dev; | |
210 | } | |
211 | ||
212 | pr_info("attached to %s\n", port->name); | |
213 | attached = 1; | |
214 | ||
215 | calibrate_port(&device); | |
216 | ||
217 | hrtimer_init(&device.timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); | |
218 | device.timer.function = hrtimer_event; | |
46b402a0 AG |
219 | hrtimer_start(&device.timer, next_intr_time(&device), HRTIMER_MODE_ABS); |
220 | ||
221 | return; | |
222 | ||
223 | err_unregister_dev: | |
224 | parport_unregister_device(device.pardev); | |
225 | } | |
226 | ||
227 | static void parport_detach(struct parport *port) | |
228 | { | |
229 | if (port->cad != device.pardev) | |
230 | return; /* not our port */ | |
231 | ||
232 | hrtimer_cancel(&device.timer); | |
233 | parport_release(device.pardev); | |
234 | parport_unregister_device(device.pardev); | |
235 | } | |
236 | ||
237 | static struct parport_driver pps_gen_parport_driver = { | |
238 | .name = KBUILD_MODNAME, | |
239 | .attach = parport_attach, | |
240 | .detach = parport_detach, | |
241 | }; | |
242 | ||
243 | /* module staff */ | |
244 | ||
245 | static int __init pps_gen_parport_init(void) | |
246 | { | |
247 | int ret; | |
248 | ||
249 | pr_info(DRVDESC "\n"); | |
250 | ||
251 | if (send_delay > SEND_DELAY_MAX) { | |
252 | pr_err("delay value should be not greater" | |
253 | " then %d\n", SEND_DELAY_MAX); | |
254 | return -EINVAL; | |
255 | } | |
256 | ||
257 | ret = parport_register_driver(&pps_gen_parport_driver); | |
258 | if (ret) { | |
259 | pr_err("unable to register with parport\n"); | |
260 | return ret; | |
261 | } | |
262 | ||
263 | return 0; | |
264 | } | |
265 | ||
266 | static void __exit pps_gen_parport_exit(void) | |
267 | { | |
268 | parport_unregister_driver(&pps_gen_parport_driver); | |
269 | pr_info("hrtimer avg error is %ldns\n", hrtimer_error); | |
270 | } | |
271 | ||
272 | module_init(pps_gen_parport_init); | |
273 | module_exit(pps_gen_parport_exit); | |
274 | ||
275 | MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>"); | |
276 | MODULE_DESCRIPTION(DRVDESC); | |
277 | MODULE_LICENSE("GPL"); |