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