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