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