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