1/*
2 * addi_apci_2032.c
3 * Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code of this module.
4 * Project manager: Eric Stolz
5 *
6 *	ADDI-DATA GmbH
7 *	Dieselstrasse 3
8 *	D-77833 Ottersweier
9 *	Tel: +19(0)7223/9493-0
10 *	Fax: +49(0)7223/9493-92
11 *	http://www.addi-data.com
12 *	info@addi-data.com
13 *
14 * This program is free software; you can redistribute it and/or modify it
15 * under the terms of the GNU General Public License as published by the
16 * Free Software Foundation; either version 2 of the License, or (at your
17 * option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful, but WITHOUT
20 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
22 * more details.
23 */
24
25#include <linux/module.h>
26#include <linux/pci.h>
27#include <linux/interrupt.h>
28#include <linux/slab.h>
29
30#include "../comedidev.h"
31#include "addi_watchdog.h"
32#include "comedi_fc.h"
33
34/*
35 * PCI bar 1 I/O Register map
36 */
37#define APCI2032_DO_REG			0x00
38#define APCI2032_INT_CTRL_REG		0x04
39#define APCI2032_INT_CTRL_VCC_ENA	(1 << 0)
40#define APCI2032_INT_CTRL_CC_ENA	(1 << 1)
41#define APCI2032_INT_STATUS_REG		0x08
42#define APCI2032_INT_STATUS_VCC		(1 << 0)
43#define APCI2032_INT_STATUS_CC		(1 << 1)
44#define APCI2032_STATUS_REG		0x0c
45#define APCI2032_STATUS_IRQ		(1 << 0)
46#define APCI2032_WDOG_REG		0x10
47
48struct apci2032_int_private {
49	spinlock_t spinlock;
50	unsigned int stop_count;
51	bool active;
52	unsigned char enabled_isns;
53};
54
55static int apci2032_do_insn_bits(struct comedi_device *dev,
56				 struct comedi_subdevice *s,
57				 struct comedi_insn *insn,
58				 unsigned int *data)
59{
60	s->state = inl(dev->iobase + APCI2032_DO_REG);
61
62	if (comedi_dio_update_state(s, data))
63		outl(s->state, dev->iobase + APCI2032_DO_REG);
64
65	data[1] = s->state;
66
67	return insn->n;
68}
69
70static int apci2032_int_insn_bits(struct comedi_device *dev,
71				  struct comedi_subdevice *s,
72				  struct comedi_insn *insn,
73				  unsigned int *data)
74{
75	data[1] = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3;
76	return insn->n;
77}
78
79static void apci2032_int_stop(struct comedi_device *dev,
80			      struct comedi_subdevice *s)
81{
82	struct apci2032_int_private *subpriv = s->private;
83
84	subpriv->active = false;
85	subpriv->enabled_isns = 0;
86	outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
87}
88
89static int apci2032_int_cmdtest(struct comedi_device *dev,
90				struct comedi_subdevice *s,
91				struct comedi_cmd *cmd)
92{
93	int err = 0;
94
95	/* Step 1 : check if triggers are trivially valid */
96
97	err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
98	err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
99	err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
100	err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
101	err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
102
103	if (err)
104		return 1;
105
106	/* Step 2a : make sure trigger sources are unique */
107	err |= cfc_check_trigger_is_unique(cmd->stop_src);
108
109	/* Step 2b : and mutually compatible */
110
111	if (err)
112		return 2;
113
114	/* Step 3: check if arguments are trivially valid */
115
116	err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
117	err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
118	err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
119	err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
120	if (cmd->stop_src == TRIG_COUNT)
121		err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
122	else	/* TRIG_NONE */
123		err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
124
125	if (err)
126		return 3;
127
128	/* Step 4: fix up any arguments */
129
130	/* Step 5: check channel list if it exists */
131
132	return 0;
133}
134
135static int apci2032_int_cmd(struct comedi_device *dev,
136			    struct comedi_subdevice *s)
137{
138	struct comedi_cmd *cmd = &s->async->cmd;
139	struct apci2032_int_private *subpriv = s->private;
140	unsigned char enabled_isns;
141	unsigned int n;
142	unsigned long flags;
143
144	enabled_isns = 0;
145	for (n = 0; n < cmd->chanlist_len; n++)
146		enabled_isns |= 1 << CR_CHAN(cmd->chanlist[n]);
147
148	spin_lock_irqsave(&subpriv->spinlock, flags);
149
150	subpriv->enabled_isns = enabled_isns;
151	subpriv->stop_count = cmd->stop_arg;
152	subpriv->active = true;
153	outl(enabled_isns, dev->iobase + APCI2032_INT_CTRL_REG);
154
155	spin_unlock_irqrestore(&subpriv->spinlock, flags);
156
157	return 0;
158}
159
160static int apci2032_int_cancel(struct comedi_device *dev,
161			       struct comedi_subdevice *s)
162{
163	struct apci2032_int_private *subpriv = s->private;
164	unsigned long flags;
165
166	spin_lock_irqsave(&subpriv->spinlock, flags);
167	if (subpriv->active)
168		apci2032_int_stop(dev, s);
169	spin_unlock_irqrestore(&subpriv->spinlock, flags);
170
171	return 0;
172}
173
174static irqreturn_t apci2032_interrupt(int irq, void *d)
175{
176	struct comedi_device *dev = d;
177	struct comedi_subdevice *s = dev->read_subdev;
178	struct comedi_cmd *cmd = &s->async->cmd;
179	struct apci2032_int_private *subpriv;
180	unsigned int val;
181	bool do_event = false;
182
183	if (!dev->attached)
184		return IRQ_NONE;
185
186	/* Check if VCC OR CC interrupt has occurred */
187	val = inl(dev->iobase + APCI2032_STATUS_REG) & APCI2032_STATUS_IRQ;
188	if (!val)
189		return IRQ_NONE;
190
191	subpriv = s->private;
192	spin_lock(&subpriv->spinlock);
193
194	val = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3;
195	/* Disable triggered interrupt sources. */
196	outl(~val & 3, dev->iobase + APCI2032_INT_CTRL_REG);
197	/*
198	 * Note: We don't reenable the triggered interrupt sources because they
199	 * are level-sensitive, hardware error status interrupt sources and
200	 * they'd keep triggering interrupts repeatedly.
201	 */
202
203	if (subpriv->active && (val & subpriv->enabled_isns) != 0) {
204		unsigned short bits = 0;
205		int i;
206
207		/* Bits in scan data correspond to indices in channel list. */
208		for (i = 0; i < cmd->chanlist_len; i++) {
209			unsigned int chan = CR_CHAN(cmd->chanlist[i]);
210
211			if (val & (1 << chan))
212				bits |= (1 << i);
213		}
214
215		if (comedi_buf_put(s, bits)) {
216			s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
217			if (cmd->stop_src == TRIG_COUNT &&
218			    subpriv->stop_count > 0) {
219				subpriv->stop_count--;
220				if (subpriv->stop_count == 0) {
221					/* end of acquisition */
222					s->async->events |= COMEDI_CB_EOA;
223					apci2032_int_stop(dev, s);
224				}
225			}
226		} else {
227			apci2032_int_stop(dev, s);
228			s->async->events |= COMEDI_CB_OVERFLOW;
229		}
230		do_event = true;
231	}
232
233	spin_unlock(&subpriv->spinlock);
234	if (do_event)
235		comedi_event(dev, s);
236
237	return IRQ_HANDLED;
238}
239
240static int apci2032_reset(struct comedi_device *dev)
241{
242	outl(0x0, dev->iobase + APCI2032_DO_REG);
243	outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG);
244
245	addi_watchdog_reset(dev->iobase + APCI2032_WDOG_REG);
246
247	return 0;
248}
249
250static int apci2032_auto_attach(struct comedi_device *dev,
251				unsigned long context_unused)
252{
253	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
254	struct comedi_subdevice *s;
255	int ret;
256
257	ret = comedi_pci_enable(dev);
258	if (ret)
259		return ret;
260	dev->iobase = pci_resource_start(pcidev, 1);
261	apci2032_reset(dev);
262
263	if (pcidev->irq > 0) {
264		ret = request_irq(pcidev->irq, apci2032_interrupt,
265				  IRQF_SHARED, dev->board_name, dev);
266		if (ret == 0)
267			dev->irq = pcidev->irq;
268	}
269
270	ret = comedi_alloc_subdevices(dev, 3);
271	if (ret)
272		return ret;
273
274	/* Initialize the digital output subdevice */
275	s = &dev->subdevices[0];
276	s->type		= COMEDI_SUBD_DO;
277	s->subdev_flags	= SDF_WRITEABLE;
278	s->n_chan	= 32;
279	s->maxdata	= 1;
280	s->range_table	= &range_digital;
281	s->insn_bits	= apci2032_do_insn_bits;
282
283	/* Initialize the watchdog subdevice */
284	s = &dev->subdevices[1];
285	ret = addi_watchdog_init(s, dev->iobase + APCI2032_WDOG_REG);
286	if (ret)
287		return ret;
288
289	/* Initialize the interrupt subdevice */
290	s = &dev->subdevices[2];
291	s->type		= COMEDI_SUBD_DI;
292	s->subdev_flags	= SDF_READABLE;
293	s->n_chan	= 2;
294	s->maxdata	= 1;
295	s->range_table	= &range_digital;
296	s->insn_bits	= apci2032_int_insn_bits;
297	if (dev->irq) {
298		struct apci2032_int_private *subpriv;
299
300		dev->read_subdev = s;
301		subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL);
302		if (!subpriv)
303			return -ENOMEM;
304		spin_lock_init(&subpriv->spinlock);
305		s->private	= subpriv;
306		s->subdev_flags	= SDF_READABLE | SDF_CMD_READ;
307		s->len_chanlist = 2;
308		s->do_cmdtest	= apci2032_int_cmdtest;
309		s->do_cmd	= apci2032_int_cmd;
310		s->cancel	= apci2032_int_cancel;
311	}
312
313	return 0;
314}
315
316static void apci2032_detach(struct comedi_device *dev)
317{
318	if (dev->iobase)
319		apci2032_reset(dev);
320	comedi_pci_detach(dev);
321	if (dev->read_subdev)
322		kfree(dev->read_subdev->private);
323}
324
325static struct comedi_driver apci2032_driver = {
326	.driver_name	= "addi_apci_2032",
327	.module		= THIS_MODULE,
328	.auto_attach	= apci2032_auto_attach,
329	.detach		= apci2032_detach,
330};
331
332static int apci2032_pci_probe(struct pci_dev *dev,
333			      const struct pci_device_id *id)
334{
335	return comedi_pci_auto_config(dev, &apci2032_driver, id->driver_data);
336}
337
338static const struct pci_device_id apci2032_pci_table[] = {
339	{ PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1004) },
340	{ 0 }
341};
342MODULE_DEVICE_TABLE(pci, apci2032_pci_table);
343
344static struct pci_driver apci2032_pci_driver = {
345	.name		= "addi_apci_2032",
346	.id_table	= apci2032_pci_table,
347	.probe		= apci2032_pci_probe,
348	.remove		= comedi_pci_auto_unconfig,
349};
350module_comedi_pci_driver(apci2032_driver, apci2032_pci_driver);
351
352MODULE_AUTHOR("Comedi http://www.comedi.org");
353MODULE_DESCRIPTION("ADDI-DATA APCI-2032, 32 channel DO boards");
354MODULE_LICENSE("GPL");
355