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