amplc_pc236.c revision e7637a912911ae4346e19e954d9481f7a5b0e48b
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)
25 * Updated: Thu, 24 Jul 2014 14:25:26 +0000
26 * Status: works
27 *
28 * Configuration options - PC36AT:
29 *   [0] - I/O port base address
30 *   [1] - IRQ (optional)
31 *
32 * Manual configuration of PCI board (PCI236) is not supported; it is
33 * configured automatically.
34 *
35 * The PC36AT ISA board and PCI236 PCI board have a single 8255 appearing
36 * as subdevice 0.
37 *
38 * Subdevice 1 pretends to be a digital input device, but it always returns
39 * 0 when read. However, if you run a command with scan_begin_src=TRIG_EXT,
40 * a rising edge on port C bit 3 acts as an external trigger, which can be
41 * used to wake up tasks.  This is like the comedi_parport device, but the
42 * only way to physically disable the interrupt on the PC36AT is to remove
43 * the IRQ jumper.  If no interrupt is connected, then subdevice 1 is
44 * unused.
45 */
46
47#include <linux/module.h>
48#include <linux/pci.h>
49#include <linux/interrupt.h>
50
51#include "../comedidev.h"
52
53#include "comedi_fc.h"
54#include "8255.h"
55#include "plx9052.h"
56
57#define DO_ISA	IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_ISA)
58#define DO_PCI	IS_ENABLED(CONFIG_COMEDI_AMPLC_PC236_PCI)
59
60/* PC36AT / PCI236 registers */
61
62/* Disable, and clear, interrupts */
63#define PCI236_INTR_DISABLE	(PLX9052_INTCSR_LI1POL |	\
64				 PLX9052_INTCSR_LI2POL |	\
65				 PLX9052_INTCSR_LI1SEL |	\
66				 PLX9052_INTCSR_LI1CLRINT)
67
68/* Enable, and clear, interrupts */
69#define PCI236_INTR_ENABLE	(PLX9052_INTCSR_LI1ENAB |	\
70				 PLX9052_INTCSR_LI1POL |	\
71				 PLX9052_INTCSR_LI2POL |	\
72				 PLX9052_INTCSR_PCIENAB |	\
73				 PLX9052_INTCSR_LI1SEL |	\
74				 PLX9052_INTCSR_LI1CLRINT)
75
76/*
77 * Board descriptions for Amplicon PC36AT and PCI236.
78 */
79
80enum pc236_bustype { isa_bustype, pci_bustype };
81
82struct pc236_board {
83	const char *name;
84	enum pc236_bustype bustype;
85};
86
87struct pc236_private {
88	unsigned long lcr_iobase; /* PLX PCI9052 config registers in PCIBAR1 */
89	int enable_irq;
90};
91
92/* test if ISA supported and this is an ISA board */
93static inline bool is_isa_board(const struct pc236_board *board)
94{
95	return DO_ISA && board->bustype == isa_bustype;
96}
97
98/* test if PCI supported and this is a PCI board */
99static inline bool is_pci_board(const struct pc236_board *board)
100{
101	return DO_PCI && board->bustype == pci_bustype;
102}
103
104/*
105 * This function is called to mark the interrupt as disabled (no command
106 * configured on subdevice 1) and to physically disable the interrupt
107 * (not possible on the PC36AT, except by removing the IRQ jumper!).
108 */
109static void pc236_intr_disable(struct comedi_device *dev)
110{
111	const struct pc236_board *thisboard = comedi_board(dev);
112	struct pc236_private *devpriv = dev->private;
113	unsigned long flags;
114
115	spin_lock_irqsave(&dev->spinlock, flags);
116	devpriv->enable_irq = 0;
117	if (is_pci_board(thisboard))
118		outl(PCI236_INTR_DISABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
119	spin_unlock_irqrestore(&dev->spinlock, flags);
120}
121
122/*
123 * This function is called to mark the interrupt as enabled (a command
124 * configured on subdevice 1) and to physically enable the interrupt
125 * (not possible on the PC36AT, except by (re)connecting the IRQ jumper!).
126 */
127static void pc236_intr_enable(struct comedi_device *dev)
128{
129	const struct pc236_board *thisboard = comedi_board(dev);
130	struct pc236_private *devpriv = dev->private;
131	unsigned long flags;
132
133	spin_lock_irqsave(&dev->spinlock, flags);
134	devpriv->enable_irq = 1;
135	if (is_pci_board(thisboard))
136		outl(PCI236_INTR_ENABLE, devpriv->lcr_iobase + PLX9052_INTCSR);
137	spin_unlock_irqrestore(&dev->spinlock, flags);
138}
139
140/*
141 * This function is called when an interrupt occurs to check whether
142 * the interrupt has been marked as enabled and was generated by the
143 * board.  If so, the function prepares the hardware for the next
144 * interrupt.
145 * Returns 0 if the interrupt should be ignored.
146 */
147static int pc236_intr_check(struct comedi_device *dev)
148{
149	const struct pc236_board *thisboard = comedi_board(dev);
150	struct pc236_private *devpriv = dev->private;
151	int retval = 0;
152	unsigned long flags;
153	unsigned int intcsr;
154
155	spin_lock_irqsave(&dev->spinlock, flags);
156	if (devpriv->enable_irq) {
157		retval = 1;
158		if (is_pci_board(thisboard)) {
159			intcsr = inl(devpriv->lcr_iobase + PLX9052_INTCSR);
160			if (!(intcsr & PLX9052_INTCSR_LI1STAT)) {
161				retval = 0;
162			} else {
163				/* Clear interrupt and keep it enabled. */
164				outl(PCI236_INTR_ENABLE,
165				     devpriv->lcr_iobase + PLX9052_INTCSR);
166			}
167		}
168	}
169	spin_unlock_irqrestore(&dev->spinlock, flags);
170
171	return retval;
172}
173
174/*
175 * Input from subdevice 1.
176 * Copied from the comedi_parport driver.
177 */
178static int pc236_intr_insn(struct comedi_device *dev,
179			   struct comedi_subdevice *s, struct comedi_insn *insn,
180			   unsigned int *data)
181{
182	data[1] = 0;
183	return insn->n;
184}
185
186/*
187 * Subdevice 1 command test.
188 * Copied from the comedi_parport driver.
189 */
190static int pc236_intr_cmdtest(struct comedi_device *dev,
191			      struct comedi_subdevice *s,
192			      struct comedi_cmd *cmd)
193{
194	int err = 0;
195
196	/* Step 1 : check if triggers are trivially valid */
197
198	err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
199	err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
200	err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
201	err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
202	err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_NONE);
203
204	if (err)
205		return 1;
206
207	/* Step 2a : make sure trigger sources are unique */
208	/* Step 2b : and mutually compatible */
209
210	if (err)
211		return 2;
212
213	/* Step 3: check it arguments are trivially valid */
214
215	err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
216	err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
217	err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
218	err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
219	err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
220
221	if (err)
222		return 3;
223
224	/* step 4: ignored */
225
226	if (err)
227		return 4;
228
229	return 0;
230}
231
232/*
233 * Subdevice 1 command.
234 */
235static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
236{
237	pc236_intr_enable(dev);
238
239	return 0;
240}
241
242/*
243 * Subdevice 1 cancel command.
244 */
245static int pc236_intr_cancel(struct comedi_device *dev,
246			     struct comedi_subdevice *s)
247{
248	pc236_intr_disable(dev);
249
250	return 0;
251}
252
253/*
254 * Interrupt service routine.
255 * Based on the comedi_parport driver.
256 */
257static irqreturn_t pc236_interrupt(int irq, void *d)
258{
259	struct comedi_device *dev = d;
260	struct comedi_subdevice *s = dev->read_subdev;
261	int handled;
262
263	handled = pc236_intr_check(dev);
264	if (dev->attached && handled) {
265		comedi_buf_put(s, 0);
266		s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
267		comedi_event(dev, s);
268	}
269	return IRQ_RETVAL(handled);
270}
271
272static int pc236_common_attach(struct comedi_device *dev, unsigned long iobase,
273			       unsigned int irq, unsigned long req_irq_flags)
274{
275	struct comedi_subdevice *s;
276	int ret;
277
278	dev->iobase = iobase;
279
280	ret = comedi_alloc_subdevices(dev, 2);
281	if (ret)
282		return ret;
283
284	s = &dev->subdevices[0];
285	/* digital i/o subdevice (8255) */
286	ret = subdev_8255_init(dev, s, NULL, iobase);
287	if (ret)
288		return ret;
289
290	s = &dev->subdevices[1];
291	dev->read_subdev = s;
292	s->type = COMEDI_SUBD_UNUSED;
293	pc236_intr_disable(dev);
294	if (irq) {
295		if (request_irq(irq, pc236_interrupt, req_irq_flags,
296				dev->board_name, dev) >= 0) {
297			dev->irq = irq;
298			s->type = COMEDI_SUBD_DI;
299			s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
300			s->n_chan = 1;
301			s->maxdata = 1;
302			s->range_table = &range_digital;
303			s->insn_bits = pc236_intr_insn;
304			s->len_chanlist	= 1;
305			s->do_cmdtest = pc236_intr_cmdtest;
306			s->do_cmd = pc236_intr_cmd;
307			s->cancel = pc236_intr_cancel;
308		}
309	}
310
311	return 0;
312}
313
314static int pc236_attach(struct comedi_device *dev, struct comedi_devconfig *it)
315{
316	struct pc236_private *devpriv;
317	int ret;
318
319	if (!DO_ISA)
320		return -EINVAL;
321
322	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
323	if (!devpriv)
324		return -ENOMEM;
325
326	ret = comedi_request_region(dev, it->options[0], 0x4);
327	if (ret)
328		return ret;
329
330	return pc236_common_attach(dev, dev->iobase, it->options[1], 0);
331}
332
333static const struct pc236_board pc236_pci_board = {
334	.name = "pci236",
335	.bustype = pci_bustype,
336};
337
338static int pc236_auto_attach(struct comedi_device *dev,
339				       unsigned long context_unused)
340{
341	struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
342	struct pc236_private *devpriv;
343	unsigned long iobase;
344	int ret;
345
346	if (!DO_PCI)
347		return -EINVAL;
348
349	dev_info(dev->class_dev, "attach pci %s\n", pci_name(pci_dev));
350
351	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
352	if (!devpriv)
353		return -ENOMEM;
354
355	dev->board_ptr = &pc236_pci_board;
356	dev->board_name = pc236_pci_board.name;
357	ret = comedi_pci_enable(dev);
358	if (ret)
359		return ret;
360
361	devpriv->lcr_iobase = pci_resource_start(pci_dev, 1);
362	iobase = pci_resource_start(pci_dev, 2);
363	return pc236_common_attach(dev, iobase, pci_dev->irq, IRQF_SHARED);
364}
365
366static void pc236_detach(struct comedi_device *dev)
367{
368	const struct pc236_board *thisboard = comedi_board(dev);
369
370	if (!thisboard)
371		return;
372	if (is_isa_board(thisboard)) {
373		comedi_legacy_detach(dev);
374	} else if (is_pci_board(thisboard)) {
375		if (dev->irq)
376			free_irq(dev->irq, dev);
377		comedi_pci_disable(dev);
378	}
379}
380
381static const struct pc236_board pc236_isa_boards[] = {
382	{
383		.name = "pc36at",
384		.bustype = isa_bustype,
385	},
386};
387
388static struct comedi_driver amplc_pc236_driver = {
389	.driver_name = "amplc_pc236",
390	.module = THIS_MODULE,
391	.attach = pc236_attach,
392	.auto_attach = pc236_auto_attach,
393	.detach = pc236_detach,
394#if DO_ISA
395	.board_name = &pc236_isa_boards[0].name,
396	.offset = sizeof(struct pc236_board),
397	.num_names = ARRAY_SIZE(pc236_isa_boards),
398#endif
399};
400
401#if DO_PCI
402static const struct pci_device_id pc236_pci_table[] = {
403	{ PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, 0x0009) },
404	{ 0 }
405};
406
407MODULE_DEVICE_TABLE(pci, pc236_pci_table);
408
409static int amplc_pc236_pci_probe(struct pci_dev *dev,
410				 const struct pci_device_id *id)
411{
412	return comedi_pci_auto_config(dev, &amplc_pc236_driver,
413				      id->driver_data);
414}
415
416static struct pci_driver amplc_pc236_pci_driver = {
417	.name = "amplc_pc236",
418	.id_table = pc236_pci_table,
419	.probe = &amplc_pc236_pci_probe,
420	.remove		= comedi_pci_auto_unconfig,
421};
422
423module_comedi_pci_driver(amplc_pc236_driver, amplc_pc236_pci_driver);
424#else
425module_comedi_driver(amplc_pc236_driver);
426#endif
427
428MODULE_AUTHOR("Comedi http://www.comedi.org");
429MODULE_DESCRIPTION("Comedi low-level driver");
430MODULE_LICENSE("GPL");
431