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