]>
Commit | Line | Data |
---|---|---|
d02178b7 HS |
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. | |
d02178b7 HS |
23 | */ |
24 | ||
33782dd5 | 25 | #include <linux/pci.h> |
abac8b54 HS |
26 | #include <linux/interrupt.h> |
27 | ||
3d41c443 | 28 | #include "../comedidev.h" |
5b62fe2a | 29 | #include "addi_watchdog.h" |
3d41c443 HS |
30 | #include "comedi_fc.h" |
31 | ||
d02178b7 HS |
32 | /* |
33 | * PCI bar 1 I/O Register map | |
34 | */ | |
7180eb30 HS |
35 | #define APCI2032_DO_REG 0x00 |
36 | #define APCI2032_INT_CTRL_REG 0x04 | |
37 | #define APCI2032_INT_CTRL_VCC_ENA (1 << 0) | |
38 | #define APCI2032_INT_CTRL_CC_ENA (1 << 1) | |
39 | #define APCI2032_INT_STATUS_REG 0x08 | |
40 | #define APCI2032_INT_STATUS_VCC (1 << 0) | |
41 | #define APCI2032_INT_STATUS_CC (1 << 1) | |
42 | #define APCI2032_STATUS_REG 0x0c | |
43 | #define APCI2032_STATUS_IRQ (1 << 0) | |
44 | #define APCI2032_WDOG_REG 0x10 | |
23fb1747 | 45 | |
5c2d4cba IA |
46 | struct apci2032_int_private { |
47 | spinlock_t spinlock; | |
215caceb | 48 | unsigned int stop_count; |
5c2d4cba IA |
49 | bool active; |
50 | unsigned char enabled_isns; | |
51 | }; | |
52 | ||
d02178b7 HS |
53 | static int apci2032_do_insn_bits(struct comedi_device *dev, |
54 | struct comedi_subdevice *s, | |
55 | struct comedi_insn *insn, | |
56 | unsigned int *data) | |
57 | { | |
58 | unsigned int mask = data[0]; | |
59 | unsigned int bits = data[1]; | |
60 | ||
7180eb30 | 61 | s->state = inl(dev->iobase + APCI2032_DO_REG); |
d02178b7 HS |
62 | if (mask) { |
63 | s->state &= ~mask; | |
64 | s->state |= (bits & mask); | |
65 | ||
7180eb30 | 66 | outl(s->state, dev->iobase + APCI2032_DO_REG); |
d02178b7 HS |
67 | } |
68 | ||
69 | data[1] = s->state; | |
70 | ||
71 | return insn->n; | |
72 | } | |
73 | ||
05fcdced HS |
74 | static int apci2032_int_insn_bits(struct comedi_device *dev, |
75 | struct comedi_subdevice *s, | |
76 | struct comedi_insn *insn, | |
77 | unsigned int *data) | |
b3b7dab7 | 78 | { |
b652bd83 | 79 | data[1] = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3; |
05fcdced HS |
80 | return insn->n; |
81 | } | |
b3b7dab7 | 82 | |
5c2d4cba IA |
83 | static void apci2032_int_stop(struct comedi_device *dev, |
84 | struct comedi_subdevice *s) | |
85 | { | |
86 | struct apci2032_int_private *subpriv = s->private; | |
87 | ||
88 | subpriv->active = false; | |
89 | subpriv->enabled_isns = 0; | |
90 | outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG); | |
91 | } | |
92 | ||
215caceb IA |
93 | static bool apci2032_int_start(struct comedi_device *dev, |
94 | struct comedi_subdevice *s, | |
95 | unsigned char enabled_isns) | |
96 | { | |
97 | struct apci2032_int_private *subpriv = s->private; | |
98 | struct comedi_cmd *cmd = &s->async->cmd; | |
99 | bool do_event; | |
100 | ||
101 | subpriv->enabled_isns = enabled_isns; | |
102 | subpriv->stop_count = cmd->stop_arg; | |
103 | if (cmd->stop_src == TRIG_COUNT && subpriv->stop_count == 0) { | |
104 | /* An empty acquisition! */ | |
105 | s->async->events |= COMEDI_CB_EOA; | |
106 | subpriv->active = false; | |
107 | do_event = true; | |
108 | } else { | |
109 | subpriv->active = true; | |
110 | outl(enabled_isns, dev->iobase + APCI2032_INT_CTRL_REG); | |
111 | do_event = false; | |
112 | } | |
113 | ||
114 | return do_event; | |
115 | } | |
116 | ||
05fcdced HS |
117 | static int apci2032_int_cmdtest(struct comedi_device *dev, |
118 | struct comedi_subdevice *s, | |
119 | struct comedi_cmd *cmd) | |
120 | { | |
121 | int err = 0; | |
b3b7dab7 | 122 | |
05fcdced | 123 | /* Step 1 : check if triggers are trivially valid */ |
b3b7dab7 | 124 | |
05fcdced | 125 | err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); |
5c2d4cba IA |
126 | err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT); |
127 | err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); | |
05fcdced | 128 | err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); |
215caceb | 129 | err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
b3b7dab7 | 130 | |
05fcdced HS |
131 | if (err) |
132 | return 1; | |
b3b7dab7 | 133 | |
05fcdced | 134 | /* Step 2a : make sure trigger sources are unique */ |
215caceb IA |
135 | err |= cfc_check_trigger_is_unique(cmd->stop_src); |
136 | ||
05fcdced HS |
137 | /* Step 2b : and mutually compatible */ |
138 | ||
139 | if (err) | |
140 | return 2; | |
141 | ||
142 | /* Step 3: check if arguments are trivially valid */ | |
143 | ||
144 | err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); | |
5c2d4cba | 145 | err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); |
05fcdced | 146 | err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); |
5c2d4cba | 147 | err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); |
215caceb IA |
148 | if (cmd->stop_src == TRIG_NONE) |
149 | err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); | |
05fcdced HS |
150 | |
151 | if (err) | |
152 | return 3; | |
153 | ||
154 | /* step 4: ignored */ | |
155 | ||
156 | if (err) | |
157 | return 4; | |
158 | ||
159 | return 0; | |
b3b7dab7 HS |
160 | } |
161 | ||
05fcdced HS |
162 | static int apci2032_int_cmd(struct comedi_device *dev, |
163 | struct comedi_subdevice *s) | |
d02178b7 | 164 | { |
05fcdced | 165 | struct comedi_cmd *cmd = &s->async->cmd; |
5c2d4cba IA |
166 | struct apci2032_int_private *subpriv = s->private; |
167 | unsigned char enabled_isns; | |
168 | unsigned int n; | |
169 | unsigned long flags; | |
215caceb | 170 | bool do_event; |
5c2d4cba IA |
171 | |
172 | enabled_isns = 0; | |
173 | for (n = 0; n < cmd->chanlist_len; n++) | |
174 | enabled_isns |= 1 << CR_CHAN(cmd->chanlist[n]); | |
05fcdced | 175 | |
5c2d4cba | 176 | spin_lock_irqsave(&subpriv->spinlock, flags); |
215caceb | 177 | do_event = apci2032_int_start(dev, s, enabled_isns); |
5c2d4cba | 178 | spin_unlock_irqrestore(&subpriv->spinlock, flags); |
05fcdced | 179 | |
215caceb IA |
180 | if (do_event) |
181 | comedi_event(dev, s); | |
182 | ||
05fcdced | 183 | return 0; |
d02178b7 | 184 | } |
7180eb30 | 185 | |
05fcdced HS |
186 | static int apci2032_int_cancel(struct comedi_device *dev, |
187 | struct comedi_subdevice *s) | |
d02178b7 | 188 | { |
5c2d4cba IA |
189 | struct apci2032_int_private *subpriv = s->private; |
190 | unsigned long flags; | |
191 | ||
192 | spin_lock_irqsave(&subpriv->spinlock, flags); | |
193 | if (subpriv->active) | |
194 | apci2032_int_stop(dev, s); | |
195 | spin_unlock_irqrestore(&subpriv->spinlock, flags); | |
d02178b7 | 196 | |
05fcdced | 197 | return 0; |
d02178b7 | 198 | } |
317285d7 | 199 | |
05fcdced | 200 | static irqreturn_t apci2032_interrupt(int irq, void *d) |
25adf2cc | 201 | { |
05fcdced HS |
202 | struct comedi_device *dev = d; |
203 | struct comedi_subdevice *s = dev->read_subdev; | |
5c2d4cba | 204 | struct apci2032_int_private *subpriv; |
05fcdced | 205 | unsigned int val; |
5c2d4cba | 206 | bool do_event = false; |
05fcdced | 207 | |
0774c2b5 IA |
208 | if (!dev->attached) |
209 | return IRQ_NONE; | |
210 | ||
05fcdced HS |
211 | /* Check if VCC OR CC interrupt has occurred */ |
212 | val = inl(dev->iobase + APCI2032_STATUS_REG) & APCI2032_STATUS_IRQ; | |
213 | if (!val) | |
214 | return IRQ_NONE; | |
215 | ||
5c2d4cba IA |
216 | subpriv = s->private; |
217 | spin_lock(&subpriv->spinlock); | |
218 | ||
b652bd83 | 219 | val = inl(dev->iobase + APCI2032_INT_STATUS_REG) & 3; |
ef6543db IA |
220 | /* Disable triggered interrupt sources. */ |
221 | outl(~val & 3, dev->iobase + APCI2032_INT_CTRL_REG); | |
222 | /* | |
223 | * Note: We don't reenable the triggered interrupt sources because they | |
224 | * are level-sensitive, hardware error status interrupt sources and | |
225 | * they'd keep triggering interrupts repeatedly. | |
226 | */ | |
05fcdced | 227 | |
5c2d4cba IA |
228 | if (subpriv->active && (val & subpriv->enabled_isns) != 0) { |
229 | unsigned short bits; | |
230 | unsigned int n, len; | |
231 | unsigned int *chanlist; | |
232 | ||
233 | /* Bits in scan data correspond to indices in channel list. */ | |
234 | bits = 0; | |
235 | len = s->async->cmd.chanlist_len; | |
236 | chanlist = &s->async->cmd.chanlist[0]; | |
237 | for (n = 0; n < len; n++) | |
238 | if ((val & (1U << CR_CHAN(chanlist[n]))) != 0) | |
239 | bits |= 1U << n; | |
240 | ||
241 | if (comedi_buf_put(s->async, bits)) { | |
242 | s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; | |
215caceb IA |
243 | if (s->async->cmd.stop_src == TRIG_COUNT && |
244 | subpriv->stop_count > 0) { | |
245 | subpriv->stop_count--; | |
246 | if (subpriv->stop_count == 0) { | |
247 | /* end of acquisition */ | |
248 | s->async->events |= COMEDI_CB_EOA; | |
249 | apci2032_int_stop(dev, s); | |
250 | } | |
251 | } | |
5c2d4cba IA |
252 | } else { |
253 | apci2032_int_stop(dev, s); | |
254 | s->async->events |= COMEDI_CB_OVERFLOW; | |
255 | } | |
256 | do_event = true; | |
257 | } | |
258 | ||
259 | spin_unlock(&subpriv->spinlock); | |
260 | if (do_event) | |
261 | comedi_event(dev, s); | |
05fcdced HS |
262 | |
263 | return IRQ_HANDLED; | |
25adf2cc HS |
264 | } |
265 | ||
791c9792 | 266 | static int apci2032_reset(struct comedi_device *dev) |
25adf2cc | 267 | { |
7180eb30 HS |
268 | outl(0x0, dev->iobase + APCI2032_DO_REG); |
269 | outl(0x0, dev->iobase + APCI2032_INT_CTRL_REG); | |
5b62fe2a HS |
270 | |
271 | addi_watchdog_reset(dev->iobase + APCI2032_WDOG_REG); | |
25adf2cc | 272 | |
25adf2cc HS |
273 | return 0; |
274 | } | |
275 | ||
25adf2cc HS |
276 | static int apci2032_auto_attach(struct comedi_device *dev, |
277 | unsigned long context_unused) | |
278 | { | |
279 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); | |
25adf2cc | 280 | struct comedi_subdevice *s; |
0c33bdd0 | 281 | int ret; |
25adf2cc | 282 | |
818f569f | 283 | ret = comedi_pci_enable(dev); |
25adf2cc HS |
284 | if (ret) |
285 | return ret; | |
70ff4065 | 286 | dev->iobase = pci_resource_start(pcidev, 1); |
0774c2b5 | 287 | apci2032_reset(dev); |
25adf2cc | 288 | |
25adf2cc | 289 | if (pcidev->irq > 0) { |
05fcdced HS |
290 | ret = request_irq(pcidev->irq, apci2032_interrupt, |
291 | IRQF_SHARED, dev->board_name, dev); | |
25adf2cc HS |
292 | if (ret == 0) |
293 | dev->irq = pcidev->irq; | |
294 | } | |
295 | ||
05fcdced | 296 | ret = comedi_alloc_subdevices(dev, 3); |
25adf2cc HS |
297 | if (ret) |
298 | return ret; | |
299 | ||
0c33bdd0 | 300 | /* Initialize the digital output subdevice */ |
25adf2cc | 301 | s = &dev->subdevices[0]; |
cf110882 HS |
302 | s->type = COMEDI_SUBD_DO; |
303 | s->subdev_flags = SDF_WRITEABLE; | |
304 | s->n_chan = 32; | |
305 | s->maxdata = 1; | |
306 | s->range_table = &range_digital; | |
cf110882 | 307 | s->insn_bits = apci2032_do_insn_bits; |
25adf2cc | 308 | |
0c33bdd0 HS |
309 | /* Initialize the watchdog subdevice */ |
310 | s = &dev->subdevices[1]; | |
5b62fe2a HS |
311 | ret = addi_watchdog_init(s, dev->iobase + APCI2032_WDOG_REG); |
312 | if (ret) | |
313 | return ret; | |
25adf2cc | 314 | |
05fcdced HS |
315 | /* Initialize the interrupt subdevice */ |
316 | s = &dev->subdevices[2]; | |
5f6c2a95 IA |
317 | s->type = COMEDI_SUBD_DI; |
318 | s->subdev_flags = SDF_READABLE; | |
319 | s->n_chan = 2; | |
320 | s->maxdata = 1; | |
321 | s->range_table = &range_digital; | |
322 | s->insn_bits = apci2032_int_insn_bits; | |
05fcdced | 323 | if (dev->irq) { |
5c2d4cba IA |
324 | struct apci2032_int_private *subpriv; |
325 | ||
05fcdced | 326 | dev->read_subdev = s; |
5c2d4cba IA |
327 | subpriv = kzalloc(sizeof(*subpriv), GFP_KERNEL); |
328 | if (!subpriv) | |
329 | return -ENOMEM; | |
330 | spin_lock_init(&subpriv->spinlock); | |
331 | s->private = subpriv; | |
b82fe57c | 332 | s->subdev_flags = SDF_READABLE | SDF_CMD_READ; |
5c2d4cba | 333 | s->len_chanlist = 2; |
05fcdced HS |
334 | s->do_cmdtest = apci2032_int_cmdtest; |
335 | s->do_cmd = apci2032_int_cmd; | |
336 | s->cancel = apci2032_int_cancel; | |
05fcdced HS |
337 | } |
338 | ||
25adf2cc HS |
339 | return 0; |
340 | } | |
341 | ||
342 | static void apci2032_detach(struct comedi_device *dev) | |
343 | { | |
dce10abc HS |
344 | if (dev->iobase) |
345 | apci2032_reset(dev); | |
346 | if (dev->irq) | |
347 | free_irq(dev->irq, dev); | |
5c2d4cba IA |
348 | if (dev->read_subdev) |
349 | kfree(dev->read_subdev->private); | |
7f072f54 | 350 | comedi_pci_disable(dev); |
25adf2cc HS |
351 | } |
352 | ||
20a22b70 HS |
353 | static struct comedi_driver apci2032_driver = { |
354 | .driver_name = "addi_apci_2032", | |
355 | .module = THIS_MODULE, | |
25adf2cc HS |
356 | .auto_attach = apci2032_auto_attach, |
357 | .detach = apci2032_detach, | |
20a22b70 HS |
358 | }; |
359 | ||
a690b7e5 | 360 | static int apci2032_pci_probe(struct pci_dev *dev, |
b8f4ac23 | 361 | const struct pci_device_id *id) |
20a22b70 | 362 | { |
b8f4ac23 | 363 | return comedi_pci_auto_config(dev, &apci2032_driver, id->driver_data); |
20a22b70 HS |
364 | } |
365 | ||
20a22b70 | 366 | static DEFINE_PCI_DEVICE_TABLE(apci2032_pci_table) = { |
317285d7 HS |
367 | { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1004) }, |
368 | { 0 } | |
369 | }; | |
20a22b70 | 370 | MODULE_DEVICE_TABLE(pci, apci2032_pci_table); |
317285d7 | 371 | |
20a22b70 HS |
372 | static struct pci_driver apci2032_pci_driver = { |
373 | .name = "addi_apci_2032", | |
374 | .id_table = apci2032_pci_table, | |
375 | .probe = apci2032_pci_probe, | |
9901a4d7 | 376 | .remove = comedi_pci_auto_unconfig, |
20a22b70 HS |
377 | }; |
378 | module_comedi_pci_driver(apci2032_driver, apci2032_pci_driver); | |
90f703d3 AT |
379 | |
380 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
381 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
382 | MODULE_LICENSE("GPL"); |