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