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