aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/pps
diff options
context:
space:
mode:
authorAlexander Gordeev <lasaine@lvk.cs.msu.su>2011-01-12 20:00:58 -0500
committerLinus Torvalds <torvalds@linux-foundation.org>2011-01-13 11:03:21 -0500
commita10203c691eac287664f531b149ddc23056c2f61 (patch)
treec1fc9f1430aeb7d2b73bc0509f85d6477f5c3c26 /drivers/pps
parent717c033669ed3ceaee8df57d4562fafcc1a6267a (diff)
pps: add parallel port PPS client
Add parallel port PPS client. It uses a standard method for capturing timestamps for assert edge transitions: getting a timestamp soon after an interrupt has happened. This is not a very precise source of time information due to interrupt handling delays. However, timestamps for clear edge transitions are much more precise because the interrupt handler continuously polls hardware port until the transition is done. Hardware port operations require only about 1us so the maximum error should not exceed this value. This was my primary goal when developing this client. Clear edge capture could be disabled using clear_wait parameter. Signed-off-by: Alexander Gordeev <lasaine@lvk.cs.msu.su> Acked-by: Rodolfo Giometti <giometti@linux.it> 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/clients/Kconfig7
-rw-r--r--drivers/pps/clients/Makefile1
-rw-r--r--drivers/pps/clients/pps_parport.c258
3 files changed, 266 insertions, 0 deletions
diff --git a/drivers/pps/clients/Kconfig b/drivers/pps/clients/Kconfig
index 4e801bd7254f..8520a7f4dd62 100644
--- a/drivers/pps/clients/Kconfig
+++ b/drivers/pps/clients/Kconfig
@@ -22,4 +22,11 @@ config PPS_CLIENT_LDISC
22 If you say yes here you get support for a PPS source connected 22 If you say yes here you get support for a PPS source connected
23 with the CD (Carrier Detect) pin of your serial port. 23 with the CD (Carrier Detect) pin of your serial port.
24 24
25config PPS_CLIENT_PARPORT
26 tristate "Parallel port PPS client"
27 depends on PPS && PARPORT
28 help
29 If you say yes here you get support for a PPS source connected
30 with the interrupt pin of your parallel port.
31
25endif 32endif
diff --git a/drivers/pps/clients/Makefile b/drivers/pps/clients/Makefile
index 812c9b19b430..42517da07049 100644
--- a/drivers/pps/clients/Makefile
+++ b/drivers/pps/clients/Makefile
@@ -4,6 +4,7 @@
4 4
5obj-$(CONFIG_PPS_CLIENT_KTIMER) += pps-ktimer.o 5obj-$(CONFIG_PPS_CLIENT_KTIMER) += pps-ktimer.o
6obj-$(CONFIG_PPS_CLIENT_LDISC) += pps-ldisc.o 6obj-$(CONFIG_PPS_CLIENT_LDISC) += pps-ldisc.o
7obj-$(CONFIG_PPS_CLIENT_PARPORT) += pps_parport.o
7 8
8ifeq ($(CONFIG_PPS_DEBUG),y) 9ifeq ($(CONFIG_PPS_DEBUG),y)
9EXTRA_CFLAGS += -DDEBUG 10EXTRA_CFLAGS += -DDEBUG
diff --git a/drivers/pps/clients/pps_parport.c b/drivers/pps/clients/pps_parport.c
new file mode 100644
index 000000000000..32221efd9ca9
--- /dev/null
+++ b/drivers/pps/clients/pps_parport.c
@@ -0,0 +1,258 @@
1/*
2 * pps_parport.c -- kernel parallel port PPS client
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 * implement echo over SEL pin
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/irqnr.h>
34#include <linux/time.h>
35#include <linux/parport.h>
36#include <linux/pps_kernel.h>
37
38#define DRVDESC "parallel port PPS client"
39
40/* module parameters */
41
42#define CLEAR_WAIT_MAX 100
43#define CLEAR_WAIT_MAX_ERRORS 5
44
45static unsigned int clear_wait = 100;
46MODULE_PARM_DESC(clear_wait,
47 "Maximum number of port reads when polling for signal clear,"
48 " zero turns clear edge capture off entirely");
49module_param(clear_wait, uint, 0);
50
51
52/* internal per port structure */
53struct pps_client_pp {
54 struct pardevice *pardev; /* parport device */
55 struct pps_device *pps; /* PPS device */
56 unsigned int cw; /* port clear timeout */
57 unsigned int cw_err; /* number of timeouts */
58};
59
60static inline int signal_is_set(struct parport *port)
61{
62 return (port->ops->read_status(port) & PARPORT_STATUS_ACK) != 0;
63}
64
65/* parport interrupt handler */
66static void parport_irq(void *handle)
67{
68 struct pps_event_time ts_assert, ts_clear;
69 struct pps_client_pp *dev = handle;
70 struct parport *port = dev->pardev->port;
71 unsigned int i;
72 unsigned long flags;
73
74 /* first of all we get the time stamp... */
75 pps_get_ts(&ts_assert);
76
77 if (dev->cw == 0)
78 /* clear edge capture disabled */
79 goto out_assert;
80
81 /* try capture the clear edge */
82
83 /* We have to disable interrupts here. The idea is to prevent
84 * other interrupts on the same processor to introduce random
85 * lags while polling the port. Reading from IO port is known
86 * to take approximately 1us while other interrupt handlers can
87 * take much more potentially.
88 *
89 * Interrupts won't be disabled for a long time because the
90 * number of polls is limited by clear_wait parameter which is
91 * kept rather low. So it should never be an issue.
92 */
93 local_irq_save(flags);
94 /* check the signal (no signal means the pulse is lost this time) */
95 if (!signal_is_set(port)) {
96 local_irq_restore(flags);
97 dev_err(dev->pps->dev, "lost the signal\n");
98 goto out_assert;
99 }
100
101 /* poll the port until the signal is unset */
102 for (i = dev->cw; i; i--)
103 if (!signal_is_set(port)) {
104 pps_get_ts(&ts_clear);
105 local_irq_restore(flags);
106 dev->cw_err = 0;
107 goto out_both;
108 }
109 local_irq_restore(flags);
110
111 /* timeout */
112 dev->cw_err++;
113 if (dev->cw_err >= CLEAR_WAIT_MAX_ERRORS) {
114 dev_err(dev->pps->dev, "disabled clear edge capture after %d"
115 " timeouts\n", dev->cw_err);
116 dev->cw = 0;
117 dev->cw_err = 0;
118 }
119
120out_assert:
121 /* fire assert event */
122 pps_event(dev->pps, &ts_assert,
123 PPS_CAPTUREASSERT, NULL);
124 return;
125
126out_both:
127 /* fire assert event */
128 pps_event(dev->pps, &ts_assert,
129 PPS_CAPTUREASSERT, NULL);
130 /* fire clear event */
131 pps_event(dev->pps, &ts_clear,
132 PPS_CAPTURECLEAR, NULL);
133 return;
134}
135
136/* the PPS echo function */
137static void pps_echo(struct pps_device *pps, int event, void *data)
138{
139 dev_info(pps->dev, "echo %s %s\n",
140 event & PPS_CAPTUREASSERT ? "assert" : "",
141 event & PPS_CAPTURECLEAR ? "clear" : "");
142}
143
144static void parport_attach(struct parport *port)
145{
146 struct pps_client_pp *device;
147 struct pps_source_info info = {
148 .name = KBUILD_MODNAME,
149 .path = "",
150 .mode = PPS_CAPTUREBOTH | \
151 PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \
152 PPS_ECHOASSERT | PPS_ECHOCLEAR | \
153 PPS_CANWAIT | PPS_TSFMT_TSPEC,
154 .echo = pps_echo,
155 .owner = THIS_MODULE,
156 .dev = NULL
157 };
158
159 device = kzalloc(sizeof(struct pps_client_pp), GFP_KERNEL);
160 if (!device) {
161 pr_err("memory allocation failed, not attaching\n");
162 return;
163 }
164
165 device->pardev = parport_register_device(port, KBUILD_MODNAME,
166 NULL, NULL, parport_irq, 0, device);
167 if (!device->pardev) {
168 pr_err("couldn't register with %s\n", port->name);
169 goto err_free;
170 }
171
172 if (parport_claim_or_block(device->pardev) < 0) {
173 pr_err("couldn't claim %s\n", port->name);
174 goto err_unregister_dev;
175 }
176
177 device->pps = pps_register_source(&info,
178 PPS_CAPTUREBOTH | PPS_OFFSETASSERT | PPS_OFFSETCLEAR);
179 if (device->pps == NULL) {
180 pr_err("couldn't register PPS source\n");
181 goto err_release_dev;
182 }
183
184 device->cw = clear_wait;
185
186 port->ops->enable_irq(port);
187
188 pr_info("attached to %s\n", port->name);
189
190 return;
191
192err_release_dev:
193 parport_release(device->pardev);
194err_unregister_dev:
195 parport_unregister_device(device->pardev);
196err_free:
197 kfree(device);
198}
199
200static void parport_detach(struct parport *port)
201{
202 struct pardevice *pardev = port->cad;
203 struct pps_client_pp *device;
204
205 /* FIXME: oooh, this is ugly! */
206 if (strcmp(pardev->name, KBUILD_MODNAME))
207 /* not our port */
208 return;
209
210 device = pardev->private;
211
212 port->ops->disable_irq(port);
213 pps_unregister_source(device->pps);
214 parport_release(pardev);
215 parport_unregister_device(pardev);
216 kfree(device);
217}
218
219static struct parport_driver pps_parport_driver = {
220 .name = KBUILD_MODNAME,
221 .attach = parport_attach,
222 .detach = parport_detach,
223};
224
225/* module staff */
226
227static int __init pps_parport_init(void)
228{
229 int ret;
230
231 pr_info(DRVDESC "\n");
232
233 if (clear_wait > CLEAR_WAIT_MAX) {
234 pr_err("clear_wait value should be not greater"
235 " then %d\n", CLEAR_WAIT_MAX);
236 return -EINVAL;
237 }
238
239 ret = parport_register_driver(&pps_parport_driver);
240 if (ret) {
241 pr_err("unable to register with parport\n");
242 return ret;
243 }
244
245 return 0;
246}
247
248static void __exit pps_parport_exit(void)
249{
250 parport_unregister_driver(&pps_parport_driver);
251}
252
253module_init(pps_parport_init);
254module_exit(pps_parport_exit);
255
256MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");
257MODULE_DESCRIPTION(DRVDESC);
258MODULE_LICENSE("GPL");