1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * addi_apci_1564.c |
4 | * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. |
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 | |
15 | /* |
16 | * Driver: addi_apci_1564 |
17 | * Description: ADDI-DATA APCI-1564 Digital I/O board |
18 | * Devices: [ADDI-DATA] APCI-1564 (addi_apci_1564) |
19 | * Author: H Hartley Sweeten <hsweeten@visionengravers.com> |
20 | * Updated: Thu, 02 Jun 2016 13:12:46 -0700 |
21 | * Status: untested |
22 | * |
23 | * Configuration Options: not applicable, uses comedi PCI auto config |
24 | * |
25 | * This board has the following features: |
26 | * - 32 optically isolated digital inputs (24V), 16 of which can |
27 | * generate change-of-state (COS) interrupts (channels 4 to 19) |
28 | * - 32 optically isolated digital outputs (10V to 36V) |
29 | * - 1 8-bit watchdog for resetting the outputs |
30 | * - 1 12-bit timer |
31 | * - 3 32-bit counters |
32 | * - 2 diagnostic inputs |
33 | * |
34 | * The COS, timer, and counter subdevices all use the dev->read_subdev to |
35 | * return the interrupt status. The sample data is updated and returned when |
36 | * any of these subdevices generate an interrupt. The sample data format is: |
37 | * |
38 | * Bit Description |
39 | * ----- ------------------------------------------ |
40 | * 31 COS interrupt |
41 | * 30 timer interrupt |
42 | * 29 counter 2 interrupt |
43 | * 28 counter 1 interrupt |
44 | * 27 counter 0 interrupt |
45 | * 26:20 not used |
46 | * 19:4 COS digital input state (channels 19 to 4) |
47 | * 3:0 not used |
48 | * |
49 | * The COS interrupts must be configured using an INSN_CONFIG_DIGITAL_TRIG |
50 | * instruction before they can be enabled by an async command. The COS |
51 | * interrupts will stay active until canceled. |
52 | * |
53 | * The timer subdevice does not use an async command. All control is handled |
54 | * by the (*insn_config). |
55 | * |
56 | * FIXME: The format of the ADDI_TCW_TIMEBASE_REG is not descibed in the |
57 | * datasheet I have. The INSN_CONFIG_SET_CLOCK_SRC currently just writes |
58 | * the raw data[1] to this register along with the raw data[2] value to the |
59 | * ADDI_TCW_RELOAD_REG. If anyone tests this and can determine the actual |
60 | * timebase/reload operation please let me know. |
61 | * |
62 | * The counter subdevice also does not use an async command. All control is |
63 | * handled by the (*insn_config). |
64 | * |
65 | * FIXME: The operation of the counters is not really described in the |
66 | * datasheet I have. The (*insn_config) needs more work. |
67 | */ |
68 | |
69 | #include <linux/module.h> |
70 | #include <linux/interrupt.h> |
71 | #include <linux/comedi/comedi_pci.h> |
72 | |
73 | #include "addi_tcw.h" |
74 | #include "addi_watchdog.h" |
75 | |
76 | /* |
77 | * PCI BAR 0 |
78 | * |
79 | * PLD Revision 1.0 I/O Mapping |
80 | * 0x00 93C76 EEPROM |
81 | * 0x04 - 0x18 Timer 12-Bit |
82 | * |
83 | * PLD Revision 2.x I/O Mapping |
84 | * 0x00 93C76 EEPROM |
85 | * 0x04 - 0x14 Digital Input |
86 | * 0x18 - 0x25 Digital Output |
87 | * 0x28 - 0x44 Watchdog 8-Bit |
88 | * 0x48 - 0x64 Timer 12-Bit |
89 | */ |
90 | #define APCI1564_EEPROM_REG 0x00 |
91 | #define APCI1564_EEPROM_VCC_STATUS BIT(8) |
92 | #define APCI1564_EEPROM_TO_REV(x) (((x) >> 4) & 0xf) |
93 | #define APCI1564_EEPROM_DI BIT(3) |
94 | #define APCI1564_EEPROM_DO BIT(2) |
95 | #define APCI1564_EEPROM_CS BIT(1) |
96 | #define APCI1564_EEPROM_CLK BIT(0) |
97 | #define APCI1564_REV1_TIMER_IOBASE 0x04 |
98 | #define APCI1564_REV2_MAIN_IOBASE 0x04 |
99 | #define APCI1564_REV2_TIMER_IOBASE 0x48 |
100 | |
101 | /* |
102 | * PCI BAR 1 |
103 | * |
104 | * PLD Revision 1.0 I/O Mapping |
105 | * 0x00 - 0x10 Digital Input |
106 | * 0x14 - 0x20 Digital Output |
107 | * 0x24 - 0x3c Watchdog 8-Bit |
108 | * |
109 | * PLD Revision 2.x I/O Mapping |
110 | * 0x00 Counter_0 |
111 | * 0x20 Counter_1 |
112 | * 0x30 Counter_3 |
113 | */ |
114 | #define APCI1564_REV1_MAIN_IOBASE 0x00 |
115 | |
116 | /* |
117 | * dev->iobase Register Map |
118 | * PLD Revision 1.0 - PCI BAR 1 + 0x00 |
119 | * PLD Revision 2.x - PCI BAR 0 + 0x04 |
120 | */ |
121 | #define APCI1564_DI_REG 0x00 |
122 | #define APCI1564_DI_INT_MODE1_REG 0x04 |
123 | #define APCI1564_DI_INT_MODE2_REG 0x08 |
124 | #define APCI1564_DI_INT_MODE_MASK 0x000ffff0 /* chans [19:4] */ |
125 | #define APCI1564_DI_INT_STATUS_REG 0x0c |
126 | #define APCI1564_DI_IRQ_REG 0x10 |
127 | #define APCI1564_DI_IRQ_ENA BIT(2) |
128 | #define APCI1564_DI_IRQ_MODE BIT(1) /* 1=AND, 0=OR */ |
129 | #define APCI1564_DO_REG 0x14 |
130 | #define APCI1564_DO_INT_CTRL_REG 0x18 |
131 | #define APCI1564_DO_INT_CTRL_CC_INT_ENA BIT(1) |
132 | #define APCI1564_DO_INT_CTRL_VCC_INT_ENA BIT(0) |
133 | #define APCI1564_DO_INT_STATUS_REG 0x1c |
134 | #define APCI1564_DO_INT_STATUS_CC BIT(1) |
135 | #define APCI1564_DO_INT_STATUS_VCC BIT(0) |
136 | #define APCI1564_DO_IRQ_REG 0x20 |
137 | #define APCI1564_DO_IRQ_INTR BIT(0) |
138 | #define APCI1564_WDOG_IOBASE 0x24 |
139 | |
140 | /* |
141 | * devpriv->timer Register Map (see addi_tcw.h for register/bit defines) |
142 | * PLD Revision 1.0 - PCI BAR 0 + 0x04 |
143 | * PLD Revision 2.x - PCI BAR 0 + 0x48 |
144 | */ |
145 | |
146 | /* |
147 | * devpriv->counters Register Map (see addi_tcw.h for register/bit defines) |
148 | * PLD Revision 2.x - PCI BAR 1 + 0x00 |
149 | */ |
150 | #define APCI1564_COUNTER(x) ((x) * 0x20) |
151 | |
152 | /* |
153 | * The dev->read_subdev is used to return the interrupt events along with |
154 | * the state of the interrupt capable inputs. |
155 | */ |
156 | #define APCI1564_EVENT_COS BIT(31) |
157 | #define APCI1564_EVENT_TIMER BIT(30) |
158 | #define APCI1564_EVENT_COUNTER(x) BIT(27 + (x)) /* counter 0-2 */ |
159 | #define APCI1564_EVENT_MASK 0xfff0000f /* all but [19:4] */ |
160 | |
161 | struct apci1564_private { |
162 | unsigned long eeprom; /* base address of EEPROM register */ |
163 | unsigned long timer; /* base address of 12-bit timer */ |
164 | unsigned long counters; /* base address of 32-bit counters */ |
165 | unsigned int mode1; /* rising-edge/high level channels */ |
166 | unsigned int mode2; /* falling-edge/low level channels */ |
167 | unsigned int ctrl; /* interrupt mode OR (edge) . AND (level) */ |
168 | }; |
169 | |
170 | static int apci1564_reset(struct comedi_device *dev) |
171 | { |
172 | struct apci1564_private *devpriv = dev->private; |
173 | |
174 | /* Disable the input interrupts and reset status register */ |
175 | outl(value: 0x0, port: dev->iobase + APCI1564_DI_IRQ_REG); |
176 | inl(port: dev->iobase + APCI1564_DI_INT_STATUS_REG); |
177 | outl(value: 0x0, port: dev->iobase + APCI1564_DI_INT_MODE1_REG); |
178 | outl(value: 0x0, port: dev->iobase + APCI1564_DI_INT_MODE2_REG); |
179 | |
180 | /* Reset the output channels and disable interrupts */ |
181 | outl(value: 0x0, port: dev->iobase + APCI1564_DO_REG); |
182 | outl(value: 0x0, port: dev->iobase + APCI1564_DO_INT_CTRL_REG); |
183 | |
184 | /* Reset the watchdog registers */ |
185 | addi_watchdog_reset(iobase: dev->iobase + APCI1564_WDOG_IOBASE); |
186 | |
187 | /* Reset the timer registers */ |
188 | outl(value: 0x0, port: devpriv->timer + ADDI_TCW_CTRL_REG); |
189 | outl(value: 0x0, port: devpriv->timer + ADDI_TCW_RELOAD_REG); |
190 | |
191 | if (devpriv->counters) { |
192 | unsigned long iobase = devpriv->counters + ADDI_TCW_CTRL_REG; |
193 | |
194 | /* Reset the counter registers */ |
195 | outl(value: 0x0, port: iobase + APCI1564_COUNTER(0)); |
196 | outl(value: 0x0, port: iobase + APCI1564_COUNTER(1)); |
197 | outl(value: 0x0, port: iobase + APCI1564_COUNTER(2)); |
198 | } |
199 | |
200 | return 0; |
201 | } |
202 | |
203 | static irqreturn_t apci1564_interrupt(int irq, void *d) |
204 | { |
205 | struct comedi_device *dev = d; |
206 | struct apci1564_private *devpriv = dev->private; |
207 | struct comedi_subdevice *s = dev->read_subdev; |
208 | unsigned int status; |
209 | unsigned int ctrl; |
210 | unsigned int chan; |
211 | |
212 | s->state &= ~APCI1564_EVENT_MASK; |
213 | |
214 | status = inl(port: dev->iobase + APCI1564_DI_IRQ_REG); |
215 | if (status & APCI1564_DI_IRQ_ENA) { |
216 | /* get the COS interrupt state and set the event flag */ |
217 | s->state = inl(port: dev->iobase + APCI1564_DI_INT_STATUS_REG); |
218 | s->state &= APCI1564_DI_INT_MODE_MASK; |
219 | s->state |= APCI1564_EVENT_COS; |
220 | |
221 | /* clear the interrupt */ |
222 | outl(value: status & ~APCI1564_DI_IRQ_ENA, |
223 | port: dev->iobase + APCI1564_DI_IRQ_REG); |
224 | outl(value: status, port: dev->iobase + APCI1564_DI_IRQ_REG); |
225 | } |
226 | |
227 | status = inl(port: devpriv->timer + ADDI_TCW_IRQ_REG); |
228 | if (status & ADDI_TCW_IRQ) { |
229 | s->state |= APCI1564_EVENT_TIMER; |
230 | |
231 | /* clear the interrupt */ |
232 | ctrl = inl(port: devpriv->timer + ADDI_TCW_CTRL_REG); |
233 | outl(value: 0x0, port: devpriv->timer + ADDI_TCW_CTRL_REG); |
234 | outl(value: ctrl, port: devpriv->timer + ADDI_TCW_CTRL_REG); |
235 | } |
236 | |
237 | if (devpriv->counters) { |
238 | for (chan = 0; chan < 3; chan++) { |
239 | unsigned long iobase; |
240 | |
241 | iobase = devpriv->counters + APCI1564_COUNTER(chan); |
242 | |
243 | status = inl(port: iobase + ADDI_TCW_IRQ_REG); |
244 | if (status & ADDI_TCW_IRQ) { |
245 | s->state |= APCI1564_EVENT_COUNTER(chan); |
246 | |
247 | /* clear the interrupt */ |
248 | ctrl = inl(port: iobase + ADDI_TCW_CTRL_REG); |
249 | outl(value: 0x0, port: iobase + ADDI_TCW_CTRL_REG); |
250 | outl(value: ctrl, port: iobase + ADDI_TCW_CTRL_REG); |
251 | } |
252 | } |
253 | } |
254 | |
255 | if (s->state & APCI1564_EVENT_MASK) { |
256 | comedi_buf_write_samples(s, data: &s->state, nsamples: 1); |
257 | comedi_handle_events(dev, s); |
258 | } |
259 | |
260 | return IRQ_HANDLED; |
261 | } |
262 | |
263 | static int apci1564_di_insn_bits(struct comedi_device *dev, |
264 | struct comedi_subdevice *s, |
265 | struct comedi_insn *insn, |
266 | unsigned int *data) |
267 | { |
268 | data[1] = inl(port: dev->iobase + APCI1564_DI_REG); |
269 | |
270 | return insn->n; |
271 | } |
272 | |
273 | static int apci1564_do_insn_bits(struct comedi_device *dev, |
274 | struct comedi_subdevice *s, |
275 | struct comedi_insn *insn, |
276 | unsigned int *data) |
277 | { |
278 | s->state = inl(port: dev->iobase + APCI1564_DO_REG); |
279 | |
280 | if (comedi_dio_update_state(s, data)) |
281 | outl(value: s->state, port: dev->iobase + APCI1564_DO_REG); |
282 | |
283 | data[1] = s->state; |
284 | |
285 | return insn->n; |
286 | } |
287 | |
288 | static int apci1564_diag_insn_bits(struct comedi_device *dev, |
289 | struct comedi_subdevice *s, |
290 | struct comedi_insn *insn, |
291 | unsigned int *data) |
292 | { |
293 | data[1] = inl(port: dev->iobase + APCI1564_DO_INT_STATUS_REG) & 3; |
294 | |
295 | return insn->n; |
296 | } |
297 | |
298 | /* |
299 | * Change-Of-State (COS) interrupt configuration |
300 | * |
301 | * Channels 4 to 19 are interruptible. These channels can be configured |
302 | * to generate interrupts based on AND/OR logic for the desired channels. |
303 | * |
304 | * OR logic |
305 | * - reacts to rising or falling edges |
306 | * - interrupt is generated when any enabled channel |
307 | * meet the desired interrupt condition |
308 | * |
309 | * AND logic |
310 | * - reacts to changes in level of the selected inputs |
311 | * - interrupt is generated when all enabled channels |
312 | * meet the desired interrupt condition |
313 | * - after an interrupt, a change in level must occur on |
314 | * the selected inputs to release the IRQ logic |
315 | * |
316 | * The COS interrupt must be configured before it can be enabled. |
317 | * |
318 | * data[0] : INSN_CONFIG_DIGITAL_TRIG |
319 | * data[1] : trigger number (= 0) |
320 | * data[2] : configuration operation: |
321 | * COMEDI_DIGITAL_TRIG_DISABLE = no interrupts |
322 | * COMEDI_DIGITAL_TRIG_ENABLE_EDGES = OR (edge) interrupts |
323 | * COMEDI_DIGITAL_TRIG_ENABLE_LEVELS = AND (level) interrupts |
324 | * data[3] : left-shift for data[4] and data[5] |
325 | * data[4] : rising-edge/high level channels |
326 | * data[5] : falling-edge/low level channels |
327 | */ |
328 | static int apci1564_cos_insn_config(struct comedi_device *dev, |
329 | struct comedi_subdevice *s, |
330 | struct comedi_insn *insn, |
331 | unsigned int *data) |
332 | { |
333 | struct apci1564_private *devpriv = dev->private; |
334 | unsigned int shift, oldmask, himask, lomask; |
335 | |
336 | switch (data[0]) { |
337 | case INSN_CONFIG_DIGITAL_TRIG: |
338 | if (data[1] != 0) |
339 | return -EINVAL; |
340 | shift = data[3]; |
341 | if (shift < 32) { |
342 | oldmask = (1U << shift) - 1; |
343 | himask = data[4] << shift; |
344 | lomask = data[5] << shift; |
345 | } else { |
346 | oldmask = 0xffffffffu; |
347 | himask = 0; |
348 | lomask = 0; |
349 | } |
350 | switch (data[2]) { |
351 | case COMEDI_DIGITAL_TRIG_DISABLE: |
352 | devpriv->ctrl = 0; |
353 | devpriv->mode1 = 0; |
354 | devpriv->mode2 = 0; |
355 | outl(value: 0x0, port: dev->iobase + APCI1564_DI_IRQ_REG); |
356 | inl(port: dev->iobase + APCI1564_DI_INT_STATUS_REG); |
357 | outl(value: 0x0, port: dev->iobase + APCI1564_DI_INT_MODE1_REG); |
358 | outl(value: 0x0, port: dev->iobase + APCI1564_DI_INT_MODE2_REG); |
359 | break; |
360 | case COMEDI_DIGITAL_TRIG_ENABLE_EDGES: |
361 | if (devpriv->ctrl != APCI1564_DI_IRQ_ENA) { |
362 | /* switching to 'OR' mode */ |
363 | devpriv->ctrl = APCI1564_DI_IRQ_ENA; |
364 | /* wipe old channels */ |
365 | devpriv->mode1 = 0; |
366 | devpriv->mode2 = 0; |
367 | } else { |
368 | /* preserve unspecified channels */ |
369 | devpriv->mode1 &= oldmask; |
370 | devpriv->mode2 &= oldmask; |
371 | } |
372 | /* configure specified channels */ |
373 | devpriv->mode1 |= himask; |
374 | devpriv->mode2 |= lomask; |
375 | break; |
376 | case COMEDI_DIGITAL_TRIG_ENABLE_LEVELS: |
377 | if (devpriv->ctrl != (APCI1564_DI_IRQ_ENA | |
378 | APCI1564_DI_IRQ_MODE)) { |
379 | /* switching to 'AND' mode */ |
380 | devpriv->ctrl = APCI1564_DI_IRQ_ENA | |
381 | APCI1564_DI_IRQ_MODE; |
382 | /* wipe old channels */ |
383 | devpriv->mode1 = 0; |
384 | devpriv->mode2 = 0; |
385 | } else { |
386 | /* preserve unspecified channels */ |
387 | devpriv->mode1 &= oldmask; |
388 | devpriv->mode2 &= oldmask; |
389 | } |
390 | /* configure specified channels */ |
391 | devpriv->mode1 |= himask; |
392 | devpriv->mode2 |= lomask; |
393 | break; |
394 | default: |
395 | return -EINVAL; |
396 | } |
397 | |
398 | /* ensure the mode bits are in-range for channels [19:4] */ |
399 | devpriv->mode1 &= APCI1564_DI_INT_MODE_MASK; |
400 | devpriv->mode2 &= APCI1564_DI_INT_MODE_MASK; |
401 | break; |
402 | default: |
403 | return -EINVAL; |
404 | } |
405 | return insn->n; |
406 | } |
407 | |
408 | static int apci1564_cos_insn_bits(struct comedi_device *dev, |
409 | struct comedi_subdevice *s, |
410 | struct comedi_insn *insn, |
411 | unsigned int *data) |
412 | { |
413 | data[1] = s->state; |
414 | |
415 | return 0; |
416 | } |
417 | |
418 | static int apci1564_cos_cmdtest(struct comedi_device *dev, |
419 | struct comedi_subdevice *s, |
420 | struct comedi_cmd *cmd) |
421 | { |
422 | int err = 0; |
423 | |
424 | /* Step 1 : check if triggers are trivially valid */ |
425 | |
426 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
427 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_EXT); |
428 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_FOLLOW); |
429 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
430 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_NONE); |
431 | |
432 | if (err) |
433 | return 1; |
434 | |
435 | /* Step 2a : make sure trigger sources are unique */ |
436 | /* Step 2b : and mutually compatible */ |
437 | |
438 | /* Step 3: check if arguments are trivially valid */ |
439 | |
440 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
441 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
442 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
443 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
444 | val: cmd->chanlist_len); |
445 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
446 | |
447 | if (err) |
448 | return 3; |
449 | |
450 | /* Step 4: fix up any arguments */ |
451 | |
452 | /* Step 5: check channel list if it exists */ |
453 | |
454 | return 0; |
455 | } |
456 | |
457 | /* |
458 | * Change-Of-State (COS) 'do_cmd' operation |
459 | * |
460 | * Enable the COS interrupt as configured by apci1564_cos_insn_config(). |
461 | */ |
462 | static int apci1564_cos_cmd(struct comedi_device *dev, |
463 | struct comedi_subdevice *s) |
464 | { |
465 | struct apci1564_private *devpriv = dev->private; |
466 | |
467 | if (!devpriv->ctrl && !(devpriv->mode1 || devpriv->mode2)) { |
468 | dev_warn(dev->class_dev, |
469 | "Interrupts disabled due to mode configuration!\n" ); |
470 | return -EINVAL; |
471 | } |
472 | |
473 | outl(value: devpriv->mode1, port: dev->iobase + APCI1564_DI_INT_MODE1_REG); |
474 | outl(value: devpriv->mode2, port: dev->iobase + APCI1564_DI_INT_MODE2_REG); |
475 | outl(value: devpriv->ctrl, port: dev->iobase + APCI1564_DI_IRQ_REG); |
476 | |
477 | return 0; |
478 | } |
479 | |
480 | static int apci1564_cos_cancel(struct comedi_device *dev, |
481 | struct comedi_subdevice *s) |
482 | { |
483 | outl(value: 0x0, port: dev->iobase + APCI1564_DI_IRQ_REG); |
484 | inl(port: dev->iobase + APCI1564_DI_INT_STATUS_REG); |
485 | outl(value: 0x0, port: dev->iobase + APCI1564_DI_INT_MODE1_REG); |
486 | outl(value: 0x0, port: dev->iobase + APCI1564_DI_INT_MODE2_REG); |
487 | |
488 | return 0; |
489 | } |
490 | |
491 | static int apci1564_timer_insn_config(struct comedi_device *dev, |
492 | struct comedi_subdevice *s, |
493 | struct comedi_insn *insn, |
494 | unsigned int *data) |
495 | { |
496 | struct apci1564_private *devpriv = dev->private; |
497 | unsigned int val; |
498 | |
499 | switch (data[0]) { |
500 | case INSN_CONFIG_ARM: |
501 | if (data[1] > s->maxdata) |
502 | return -EINVAL; |
503 | outl(value: data[1], port: devpriv->timer + ADDI_TCW_RELOAD_REG); |
504 | outl(ADDI_TCW_CTRL_IRQ_ENA | ADDI_TCW_CTRL_TIMER_ENA, |
505 | port: devpriv->timer + ADDI_TCW_CTRL_REG); |
506 | break; |
507 | case INSN_CONFIG_DISARM: |
508 | outl(value: 0x0, port: devpriv->timer + ADDI_TCW_CTRL_REG); |
509 | break; |
510 | case INSN_CONFIG_GET_COUNTER_STATUS: |
511 | data[1] = 0; |
512 | val = inl(port: devpriv->timer + ADDI_TCW_CTRL_REG); |
513 | if (val & ADDI_TCW_CTRL_IRQ_ENA) |
514 | data[1] |= COMEDI_COUNTER_ARMED; |
515 | if (val & ADDI_TCW_CTRL_TIMER_ENA) |
516 | data[1] |= COMEDI_COUNTER_COUNTING; |
517 | val = inl(port: devpriv->timer + ADDI_TCW_STATUS_REG); |
518 | if (val & ADDI_TCW_STATUS_OVERFLOW) |
519 | data[1] |= COMEDI_COUNTER_TERMINAL_COUNT; |
520 | data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING | |
521 | COMEDI_COUNTER_TERMINAL_COUNT; |
522 | break; |
523 | case INSN_CONFIG_SET_CLOCK_SRC: |
524 | if (data[2] > s->maxdata) |
525 | return -EINVAL; |
526 | outl(value: data[1], port: devpriv->timer + ADDI_TCW_TIMEBASE_REG); |
527 | outl(value: data[2], port: devpriv->timer + ADDI_TCW_RELOAD_REG); |
528 | break; |
529 | case INSN_CONFIG_GET_CLOCK_SRC: |
530 | data[1] = inl(port: devpriv->timer + ADDI_TCW_TIMEBASE_REG); |
531 | data[2] = inl(port: devpriv->timer + ADDI_TCW_RELOAD_REG); |
532 | break; |
533 | default: |
534 | return -EINVAL; |
535 | } |
536 | |
537 | return insn->n; |
538 | } |
539 | |
540 | static int apci1564_timer_insn_write(struct comedi_device *dev, |
541 | struct comedi_subdevice *s, |
542 | struct comedi_insn *insn, |
543 | unsigned int *data) |
544 | { |
545 | struct apci1564_private *devpriv = dev->private; |
546 | |
547 | /* just write the last to the reload register */ |
548 | if (insn->n) { |
549 | unsigned int val = data[insn->n - 1]; |
550 | |
551 | outl(value: val, port: devpriv->timer + ADDI_TCW_RELOAD_REG); |
552 | } |
553 | |
554 | return insn->n; |
555 | } |
556 | |
557 | static int apci1564_timer_insn_read(struct comedi_device *dev, |
558 | struct comedi_subdevice *s, |
559 | struct comedi_insn *insn, |
560 | unsigned int *data) |
561 | { |
562 | struct apci1564_private *devpriv = dev->private; |
563 | int i; |
564 | |
565 | /* return the actual value of the timer */ |
566 | for (i = 0; i < insn->n; i++) |
567 | data[i] = inl(port: devpriv->timer + ADDI_TCW_VAL_REG); |
568 | |
569 | return insn->n; |
570 | } |
571 | |
572 | static int apci1564_counter_insn_config(struct comedi_device *dev, |
573 | struct comedi_subdevice *s, |
574 | struct comedi_insn *insn, |
575 | unsigned int *data) |
576 | { |
577 | struct apci1564_private *devpriv = dev->private; |
578 | unsigned int chan = CR_CHAN(insn->chanspec); |
579 | unsigned long iobase = devpriv->counters + APCI1564_COUNTER(chan); |
580 | unsigned int val; |
581 | |
582 | switch (data[0]) { |
583 | case INSN_CONFIG_ARM: |
584 | val = inl(port: iobase + ADDI_TCW_CTRL_REG); |
585 | val |= ADDI_TCW_CTRL_IRQ_ENA | ADDI_TCW_CTRL_CNTR_ENA; |
586 | outl(value: data[1], port: iobase + ADDI_TCW_RELOAD_REG); |
587 | outl(value: val, port: iobase + ADDI_TCW_CTRL_REG); |
588 | break; |
589 | case INSN_CONFIG_DISARM: |
590 | val = inl(port: iobase + ADDI_TCW_CTRL_REG); |
591 | val &= ~(ADDI_TCW_CTRL_IRQ_ENA | ADDI_TCW_CTRL_CNTR_ENA); |
592 | outl(value: val, port: iobase + ADDI_TCW_CTRL_REG); |
593 | break; |
594 | case INSN_CONFIG_SET_COUNTER_MODE: |
595 | /* |
596 | * FIXME: The counter operation is not described in the |
597 | * datasheet. For now just write the raw data[1] value to |
598 | * the control register. |
599 | */ |
600 | outl(value: data[1], port: iobase + ADDI_TCW_CTRL_REG); |
601 | break; |
602 | case INSN_CONFIG_GET_COUNTER_STATUS: |
603 | data[1] = 0; |
604 | val = inl(port: iobase + ADDI_TCW_CTRL_REG); |
605 | if (val & ADDI_TCW_CTRL_IRQ_ENA) |
606 | data[1] |= COMEDI_COUNTER_ARMED; |
607 | if (val & ADDI_TCW_CTRL_CNTR_ENA) |
608 | data[1] |= COMEDI_COUNTER_COUNTING; |
609 | val = inl(port: iobase + ADDI_TCW_STATUS_REG); |
610 | if (val & ADDI_TCW_STATUS_OVERFLOW) |
611 | data[1] |= COMEDI_COUNTER_TERMINAL_COUNT; |
612 | data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING | |
613 | COMEDI_COUNTER_TERMINAL_COUNT; |
614 | break; |
615 | default: |
616 | return -EINVAL; |
617 | } |
618 | |
619 | return insn->n; |
620 | } |
621 | |
622 | static int apci1564_counter_insn_write(struct comedi_device *dev, |
623 | struct comedi_subdevice *s, |
624 | struct comedi_insn *insn, |
625 | unsigned int *data) |
626 | { |
627 | struct apci1564_private *devpriv = dev->private; |
628 | unsigned int chan = CR_CHAN(insn->chanspec); |
629 | unsigned long iobase = devpriv->counters + APCI1564_COUNTER(chan); |
630 | |
631 | /* just write the last to the reload register */ |
632 | if (insn->n) { |
633 | unsigned int val = data[insn->n - 1]; |
634 | |
635 | outl(value: val, port: iobase + ADDI_TCW_RELOAD_REG); |
636 | } |
637 | |
638 | return insn->n; |
639 | } |
640 | |
641 | static int apci1564_counter_insn_read(struct comedi_device *dev, |
642 | struct comedi_subdevice *s, |
643 | struct comedi_insn *insn, |
644 | unsigned int *data) |
645 | { |
646 | struct apci1564_private *devpriv = dev->private; |
647 | unsigned int chan = CR_CHAN(insn->chanspec); |
648 | unsigned long iobase = devpriv->counters + APCI1564_COUNTER(chan); |
649 | int i; |
650 | |
651 | /* return the actual value of the counter */ |
652 | for (i = 0; i < insn->n; i++) |
653 | data[i] = inl(port: iobase + ADDI_TCW_VAL_REG); |
654 | |
655 | return insn->n; |
656 | } |
657 | |
658 | static int apci1564_auto_attach(struct comedi_device *dev, |
659 | unsigned long context_unused) |
660 | { |
661 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
662 | struct apci1564_private *devpriv; |
663 | struct comedi_subdevice *s; |
664 | unsigned int val; |
665 | int ret; |
666 | |
667 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
668 | if (!devpriv) |
669 | return -ENOMEM; |
670 | |
671 | ret = comedi_pci_enable(dev); |
672 | if (ret) |
673 | return ret; |
674 | |
675 | /* read the EEPROM register and check the I/O map revision */ |
676 | devpriv->eeprom = pci_resource_start(pcidev, 0); |
677 | val = inl(port: devpriv->eeprom + APCI1564_EEPROM_REG); |
678 | if (APCI1564_EEPROM_TO_REV(val) == 0) { |
679 | /* PLD Revision 1.0 I/O Mapping */ |
680 | dev->iobase = pci_resource_start(pcidev, 1) + |
681 | APCI1564_REV1_MAIN_IOBASE; |
682 | devpriv->timer = devpriv->eeprom + APCI1564_REV1_TIMER_IOBASE; |
683 | } else { |
684 | /* PLD Revision 2.x I/O Mapping */ |
685 | dev->iobase = devpriv->eeprom + APCI1564_REV2_MAIN_IOBASE; |
686 | devpriv->timer = devpriv->eeprom + APCI1564_REV2_TIMER_IOBASE; |
687 | devpriv->counters = pci_resource_start(pcidev, 1); |
688 | } |
689 | |
690 | apci1564_reset(dev); |
691 | |
692 | if (pcidev->irq > 0) { |
693 | ret = request_irq(irq: pcidev->irq, handler: apci1564_interrupt, IRQF_SHARED, |
694 | name: dev->board_name, dev); |
695 | if (ret == 0) |
696 | dev->irq = pcidev->irq; |
697 | } |
698 | |
699 | ret = comedi_alloc_subdevices(dev, num_subdevices: 7); |
700 | if (ret) |
701 | return ret; |
702 | |
703 | /* Allocate and Initialise DI Subdevice Structures */ |
704 | s = &dev->subdevices[0]; |
705 | s->type = COMEDI_SUBD_DI; |
706 | s->subdev_flags = SDF_READABLE; |
707 | s->n_chan = 32; |
708 | s->maxdata = 1; |
709 | s->range_table = &range_digital; |
710 | s->insn_bits = apci1564_di_insn_bits; |
711 | |
712 | /* Allocate and Initialise DO Subdevice Structures */ |
713 | s = &dev->subdevices[1]; |
714 | s->type = COMEDI_SUBD_DO; |
715 | s->subdev_flags = SDF_WRITABLE; |
716 | s->n_chan = 32; |
717 | s->maxdata = 1; |
718 | s->range_table = &range_digital; |
719 | s->insn_bits = apci1564_do_insn_bits; |
720 | |
721 | /* Change-Of-State (COS) interrupt subdevice */ |
722 | s = &dev->subdevices[2]; |
723 | if (dev->irq) { |
724 | dev->read_subdev = s; |
725 | s->type = COMEDI_SUBD_DI; |
726 | s->subdev_flags = SDF_READABLE | SDF_CMD_READ | SDF_LSAMPL; |
727 | s->n_chan = 1; |
728 | s->maxdata = 1; |
729 | s->range_table = &range_digital; |
730 | s->len_chanlist = 1; |
731 | s->insn_config = apci1564_cos_insn_config; |
732 | s->insn_bits = apci1564_cos_insn_bits; |
733 | s->do_cmdtest = apci1564_cos_cmdtest; |
734 | s->do_cmd = apci1564_cos_cmd; |
735 | s->cancel = apci1564_cos_cancel; |
736 | } else { |
737 | s->type = COMEDI_SUBD_UNUSED; |
738 | } |
739 | |
740 | /* Timer subdevice */ |
741 | s = &dev->subdevices[3]; |
742 | s->type = COMEDI_SUBD_TIMER; |
743 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; |
744 | s->n_chan = 1; |
745 | s->maxdata = 0x0fff; |
746 | s->range_table = &range_digital; |
747 | s->insn_config = apci1564_timer_insn_config; |
748 | s->insn_write = apci1564_timer_insn_write; |
749 | s->insn_read = apci1564_timer_insn_read; |
750 | |
751 | /* Counter subdevice */ |
752 | s = &dev->subdevices[4]; |
753 | if (devpriv->counters) { |
754 | s->type = COMEDI_SUBD_COUNTER; |
755 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE | SDF_LSAMPL; |
756 | s->n_chan = 3; |
757 | s->maxdata = 0xffffffff; |
758 | s->range_table = &range_digital; |
759 | s->insn_config = apci1564_counter_insn_config; |
760 | s->insn_write = apci1564_counter_insn_write; |
761 | s->insn_read = apci1564_counter_insn_read; |
762 | } else { |
763 | s->type = COMEDI_SUBD_UNUSED; |
764 | } |
765 | |
766 | /* Initialize the watchdog subdevice */ |
767 | s = &dev->subdevices[5]; |
768 | ret = addi_watchdog_init(s, iobase: dev->iobase + APCI1564_WDOG_IOBASE); |
769 | if (ret) |
770 | return ret; |
771 | |
772 | /* Initialize the diagnostic status subdevice */ |
773 | s = &dev->subdevices[6]; |
774 | s->type = COMEDI_SUBD_DI; |
775 | s->subdev_flags = SDF_READABLE; |
776 | s->n_chan = 2; |
777 | s->maxdata = 1; |
778 | s->range_table = &range_digital; |
779 | s->insn_bits = apci1564_diag_insn_bits; |
780 | |
781 | return 0; |
782 | } |
783 | |
784 | static void apci1564_detach(struct comedi_device *dev) |
785 | { |
786 | if (dev->iobase) |
787 | apci1564_reset(dev); |
788 | comedi_pci_detach(dev); |
789 | } |
790 | |
791 | static struct comedi_driver apci1564_driver = { |
792 | .driver_name = "addi_apci_1564" , |
793 | .module = THIS_MODULE, |
794 | .auto_attach = apci1564_auto_attach, |
795 | .detach = apci1564_detach, |
796 | }; |
797 | |
798 | static int apci1564_pci_probe(struct pci_dev *dev, |
799 | const struct pci_device_id *id) |
800 | { |
801 | return comedi_pci_auto_config(pcidev: dev, driver: &apci1564_driver, context: id->driver_data); |
802 | } |
803 | |
804 | static const struct pci_device_id apci1564_pci_table[] = { |
805 | { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x1006) }, |
806 | { 0 } |
807 | }; |
808 | MODULE_DEVICE_TABLE(pci, apci1564_pci_table); |
809 | |
810 | static struct pci_driver apci1564_pci_driver = { |
811 | .name = "addi_apci_1564" , |
812 | .id_table = apci1564_pci_table, |
813 | .probe = apci1564_pci_probe, |
814 | .remove = comedi_pci_auto_unconfig, |
815 | }; |
816 | module_comedi_pci_driver(apci1564_driver, apci1564_pci_driver); |
817 | |
818 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
819 | MODULE_DESCRIPTION("ADDI-DATA APCI-1564, 32 channel DI / 32 channel DO boards" ); |
820 | MODULE_LICENSE("GPL" ); |
821 | |