1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * addi_apci_1032.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/*
17 * Driver: addi_apci_1032
18 * Description: ADDI-DATA APCI-1032 Digital Input Board
19 * Author: ADDI-DATA GmbH <info@addi-data.com>,
20 * H Hartley Sweeten <hsweeten@visionengravers.com>
21 * Status: untested
22 * Devices: [ADDI-DATA] APCI-1032 (addi_apci_1032)
23 *
24 * Configuration options:
25 * None; devices are configured automatically.
26 *
27 * This driver models the APCI-1032 as a 32-channel, digital input subdevice
28 * plus an additional digital input subdevice to handle change-of-state (COS)
29 * interrupts (if an interrupt handler can be set up successfully).
30 *
31 * The COS subdevice supports comedi asynchronous read commands.
32 *
33 * Change-Of-State (COS) interrupt configuration:
34 *
35 * Channels 0 to 15 are interruptible. These channels can be configured
36 * to generate interrupts based on AND/OR logic for the desired channels.
37 *
38 * OR logic:
39 * - reacts to rising or falling edges
40 * - interrupt is generated when any enabled channel meets the desired
41 * interrupt condition
42 *
43 * AND logic:
44 * - reacts to changes in level of the selected inputs
45 * - interrupt is generated when all enabled channels meet the desired
46 * interrupt condition
47 * - after an interrupt, a change in level must occur on the selected
48 * inputs to release the IRQ logic
49 *
50 * The COS subdevice must be configured before setting up a comedi
51 * asynchronous command:
52 *
53 * data[0] : INSN_CONFIG_DIGITAL_TRIG
54 * data[1] : trigger number (= 0)
55 * data[2] : configuration operation:
56 * - COMEDI_DIGITAL_TRIG_DISABLE = no interrupts
57 * - COMEDI_DIGITAL_TRIG_ENABLE_EDGES = OR (edge) interrupts
58 * - COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = AND (level) interrupts
59 * data[3] : left-shift for data[4] and data[5]
60 * data[4] : rising-edge/high level channels
61 * data[5] : falling-edge/low level channels
62 */
63
64#include <linux/module.h>
65#include <linux/interrupt.h>
66#include <linux/comedi/comedi_pci.h>
67
68#include "amcc_s5933.h"
69
70/*
71 * I/O Register Map
72 */
73#define APCI1032_DI_REG 0x00
74#define APCI1032_MODE1_REG 0x04
75#define APCI1032_MODE2_REG 0x08
76#define APCI1032_STATUS_REG 0x0c
77#define APCI1032_CTRL_REG 0x10
78#define APCI1032_CTRL_INT_MODE(x) (((x) & 0x1) << 1)
79#define APCI1032_CTRL_INT_OR APCI1032_CTRL_INT_MODE(0)
80#define APCI1032_CTRL_INT_AND APCI1032_CTRL_INT_MODE(1)
81#define APCI1032_CTRL_INT_ENA BIT(2)
82
83struct apci1032_private {
84 unsigned long amcc_iobase; /* base of AMCC I/O registers */
85 unsigned int mode1; /* rising-edge/high level channels */
86 unsigned int mode2; /* falling-edge/low level channels */
87 unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */
88};
89
90static int apci1032_reset(struct comedi_device *dev)
91{
92 /* disable the interrupts */
93 outl(value: 0x0, port: dev->iobase + APCI1032_CTRL_REG);
94 /* Reset the interrupt status register */
95 inl(port: dev->iobase + APCI1032_STATUS_REG);
96 /* Disable the and/or interrupt */
97 outl(value: 0x0, port: dev->iobase + APCI1032_MODE1_REG);
98 outl(value: 0x0, port: dev->iobase + APCI1032_MODE2_REG);
99
100 return 0;
101}
102
103static int apci1032_cos_insn_config(struct comedi_device *dev,
104 struct comedi_subdevice *s,
105 struct comedi_insn *insn,
106 unsigned int *data)
107{
108 struct apci1032_private *devpriv = dev->private;
109 unsigned int shift, oldmask, himask, lomask;
110
111 switch (data[0]) {
112 case INSN_CONFIG_DIGITAL_TRIG:
113 if (data[1] != 0)
114 return -EINVAL;
115 shift = data[3];
116 if (shift < 32) {
117 oldmask = (1U << shift) - 1;
118 himask = data[4] << shift;
119 lomask = data[5] << shift;
120 } else {
121 oldmask = 0xffffffffu;
122 himask = 0;
123 lomask = 0;
124 }
125 switch (data[2]) {
126 case COMEDI_DIGITAL_TRIG_DISABLE:
127 devpriv->ctrl = 0;
128 devpriv->mode1 = 0;
129 devpriv->mode2 = 0;
130 apci1032_reset(dev);
131 break;
132 case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
133 if (devpriv->ctrl != (APCI1032_CTRL_INT_ENA |
134 APCI1032_CTRL_INT_OR)) {
135 /* switching to 'OR' mode */
136 devpriv->ctrl = APCI1032_CTRL_INT_ENA |
137 APCI1032_CTRL_INT_OR;
138 /* wipe old channels */
139 devpriv->mode1 = 0;
140 devpriv->mode2 = 0;
141 } else {
142 /* preserve unspecified channels */
143 devpriv->mode1 &= oldmask;
144 devpriv->mode2 &= oldmask;
145 }
146 /* configure specified channels */
147 devpriv->mode1 |= himask;
148 devpriv->mode2 |= lomask;
149 break;
150 case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS:
151 if (devpriv->ctrl != (APCI1032_CTRL_INT_ENA |
152 APCI1032_CTRL_INT_AND)) {
153 /* switching to 'AND' mode */
154 devpriv->ctrl = APCI1032_CTRL_INT_ENA |
155 APCI1032_CTRL_INT_AND;
156 /* wipe old channels */
157 devpriv->mode1 = 0;
158 devpriv->mode2 = 0;
159 } else {
160 /* preserve unspecified channels */
161 devpriv->mode1 &= oldmask;
162 devpriv->mode2 &= oldmask;
163 }
164 /* configure specified channels */
165 devpriv->mode1 |= himask;
166 devpriv->mode2 |= lomask;
167 break;
168 default:
169 return -EINVAL;
170 }
171 break;
172 default:
173 return -EINVAL;
174 }
175
176 return insn->n;
177}
178
179static int apci1032_cos_insn_bits(struct comedi_device *dev,
180 struct comedi_subdevice *s,
181 struct comedi_insn *insn,
182 unsigned int *data)
183{
184 data[1] = s->state;
185
186 return 0;
187}
188
189static int apci1032_cos_cmdtest(struct comedi_device *dev,
190 struct comedi_subdevice *s,
191 struct comedi_cmd *cmd)
192{
193 int err = 0;
194
195 /* Step 1 : check if triggers are trivially valid */
196
197 err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW);
198 err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_EXT);
199 err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_FOLLOW);
200 err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT);
201 err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_NONE);
202
203 if (err)
204 return 1;
205
206 /* Step 2a : make sure trigger sources are unique */
207 /* Step 2b : and mutually compatible */
208
209 /* Step 3: check if arguments are trivially valid */
210
211 err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0);
212 err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0);
213 err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0);
214 err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg,
215 val: cmd->chanlist_len);
216 err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0);
217
218 if (err)
219 return 3;
220
221 /* Step 4: fix up any arguments */
222
223 /* Step 5: check channel list if it exists */
224
225 return 0;
226}
227
228/*
229 * Change-Of-State (COS) 'do_cmd' operation
230 *
231 * Enable the COS interrupt as configured by apci1032_cos_insn_config().
232 */
233static int apci1032_cos_cmd(struct comedi_device *dev,
234 struct comedi_subdevice *s)
235{
236 struct apci1032_private *devpriv = dev->private;
237
238 if (!devpriv->ctrl) {
239 dev_warn(dev->class_dev,
240 "Interrupts disabled due to mode configuration!\n");
241 return -EINVAL;
242 }
243
244 outl(value: devpriv->mode1, port: dev->iobase + APCI1032_MODE1_REG);
245 outl(value: devpriv->mode2, port: dev->iobase + APCI1032_MODE2_REG);
246 outl(value: devpriv->ctrl, port: dev->iobase + APCI1032_CTRL_REG);
247
248 return 0;
249}
250
251static int apci1032_cos_cancel(struct comedi_device *dev,
252 struct comedi_subdevice *s)
253{
254 return apci1032_reset(dev);
255}
256
257static irqreturn_t apci1032_interrupt(int irq, void *d)
258{
259 struct comedi_device *dev = d;
260 struct apci1032_private *devpriv = dev->private;
261 struct comedi_subdevice *s = dev->read_subdev;
262 unsigned int ctrl;
263 unsigned short val;
264
265 /* check interrupt is from this device */
266 if ((inl(port: devpriv->amcc_iobase + AMCC_OP_REG_INTCSR) &
267 INTCSR_INTR_ASSERTED) == 0)
268 return IRQ_NONE;
269
270 /* check interrupt is enabled */
271 ctrl = inl(port: dev->iobase + APCI1032_CTRL_REG);
272 if ((ctrl & APCI1032_CTRL_INT_ENA) == 0)
273 return IRQ_HANDLED;
274
275 /* disable the interrupt */
276 outl(value: ctrl & ~APCI1032_CTRL_INT_ENA, port: dev->iobase + APCI1032_CTRL_REG);
277
278 s->state = inl(port: dev->iobase + APCI1032_STATUS_REG) & 0xffff;
279 val = s->state;
280 comedi_buf_write_samples(s, data: &val, nsamples: 1);
281 comedi_handle_events(dev, s);
282
283 /* enable the interrupt */
284 outl(value: ctrl, port: dev->iobase + APCI1032_CTRL_REG);
285
286 return IRQ_HANDLED;
287}
288
289static int apci1032_di_insn_bits(struct comedi_device *dev,
290 struct comedi_subdevice *s,
291 struct comedi_insn *insn,
292 unsigned int *data)
293{
294 data[1] = inl(port: dev->iobase + APCI1032_DI_REG);
295
296 return insn->n;
297}
298
299static int apci1032_auto_attach(struct comedi_device *dev,
300 unsigned long context_unused)
301{
302 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
303 struct apci1032_private *devpriv;
304 struct comedi_subdevice *s;
305 int ret;
306
307 devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv));
308 if (!devpriv)
309 return -ENOMEM;
310
311 ret = comedi_pci_enable(dev);
312 if (ret)
313 return ret;
314
315 devpriv->amcc_iobase = pci_resource_start(pcidev, 0);
316 dev->iobase = pci_resource_start(pcidev, 1);
317 apci1032_reset(dev);
318 if (pcidev->irq > 0) {
319 ret = request_irq(irq: pcidev->irq, handler: apci1032_interrupt, IRQF_SHARED,
320 name: dev->board_name, dev);
321 if (ret == 0)
322 dev->irq = pcidev->irq;
323 }
324
325 ret = comedi_alloc_subdevices(dev, num_subdevices: 2);
326 if (ret)
327 return ret;
328
329 /* Allocate and Initialise DI Subdevice Structures */
330 s = &dev->subdevices[0];
331 s->type = COMEDI_SUBD_DI;
332 s->subdev_flags = SDF_READABLE;
333 s->n_chan = 32;
334 s->maxdata = 1;
335 s->range_table = &range_digital;
336 s->insn_bits = apci1032_di_insn_bits;
337
338 /* Change-Of-State (COS) interrupt subdevice */
339 s = &dev->subdevices[1];
340 if (dev->irq) {
341 dev->read_subdev = s;
342 s->type = COMEDI_SUBD_DI;
343 s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
344 s->n_chan = 1;
345 s->maxdata = 1;
346 s->range_table = &range_digital;
347 s->insn_config = apci1032_cos_insn_config;
348 s->insn_bits = apci1032_cos_insn_bits;
349 s->len_chanlist = 1;
350 s->do_cmdtest = apci1032_cos_cmdtest;
351 s->do_cmd = apci1032_cos_cmd;
352 s->cancel = apci1032_cos_cancel;
353 } else {
354 s->type = COMEDI_SUBD_UNUSED;
355 }
356
357 return 0;
358}
359
360static void apci1032_detach(struct comedi_device *dev)
361{
362 if (dev->iobase)
363 apci1032_reset(dev);
364 comedi_pci_detach(dev);
365}
366
367static struct comedi_driver apci1032_driver = {
368 .driver_name = "addi_apci_1032",
369 .module = THIS_MODULE,
370 .auto_attach = apci1032_auto_attach,
371 .detach = apci1032_detach,
372};
373
374static int apci1032_pci_probe(struct pci_dev *dev,
375 const struct pci_device_id *id)
376{
377 return comedi_pci_auto_config(pcidev: dev, driver: &apci1032_driver, context: id->driver_data);
378}
379
380static const struct pci_device_id apci1032_pci_table[] = {
381 { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1003) },
382 { 0 }
383};
384MODULE_DEVICE_TABLE(pci, apci1032_pci_table);
385
386static struct pci_driver apci1032_pci_driver = {
387 .name = "addi_apci_1032",
388 .id_table = apci1032_pci_table,
389 .probe = apci1032_pci_probe,
390 .remove = comedi_pci_auto_unconfig,
391};
392module_comedi_pci_driver(apci1032_driver, apci1032_pci_driver);
393
394MODULE_AUTHOR("Comedi https://www.comedi.org");
395MODULE_DESCRIPTION("ADDI-DATA APCI-1032, 32 channel DI boards");
396MODULE_LICENSE("GPL");
397

source code of linux/drivers/comedi/drivers/addi_apci_1032.c