1 /*
2     comedi/drivers/amplc_pc236.c
3     Driver for Amplicon PC36AT and PCI236 DIO boards.
4 
5     Copyright (C) 2002 MEV Ltd. <http://www.mev.co.uk/>
6 
7     COMEDI - Linux Control and Measurement Device Interface
8     Copyright (C) 2000 David A. Schleef <ds@schleef.org>
9 
10     This program is free software; you can redistribute it and/or modify
11     it under the terms of the GNU General Public License as published by
12     the Free Software Foundation; either version 2 of the License, or
13     (at your option) any later version.
14 
15     This program is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18     GNU General Public License for more details.
19 
20     You should have received a copy of the GNU General Public License
21     along with this program; if not, write to the Free Software
22     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 
24 */
25 /*
26 Driver: amplc_pc236
27 Description: Amplicon PC36AT, PCI236
28 Author: Ian Abbott <abbotti@mev.co.uk>
29 Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236 or amplc_pc236)
30 Updated: Wed, 01 Apr 2009 15:41:25 +0100
31 Status: works
32 
33 Configuration options - PC36AT:
34   [0] - I/O port base address
35   [1] - IRQ (optional)
36 
37 Configuration options - PCI236:
38   [0] - PCI bus of device (optional)
39   [1] - PCI slot of device (optional)
40   If bus/slot is not specified, the first available PCI device will be
41   used.
42 
43 The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing
44 as subdevice 0.
45 
46 Subdevice 1 pretends to be a digital input device, but it always returns
47 0 when read. However, if you run a command with scan_begin_src=TRIG_EXT,
48 a rising edge on port C bit 3 acts as an external trigger, which can be
49 used to wake up tasks.  This is like the comedi_parport device, but the
50 only way to physically disable the interrupt on the PC36AT is to remove
51 the IRQ jumper.  If no interrupt is connected, then subdevice 1 is
52 unused.
53 */
54 
55 #include <linux/interrupt.h>
56 
57 #include "../comedidev.h"
58 
59 #include "comedi_pci.h"
60 
61 #include "8255.h"
62 #include "plx9052.h"
63 
64 #define PC236_DRIVER_NAME	"amplc_pc236"
65 
66 /* PCI236 PCI configuration register information */
67 #define PCI_VENDOR_ID_AMPLICON 0x14dc
68 #define PCI_DEVICE_ID_AMPLICON_PCI236 0x0009
69 #define PCI_DEVICE_ID_INVALID 0xffff
70 
71 /* PC36AT / PCI236 registers */
72 
73 #define PC236_IO_SIZE		4
74 #define PC236_LCR_IO_SIZE	128
75 
76 /*
77  * INTCSR values for PCI236.
78  */
79 /* Disable interrupt, also clear any interrupt there */
80 #define PCI236_INTR_DISABLE (PLX9052_INTCSR_LI1ENAB_DISABLED \
81 	| PLX9052_INTCSR_LI1POL_HIGH \
82 	| PLX9052_INTCSR_LI2POL_HIGH \
83 	| PLX9052_INTCSR_PCIENAB_DISABLED \
84 	| PLX9052_INTCSR_LI1SEL_EDGE \
85 	| PLX9052_INTCSR_LI1CLRINT_ASSERTED)
86 /* Enable interrupt, also clear any interrupt there. */
87 #define PCI236_INTR_ENABLE (PLX9052_INTCSR_LI1ENAB_ENABLED \
88 	| PLX9052_INTCSR_LI1POL_HIGH \
89 	| PLX9052_INTCSR_LI2POL_HIGH \
90 	| PLX9052_INTCSR_PCIENAB_ENABLED \
91 	| PLX9052_INTCSR_LI1SEL_EDGE \
92 	| PLX9052_INTCSR_LI1CLRINT_ASSERTED)
93 
94 /*
95  * Board descriptions for Amplicon PC36AT and PCI236.
96  */
97 
98 enum pc236_bustype { isa_bustype, pci_bustype };
99 enum pc236_model { pc36at_model, pci236_model, anypci_model };
100 
101 struct pc236_board {
102 	const char *name;
103 	const char *fancy_name;
104 	unsigned short devid;
105 	enum pc236_bustype bustype;
106 	enum pc236_model model;
107 };
108 static const struct pc236_board pc236_boards[] = {
109 	{
110 	 .name = "pc36at",
111 	 .fancy_name = "PC36AT",
112 	 .bustype = isa_bustype,
113 	 .model = pc36at_model,
114 	 },
115 #ifdef CONFIG_COMEDI_PCI
116 	{
117 	 .name = "pci236",
118 	 .fancy_name = "PCI236",
119 	 .devid = PCI_DEVICE_ID_AMPLICON_PCI236,
120 	 .bustype = pci_bustype,
121 	 .model = pci236_model,
122 	 },
123 #endif
124 #ifdef CONFIG_COMEDI_PCI
125 	{
126 	 .name = PC236_DRIVER_NAME,
127 	 .fancy_name = PC236_DRIVER_NAME,
128 	 .devid = PCI_DEVICE_ID_INVALID,
129 	 .bustype = pci_bustype,
130 	 .model = anypci_model,	/* wildcard */
131 	 },
132 #endif
133 };
134 
135 #ifdef CONFIG_COMEDI_PCI
136 static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
137 	{ PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
138 	{0}
139 };
140 
141 MODULE_DEVICE_TABLE(pci, pc236_pci_table);
142 #endif /* CONFIG_COMEDI_PCI */
143 
144 /*
145  * Useful for shorthand access to the particular board structure
146  */
147 #define thisboard ((const struct pc236_board *)dev->board_ptr)
148 
149 /* this structure is for data unique to this hardware driver.  If
150    several hardware drivers keep similar information in this structure,
151    feel free to suggest moving the variable to the struct comedi_device struct.
152  */
153 struct pc236_private {
154 #ifdef CONFIG_COMEDI_PCI
155 	/* PCI device */
156 	struct pci_dev *pci_dev;
157 	unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
158 #endif
159 	int enable_irq;
160 };
161 
162 #define devpriv ((struct pc236_private *)dev->private)
163 
164 /*
165  * The struct comedi_driver structure tells the Comedi core module
166  * which functions to call to configure/deconfigure (attach/detach)
167  * the board, and also about the kernel module that contains
168  * the device code.
169  */
170 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
171 static int pc236_detach(struct comedi_device *dev);
172 static struct comedi_driver driver_amplc_pc236 = {
173 	.driver_name = PC236_DRIVER_NAME,
174 	.module = THIS_MODULE,
175 	.attach = pc236_attach,
176 	.detach = pc236_detach,
177 	.board_name = &pc236_boards[0].name,
178 	.offset = sizeof(struct pc236_board),
179 	.num_names = ARRAY_SIZE(pc236_boards),
180 };
181 
182 #ifdef CONFIG_COMEDI_PCI
driver_amplc_pc236_pci_probe(struct pci_dev * dev,const struct pci_device_id * ent)183 static int __devinit driver_amplc_pc236_pci_probe(struct pci_dev *dev,
184 						  const struct pci_device_id
185 						  *ent)
186 {
187 	return comedi_pci_auto_config(dev, driver_amplc_pc236.driver_name);
188 }
189 
driver_amplc_pc236_pci_remove(struct pci_dev * dev)190 static void __devexit driver_amplc_pc236_pci_remove(struct pci_dev *dev)
191 {
192 	comedi_pci_auto_unconfig(dev);
193 }
194 
195 static struct pci_driver driver_amplc_pc236_pci_driver = {
196 	.id_table = pc236_pci_table,
197 	.probe = &driver_amplc_pc236_pci_probe,
198 	.remove = __devexit_p(&driver_amplc_pc236_pci_remove)
199 };
200 
driver_amplc_pc236_init_module(void)201 static int __init driver_amplc_pc236_init_module(void)
202 {
203 	int retval;
204 
205 	retval = comedi_driver_register(&driver_amplc_pc236);
206 	if (retval < 0)
207 		return retval;
208 
209 	driver_amplc_pc236_pci_driver.name =
210 	    (char *)driver_amplc_pc236.driver_name;
211 	return pci_register_driver(&driver_amplc_pc236_pci_driver);
212 }
213 
driver_amplc_pc236_cleanup_module(void)214 static void __exit driver_amplc_pc236_cleanup_module(void)
215 {
216 	pci_unregister_driver(&driver_amplc_pc236_pci_driver);
217 	comedi_driver_unregister(&driver_amplc_pc236);
218 }
219 
220 module_init(driver_amplc_pc236_init_module);
221 module_exit(driver_amplc_pc236_cleanup_module);
222 #else
driver_amplc_pc236_init_module(void)223 static int __init driver_amplc_pc236_init_module(void)
224 {
225 	return comedi_driver_register(&driver_amplc_pc236);
226 }
227 
driver_amplc_pc236_cleanup_module(void)228 static void __exit driver_amplc_pc236_cleanup_module(void)
229 {
230 	comedi_driver_unregister(&driver_amplc_pc236);
231 }
232 
233 module_init(driver_amplc_pc236_init_module);
234 module_exit(driver_amplc_pc236_cleanup_module);
235 #endif
236 
237 static int pc236_request_region(unsigned minor, unsigned long from,
238 				unsigned long extent);
239 static void pc236_intr_disable(struct comedi_device *dev);
240 static void pc236_intr_enable(struct comedi_device *dev);
241 static int pc236_intr_check(struct comedi_device *dev);
242 static int pc236_intr_insn(struct comedi_device *dev,
243 			   struct comedi_subdevice *s, struct comedi_insn *insn,
244 			   unsigned int *data);
245 static int pc236_intr_cmdtest(struct comedi_device *dev,
246 			      struct comedi_subdevice *s,
247 			      struct comedi_cmd *cmd);
248 static int pc236_intr_cmd(struct comedi_device *dev,
249 			  struct comedi_subdevice *s);
250 static int pc236_intr_cancel(struct comedi_device *dev,
251 			     struct comedi_subdevice *s);
252 static irqreturn_t pc236_interrupt(int irq, void *d);
253 
254 /*
255  * This function looks for a PCI device matching the requested board name,
256  * bus and slot.
257  */
258 #ifdef CONFIG_COMEDI_PCI
259 static int
pc236_find_pci(struct comedi_device * dev,int bus,int slot,struct pci_dev ** pci_dev_p)260 pc236_find_pci(struct comedi_device *dev, int bus, int slot,
261 	       struct pci_dev **pci_dev_p)
262 {
263 	struct pci_dev *pci_dev = NULL;
264 
265 	*pci_dev_p = NULL;
266 
267 	/* Look for matching PCI device. */
268 	for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
269 	     pci_dev != NULL;
270 	     pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
271 				      PCI_ANY_ID, pci_dev)) {
272 		/* If bus/slot specified, check them. */
273 		if (bus || slot) {
274 			if (bus != pci_dev->bus->number
275 			    || slot != PCI_SLOT(pci_dev->devfn))
276 				continue;
277 		}
278 		if (thisboard->model == anypci_model) {
279 			/* Match any supported model. */
280 			int i;
281 
282 			for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) {
283 				if (pc236_boards[i].bustype != pci_bustype)
284 					continue;
285 				if (pci_dev->device == pc236_boards[i].devid) {
286 					/* Change board_ptr to matched board. */
287 					dev->board_ptr = &pc236_boards[i];
288 					break;
289 				}
290 			}
291 			if (i == ARRAY_SIZE(pc236_boards))
292 				continue;
293 		} else {
294 			/* Match specific model name. */
295 			if (pci_dev->device != thisboard->devid)
296 				continue;
297 		}
298 
299 		/* Found a match. */
300 		*pci_dev_p = pci_dev;
301 		return 0;
302 	}
303 	/* No match found. */
304 	if (bus || slot) {
305 		printk(KERN_ERR
306 		       "comedi%d: error! no %s found at pci %02x:%02x!\n",
307 		       dev->minor, thisboard->name, bus, slot);
308 	} else {
309 		printk(KERN_ERR "comedi%d: error! no %s found!\n",
310 		       dev->minor, thisboard->name);
311 	}
312 	return -EIO;
313 }
314 #endif
315 
316 /*
317  * Attach is called by the Comedi core to configure the driver
318  * for a particular board.  If you specified a board_name array
319  * in the driver structure, dev->board_ptr contains that
320  * address.
321  */
pc236_attach(struct comedi_device * dev,struct comedi_devconfig * it)322 static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
323 {
324 	struct comedi_subdevice *s;
325 	unsigned long iobase = 0;
326 	unsigned int irq = 0;
327 #ifdef CONFIG_COMEDI_PCI
328 	struct pci_dev *pci_dev = NULL;
329 	int bus = 0, slot = 0;
330 #endif
331 	int share_irq = 0;
332 	int ret;
333 
334 	printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
335 	       PC236_DRIVER_NAME);
336 /*
337  * Allocate the private structure area.  alloc_private() is a
338  * convenient macro defined in comedidev.h.
339  */
340 	ret = alloc_private(dev, sizeof(struct pc236_private));
341 	if (ret < 0) {
342 		printk(KERN_ERR "comedi%d: error! out of memory!\n",
343 		       dev->minor);
344 		return ret;
345 	}
346 	/* Process options. */
347 	switch (thisboard->bustype) {
348 	case isa_bustype:
349 		iobase = it->options[0];
350 		irq = it->options[1];
351 		share_irq = 0;
352 		break;
353 #ifdef CONFIG_COMEDI_PCI
354 	case pci_bustype:
355 		bus = it->options[0];
356 		slot = it->options[1];
357 		share_irq = 1;
358 
359 		ret = pc236_find_pci(dev, bus, slot, &pci_dev);
360 		if (ret < 0)
361 			return ret;
362 		devpriv->pci_dev = pci_dev;
363 		break;
364 #endif /* CONFIG_COMEDI_PCI */
365 	default:
366 		printk(KERN_ERR
367 		       "comedi%d: %s: BUG! cannot determine board type!\n",
368 		       dev->minor, PC236_DRIVER_NAME);
369 		return -EINVAL;
370 		break;
371 	}
372 
373 /*
374  * Initialize dev->board_name.
375  */
376 	dev->board_name = thisboard->name;
377 
378 	/* Enable device and reserve I/O spaces. */
379 #ifdef CONFIG_COMEDI_PCI
380 	if (pci_dev) {
381 
382 		ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
383 		if (ret < 0) {
384 			printk(KERN_ERR
385 			       "comedi%d: error! cannot enable PCI device and request regions!\n",
386 			       dev->minor);
387 			return ret;
388 		}
389 		devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
390 		iobase = pci_resource_start(pci_dev, 2);
391 		irq = pci_dev->irq;
392 	} else
393 #endif
394 	{
395 		ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
396 		if (ret < 0)
397 			return ret;
398 	}
399 	dev->iobase = iobase;
400 
401 /*
402  * Allocate the subdevice structures.  alloc_subdevice() is a
403  * convenient macro defined in comedidev.h.
404  */
405 	ret = alloc_subdevices(dev, 2);
406 	if (ret < 0) {
407 		printk(KERN_ERR "comedi%d: error! out of memory!\n",
408 		       dev->minor);
409 		return ret;
410 	}
411 
412 	s = dev->subdevices + 0;
413 	/* digital i/o subdevice (8255) */
414 	ret = subdev_8255_init(dev, s, NULL, iobase);
415 	if (ret < 0) {
416 		printk(KERN_ERR "comedi%d: error! out of memory!\n",
417 		       dev->minor);
418 		return ret;
419 	}
420 	s = dev->subdevices + 1;
421 	dev->read_subdev = s;
422 	s->type = COMEDI_SUBD_UNUSED;
423 	pc236_intr_disable(dev);
424 	if (irq) {
425 		unsigned long flags = share_irq ? IRQF_SHARED : 0;
426 
427 		if (request_irq(irq, pc236_interrupt, flags,
428 				PC236_DRIVER_NAME, dev) >= 0) {
429 			dev->irq = irq;
430 			s->type = COMEDI_SUBD_DI;
431 			s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
432 			s->n_chan = 1;
433 			s->maxdata = 1;
434 			s->range_table = &range_digital;
435 			s->insn_bits = pc236_intr_insn;
436 			s->do_cmdtest = pc236_intr_cmdtest;
437 			s->do_cmd = pc236_intr_cmd;
438 			s->cancel = pc236_intr_cancel;
439 		}
440 	}
441 	printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
442 	if (thisboard->bustype == isa_bustype) {
443 		printk("(base %#lx) ", iobase);
444 	} else {
445 #ifdef CONFIG_COMEDI_PCI
446 		printk("(pci %s) ", pci_name(pci_dev));
447 #endif
448 	}
449 	if (irq)
450 		printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
451 	else
452 		printk("(no irq) ");
453 
454 	printk("attached\n");
455 
456 	return 1;
457 }
458 
459 /*
460  * _detach is called to deconfigure a device.  It should deallocate
461  * resources.
462  * This function is also called when _attach() fails, so it should be
463  * careful not to release resources that were not necessarily
464  * allocated by _attach().  dev->private and dev->subdevices are
465  * deallocated automatically by the core.
466  */
pc236_detach(struct comedi_device * dev)467 static int pc236_detach(struct comedi_device *dev)
468 {
469 	printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
470 	       PC236_DRIVER_NAME);
471 	if (devpriv)
472 		pc236_intr_disable(dev);
473 
474 	if (dev->irq)
475 		free_irq(dev->irq, dev);
476 	if (dev->subdevices)
477 		subdev_8255_cleanup(dev, dev->subdevices + 0);
478 	if (devpriv) {
479 #ifdef CONFIG_COMEDI_PCI
480 		if (devpriv->pci_dev) {
481 			if (dev->iobase)
482 				comedi_pci_disable(devpriv->pci_dev);
483 			pci_dev_put(devpriv->pci_dev);
484 		} else
485 #endif
486 		{
487 			if (dev->iobase)
488 				release_region(dev->iobase, PC236_IO_SIZE);
489 		}
490 	}
491 	if (dev->board_name) {
492 		printk(KERN_INFO "comedi%d: %s removed\n",
493 		       dev->minor, dev->board_name);
494 	}
495 	return 0;
496 }
497 
498 /*
499  * This function checks and requests an I/O region, reporting an error
500  * if there is a conflict.
501  */
pc236_request_region(unsigned minor,unsigned long from,unsigned long extent)502 static int pc236_request_region(unsigned minor, unsigned long from,
503 				unsigned long extent)
504 {
505 	if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
506 		printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
507 		       minor, from, extent);
508 		return -EIO;
509 	}
510 	return 0;
511 }
512 
513 /*
514  * This function is called to mark the interrupt as disabled (no command
515  * configured on subdevice 1) and to physically disable the interrupt
516  * (not possible on the PC36AT, except by removing the IRQ jumper!).
517  */
pc236_intr_disable(struct comedi_device * dev)518 static void pc236_intr_disable(struct comedi_device *dev)
519 {
520 	unsigned long flags;
521 
522 	spin_lock_irqsave(&dev->spinlock, flags);
523 	devpriv->enable_irq = 0;
524 #ifdef CONFIG_COMEDI_PCI
525 	if (devpriv->lcr_iobase)
526 		outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
527 #endif
528 	spin_unlock_irqrestore(&dev->spinlock, flags);
529 }
530 
531 /*
532  * This function is called to mark the interrupt as enabled (a command
533  * configured on subdevice 1) and to physically enable the interrupt
534  * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
535  */
pc236_intr_enable(struct comedi_device * dev)536 static void pc236_intr_enable(struct comedi_device *dev)
537 {
538 	unsigned long flags;
539 
540 	spin_lock_irqsave(&dev->spinlock, flags);
541 	devpriv->enable_irq = 1;
542 #ifdef CONFIG_COMEDI_PCI
543 	if (devpriv->lcr_iobase)
544 		outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
545 #endif
546 	spin_unlock_irqrestore(&dev->spinlock, flags);
547 }
548 
549 /*
550  * This function is called when an interrupt occurs to check whether
551  * the interrupt has been marked as enabled and was generated by the
552  * board.  If so, the function prepares the hardware for the next
553  * interrupt.
554  * Returns 0 if the interrupt should be ignored.
555  */
pc236_intr_check(struct comedi_device * dev)556 static int pc236_intr_check(struct comedi_device *dev)
557 {
558 	int retval = 0;
559 	unsigned long flags;
560 
561 	spin_lock_irqsave(&dev->spinlock, flags);
562 	if (devpriv->enable_irq) {
563 		retval = 1;
564 #ifdef CONFIG_COMEDI_PCI
565 		if (devpriv->lcr_iobase) {
566 			if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
567 			     & PLX9052_INTCSR_LI1STAT_MASK)
568 			    == PLX9052_INTCSR_LI1STAT_INACTIVE) {
569 				retval = 0;
570 			} else {
571 				/* Clear interrupt and keep it enabled. */
572 				outl(PCI236_INTR_ENABLE,
573 				     devpriv->lcr_iobase + PLX9052_INTCSR);
574 			}
575 		}
576 #endif
577 	}
578 	spin_unlock_irqrestore(&dev->spinlock, flags);
579 
580 	return retval;
581 }
582 
583 /*
584  * Input from subdevice 1.
585  * Copied from the comedi_parport driver.
586  */
pc236_intr_insn(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)587 static int pc236_intr_insn(struct comedi_device *dev,
588 			   struct comedi_subdevice *s, struct comedi_insn *insn,
589 			   unsigned int *data)
590 {
591 	data[1] = 0;
592 	return 2;
593 }
594 
595 /*
596  * Subdevice 1 command test.
597  * Copied from the comedi_parport driver.
598  */
pc236_intr_cmdtest(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_cmd * cmd)599 static int pc236_intr_cmdtest(struct comedi_device *dev,
600 			      struct comedi_subdevice *s,
601 			      struct comedi_cmd *cmd)
602 {
603 	int err = 0;
604 	int tmp;
605 
606 	/* step 1 */
607 
608 	tmp = cmd->start_src;
609 	cmd->start_src &= TRIG_NOW;
610 	if (!cmd->start_src || tmp != cmd->start_src)
611 		err++;
612 
613 	tmp = cmd->scan_begin_src;
614 	cmd->scan_begin_src &= TRIG_EXT;
615 	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
616 		err++;
617 
618 	tmp = cmd->convert_src;
619 	cmd->convert_src &= TRIG_FOLLOW;
620 	if (!cmd->convert_src || tmp != cmd->convert_src)
621 		err++;
622 
623 	tmp = cmd->scan_end_src;
624 	cmd->scan_end_src &= TRIG_COUNT;
625 	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
626 		err++;
627 
628 	tmp = cmd->stop_src;
629 	cmd->stop_src &= TRIG_NONE;
630 	if (!cmd->stop_src || tmp != cmd->stop_src)
631 		err++;
632 
633 	if (err)
634 		return 1;
635 
636 	/* step 2: ignored */
637 
638 	if (err)
639 		return 2;
640 
641 	/* step 3: */
642 
643 	if (cmd->start_arg != 0) {
644 		cmd->start_arg = 0;
645 		err++;
646 	}
647 	if (cmd->scan_begin_arg != 0) {
648 		cmd->scan_begin_arg = 0;
649 		err++;
650 	}
651 	if (cmd->convert_arg != 0) {
652 		cmd->convert_arg = 0;
653 		err++;
654 	}
655 	if (cmd->scan_end_arg != 1) {
656 		cmd->scan_end_arg = 1;
657 		err++;
658 	}
659 	if (cmd->stop_arg != 0) {
660 		cmd->stop_arg = 0;
661 		err++;
662 	}
663 
664 	if (err)
665 		return 3;
666 
667 	/* step 4: ignored */
668 
669 	if (err)
670 		return 4;
671 
672 	return 0;
673 }
674 
675 /*
676  * Subdevice 1 command.
677  */
pc236_intr_cmd(struct comedi_device * dev,struct comedi_subdevice * s)678 static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
679 {
680 	pc236_intr_enable(dev);
681 
682 	return 0;
683 }
684 
685 /*
686  * Subdevice 1 cancel command.
687  */
pc236_intr_cancel(struct comedi_device * dev,struct comedi_subdevice * s)688 static int pc236_intr_cancel(struct comedi_device *dev,
689 			     struct comedi_subdevice *s)
690 {
691 	pc236_intr_disable(dev);
692 
693 	return 0;
694 }
695 
696 /*
697  * Interrupt service routine.
698  * Based on the comedi_parport driver.
699  */
pc236_interrupt(int irq,void * d)700 static irqreturn_t pc236_interrupt(int irq, void *d)
701 {
702 	struct comedi_device *dev = d;
703 	struct comedi_subdevice *s = dev->subdevices + 1;
704 	int handled;
705 
706 	handled = pc236_intr_check(dev);
707 	if (dev->attached && handled) {
708 		comedi_buf_put(s->async, 0);
709 		s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
710 		comedi_event(dev, s);
711 	}
712 	return IRQ_RETVAL(handled);
713 }
714 
715 MODULE_AUTHOR("Comedi http://www.comedi.org");
716 MODULE_DESCRIPTION("Comedi low-level driver");
717 MODULE_LICENSE("GPL");
718