aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/pps/pps.txt46
-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
6 files changed, 350 insertions, 1 deletions
diff --git a/Documentation/pps/pps.txt b/Documentation/pps/pps.txt
index 125f4ab48998..d35dcdd82ff6 100644
--- a/Documentation/pps/pps.txt
+++ b/Documentation/pps/pps.txt
@@ -170,3 +170,49 @@ and the run ppstest as follow:
170 170
171Please, note that to compile userland programs you need the file timepps.h 171Please, note that to compile userland programs you need the file timepps.h
172(see Documentation/pps/). 172(see Documentation/pps/).
173
174
175Generators
176----------
177
178Sometimes one needs to be able not only to catch PPS signals but to produce
179them also. For example, running a distributed simulation, which requires
180computers' clock to be synchronized very tightly. One way to do this is to
181invent some complicated hardware solutions but it may be neither necessary
182nor affordable. The cheap way is to load a PPS generator on one of the
183computers (master) and PPS clients on others (slaves), and use very simple
184cables to deliver signals using parallel ports, for example.
185
186Parallel port cable pinout:
187pin name master slave
1881 STROBE *------ *
1892 D0 * | *
1903 D1 * | *
1914 D2 * | *
1925 D3 * | *
1936 D4 * | *
1947 D5 * | *
1958 D6 * | *
1969 D7 * | *
19710 ACK * ------*
19811 BUSY * *
19912 PE * *
20013 SEL * *
20114 AUTOFD * *
20215 ERROR * *
20316 INIT * *
20417 SELIN * *
20518-25 GND *-----------*
206
207Please note that parallel port interrupt occurs only on high->low transition,
208so it is used for PPS assert edge. PPS clear edge can be determined only
209using polling in the interrupt handler which actually can be done way more
210precisely because interrupt handling delays can be quite big and random. So
211current parport PPS generator implementation (pps_gen_parport module) is
212geared towards using the clear edge for time synchronization.
213
214Clear edge polling is done with disabled interrupts so it's better to select
215delay between assert and clear edge as small as possible to reduce system
216latencies. But if it is too small slave won't be able to capture clear edge
217transition. The default of 30us should be good enough in most situations.
218The delay can be selected using 'delay' pps_gen_parport module parameter.
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");