amplc_pc236.c revision 7b0be12b26d86ea2bd5079f0d723289e2f5a43a9
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/*
26Driver: amplc_pc236
27Description: Amplicon PC36AT, PCI236
28Author: Ian Abbott <abbotti@mev.co.uk>
29Devices: [Amplicon] PC36AT (pc36at), PCI236 (pci236 or amplc_pc236)
30Updated: Wed, 01 Apr 2009 15:41:25 +0100
31Status: works
32
33Configuration options - PC36AT:
34  [0] - I/O port base address
35  [1] - IRQ (optional)
36
37Configuration 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
43The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing
44as subdevice 0.
45
46Subdevice 1 pretends to be a digital input device, but it always returns
470 when read. However, if you run a command with scan_begin_src=TRIG_EXT,
48a rising edge on port C bit 3 acts as an external trigger, which can be
49used to wake up tasks.  This is like the comedi_parport device, but the
50only way to physically disable the interrupt on the PC36AT is to remove
51the IRQ jumper.  If no interrupt is connected, then subdevice 1 is
52unused.
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
98enum pc236_bustype { isa_bustype, pci_bustype };
99enum pc236_model { pc36at_model, pci236_model, anypci_model };
100
101struct 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};
108static 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
136static DEFINE_PCI_DEVICE_TABLE(pc236_pci_table) = {
137	{ PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236) },
138	{0}
139};
140
141MODULE_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 */
153struct 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 */
170static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
171static int pc236_detach(struct comedi_device *dev);
172static 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
183static 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
190static void __devexit driver_amplc_pc236_pci_remove(struct pci_dev *dev)
191{
192	comedi_pci_auto_unconfig(dev);
193}
194
195static 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
201static 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
214static 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
220module_init(driver_amplc_pc236_init_module);
221module_exit(driver_amplc_pc236_cleanup_module);
222#else
223static int __init driver_amplc_pc236_init_module(void)
224{
225	return comedi_driver_register(&driver_amplc_pc236);
226}
227
228static void __exit driver_amplc_pc236_cleanup_module(void)
229{
230	comedi_driver_unregister(&driver_amplc_pc236);
231}
232
233module_init(driver_amplc_pc236_init_module);
234module_exit(driver_amplc_pc236_cleanup_module);
235#endif
236
237static int pc236_request_region(unsigned minor, unsigned long from,
238				unsigned long extent);
239static void pc236_intr_disable(struct comedi_device *dev);
240static void pc236_intr_enable(struct comedi_device *dev);
241static int pc236_intr_check(struct comedi_device *dev);
242static int pc236_intr_insn(struct comedi_device *dev,
243			   struct comedi_subdevice *s, struct comedi_insn *insn,
244			   unsigned int *data);
245static int pc236_intr_cmdtest(struct comedi_device *dev,
246			      struct comedi_subdevice *s,
247			      struct comedi_cmd *cmd);
248static int pc236_intr_cmd(struct comedi_device *dev,
249			  struct comedi_subdevice *s);
250static int pc236_intr_cancel(struct comedi_device *dev,
251			     struct comedi_subdevice *s);
252static 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
259static int
260pc236_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 */
322static 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 */
467static 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 */
502static 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 */
518static 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 */
536static 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 */
556static 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 */
587static 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 */
599static 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 */
678static 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 */
688static 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 */
700static 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
715MODULE_AUTHOR("Comedi http://www.comedi.org");
716MODULE_DESCRIPTION("Comedi low-level driver");
717MODULE_LICENSE("GPL");
718