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