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 
37 struct 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 
43 static 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 
58 static 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 
67 static 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 
77 static 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 
124 static 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 
148 static 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 
162 static 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 
216 static 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 
226 static 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 
292 static 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 
301 static 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 
308 static 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 
314 static const struct pci_device_id apci2032_pci_table[] = {
315 	{ PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1004) },
316 	{ 0 }
317 };
318 MODULE_DEVICE_TABLE(pci, apci2032_pci_table);
319 
320 static 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 };
326 module_comedi_pci_driver(apci2032_driver, apci2032_pci_driver);
327 
328 MODULE_AUTHOR("Comedi https://www.comedi.org");
329 MODULE_DESCRIPTION("ADDI-DATA APCI-2032, 32 channel DO boards");
330 MODULE_LICENSE("GPL");
331