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