GNU Linux-libre 4.19.245-gnu1
[releases.git] / drivers / staging / comedi / drivers / addi_apci_2032.c
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
20 #include "../comedi_pci.h"
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 http://www.comedi.org");
329 MODULE_DESCRIPTION("ADDI-DATA APCI-2032, 32 channel DO boards");
330 MODULE_LICENSE("GPL");