linux/drivers/pps/generators/pps_gen_parport.c
<<
>>
Prefs
   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        /* We have to disable interrupts here. The idea is to prevent
  80         * other interrupts on the same processor to introduce random
  81         * lags while polling the clock. getnstimeofday() takes <1us on
  82         * most machines while other interrupt handlers can take much
  83         * more potentially.
  84         *
  85         * NB: approx time with blocked interrupts =
  86         * send_delay + 3 * SAFETY_INTERVAL
  87         */
  88        local_irq_save(flags);
  89
  90        /* first of all we get the time stamp... */
  91        getnstimeofday(&ts1);
  92        expire_time = ktime_to_timespec(hrtimer_get_softexpires(timer));
  93        dev = container_of(timer, struct pps_generator_pp, timer);
  94        lim = NSEC_PER_SEC - send_delay - dev->port_write_time;
  95
  96        /* check if we are late */
  97        if (expire_time.tv_sec != ts1.tv_sec || ts1.tv_nsec > lim) {
  98                local_irq_restore(flags);
  99                pr_err("we are late this time %ld.%09ld\n",
 100                                ts1.tv_sec, ts1.tv_nsec);
 101                goto done;
 102        }
 103
 104        /* busy loop until the time is right for an assert edge */
 105        do {
 106                getnstimeofday(&ts2);
 107        } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim);
 108
 109        /* set the signal */
 110        port = dev->pardev->port;
 111        port->ops->write_control(port, SIGNAL);
 112
 113        /* busy loop until the time is right for a clear edge */
 114        lim = NSEC_PER_SEC - dev->port_write_time;
 115        do {
 116                getnstimeofday(&ts2);
 117        } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim);
 118
 119        /* unset the signal */
 120        port->ops->write_control(port, NO_SIGNAL);
 121
 122        getnstimeofday(&ts3);
 123
 124        local_irq_restore(flags);
 125
 126        /* update calibrated port write time */
 127        dts = timespec_sub(ts3, ts2);
 128        dev->port_write_time =
 129                (dev->port_write_time + timespec_to_ns(&dts)) >> 1;
 130
 131done:
 132        /* update calibrated hrtimer error */
 133        dts = timespec_sub(ts1, expire_time);
 134        delta = timespec_to_ns(&dts);
 135        /* If the new error value is bigger then the old, use the new
 136         * value, if not then slowly move towards the new value. This
 137         * way it should be safe in bad conditions and efficient in
 138         * good conditions.
 139         */
 140        if (delta >= hrtimer_error)
 141                hrtimer_error = delta;
 142        else
 143                hrtimer_error = (3 * hrtimer_error + delta) >> 2;
 144
 145        /* update the hrtimer expire time */
 146        hrtimer_set_expires(timer,
 147                        ktime_set(expire_time.tv_sec + 1,
 148                                NSEC_PER_SEC - (send_delay +
 149                                dev->port_write_time + SAFETY_INTERVAL +
 150                                2 * hrtimer_error)));
 151
 152        return HRTIMER_RESTART;
 153}
 154
 155/* calibrate port write time */
 156#define PORT_NTESTS_SHIFT       5
 157static void calibrate_port(struct pps_generator_pp *dev)
 158{
 159        struct parport *port = dev->pardev->port;
 160        int i;
 161        long acc = 0;
 162
 163        for (i = 0; i < (1 << PORT_NTESTS_SHIFT); i++) {
 164                struct timespec a, b;
 165                unsigned long irq_flags;
 166
 167                local_irq_save(irq_flags);
 168                getnstimeofday(&a);
 169                port->ops->write_control(port, NO_SIGNAL);
 170                getnstimeofday(&b);
 171                local_irq_restore(irq_flags);
 172
 173                b = timespec_sub(b, a);
 174                acc += timespec_to_ns(&b);
 175        }
 176
 177        dev->port_write_time = acc >> PORT_NTESTS_SHIFT;
 178        pr_info("port write takes %ldns\n", dev->port_write_time);
 179}
 180
 181static inline ktime_t next_intr_time(struct pps_generator_pp *dev)
 182{
 183        struct timespec ts;
 184
 185        getnstimeofday(&ts);
 186
 187        return ktime_set(ts.tv_sec +
 188                        ((ts.tv_nsec > 990 * NSEC_PER_MSEC) ? 1 : 0),
 189                        NSEC_PER_SEC - (send_delay +
 190                        dev->port_write_time + 3 * SAFETY_INTERVAL));
 191}
 192
 193static void parport_attach(struct parport *port)
 194{
 195        if (attached) {
 196                /* we already have a port */
 197                return;
 198        }
 199
 200        device.pardev = parport_register_device(port, KBUILD_MODNAME,
 201                        NULL, NULL, NULL, PARPORT_FLAG_EXCL, &device);
 202        if (!device.pardev) {
 203                pr_err("couldn't register with %s\n", port->name);
 204                return;
 205        }
 206
 207        if (parport_claim_or_block(device.pardev) < 0) {
 208                pr_err("couldn't claim %s\n", port->name);
 209                goto err_unregister_dev;
 210        }
 211
 212        pr_info("attached to %s\n", port->name);
 213        attached = 1;
 214
 215        calibrate_port(&device);
 216
 217        hrtimer_init(&device.timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
 218        device.timer.function = hrtimer_event;
 219        hrtimer_start(&device.timer, next_intr_time(&device), HRTIMER_MODE_ABS);
 220
 221        return;
 222
 223err_unregister_dev:
 224        parport_unregister_device(device.pardev);
 225}
 226
 227static void parport_detach(struct parport *port)
 228{
 229        if (port->cad != device.pardev)
 230                return; /* not our port */
 231
 232        hrtimer_cancel(&device.timer);
 233        parport_release(device.pardev);
 234        parport_unregister_device(device.pardev);
 235}
 236
 237static struct parport_driver pps_gen_parport_driver = {
 238        .name = KBUILD_MODNAME,
 239        .attach = parport_attach,
 240        .detach = parport_detach,
 241};
 242
 243/* module staff */
 244
 245static int __init pps_gen_parport_init(void)
 246{
 247        int ret;
 248
 249        pr_info(DRVDESC "\n");
 250
 251        if (send_delay > SEND_DELAY_MAX) {
 252                pr_err("delay value should be not greater"
 253                                " then %d\n", SEND_DELAY_MAX);
 254                return -EINVAL;
 255        }
 256
 257        ret = parport_register_driver(&pps_gen_parport_driver);
 258        if (ret) {
 259                pr_err("unable to register with parport\n");
 260                return ret;
 261        }
 262
 263        return  0;
 264}
 265
 266static void __exit pps_gen_parport_exit(void)
 267{
 268        parport_unregister_driver(&pps_gen_parport_driver);
 269        pr_info("hrtimer avg error is %ldns\n", hrtimer_error);
 270}
 271
 272module_init(pps_gen_parport_init);
 273module_exit(pps_gen_parport_exit);
 274
 275MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");
 276MODULE_DESCRIPTION(DRVDESC);
 277MODULE_LICENSE("GPL");
 278