amplc_pc236.c revision 7114a28011f9d5f3d981731ad341177c21f9d948
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	{
138	PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI236,
139		    PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {
140	0}
141};
142
143MODULE_DEVICE_TABLE(pci, pc236_pci_table);
144#endif /* CONFIG_COMEDI_PCI */
145
146/*
147 * Useful for shorthand access to the particular board structure
148 */
149#define thisboard ((const struct pc236_board *)dev->board_ptr)
150
151/* this structure is for data unique to this hardware driver.  If
152   several hardware drivers keep similar information in this structure,
153   feel free to suggest moving the variable to the struct comedi_device struct.
154 */
155struct pc236_private {
156#ifdef CONFIG_COMEDI_PCI
157	/* PCI device */
158	struct pci_dev *pci_dev;
159	unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
160#endif
161	int enable_irq;
162};
163
164#define devpriv ((struct pc236_private *)dev->private)
165
166/*
167 * The struct comedi_driver structure tells the Comedi core module
168 * which functions to call to configure/deconfigure (attach/detach)
169 * the board, and also about the kernel module that contains
170 * the device code.
171 */
172static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it);
173static int pc236_detach(struct comedi_device *dev);
174static struct comedi_driver driver_amplc_pc236 = {
175	.driver_name = PC236_DRIVER_NAME,
176	.module = THIS_MODULE,
177	.attach = pc236_attach,
178	.detach = pc236_detach,
179	.board_name = &pc236_boards[0].name,
180	.offset = sizeof(struct pc236_board),
181	.num_names = ARRAY_SIZE(pc236_boards),
182};
183
184#ifdef CONFIG_COMEDI_PCI
185COMEDI_PCI_INITCLEANUP(driver_amplc_pc236, pc236_pci_table);
186#else
187static int __init driver_amplc_pc236_init_module(void)
188{
189	return comedi_driver_register(&driver_amplc_pc236);
190}
191
192static void __exit driver_amplc_pc236_cleanup_module(void)
193{
194	comedi_driver_unregister(&driver_amplc_pc236);
195}
196
197module_init(driver_amplc_pc236_init_module);
198module_exit(driver_amplc_pc236_cleanup_module);
199#endif
200
201static int pc236_request_region(unsigned minor, unsigned long from,
202				unsigned long extent);
203static void pc236_intr_disable(struct comedi_device *dev);
204static void pc236_intr_enable(struct comedi_device *dev);
205static int pc236_intr_check(struct comedi_device *dev);
206static int pc236_intr_insn(struct comedi_device *dev,
207			   struct comedi_subdevice *s, struct comedi_insn *insn,
208			   unsigned int *data);
209static int pc236_intr_cmdtest(struct comedi_device *dev,
210			      struct comedi_subdevice *s,
211			      struct comedi_cmd *cmd);
212static int pc236_intr_cmd(struct comedi_device *dev,
213			  struct comedi_subdevice *s);
214static int pc236_intr_cancel(struct comedi_device *dev,
215			     struct comedi_subdevice *s);
216static irqreturn_t pc236_interrupt(int irq, void *d);
217
218/*
219 * This function looks for a PCI device matching the requested board name,
220 * bus and slot.
221 */
222#ifdef CONFIG_COMEDI_PCI
223static int
224pc236_find_pci(struct comedi_device *dev, int bus, int slot,
225	       struct pci_dev **pci_dev_p)
226{
227	struct pci_dev *pci_dev = NULL;
228
229	*pci_dev_p = NULL;
230
231	/* Look for matching PCI device. */
232	for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
233	     pci_dev != NULL;
234	     pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON,
235				      PCI_ANY_ID, pci_dev)) {
236		/* If bus/slot specified, check them. */
237		if (bus || slot) {
238			if (bus != pci_dev->bus->number
239			    || slot != PCI_SLOT(pci_dev->devfn))
240				continue;
241		}
242		if (thisboard->model == anypci_model) {
243			/* Match any supported model. */
244			int i;
245
246			for (i = 0; i < ARRAY_SIZE(pc236_boards); i++) {
247				if (pc236_boards[i].bustype != pci_bustype)
248					continue;
249				if (pci_dev->device == pc236_boards[i].devid) {
250					/* Change board_ptr to matched board. */
251					dev->board_ptr = &pc236_boards[i];
252					break;
253				}
254			}
255			if (i == ARRAY_SIZE(pc236_boards))
256				continue;
257		} else {
258			/* Match specific model name. */
259			if (pci_dev->device != thisboard->devid)
260				continue;
261		}
262
263		/* Found a match. */
264		*pci_dev_p = pci_dev;
265		return 0;
266	}
267	/* No match found. */
268	if (bus || slot) {
269		printk(KERN_ERR
270		       "comedi%d: error! no %s found at pci %02x:%02x!\n",
271		       dev->minor, thisboard->name, bus, slot);
272	} else {
273		printk(KERN_ERR "comedi%d: error! no %s found!\n",
274		       dev->minor, thisboard->name);
275	}
276	return -EIO;
277}
278#endif
279
280/*
281 * Attach is called by the Comedi core to configure the driver
282 * for a particular board.  If you specified a board_name array
283 * in the driver structure, dev->board_ptr contains that
284 * address.
285 */
286static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
287{
288	struct comedi_subdevice *s;
289	unsigned long iobase = 0;
290	unsigned int irq = 0;
291#ifdef CONFIG_COMEDI_PCI
292	struct pci_dev *pci_dev = NULL;
293	int bus = 0, slot = 0;
294#endif
295	int share_irq = 0;
296	int ret;
297
298	printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor,
299	       PC236_DRIVER_NAME);
300/*
301 * Allocate the private structure area.  alloc_private() is a
302 * convenient macro defined in comedidev.h.
303 */
304	ret = alloc_private(dev, sizeof(struct pc236_private));
305	if (ret < 0) {
306		printk(KERN_ERR "comedi%d: error! out of memory!\n",
307		       dev->minor);
308		return ret;
309	}
310	/* Process options. */
311	switch (thisboard->bustype) {
312	case isa_bustype:
313		iobase = it->options[0];
314		irq = it->options[1];
315		share_irq = 0;
316		break;
317#ifdef CONFIG_COMEDI_PCI
318	case pci_bustype:
319		bus = it->options[0];
320		slot = it->options[1];
321		share_irq = 1;
322
323		ret = pc236_find_pci(dev, bus, slot, &pci_dev);
324		if (ret < 0)
325			return ret;
326		devpriv->pci_dev = pci_dev;
327		break;
328#endif /* CONFIG_COMEDI_PCI */
329	default:
330		printk(KERN_ERR
331		       "comedi%d: %s: BUG! cannot determine board type!\n",
332		       dev->minor, PC236_DRIVER_NAME);
333		return -EINVAL;
334		break;
335	}
336
337/*
338 * Initialize dev->board_name.
339 */
340	dev->board_name = thisboard->name;
341
342	/* Enable device and reserve I/O spaces. */
343#ifdef CONFIG_COMEDI_PCI
344	if (pci_dev) {
345
346		ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
347		if (ret < 0) {
348			printk(KERN_ERR
349			       "comedi%d: error! cannot enable PCI device and request regions!\n",
350			       dev->minor);
351			return ret;
352		}
353		devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
354		iobase = pci_resource_start(pci_dev, 2);
355		irq = pci_dev->irq;
356	} else
357#endif
358	{
359		ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
360		if (ret < 0)
361			return ret;
362	}
363	dev->iobase = iobase;
364
365/*
366 * Allocate the subdevice structures.  alloc_subdevice() is a
367 * convenient macro defined in comedidev.h.
368 */
369	ret = alloc_subdevices(dev, 2);
370	if (ret < 0) {
371		printk(KERN_ERR "comedi%d: error! out of memory!\n",
372		       dev->minor);
373		return ret;
374	}
375
376	s = dev->subdevices + 0;
377	/* digital i/o subdevice (8255) */
378	ret = subdev_8255_init(dev, s, NULL, iobase);
379	if (ret < 0) {
380		printk(KERN_ERR "comedi%d: error! out of memory!\n",
381		       dev->minor);
382		return ret;
383	}
384	s = dev->subdevices + 1;
385	dev->read_subdev = s;
386	s->type = COMEDI_SUBD_UNUSED;
387	pc236_intr_disable(dev);
388	if (irq) {
389		unsigned long flags = share_irq ? IRQF_SHARED : 0;
390
391		if (request_irq(irq, pc236_interrupt, flags,
392				PC236_DRIVER_NAME, dev) >= 0) {
393			dev->irq = irq;
394			s->type = COMEDI_SUBD_DI;
395			s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
396			s->n_chan = 1;
397			s->maxdata = 1;
398			s->range_table = &range_digital;
399			s->insn_bits = pc236_intr_insn;
400			s->do_cmdtest = pc236_intr_cmdtest;
401			s->do_cmd = pc236_intr_cmd;
402			s->cancel = pc236_intr_cancel;
403		}
404	}
405	printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
406	if (thisboard->bustype == isa_bustype) {
407		printk("(base %#lx) ", iobase);
408	} else {
409#ifdef CONFIG_COMEDI_PCI
410		printk("(pci %s) ", pci_name(pci_dev));
411#endif
412	}
413	if (irq)
414		printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
415	else
416		printk("(no irq) ");
417
418	printk("attached\n");
419
420	return 1;
421}
422
423/*
424 * _detach is called to deconfigure a device.  It should deallocate
425 * resources.
426 * This function is also called when _attach() fails, so it should be
427 * careful not to release resources that were not necessarily
428 * allocated by _attach().  dev->private and dev->subdevices are
429 * deallocated automatically by the core.
430 */
431static int pc236_detach(struct comedi_device *dev)
432{
433	printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
434	       PC236_DRIVER_NAME);
435	if (devpriv)
436		pc236_intr_disable(dev);
437
438	if (dev->irq)
439		free_irq(dev->irq, dev);
440	if (dev->subdevices)
441		subdev_8255_cleanup(dev, dev->subdevices + 0);
442	if (devpriv) {
443#ifdef CONFIG_COMEDI_PCI
444		if (devpriv->pci_dev) {
445			if (dev->iobase)
446				comedi_pci_disable(devpriv->pci_dev);
447			pci_dev_put(devpriv->pci_dev);
448		} else
449#endif
450		{
451			if (dev->iobase)
452				release_region(dev->iobase, PC236_IO_SIZE);
453		}
454	}
455	if (dev->board_name) {
456		printk(KERN_INFO "comedi%d: %s removed\n",
457		       dev->minor, dev->board_name);
458	}
459	return 0;
460}
461
462/*
463 * This function checks and requests an I/O region, reporting an error
464 * if there is a conflict.
465 */
466static int pc236_request_region(unsigned minor, unsigned long from,
467				unsigned long extent)
468{
469	if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
470		printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
471		       minor, from, extent);
472		return -EIO;
473	}
474	return 0;
475}
476
477/*
478 * This function is called to mark the interrupt as disabled (no command
479 * configured on subdevice 1) and to physically disable the interrupt
480 * (not possible on the PC36AT, except by removing the IRQ jumper!).
481 */
482static void pc236_intr_disable(struct comedi_device *dev)
483{
484	unsigned long flags;
485
486	spin_lock_irqsave(&dev->spinlock, flags);
487	devpriv->enable_irq = 0;
488#ifdef CONFIG_COMEDI_PCI
489	if (devpriv->lcr_iobase)
490		outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
491#endif
492	spin_unlock_irqrestore(&dev->spinlock, flags);
493}
494
495/*
496 * This function is called to mark the interrupt as enabled (a command
497 * configured on subdevice 1) and to physically enable the interrupt
498 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
499 */
500static void pc236_intr_enable(struct comedi_device *dev)
501{
502	unsigned long flags;
503
504	spin_lock_irqsave(&dev->spinlock, flags);
505	devpriv->enable_irq = 1;
506#ifdef CONFIG_COMEDI_PCI
507	if (devpriv->lcr_iobase)
508		outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
509#endif
510	spin_unlock_irqrestore(&dev->spinlock, flags);
511}
512
513/*
514 * This function is called when an interrupt occurs to check whether
515 * the interrupt has been marked as enabled and was generated by the
516 * board.  If so, the function prepares the hardware for the next
517 * interrupt.
518 * Returns 0 if the interrupt should be ignored.
519 */
520static int pc236_intr_check(struct comedi_device *dev)
521{
522	int retval = 0;
523	unsigned long flags;
524
525	spin_lock_irqsave(&dev->spinlock, flags);
526	if (devpriv->enable_irq) {
527		retval = 1;
528#ifdef CONFIG_COMEDI_PCI
529		if (devpriv->lcr_iobase) {
530			if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
531			     & PLX9052_INTCSR_LI1STAT_MASK)
532			    == PLX9052_INTCSR_LI1STAT_INACTIVE) {
533				retval = 0;
534			} else {
535				/* Clear interrupt and keep it enabled. */
536				outl(PCI236_INTR_ENABLE,
537				     devpriv->lcr_iobase + PLX9052_INTCSR);
538			}
539		}
540#endif
541	}
542	spin_unlock_irqrestore(&dev->spinlock, flags);
543
544	return retval;
545}
546
547/*
548 * Input from subdevice 1.
549 * Copied from the comedi_parport driver.
550 */
551static int pc236_intr_insn(struct comedi_device *dev,
552			   struct comedi_subdevice *s, struct comedi_insn *insn,
553			   unsigned int *data)
554{
555	data[1] = 0;
556	return 2;
557}
558
559/*
560 * Subdevice 1 command test.
561 * Copied from the comedi_parport driver.
562 */
563static int pc236_intr_cmdtest(struct comedi_device *dev,
564			      struct comedi_subdevice *s,
565			      struct comedi_cmd *cmd)
566{
567	int err = 0;
568	int tmp;
569
570	/* step 1 */
571
572	tmp = cmd->start_src;
573	cmd->start_src &= TRIG_NOW;
574	if (!cmd->start_src || tmp != cmd->start_src)
575		err++;
576
577	tmp = cmd->scan_begin_src;
578	cmd->scan_begin_src &= TRIG_EXT;
579	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
580		err++;
581
582	tmp = cmd->convert_src;
583	cmd->convert_src &= TRIG_FOLLOW;
584	if (!cmd->convert_src || tmp != cmd->convert_src)
585		err++;
586
587	tmp = cmd->scan_end_src;
588	cmd->scan_end_src &= TRIG_COUNT;
589	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
590		err++;
591
592	tmp = cmd->stop_src;
593	cmd->stop_src &= TRIG_NONE;
594	if (!cmd->stop_src || tmp != cmd->stop_src)
595		err++;
596
597	if (err)
598		return 1;
599
600	/* step 2: ignored */
601
602	if (err)
603		return 2;
604
605	/* step 3: */
606
607	if (cmd->start_arg != 0) {
608		cmd->start_arg = 0;
609		err++;
610	}
611	if (cmd->scan_begin_arg != 0) {
612		cmd->scan_begin_arg = 0;
613		err++;
614	}
615	if (cmd->convert_arg != 0) {
616		cmd->convert_arg = 0;
617		err++;
618	}
619	if (cmd->scan_end_arg != 1) {
620		cmd->scan_end_arg = 1;
621		err++;
622	}
623	if (cmd->stop_arg != 0) {
624		cmd->stop_arg = 0;
625		err++;
626	}
627
628	if (err)
629		return 3;
630
631	/* step 4: ignored */
632
633	if (err)
634		return 4;
635
636	return 0;
637}
638
639/*
640 * Subdevice 1 command.
641 */
642static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
643{
644	pc236_intr_enable(dev);
645
646	return 0;
647}
648
649/*
650 * Subdevice 1 cancel command.
651 */
652static int pc236_intr_cancel(struct comedi_device *dev,
653			     struct comedi_subdevice *s)
654{
655	pc236_intr_disable(dev);
656
657	return 0;
658}
659
660/*
661 * Interrupt service routine.
662 * Based on the comedi_parport driver.
663 */
664static irqreturn_t pc236_interrupt(int irq, void *d)
665{
666	struct comedi_device *dev = d;
667	struct comedi_subdevice *s = dev->subdevices + 1;
668	int handled;
669
670	handled = pc236_intr_check(dev);
671	if (dev->attached && handled) {
672		comedi_buf_put(s->async, 0);
673		s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
674		comedi_event(dev, s);
675	}
676	return IRQ_RETVAL(handled);
677}
678
679MODULE_AUTHOR("Comedi http://www.comedi.org");
680MODULE_DESCRIPTION("Comedi low-level driver");
681MODULE_LICENSE("GPL");
682