diff options
author | Alexander Gordeev <lasaine@lvk.cs.msu.su> | 2011-01-12 20:00:59 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-01-13 11:03:21 -0500 |
commit | 46b402a0e5e4b4d81b11c32dfb2312bf5828ecb5 (patch) | |
tree | f04bdc9a375a0f6c7447de58800dd2cd95be9ca5 /drivers/pps/generators | |
parent | a10203c691eac287664f531b149ddc23056c2f61 (diff) |
pps: add parallel port PPS signal generator
Add PPS signal generator which utilizes STROBE pin of a parallel port to
send PPS signals. It uses parport abstraction layer and hrtimers to
precisely control the signal.
[akpm@linux-foundation.org: fix build]
Signed-off-by: Alexander Gordeev <lasaine@lvk.cs.msu.su>
Acked-by: Rodolfo Giometti <giometti@linux.it>
Cc: john stultz <johnstul@us.ibm.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/pps/generators')
-rw-r--r-- | drivers/pps/generators/Kconfig | 17 | ||||
-rw-r--r-- | drivers/pps/generators/Makefile | 9 | ||||
-rw-r--r-- | drivers/pps/generators/pps_gen_parport.c | 275 |
3 files changed, 301 insertions, 0 deletions
diff --git a/drivers/pps/generators/Kconfig b/drivers/pps/generators/Kconfig new file mode 100644 index 000000000000..5fbd6148c804 --- /dev/null +++ b/drivers/pps/generators/Kconfig | |||
@@ -0,0 +1,17 @@ | |||
1 | # | ||
2 | # PPS generators configuration | ||
3 | # | ||
4 | |||
5 | if PPS | ||
6 | |||
7 | comment "PPS generators support" | ||
8 | |||
9 | config PPS_GENERATOR_PARPORT | ||
10 | tristate "Parallel port PPS signal generator" | ||
11 | depends on PARPORT != n && GENERIC_TIME | ||
12 | help | ||
13 | If you say yes here you get support for a PPS signal generator which | ||
14 | utilizes STROBE pin of a parallel port to send PPS signals. It uses | ||
15 | parport abstraction layer and hrtimers to precisely control the signal. | ||
16 | |||
17 | endif | ||
diff --git a/drivers/pps/generators/Makefile b/drivers/pps/generators/Makefile new file mode 100644 index 000000000000..303304a6b8ec --- /dev/null +++ b/drivers/pps/generators/Makefile | |||
@@ -0,0 +1,9 @@ | |||
1 | # | ||
2 | # Makefile for PPS generators. | ||
3 | # | ||
4 | |||
5 | obj-$(CONFIG_PPS_GENERATOR_PARPORT) += pps_gen_parport.o | ||
6 | |||
7 | ifeq ($(CONFIG_PPS_DEBUG),y) | ||
8 | EXTRA_CFLAGS += -DDEBUG | ||
9 | endif | ||
diff --git a/drivers/pps/generators/pps_gen_parport.c b/drivers/pps/generators/pps_gen_parport.c new file mode 100644 index 000000000000..a15fe25efd55 --- /dev/null +++ b/drivers/pps/generators/pps_gen_parport.c | |||
@@ -0,0 +1,275 @@ | |||
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 | |||
79 | /* NB: approx time with blocked interrupts = | ||
80 | send_delay + 3 * SAFETY_INTERVAL */ | ||
81 | local_irq_save(flags); | ||
82 | |||
83 | /* first of all we get the time stamp... */ | ||
84 | getnstimeofday(&ts1); | ||
85 | expire_time = ktime_to_timespec(hrtimer_get_softexpires(timer)); | ||
86 | dev = container_of(timer, struct pps_generator_pp, timer); | ||
87 | lim = NSEC_PER_SEC - send_delay - dev->port_write_time; | ||
88 | |||
89 | /* check if we are late */ | ||
90 | if (expire_time.tv_sec != ts1.tv_sec || ts1.tv_nsec > lim) { | ||
91 | local_irq_restore(flags); | ||
92 | pr_err("we are late this time %ld.%09ld\n", | ||
93 | ts1.tv_sec, ts1.tv_nsec); | ||
94 | goto done; | ||
95 | } | ||
96 | |||
97 | /* busy loop until the time is right for an assert edge */ | ||
98 | do { | ||
99 | getnstimeofday(&ts2); | ||
100 | } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim); | ||
101 | |||
102 | /* set the signal */ | ||
103 | port = dev->pardev->port; | ||
104 | port->ops->write_control(port, SIGNAL); | ||
105 | |||
106 | /* busy loop until the time is right for a clear edge */ | ||
107 | lim = NSEC_PER_SEC - dev->port_write_time; | ||
108 | do { | ||
109 | getnstimeofday(&ts2); | ||
110 | } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim); | ||
111 | |||
112 | /* unset the signal */ | ||
113 | port->ops->write_control(port, NO_SIGNAL); | ||
114 | |||
115 | getnstimeofday(&ts3); | ||
116 | |||
117 | local_irq_restore(flags); | ||
118 | |||
119 | /* update calibrated port write time */ | ||
120 | dts = timespec_sub(ts3, ts2); | ||
121 | dev->port_write_time = | ||
122 | (dev->port_write_time + timespec_to_ns(&dts)) >> 1; | ||
123 | |||
124 | done: | ||
125 | /* update calibrated hrtimer error */ | ||
126 | dts = timespec_sub(ts1, expire_time); | ||
127 | delta = timespec_to_ns(&dts); | ||
128 | /* If the new error value is bigger then the old, use the new | ||
129 | * value, if not then slowly move towards the new value. This | ||
130 | * way it should be safe in bad conditions and efficient in | ||
131 | * good conditions. | ||
132 | */ | ||
133 | if (delta >= hrtimer_error) | ||
134 | hrtimer_error = delta; | ||
135 | else | ||
136 | hrtimer_error = (3 * hrtimer_error + delta) >> 2; | ||
137 | |||
138 | /* update the hrtimer expire time */ | ||
139 | hrtimer_set_expires(timer, | ||
140 | ktime_set(expire_time.tv_sec + 1, | ||
141 | NSEC_PER_SEC - (send_delay + | ||
142 | dev->port_write_time + SAFETY_INTERVAL + | ||
143 | 2 * hrtimer_error))); | ||
144 | |||
145 | return HRTIMER_RESTART; | ||
146 | } | ||
147 | |||
148 | /* calibrate port write time */ | ||
149 | #define PORT_NTESTS_SHIFT 5 | ||
150 | static void calibrate_port(struct pps_generator_pp *dev) | ||
151 | { | ||
152 | struct parport *port = dev->pardev->port; | ||
153 | int i; | ||
154 | long acc = 0; | ||
155 | |||
156 | for (i = 0; i < (1 << PORT_NTESTS_SHIFT); i++) { | ||
157 | struct timespec a, b; | ||
158 | unsigned long irq_flags; | ||
159 | |||
160 | local_irq_save(irq_flags); | ||
161 | getnstimeofday(&a); | ||
162 | port->ops->write_control(port, NO_SIGNAL); | ||
163 | getnstimeofday(&b); | ||
164 | local_irq_restore(irq_flags); | ||
165 | |||
166 | b = timespec_sub(b, a); | ||
167 | acc += timespec_to_ns(&b); | ||
168 | } | ||
169 | |||
170 | dev->port_write_time = acc >> PORT_NTESTS_SHIFT; | ||
171 | pr_info("port write takes %ldns\n", dev->port_write_time); | ||
172 | } | ||
173 | |||
174 | static inline ktime_t next_intr_time(struct pps_generator_pp *dev) | ||
175 | { | ||
176 | struct timespec ts; | ||
177 | |||
178 | getnstimeofday(&ts); | ||
179 | |||
180 | return ktime_set(ts.tv_sec + | ||
181 | ((ts.tv_nsec > 990 * NSEC_PER_MSEC) ? 1 : 0), | ||
182 | NSEC_PER_SEC - (send_delay + | ||
183 | dev->port_write_time + 3 * SAFETY_INTERVAL)); | ||
184 | } | ||
185 | |||
186 | static void parport_attach(struct parport *port) | ||
187 | { | ||
188 | if (attached) { | ||
189 | /* we already have a port */ | ||
190 | return; | ||
191 | } | ||
192 | |||
193 | device.pardev = parport_register_device(port, KBUILD_MODNAME, | ||
194 | NULL, NULL, NULL, 0, &device); | ||
195 | if (!device.pardev) { | ||
196 | pr_err("couldn't register with %s\n", port->name); | ||
197 | return; | ||
198 | } | ||
199 | |||
200 | if (parport_claim_or_block(device.pardev) < 0) { | ||
201 | pr_err("couldn't claim %s\n", port->name); | ||
202 | goto err_unregister_dev; | ||
203 | } | ||
204 | |||
205 | pr_info("attached to %s\n", port->name); | ||
206 | attached = 1; | ||
207 | |||
208 | calibrate_port(&device); | ||
209 | |||
210 | hrtimer_init(&device.timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); | ||
211 | device.timer.function = hrtimer_event; | ||
212 | #ifdef CONFIG_PREEMPT_RT | ||
213 | /* hrtimer interrupt will run in the interrupt context with this */ | ||
214 | device.timer.irqsafe = 1; | ||
215 | #endif | ||
216 | |||
217 | hrtimer_start(&device.timer, next_intr_time(&device), HRTIMER_MODE_ABS); | ||
218 | |||
219 | return; | ||
220 | |||
221 | err_unregister_dev: | ||
222 | parport_unregister_device(device.pardev); | ||
223 | } | ||
224 | |||
225 | static void parport_detach(struct parport *port) | ||
226 | { | ||
227 | if (port->cad != device.pardev) | ||
228 | return; /* not our port */ | ||
229 | |||
230 | hrtimer_cancel(&device.timer); | ||
231 | parport_release(device.pardev); | ||
232 | parport_unregister_device(device.pardev); | ||
233 | } | ||
234 | |||
235 | static struct parport_driver pps_gen_parport_driver = { | ||
236 | .name = KBUILD_MODNAME, | ||
237 | .attach = parport_attach, | ||
238 | .detach = parport_detach, | ||
239 | }; | ||
240 | |||
241 | /* module staff */ | ||
242 | |||
243 | static int __init pps_gen_parport_init(void) | ||
244 | { | ||
245 | int ret; | ||
246 | |||
247 | pr_info(DRVDESC "\n"); | ||
248 | |||
249 | if (send_delay > SEND_DELAY_MAX) { | ||
250 | pr_err("delay value should be not greater" | ||
251 | " then %d\n", SEND_DELAY_MAX); | ||
252 | return -EINVAL; | ||
253 | } | ||
254 | |||
255 | ret = parport_register_driver(&pps_gen_parport_driver); | ||
256 | if (ret) { | ||
257 | pr_err("unable to register with parport\n"); | ||
258 | return ret; | ||
259 | } | ||
260 | |||
261 | return 0; | ||
262 | } | ||
263 | |||
264 | static void __exit pps_gen_parport_exit(void) | ||
265 | { | ||
266 | parport_unregister_driver(&pps_gen_parport_driver); | ||
267 | pr_info("hrtimer avg error is %ldns\n", hrtimer_error); | ||
268 | } | ||
269 | |||
270 | module_init(pps_gen_parport_init); | ||
271 | module_exit(pps_gen_parport_exit); | ||
272 | |||
273 | MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>"); | ||
274 | MODULE_DESCRIPTION(DRVDESC); | ||
275 | MODULE_LICENSE("GPL"); | ||