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"); | ||
