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