brintos

brintos / linux-shallow public Read only

0
0
Text · 5.5 KiB · abaffb4 Raw
229 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * pps_parport.c -- kernel parallel port PPS client4 *5 * Copyright (C) 2009   Alexander Gordeev <lasaine@lvk.cs.msu.su>6 */7 8 9/*10 * TODO:11 * implement echo over SEL pin12 */13 14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt15 16#include <linux/kernel.h>17#include <linux/module.h>18#include <linux/init.h>19#include <linux/irqnr.h>20#include <linux/time.h>21#include <linux/slab.h>22#include <linux/parport.h>23#include <linux/pps_kernel.h>24 25/* module parameters */26 27#define CLEAR_WAIT_MAX		10028#define CLEAR_WAIT_MAX_ERRORS	529 30static unsigned int clear_wait = 100;31MODULE_PARM_DESC(clear_wait,32	"Maximum number of port reads when polling for signal clear,"33	" zero turns clear edge capture off entirely");34module_param(clear_wait, uint, 0);35 36static DEFINE_IDA(pps_client_index);37 38/* internal per port structure */39struct pps_client_pp {40	struct pardevice *pardev;	/* parport device */41	struct pps_device *pps;		/* PPS device */42	unsigned int cw;		/* port clear timeout */43	unsigned int cw_err;		/* number of timeouts */44	int index;			/* device number */45};46 47static inline int signal_is_set(struct parport *port)48{49	return (port->ops->read_status(port) & PARPORT_STATUS_ACK) != 0;50}51 52/* parport interrupt handler */53static void parport_irq(void *handle)54{55	struct pps_event_time ts_assert, ts_clear;56	struct pps_client_pp *dev = handle;57	struct parport *port = dev->pardev->port;58	unsigned int i;59	unsigned long flags;60 61	/* first of all we get the time stamp... */62	pps_get_ts(&ts_assert);63 64	if (dev->cw == 0)65		/* clear edge capture disabled */66		goto out_assert;67 68	/* try capture the clear edge */69 70	/* We have to disable interrupts here. The idea is to prevent71	 * other interrupts on the same processor to introduce random72	 * lags while polling the port. Reading from IO port is known73	 * to take approximately 1us while other interrupt handlers can74	 * take much more potentially.75	 *76	 * Interrupts won't be disabled for a long time because the77	 * number of polls is limited by clear_wait parameter which is78	 * kept rather low. So it should never be an issue.79	 */80	local_irq_save(flags);81	/* check the signal (no signal means the pulse is lost this time) */82	if (!signal_is_set(port)) {83		local_irq_restore(flags);84		dev_err(dev->pps->dev, "lost the signal\n");85		goto out_assert;86	}87 88	/* poll the port until the signal is unset */89	for (i = dev->cw; i; i--)90		if (!signal_is_set(port)) {91			pps_get_ts(&ts_clear);92			local_irq_restore(flags);93			dev->cw_err = 0;94			goto out_both;95		}96	local_irq_restore(flags);97 98	/* timeout */99	dev->cw_err++;100	if (dev->cw_err >= CLEAR_WAIT_MAX_ERRORS) {101		dev_err(dev->pps->dev, "disabled clear edge capture after %d"102				" timeouts\n", dev->cw_err);103		dev->cw = 0;104		dev->cw_err = 0;105	}106 107out_assert:108	/* fire assert event */109	pps_event(dev->pps, &ts_assert,110			PPS_CAPTUREASSERT, NULL);111	return;112 113out_both:114	/* fire assert event */115	pps_event(dev->pps, &ts_assert,116			PPS_CAPTUREASSERT, NULL);117	/* fire clear event */118	pps_event(dev->pps, &ts_clear,119			PPS_CAPTURECLEAR, NULL);120	return;121}122 123static void parport_attach(struct parport *port)124{125	struct pardev_cb pps_client_cb;126	int index;127	struct pps_client_pp *device;128	struct pps_source_info info = {129		.name		= KBUILD_MODNAME,130		.path		= "",131		.mode		= PPS_CAPTUREBOTH | \132				  PPS_OFFSETASSERT | PPS_OFFSETCLEAR | \133				  PPS_ECHOASSERT | PPS_ECHOCLEAR | \134				  PPS_CANWAIT | PPS_TSFMT_TSPEC,135		.owner		= THIS_MODULE,136		.dev		= NULL137	};138 139	if (clear_wait > CLEAR_WAIT_MAX) {140		pr_err("clear_wait value should be not greater then %d\n",141		       CLEAR_WAIT_MAX);142		return;143	}144 145	device = kzalloc(sizeof(struct pps_client_pp), GFP_KERNEL);146	if (!device) {147		pr_err("memory allocation failed, not attaching\n");148		return;149	}150 151	index = ida_alloc(&pps_client_index, GFP_KERNEL);152	if (index < 0)153		goto err_free_device;154 155	memset(&pps_client_cb, 0, sizeof(pps_client_cb));156	pps_client_cb.private = device;157	pps_client_cb.irq_func = parport_irq;158	pps_client_cb.flags = PARPORT_FLAG_EXCL;159	device->pardev = parport_register_dev_model(port,160						    KBUILD_MODNAME,161						    &pps_client_cb,162						    index);163	if (!device->pardev) {164		pr_err("couldn't register with %s\n", port->name);165		goto err_free_ida;166	}167 168	if (parport_claim_or_block(device->pardev) < 0) {169		pr_err("couldn't claim %s\n", port->name);170		goto err_unregister_dev;171	}172 173	device->pps = pps_register_source(&info,174			PPS_CAPTUREBOTH | PPS_OFFSETASSERT | PPS_OFFSETCLEAR);175	if (IS_ERR(device->pps)) {176		pr_err("couldn't register PPS source\n");177		goto err_release_dev;178	}179 180	device->cw = clear_wait;181 182	port->ops->enable_irq(port);183	device->index = index;184 185	pr_info("attached to %s\n", port->name);186 187	return;188 189err_release_dev:190	parport_release(device->pardev);191err_unregister_dev:192	parport_unregister_device(device->pardev);193err_free_ida:194	ida_free(&pps_client_index, index);195err_free_device:196	kfree(device);197}198 199static void parport_detach(struct parport *port)200{201	struct pardevice *pardev = port->cad;202	struct pps_client_pp *device;203 204	/* FIXME: oooh, this is ugly! */205	if (!pardev || strcmp(pardev->name, KBUILD_MODNAME))206		/* not our port */207		return;208 209	device = pardev->private;210 211	port->ops->disable_irq(port);212	pps_unregister_source(device->pps);213	parport_release(pardev);214	parport_unregister_device(pardev);215	ida_free(&pps_client_index, device->index);216	kfree(device);217}218 219static struct parport_driver pps_parport_driver = {220	.name = KBUILD_MODNAME,221	.match_port = parport_attach,222	.detach = parport_detach,223};224module_parport_driver(pps_parport_driver);225 226MODULE_AUTHOR("Alexander Gordeev <lasaine@lvk.cs.msu.su>");227MODULE_DESCRIPTION("parallel port PPS client");228MODULE_LICENSE("GPL");229