aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pps
diff options
context:
space:
mode:
authorAlexander Gordeev <lasaine@lvk.cs.msu.su>2011-01-12 20:00:59 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2011-01-13 11:03:21 -0500
commit46b402a0e5e4b4d81b11c32dfb2312bf5828ecb5 (patch)
treef04bdc9a375a0f6c7447de58800dd2cd95be9ca5 /drivers/pps
parenta10203c691eac287664f531b149ddc23056c2f61 (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')
-rw-r--r--drivers/pps/Kconfig2
-rw-r--r--drivers/pps/Makefile2
-rw-r--r--drivers/pps/generators/Kconfig17
-rw-r--r--drivers/pps/generators/Makefile9
-rw-r--r--drivers/pps/generators/pps_gen_parport.c275
5 files changed, 304 insertions, 1 deletions
diff --git a/drivers/pps/Kconfig b/drivers/pps/Kconfig
index 0ad5ff38bfec..f0d3376b58ba 100644
--- a/drivers/pps/Kconfig
+++ b/drivers/pps/Kconfig
@@ -41,4 +41,6 @@ config NTP_PPS
41 41
42source drivers/pps/clients/Kconfig 42source drivers/pps/clients/Kconfig
43 43
44source drivers/pps/generators/Kconfig
45
44endmenu 46endmenu
diff --git a/drivers/pps/Makefile b/drivers/pps/Makefile
index 77c23457b739..4483eaadaddd 100644
--- a/drivers/pps/Makefile
+++ b/drivers/pps/Makefile
@@ -5,6 +5,6 @@
5pps_core-y := pps.o kapi.o sysfs.o 5pps_core-y := pps.o kapi.o sysfs.o
6pps_core-$(CONFIG_NTP_PPS) += kc.o 6pps_core-$(CONFIG_NTP_PPS) += kc.o
7obj-$(CONFIG_PPS) := pps_core.o 7obj-$(CONFIG_PPS) := pps_core.o
8obj-y += clients/ 8obj-y += clients/ generators/
9 9
10ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG 10ccflags-$(CONFIG_PPS_DEBUG) := -DDEBUG
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
5if PPS
6
7comment "PPS generators support"
8
9config 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
17endif
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
5obj-$(CONFIG_PPS_GENERATOR_PARPORT) += pps_gen_parport.o
6
7ifeq ($(CONFIG_PPS_DEBUG),y)
8EXTRA_CFLAGS += -DDEBUG
9endif
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
46static unsigned int send_delay = 30000;
47MODULE_PARM_DESC(delay,
48 "Delay between setting and dropping the signal (ns)");
49module_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 */
55struct 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
61static struct pps_generator_pp device = {
62 .pardev = NULL,
63};
64
65static int attached;
66
67/* calibrated time between a hrtimer event and the reaction */
68static long hrtimer_error = SAFETY_INTERVAL;
69
70/* the kernel hrtimer event */
71static 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
124done:
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
150static 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
174static 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
186static 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
221err_unregister_dev:
222 parport_unregister_device(device.pardev);
223}
224
225static 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
235static 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
243static 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
264static 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
270module_init(pps_gen_parport_init);
271module_exit(pps_gen_parport_exit);
272
273MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");
274MODULE_DESCRIPTION(DRVDESC);
275MODULE_LICENSE("GPL");