linux/drivers/pps/generators/pps_gen_parport.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * pps_gen_parport.c -- kernel parallel port PPS signal generator
   4 *
   5 * Copyright (C) 2009   Alexander Gordeev <lasaine@lvk.cs.msu.su>
   6 */
   7
   8
   9/*
  10 * TODO:
  11 * fix issues when realtime clock is adjusted in a leap
  12 */
  13
  14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
  15
  16#include <linux/kernel.h>
  17#include <linux/module.h>
  18#include <linux/init.h>
  19#include <linux/time.h>
  20#include <linux/hrtimer.h>
  21#include <linux/parport.h>
  22
  23#define DRVDESC "parallel port PPS signal generator"
  24
  25#define SIGNAL          0
  26#define NO_SIGNAL       PARPORT_CONTROL_STROBE
  27
  28/* module parameters */
  29
  30#define SEND_DELAY_MAX          100000
  31
  32static unsigned int send_delay = 30000;
  33MODULE_PARM_DESC(delay,
  34        "Delay between setting and dropping the signal (ns)");
  35module_param_named(delay, send_delay, uint, 0);
  36
  37
  38#define SAFETY_INTERVAL 3000    /* set the hrtimer earlier for safety (ns) */
  39
  40/* internal per port structure */
  41struct pps_generator_pp {
  42        struct pardevice *pardev;       /* parport device */
  43        struct hrtimer timer;
  44        long port_write_time;           /* calibrated port write time (ns) */
  45};
  46
  47static struct pps_generator_pp device = {
  48        .pardev = NULL,
  49};
  50
  51static int attached;
  52
  53/* calibrated time between a hrtimer event and the reaction */
  54static long hrtimer_error = SAFETY_INTERVAL;
  55
  56/* the kernel hrtimer event */
  57static enum hrtimer_restart hrtimer_event(struct hrtimer *timer)
  58{
  59        struct timespec64 expire_time, ts1, ts2, ts3, dts;
  60        struct pps_generator_pp *dev;
  61        struct parport *port;
  62        long lim, delta;
  63        unsigned long flags;
  64
  65        /* We have to disable interrupts here. The idea is to prevent
  66         * other interrupts on the same processor to introduce random
  67         * lags while polling the clock. ktime_get_real_ts64() takes <1us on
  68         * most machines while other interrupt handlers can take much
  69         * more potentially.
  70         *
  71         * NB: approx time with blocked interrupts =
  72         * send_delay + 3 * SAFETY_INTERVAL
  73         */
  74        local_irq_save(flags);
  75
  76        /* first of all we get the time stamp... */
  77        ktime_get_real_ts64(&ts1);
  78        expire_time = ktime_to_timespec64(hrtimer_get_softexpires(timer));
  79        dev = container_of(timer, struct pps_generator_pp, timer);
  80        lim = NSEC_PER_SEC - send_delay - dev->port_write_time;
  81
  82        /* check if we are late */
  83        if (expire_time.tv_sec != ts1.tv_sec || ts1.tv_nsec > lim) {
  84                local_irq_restore(flags);
  85                pr_err("we are late this time %lld.%09ld\n",
  86                                (s64)ts1.tv_sec, ts1.tv_nsec);
  87                goto done;
  88        }
  89
  90        /* busy loop until the time is right for an assert edge */
  91        do {
  92                ktime_get_real_ts64(&ts2);
  93        } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim);
  94
  95        /* set the signal */
  96        port = dev->pardev->port;
  97        port->ops->write_control(port, SIGNAL);
  98
  99        /* busy loop until the time is right for a clear edge */
 100        lim = NSEC_PER_SEC - dev->port_write_time;
 101        do {
 102                ktime_get_real_ts64(&ts2);
 103        } while (expire_time.tv_sec == ts2.tv_sec && ts2.tv_nsec < lim);
 104
 105        /* unset the signal */
 106        port->ops->write_control(port, NO_SIGNAL);
 107
 108        ktime_get_real_ts64(&ts3);
 109
 110        local_irq_restore(flags);
 111
 112        /* update calibrated port write time */
 113        dts = timespec64_sub(ts3, ts2);
 114        dev->port_write_time =
 115                (dev->port_write_time + timespec64_to_ns(&dts)) >> 1;
 116
 117done:
 118        /* update calibrated hrtimer error */
 119        dts = timespec64_sub(ts1, expire_time);
 120        delta = timespec64_to_ns(&dts);
 121        /* If the new error value is bigger then the old, use the new
 122         * value, if not then slowly move towards the new value. This
 123         * way it should be safe in bad conditions and efficient in
 124         * good conditions.
 125         */
 126        if (delta >= hrtimer_error)
 127                hrtimer_error = delta;
 128        else
 129                hrtimer_error = (3 * hrtimer_error + delta) >> 2;
 130
 131        /* update the hrtimer expire time */
 132        hrtimer_set_expires(timer,
 133                        ktime_set(expire_time.tv_sec + 1,
 134                                NSEC_PER_SEC - (send_delay +
 135                                dev->port_write_time + SAFETY_INTERVAL +
 136                                2 * hrtimer_error)));
 137
 138        return HRTIMER_RESTART;
 139}
 140
 141/* calibrate port write time */
 142#define PORT_NTESTS_SHIFT       5
 143static void calibrate_port(struct pps_generator_pp *dev)
 144{
 145        struct parport *port = dev->pardev->port;
 146        int i;
 147        long acc = 0;
 148
 149        for (i = 0; i < (1 << PORT_NTESTS_SHIFT); i++) {
 150                struct timespec64 a, b;
 151                unsigned long irq_flags;
 152
 153                local_irq_save(irq_flags);
 154                ktime_get_real_ts64(&a);
 155                port->ops->write_control(port, NO_SIGNAL);
 156                ktime_get_real_ts64(&b);
 157                local_irq_restore(irq_flags);
 158
 159                b = timespec64_sub(b, a);
 160                acc += timespec64_to_ns(&b);
 161        }
 162
 163        dev->port_write_time = acc >> PORT_NTESTS_SHIFT;
 164        pr_info("port write takes %ldns\n", dev->port_write_time);
 165}
 166
 167static inline ktime_t next_intr_time(struct pps_generator_pp *dev)
 168{
 169        struct timespec64 ts;
 170
 171        ktime_get_real_ts64(&ts);
 172
 173        return ktime_set(ts.tv_sec +
 174                        ((ts.tv_nsec > 990 * NSEC_PER_MSEC) ? 1 : 0),
 175                        NSEC_PER_SEC - (send_delay +
 176                        dev->port_write_time + 3 * SAFETY_INTERVAL));
 177}
 178
 179static void parport_attach(struct parport *port)
 180{
 181        struct pardev_cb pps_cb;
 182
 183        if (attached) {
 184                /* we already have a port */
 185                return;
 186        }
 187
 188        memset(&pps_cb, 0, sizeof(pps_cb));
 189        pps_cb.private = &device;
 190        pps_cb.flags = PARPORT_FLAG_EXCL;
 191        device.pardev = parport_register_dev_model(port, KBUILD_MODNAME,
 192                                                   &pps_cb, 0);
 193        if (!device.pardev) {
 194                pr_err("couldn't register with %s\n", port->name);
 195                return;
 196        }
 197
 198        if (parport_claim_or_block(device.pardev) < 0) {
 199                pr_err("couldn't claim %s\n", port->name);
 200                goto err_unregister_dev;
 201        }
 202
 203        pr_info("attached to %s\n", port->name);
 204        attached = 1;
 205
 206        calibrate_port(&device);
 207
 208        hrtimer_init(&device.timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
 209        device.timer.function = hrtimer_event;
 210        hrtimer_start(&device.timer, next_intr_time(&device), HRTIMER_MODE_ABS);
 211
 212        return;
 213
 214err_unregister_dev:
 215        parport_unregister_device(device.pardev);
 216}
 217
 218static void parport_detach(struct parport *port)
 219{
 220        if (port->cad != device.pardev)
 221                return; /* not our port */
 222
 223        hrtimer_cancel(&device.timer);
 224        parport_release(device.pardev);
 225        parport_unregister_device(device.pardev);
 226}
 227
 228static struct parport_driver pps_gen_parport_driver = {
 229        .name = KBUILD_MODNAME,
 230        .match_port = parport_attach,
 231        .detach = parport_detach,
 232        .devmodel = true,
 233};
 234
 235/* module staff */
 236
 237static int __init pps_gen_parport_init(void)
 238{
 239        int ret;
 240
 241        pr_info(DRVDESC "\n");
 242
 243        if (send_delay > SEND_DELAY_MAX) {
 244                pr_err("delay value should be not greater"
 245                                " then %d\n", SEND_DELAY_MAX);
 246                return -EINVAL;
 247        }
 248
 249        ret = parport_register_driver(&pps_gen_parport_driver);
 250        if (ret) {
 251                pr_err("unable to register with parport\n");
 252                return ret;
 253        }
 254
 255        return  0;
 256}
 257
 258static void __exit pps_gen_parport_exit(void)
 259{
 260        parport_unregister_driver(&pps_gen_parport_driver);
 261        pr_info("hrtimer avg error is %ldns\n", hrtimer_error);
 262}
 263
 264module_init(pps_gen_parport_init);
 265module_exit(pps_gen_parport_exit);
 266
 267MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");
 268MODULE_DESCRIPTION(DRVDESC);
 269MODULE_LICENSE("GPL");
 270