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