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