diff options
Diffstat (limited to 'drivers/pps/clients')
-rw-r--r-- | drivers/pps/clients/Kconfig | 7 | ||||
-rw-r--r-- | drivers/pps/clients/Makefile | 1 | ||||
-rw-r--r-- | drivers/pps/clients/pps-ktimer.c | 44 | ||||
-rw-r--r-- | drivers/pps/clients/pps-ldisc.c | 59 | ||||
-rw-r--r-- | drivers/pps/clients/pps_parport.c | 258 |
5 files changed, 313 insertions, 56 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 If you say yes here you get support for a PPS source connected with the CD (Carrier Detect) pin of your serial port. +config PPS_CLIENT_PARPORT + tristate "Parallel port PPS client" + depends on PPS && PARPORT + help + If you say yes here you get support for a PPS source connected + with the interrupt pin of your parallel port. + endif 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 @@ obj-$(CONFIG_PPS_CLIENT_KTIMER) += pps-ktimer.o obj-$(CONFIG_PPS_CLIENT_LDISC) += pps-ldisc.o +obj-$(CONFIG_PPS_CLIENT_PARPORT) += pps_parport.o ifeq ($(CONFIG_PPS_DEBUG),y) EXTRA_CFLAGS += -DDEBUG diff --git a/drivers/pps/clients/pps-ktimer.c b/drivers/pps/clients/pps-ktimer.c index e7ef5b8186d0..2728469d3884 100644 --- a/drivers/pps/clients/pps-ktimer.c +++ b/drivers/pps/clients/pps-ktimer.c @@ -19,6 +19,7 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/kernel.h> #include <linux/module.h> @@ -31,7 +32,7 @@ * Global variables */ -static int source; +static struct pps_device *pps; static struct timer_list ktimer; /* @@ -40,19 +41,14 @@ static struct timer_list ktimer; static void pps_ktimer_event(unsigned long ptr) { - struct timespec __ts; - struct pps_ktime ts; + struct pps_event_time ts; /* First of all we get the time stamp... */ - getnstimeofday(&__ts); + pps_get_ts(&ts); - pr_info("PPS event at %lu\n", jiffies); + dev_info(pps->dev, "PPS event at %lu\n", jiffies); - /* ... and translate it to PPS time data struct */ - ts.sec = __ts.tv_sec; - ts.nsec = __ts.tv_nsec; - - pps_event(source, &ts, PPS_CAPTUREASSERT, NULL); + pps_event(pps, &ts, PPS_CAPTUREASSERT, NULL); mod_timer(&ktimer, jiffies + HZ); } @@ -61,12 +57,11 @@ static void pps_ktimer_event(unsigned long ptr) * The echo function */ -static void pps_ktimer_echo(int source, int event, void *data) +static void pps_ktimer_echo(struct pps_device *pps, int event, void *data) { - pr_info("echo %s %s for source %d\n", + dev_info(pps->dev, "echo %s %s\n", event & PPS_CAPTUREASSERT ? "assert" : "", - event & PPS_CAPTURECLEAR ? "clear" : "", - source); + event & PPS_CAPTURECLEAR ? "clear" : ""); } /* @@ -89,30 +84,27 @@ static struct pps_source_info pps_ktimer_info = { static void __exit pps_ktimer_exit(void) { - del_timer_sync(&ktimer); - pps_unregister_source(source); + dev_info(pps->dev, "ktimer PPS source unregistered\n"); - pr_info("ktimer PPS source unregistered\n"); + del_timer_sync(&ktimer); + pps_unregister_source(pps); } static int __init pps_ktimer_init(void) { - int ret; - - ret = pps_register_source(&pps_ktimer_info, + pps = pps_register_source(&pps_ktimer_info, PPS_CAPTUREASSERT | PPS_OFFSETASSERT); - if (ret < 0) { - printk(KERN_ERR "cannot register ktimer source\n"); - return ret; + if (pps == NULL) { + pr_err("cannot register PPS source\n"); + return -ENOMEM; } - source = ret; setup_timer(&ktimer, pps_ktimer_event, 0); mod_timer(&ktimer, jiffies + HZ); - pr_info("ktimer PPS source registered at %d\n", source); + dev_info(pps->dev, "ktimer PPS source registered\n"); - return 0; + return 0; } module_init(pps_ktimer_init); diff --git a/drivers/pps/clients/pps-ldisc.c b/drivers/pps/clients/pps-ldisc.c index 8e1932d29fd4..79451f2dea6a 100644 --- a/drivers/pps/clients/pps-ldisc.c +++ b/drivers/pps/clients/pps-ldisc.c @@ -19,6 +19,8 @@ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/serial_core.h> #include <linux/tty.h> @@ -27,30 +29,18 @@ #define PPS_TTY_MAGIC 0x0001 static void pps_tty_dcd_change(struct tty_struct *tty, unsigned int status, - struct timespec *ts) + struct pps_event_time *ts) { - int id = (long)tty->disc_data; - struct timespec __ts; - struct pps_ktime pps_ts; - - /* First of all we get the time stamp... */ - getnstimeofday(&__ts); - - /* Does caller give us a timestamp? */ - if (ts) { /* Yes. Let's use it! */ - pps_ts.sec = ts->tv_sec; - pps_ts.nsec = ts->tv_nsec; - } else { /* No. Do it ourself! */ - pps_ts.sec = __ts.tv_sec; - pps_ts.nsec = __ts.tv_nsec; - } + struct pps_device *pps = (struct pps_device *)tty->disc_data; + + BUG_ON(pps == NULL); /* Now do the PPS event report */ - pps_event(id, &pps_ts, status ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR, - NULL); + pps_event(pps, ts, status ? PPS_CAPTUREASSERT : + PPS_CAPTURECLEAR, NULL); - pr_debug("PPS %s at %lu on source #%d\n", - status ? "assert" : "clear", jiffies, id); + dev_dbg(pps->dev, "PPS %s at %lu\n", + status ? "assert" : "clear", jiffies); } static int (*alias_n_tty_open)(struct tty_struct *tty); @@ -60,6 +50,7 @@ static int pps_tty_open(struct tty_struct *tty) struct pps_source_info info; struct tty_driver *drv = tty->driver; int index = tty->index + drv->name_base; + struct pps_device *pps; int ret; info.owner = THIS_MODULE; @@ -70,34 +61,42 @@ static int pps_tty_open(struct tty_struct *tty) PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \ PPS_CANWAIT | PPS_TSFMT_TSPEC; - ret = pps_register_source(&info, PPS_CAPTUREBOTH | \ + pps = pps_register_source(&info, PPS_CAPTUREBOTH | \ PPS_OFFSETASSERT | PPS_OFFSETCLEAR); - if (ret < 0) { + if (pps == NULL) { pr_err("cannot register PPS source \"%s\"\n", info.path); - return ret; + return -ENOMEM; } - tty->disc_data = (void *)(long)ret; + tty->disc_data = pps; /* Should open N_TTY ldisc too */ ret = alias_n_tty_open(tty); - if (ret < 0) - pps_unregister_source((long)tty->disc_data); + if (ret < 0) { + pr_err("cannot open tty ldisc \"%s\"\n", info.path); + goto err_unregister; + } - pr_info("PPS source #%d \"%s\" added\n", ret, info.path); + dev_info(pps->dev, "source \"%s\" added\n", info.path); return 0; + +err_unregister: + tty->disc_data = NULL; + pps_unregister_source(pps); + return ret; } static void (*alias_n_tty_close)(struct tty_struct *tty); static void pps_tty_close(struct tty_struct *tty) { - int id = (long)tty->disc_data; + struct pps_device *pps = (struct pps_device *)tty->disc_data; - pps_unregister_source(id); alias_n_tty_close(tty); - pr_info("PPS source #%d removed\n", id); + tty->disc_data = NULL; + dev_info(pps->dev, "removed\n"); + pps_unregister_source(pps); } static struct tty_ldisc_ops pps_ldisc_ops; 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 @@ +/* + * pps_parport.c -- kernel parallel port PPS client + * + * + * Copyright (C) 2009 Alexander Gordeev <lasaine@lvk.cs.msu.su> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +/* + * TODO: + * implement echo over SEL pin + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/irqnr.h> +#include <linux/time.h> +#include <linux/parport.h> +#include <linux/pps_kernel.h> + +#define DRVDESC "parallel port PPS client" + +/* module parameters */ + +#define CLEAR_WAIT_MAX 100 +#define CLEAR_WAIT_MAX_ERRORS 5 + +static unsigned int clear_wait = 100; +MODULE_PARM_DESC(clear_wait, + "Maximum number of port reads when polling for signal clear," + " zero turns clear edge capture off entirely"); +module_param(clear_wait, uint, 0); + + +/* internal per port structure */ +struct pps_client_pp { + struct pardevice *pardev; /* parport device */ + struct pps_device *pps; /* PPS device */ + unsigned int cw; /* port clear timeout */ + unsigned int cw_err; /* number of timeouts */ +}; + +static inline int signal_is_set(struct parport *port) +{ + return (port->ops->read_status(port) & PARPORT_STATUS_ACK) != 0; +} + +/* parport interrupt handler */ +static void parport_irq(void *handle) +{ + struct pps_event_time ts_assert, ts_clear; + struct pps_client_pp *dev = handle; + struct parport *port = dev->pardev->port; + unsigned int i; + unsigned long flags; + + /* first of all we get the time stamp... */ + pps_get_ts(&ts_assert); + + if (dev->cw == 0) + /* clear edge capture disabled */ + goto out_assert; + + /* try capture the clear edge */ + + /* We have to disable interrupts here. The idea is to prevent + * other interrupts on the same processor to introduce random + * lags while polling the port. Reading from IO port is known + * to take approximately 1us while other interrupt handlers can + * take much more potentially. + * + * Interrupts won't be disabled for a long time because the + * number of polls is limited by clear_wait parameter which is + * kept rather low. So it should never be an issue. + */ + local_irq_save(flags); + /* check the signal (no signal means the pulse is lost this time) */ + if (!signal_is_set(port)) { + local_irq_restore(flags); + dev_err(dev->pps->dev, "lost the signal\n"); + goto out_assert; + } + + /* poll the port until the signal is unset */ + for (i = dev->cw; i; i--) + if (!signal_is_set(port)) { + pps_get_ts(&ts_clear); + local_irq_restore(flags); + dev->cw_err = 0; + goto out_both; + } + local_irq_restore(flags); + + /* timeout */ + dev->cw_err++; + if (dev->cw_err >= CLEAR_WAIT_MAX_ERRORS) { + dev_err(dev->pps->dev, "disabled clear edge capture after %d" + " timeouts\n", dev->cw_err); + dev->cw = 0; + dev->cw_err = 0; + } + +out_assert: + /* fire assert event */ + pps_event(dev->pps, &ts_assert, + PPS_CAPTUREASSERT, NULL); + return; + +out_both: + /* fire assert event */ + pps_event(dev->pps, &ts_assert, + PPS_CAPTUREASSERT, NULL); + /* fire clear event */ + pps_event(dev->pps, &ts_clear, + PPS_CAPTURECLEAR, NULL); + return; +} + +/* the PPS echo function */ +static void pps_echo(struct pps_device *pps, int event, void *data) +{ + dev_info(pps->dev, "echo %s %s\n", + event & PPS_CAPTUREASSERT ? "assert" : "", + event & PPS_CAPTURECLEAR ? "clear" : ""); +} + +static void parport_attach(struct parport *port) +{ + struct pps_client_pp *device; + struct pps_source_info info = { + .name = KBUILD_MODNAME, + .path = "", + .mode = PPS_CAPTUREBOTH | \ + PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \ + PPS_ECHOASSERT | PPS_ECHOCLEAR | \ + PPS_CANWAIT | PPS_TSFMT_TSPEC, + .echo = pps_echo, + .owner = THIS_MODULE, + .dev = NULL + }; + + device = kzalloc(sizeof(struct pps_client_pp), GFP_KERNEL); + if (!device) { + pr_err("memory allocation failed, not attaching\n"); + return; + } + + device->pardev = parport_register_device(port, KBUILD_MODNAME, + NULL, NULL, parport_irq, 0, device); + if (!device->pardev) { + pr_err("couldn't register with %s\n", port->name); + goto err_free; + } + + if (parport_claim_or_block(device->pardev) < 0) { + pr_err("couldn't claim %s\n", port->name); + goto err_unregister_dev; + } + + device->pps = pps_register_source(&info, + PPS_CAPTUREBOTH | PPS_OFFSETASSERT | PPS_OFFSETCLEAR); + if (device->pps == NULL) { + pr_err("couldn't register PPS source\n"); + goto err_release_dev; + } + + device->cw = clear_wait; + + port->ops->enable_irq(port); + + pr_info("attached to %s\n", port->name); + + return; + +err_release_dev: + parport_release(device->pardev); +err_unregister_dev: + parport_unregister_device(device->pardev); +err_free: + kfree(device); +} + +static void parport_detach(struct parport *port) +{ + struct pardevice *pardev = port->cad; + struct pps_client_pp *device; + + /* FIXME: oooh, this is ugly! */ + if (strcmp(pardev->name, KBUILD_MODNAME)) + /* not our port */ + return; + + device = pardev->private; + + port->ops->disable_irq(port); + pps_unregister_source(device->pps); + parport_release(pardev); + parport_unregister_device(pardev); + kfree(device); +} + +static struct parport_driver pps_parport_driver = { + .name = KBUILD_MODNAME, + .attach = parport_attach, + .detach = parport_detach, +}; + +/* module staff */ + +static int __init pps_parport_init(void) +{ + int ret; + + pr_info(DRVDESC "\n"); + + if (clear_wait > CLEAR_WAIT_MAX) { + pr_err("clear_wait value should be not greater" + " then %d\n", CLEAR_WAIT_MAX); + return -EINVAL; + } + + ret = parport_register_driver(&pps_parport_driver); + if (ret) { + pr_err("unable to register with parport\n"); + return ret; + } + + return 0; +} + +static void __exit pps_parport_exit(void) +{ + parport_unregister_driver(&pps_parport_driver); +} + +module_init(pps_parport_init); +module_exit(pps_parport_exit); + +MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>"); +MODULE_DESCRIPTION(DRVDESC); +MODULE_LICENSE("GPL"); |