amplc_pc236.c revision 68c3dbff9fc9f25872408d0e95980d41733d48d0
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	ret = alloc_private(dev, sizeof(struct pc236_private));
288	if (ret < 0) {
289		printk(KERN_ERR "comedi%d: error! out of memory!\n",
290			dev->minor);
291		return ret;
292	}
293	/* Process options. */
294	switch (thisboard->bustype) {
295	case isa_bustype:
296		iobase = it->options[0];
297		irq = it->options[1];
298		share_irq = 0;
299		break;
300#ifdef CONFIG_COMEDI_PCI
301	case pci_bustype:
302		bus = it->options[0];
303		slot = it->options[1];
304		share_irq = 1;
305
306		ret = pc236_find_pci(dev, bus, slot, &pci_dev);
307		if (ret < 0)
308			return ret;
309		devpriv->pci_dev = pci_dev;
310		break;
311#endif /* CONFIG_COMEDI_PCI */
312	default:
313		printk(KERN_ERR
314			"comedi%d: %s: BUG! cannot determine board type!\n",
315			dev->minor, PC236_DRIVER_NAME);
316		return -EINVAL;
317		break;
318	}
319
320/*
321 * Initialize dev->board_name.
322 */
323	dev->board_name = thisboard->name;
324
325	/* Enable device and reserve I/O spaces. */
326#ifdef CONFIG_COMEDI_PCI
327	if (pci_dev) {
328
329		ret = comedi_pci_enable(pci_dev, PC236_DRIVER_NAME);
330		if (ret < 0) {
331			printk(KERN_ERR
332				"comedi%d: error! cannot enable PCI device and request regions!\n",
333				dev->minor);
334			return ret;
335		}
336		devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
337		iobase = pci_resource_start(pci_dev, 2);
338		irq = pci_dev->irq;
339	} else
340#endif
341	{
342		ret = pc236_request_region(dev->minor, iobase, PC236_IO_SIZE);
343		if (ret < 0) {
344			return ret;
345		}
346	}
347	dev->iobase = iobase;
348
349/*
350 * Allocate the subdevice structures.  alloc_subdevice() is a
351 * convenient macro defined in comedidev.h.
352 */
353	ret = alloc_subdevices(dev, 2);
354	if (ret < 0) {
355		printk(KERN_ERR "comedi%d: error! out of memory!\n",
356			dev->minor);
357		return ret;
358	}
359
360	s = dev->subdevices + 0;
361	/* digital i/o subdevice (8255) */
362	ret = subdev_8255_init(dev, s, NULL, iobase);
363	if (ret < 0) {
364		printk(KERN_ERR "comedi%d: error! out of memory!\n",
365			dev->minor);
366		return ret;
367	}
368	s = dev->subdevices + 1;
369	dev->read_subdev = s;
370	s->type = COMEDI_SUBD_UNUSED;
371	pc236_intr_disable(dev);
372	if (irq) {
373		unsigned long flags = share_irq ? IRQF_SHARED : 0;
374
375		if (comedi_request_irq(irq, pc236_interrupt, flags,
376				PC236_DRIVER_NAME, dev) >= 0) {
377			dev->irq = irq;
378			s->type = COMEDI_SUBD_DI;
379			s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
380			s->n_chan = 1;
381			s->maxdata = 1;
382			s->range_table = &range_digital;
383			s->insn_bits = pc236_intr_insn;
384			s->do_cmdtest = pc236_intr_cmdtest;
385			s->do_cmd = pc236_intr_cmd;
386			s->cancel = pc236_intr_cancel;
387		}
388	}
389	printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
390	if (thisboard->bustype == isa_bustype) {
391		printk("(base %#lx) ", iobase);
392	} else {
393#ifdef CONFIG_COMEDI_PCI
394		printk("(pci %s) ", pci_name(pci_dev));
395#endif
396	}
397	if (irq) {
398		printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
399	} else {
400		printk("(no irq) ");
401	}
402
403	printk("attached\n");
404
405	return 1;
406}
407
408/*
409 * _detach is called to deconfigure a device.  It should deallocate
410 * resources.
411 * This function is also called when _attach() fails, so it should be
412 * careful not to release resources that were not necessarily
413 * allocated by _attach().  dev->private and dev->subdevices are
414 * deallocated automatically by the core.
415 */
416static int pc236_detach(struct comedi_device *dev)
417{
418	printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor,
419		PC236_DRIVER_NAME);
420	if (devpriv) {
421		pc236_intr_disable(dev);
422	}
423	if (dev->irq)
424		comedi_free_irq(dev->irq, dev);
425	if (dev->subdevices) {
426		subdev_8255_cleanup(dev, dev->subdevices + 0);
427	}
428	if (devpriv) {
429#ifdef CONFIG_COMEDI_PCI
430		if (devpriv->pci_dev) {
431			if (dev->iobase) {
432				comedi_pci_disable(devpriv->pci_dev);
433			}
434			pci_dev_put(devpriv->pci_dev);
435		} else
436#endif
437		{
438			if (dev->iobase) {
439				release_region(dev->iobase, PC236_IO_SIZE);
440			}
441		}
442	}
443	if (dev->board_name) {
444		printk(KERN_INFO "comedi%d: %s removed\n",
445			dev->minor, dev->board_name);
446	}
447	return 0;
448}
449
450/*
451 * This function checks and requests an I/O region, reporting an error
452 * if there is a conflict.
453 */
454static int pc236_request_region(unsigned minor, unsigned long from,
455	unsigned long extent)
456{
457	if (!from || !request_region(from, extent, PC236_DRIVER_NAME)) {
458		printk(KERN_ERR "comedi%d: I/O port conflict (%#lx,%lu)!\n",
459			minor, from, extent);
460		return -EIO;
461	}
462	return 0;
463}
464
465/*
466 * This function is called to mark the interrupt as disabled (no command
467 * configured on subdevice 1) and to physically disable the interrupt
468 * (not possible on the PC36AT, except by removing the IRQ jumper!).
469 */
470static void pc236_intr_disable(struct comedi_device *dev)
471{
472	unsigned long flags;
473
474	comedi_spin_lock_irqsave(&dev->spinlock, flags);
475	devpriv->enable_irq = 0;
476#ifdef CONFIG_COMEDI_PCI
477	if (devpriv->lcr_iobase)
478		outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
479#endif
480	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
481}
482
483/*
484 * This function is called to mark the interrupt as enabled (a command
485 * configured on subdevice 1) and to physically enable the interrupt
486 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
487 */
488static void pc236_intr_enable(struct comedi_device *dev)
489{
490	unsigned long flags;
491
492	comedi_spin_lock_irqsave(&dev->spinlock, flags);
493	devpriv->enable_irq = 1;
494#ifdef CONFIG_COMEDI_PCI
495	if (devpriv->lcr_iobase)
496		outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
497#endif
498	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
499}
500
501/*
502 * This function is called when an interrupt occurs to check whether
503 * the interrupt has been marked as enabled and was generated by the
504 * board.  If so, the function prepares the hardware for the next
505 * interrupt.
506 * Returns 0 if the interrupt should be ignored.
507 */
508static int pc236_intr_check(struct comedi_device *dev)
509{
510	int retval = 0;
511	unsigned long flags;
512
513	comedi_spin_lock_irqsave(&dev->spinlock, flags);
514	if (devpriv->enable_irq) {
515		retval = 1;
516#ifdef CONFIG_COMEDI_PCI
517		if (devpriv->lcr_iobase) {
518			if ((inl(devpriv->lcr_iobase + PLX9052_INTCSR)
519					& PLX9052_INTCSR_LI1STAT_MASK)
520				== PLX9052_INTCSR_LI1STAT_INACTIVE) {
521				retval = 0;
522			} else {
523				/* Clear interrupt and keep it enabled. */
524				outl(PCI236_INTR_ENABLE,
525					devpriv->lcr_iobase + PLX9052_INTCSR);
526			}
527		}
528#endif
529	}
530	comedi_spin_unlock_irqrestore(&dev->spinlock, flags);
531
532	return retval;
533}
534
535/*
536 * Input from subdevice 1.
537 * Copied from the comedi_parport driver.
538 */
539static int pc236_intr_insn(struct comedi_device *dev, struct comedi_subdevice *s,
540	struct comedi_insn *insn, unsigned int *data)
541{
542	data[1] = 0;
543	return 2;
544}
545
546/*
547 * Subdevice 1 command test.
548 * Copied from the comedi_parport driver.
549 */
550static int pc236_intr_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
551	struct comedi_cmd *cmd)
552{
553	int err = 0;
554	int tmp;
555
556	/* step 1 */
557
558	tmp = cmd->start_src;
559	cmd->start_src &= TRIG_NOW;
560	if (!cmd->start_src || tmp != cmd->start_src)
561		err++;
562
563	tmp = cmd->scan_begin_src;
564	cmd->scan_begin_src &= TRIG_EXT;
565	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
566		err++;
567
568	tmp = cmd->convert_src;
569	cmd->convert_src &= TRIG_FOLLOW;
570	if (!cmd->convert_src || tmp != cmd->convert_src)
571		err++;
572
573	tmp = cmd->scan_end_src;
574	cmd->scan_end_src &= TRIG_COUNT;
575	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
576		err++;
577
578	tmp = cmd->stop_src;
579	cmd->stop_src &= TRIG_NONE;
580	if (!cmd->stop_src || tmp != cmd->stop_src)
581		err++;
582
583	if (err)
584		return 1;
585
586	/* step 2: ignored */
587
588	if (err)
589		return 2;
590
591	/* step 3: */
592
593	if (cmd->start_arg != 0) {
594		cmd->start_arg = 0;
595		err++;
596	}
597	if (cmd->scan_begin_arg != 0) {
598		cmd->scan_begin_arg = 0;
599		err++;
600	}
601	if (cmd->convert_arg != 0) {
602		cmd->convert_arg = 0;
603		err++;
604	}
605	if (cmd->scan_end_arg != 1) {
606		cmd->scan_end_arg = 1;
607		err++;
608	}
609	if (cmd->stop_arg != 0) {
610		cmd->stop_arg = 0;
611		err++;
612	}
613
614	if (err)
615		return 3;
616
617	/* step 4: ignored */
618
619	if (err)
620		return 4;
621
622	return 0;
623}
624
625/*
626 * Subdevice 1 command.
627 */
628static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
629{
630	pc236_intr_enable(dev);
631
632	return 0;
633}
634
635/*
636 * Subdevice 1 cancel command.
637 */
638static int pc236_intr_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
639{
640	pc236_intr_disable(dev);
641
642	return 0;
643}
644
645/*
646 * Interrupt service routine.
647 * Based on the comedi_parport driver.
648 */
649static irqreturn_t pc236_interrupt(int irq, void *d)
650{
651	struct comedi_device *dev = d;
652	struct comedi_subdevice *s = dev->subdevices + 1;
653	int handled;
654
655	handled = pc236_intr_check(dev);
656	if (dev->attached && handled) {
657		comedi_buf_put(s->async, 0);
658		s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
659		comedi_event(dev, s);
660	}
661	return IRQ_RETVAL(handled);
662}
663