1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * ni_65xx.c |
4 | * Comedi driver for National Instruments PCI-65xx static dio boards |
5 | * |
6 | * Copyright (C) 2006 Jon Grierson <jd@renko.co.uk> |
7 | * Copyright (C) 2006 Frank Mori Hess <fmhess@users.sourceforge.net> |
8 | * |
9 | * COMEDI - Linux Control and Measurement Device Interface |
10 | * Copyright (C) 1999,2002,2003 David A. Schleef <ds@schleef.org> |
11 | */ |
12 | |
13 | /* |
14 | * Driver: ni_65xx |
15 | * Description: National Instruments 65xx static dio boards |
16 | * Author: Jon Grierson <jd@renko.co.uk>, |
17 | * Frank Mori Hess <fmhess@users.sourceforge.net> |
18 | * Status: testing |
19 | * Devices: [National Instruments] PCI-6509 (pci-6509), PXI-6509 (pxi-6509), |
20 | * PCI-6510 (pci-6510), PCI-6511 (pci-6511), PXI-6511 (pxi-6511), |
21 | * PCI-6512 (pci-6512), PXI-6512 (pxi-6512), PCI-6513 (pci-6513), |
22 | * PXI-6513 (pxi-6513), PCI-6514 (pci-6514), PXI-6514 (pxi-6514), |
23 | * PCI-6515 (pxi-6515), PXI-6515 (pxi-6515), PCI-6516 (pci-6516), |
24 | * PCI-6517 (pci-6517), PCI-6518 (pci-6518), PCI-6519 (pci-6519), |
25 | * PCI-6520 (pci-6520), PCI-6521 (pci-6521), PXI-6521 (pxi-6521), |
26 | * PCI-6528 (pci-6528), PXI-6528 (pxi-6528) |
27 | * Updated: Mon, 21 Jul 2014 12:49:58 +0000 |
28 | * |
29 | * Configuration Options: not applicable, uses PCI auto config |
30 | * |
31 | * Based on the PCI-6527 driver by ds. |
32 | * The interrupt subdevice (subdevice 3) is probably broken for all |
33 | * boards except maybe the 6514. |
34 | * |
35 | * This driver previously inverted the outputs on PCI-6513 through to |
36 | * PCI-6519 and on PXI-6513 through to PXI-6515. It no longer inverts |
37 | * outputs on those cards by default as it didn't make much sense. If |
38 | * you require the outputs to be inverted on those cards for legacy |
39 | * reasons, set the module parameter "legacy_invert_outputs=true" when |
40 | * loading the module, or set "ni_65xx.legacy_invert_outputs=true" on |
41 | * the kernel command line if the driver is built in to the kernel. |
42 | */ |
43 | |
44 | /* |
45 | * Manuals (available from ftp://ftp.natinst.com/support/manuals) |
46 | * |
47 | * 370106b.pdf 6514 Register Level Programmer Manual |
48 | */ |
49 | |
50 | #include <linux/module.h> |
51 | #include <linux/interrupt.h> |
52 | #include <linux/comedi/comedi_pci.h> |
53 | |
54 | /* |
55 | * PCI BAR1 Register Map |
56 | */ |
57 | |
58 | /* Non-recurring Registers (8-bit except where noted) */ |
59 | #define NI_65XX_ID_REG 0x00 |
60 | #define NI_65XX_CLR_REG 0x01 |
61 | #define NI_65XX_CLR_WDOG_INT BIT(6) |
62 | #define NI_65XX_CLR_WDOG_PING BIT(5) |
63 | #define NI_65XX_CLR_WDOG_EXP BIT(4) |
64 | #define NI_65XX_CLR_EDGE_INT BIT(3) |
65 | #define NI_65XX_CLR_OVERFLOW_INT BIT(2) |
66 | #define NI_65XX_STATUS_REG 0x02 |
67 | #define NI_65XX_STATUS_WDOG_INT BIT(5) |
68 | #define NI_65XX_STATUS_FALL_EDGE BIT(4) |
69 | #define NI_65XX_STATUS_RISE_EDGE BIT(3) |
70 | #define NI_65XX_STATUS_INT BIT(2) |
71 | #define NI_65XX_STATUS_OVERFLOW_INT BIT(1) |
72 | #define NI_65XX_STATUS_EDGE_INT BIT(0) |
73 | #define NI_65XX_CTRL_REG 0x03 |
74 | #define NI_65XX_CTRL_WDOG_ENA BIT(5) |
75 | #define NI_65XX_CTRL_FALL_EDGE_ENA BIT(4) |
76 | #define NI_65XX_CTRL_RISE_EDGE_ENA BIT(3) |
77 | #define NI_65XX_CTRL_INT_ENA BIT(2) |
78 | #define NI_65XX_CTRL_OVERFLOW_ENA BIT(1) |
79 | #define NI_65XX_CTRL_EDGE_ENA BIT(0) |
80 | #define NI_65XX_REV_REG 0x04 /* 32-bit */ |
81 | #define NI_65XX_FILTER_REG 0x08 /* 32-bit */ |
82 | #define NI_65XX_RTSI_ROUTE_REG 0x0c /* 16-bit */ |
83 | #define NI_65XX_RTSI_EDGE_REG 0x0e /* 16-bit */ |
84 | #define NI_65XX_RTSI_WDOG_REG 0x10 /* 16-bit */ |
85 | #define NI_65XX_RTSI_TRIG_REG 0x12 /* 16-bit */ |
86 | #define NI_65XX_AUTO_CLK_SEL_REG 0x14 /* PXI-6528 only */ |
87 | #define NI_65XX_AUTO_CLK_SEL_STATUS BIT(1) |
88 | #define NI_65XX_AUTO_CLK_SEL_DISABLE BIT(0) |
89 | #define NI_65XX_WDOG_CTRL_REG 0x15 |
90 | #define NI_65XX_WDOG_CTRL_ENA BIT(0) |
91 | #define NI_65XX_RTSI_CFG_REG 0x16 |
92 | #define NI_65XX_RTSI_CFG_RISE_SENSE BIT(2) |
93 | #define NI_65XX_RTSI_CFG_FALL_SENSE BIT(1) |
94 | #define NI_65XX_RTSI_CFG_SYNC_DETECT BIT(0) |
95 | #define NI_65XX_WDOG_STATUS_REG 0x17 |
96 | #define NI_65XX_WDOG_STATUS_EXP BIT(0) |
97 | #define NI_65XX_WDOG_INTERVAL_REG 0x18 /* 32-bit */ |
98 | |
99 | /* Recurring port registers (8-bit) */ |
100 | #define NI_65XX_PORT(x) ((x) * 0x10) |
101 | #define NI_65XX_IO_DATA_REG(x) (0x40 + NI_65XX_PORT(x)) |
102 | #define NI_65XX_IO_SEL_REG(x) (0x41 + NI_65XX_PORT(x)) |
103 | #define NI_65XX_IO_SEL_OUTPUT 0 |
104 | #define NI_65XX_IO_SEL_INPUT BIT(0) |
105 | #define NI_65XX_RISE_EDGE_ENA_REG(x) (0x42 + NI_65XX_PORT(x)) |
106 | #define NI_65XX_FALL_EDGE_ENA_REG(x) (0x43 + NI_65XX_PORT(x)) |
107 | #define NI_65XX_FILTER_ENA(x) (0x44 + NI_65XX_PORT(x)) |
108 | #define NI_65XX_WDOG_HIZ_REG(x) (0x46 + NI_65XX_PORT(x)) |
109 | #define NI_65XX_WDOG_ENA(x) (0x47 + NI_65XX_PORT(x)) |
110 | #define NI_65XX_WDOG_HI_LO_REG(x) (0x48 + NI_65XX_PORT(x)) |
111 | #define NI_65XX_RTSI_ENA(x) (0x49 + NI_65XX_PORT(x)) |
112 | |
113 | #define NI_65XX_PORT_TO_CHAN(x) ((x) * 8) |
114 | #define NI_65XX_CHAN_TO_PORT(x) ((x) / 8) |
115 | #define NI_65XX_CHAN_TO_MASK(x) (1 << ((x) % 8)) |
116 | |
117 | enum ni_65xx_boardid { |
118 | BOARD_PCI6509, |
119 | BOARD_PXI6509, |
120 | BOARD_PCI6510, |
121 | BOARD_PCI6511, |
122 | BOARD_PXI6511, |
123 | BOARD_PCI6512, |
124 | BOARD_PXI6512, |
125 | BOARD_PCI6513, |
126 | BOARD_PXI6513, |
127 | BOARD_PCI6514, |
128 | BOARD_PXI6514, |
129 | BOARD_PCI6515, |
130 | BOARD_PXI6515, |
131 | BOARD_PCI6516, |
132 | BOARD_PCI6517, |
133 | BOARD_PCI6518, |
134 | BOARD_PCI6519, |
135 | BOARD_PCI6520, |
136 | BOARD_PCI6521, |
137 | BOARD_PXI6521, |
138 | BOARD_PCI6528, |
139 | BOARD_PXI6528, |
140 | }; |
141 | |
142 | struct ni_65xx_board { |
143 | const char *name; |
144 | unsigned int num_dio_ports; |
145 | unsigned int num_di_ports; |
146 | unsigned int num_do_ports; |
147 | unsigned int legacy_invert:1; |
148 | }; |
149 | |
150 | static const struct ni_65xx_board ni_65xx_boards[] = { |
151 | [BOARD_PCI6509] = { |
152 | .name = "pci-6509" , |
153 | .num_dio_ports = 12, |
154 | }, |
155 | [BOARD_PXI6509] = { |
156 | .name = "pxi-6509" , |
157 | .num_dio_ports = 12, |
158 | }, |
159 | [BOARD_PCI6510] = { |
160 | .name = "pci-6510" , |
161 | .num_di_ports = 4, |
162 | }, |
163 | [BOARD_PCI6511] = { |
164 | .name = "pci-6511" , |
165 | .num_di_ports = 8, |
166 | }, |
167 | [BOARD_PXI6511] = { |
168 | .name = "pxi-6511" , |
169 | .num_di_ports = 8, |
170 | }, |
171 | [BOARD_PCI6512] = { |
172 | .name = "pci-6512" , |
173 | .num_do_ports = 8, |
174 | }, |
175 | [BOARD_PXI6512] = { |
176 | .name = "pxi-6512" , |
177 | .num_do_ports = 8, |
178 | }, |
179 | [BOARD_PCI6513] = { |
180 | .name = "pci-6513" , |
181 | .num_do_ports = 8, |
182 | .legacy_invert = 1, |
183 | }, |
184 | [BOARD_PXI6513] = { |
185 | .name = "pxi-6513" , |
186 | .num_do_ports = 8, |
187 | .legacy_invert = 1, |
188 | }, |
189 | [BOARD_PCI6514] = { |
190 | .name = "pci-6514" , |
191 | .num_di_ports = 4, |
192 | .num_do_ports = 4, |
193 | .legacy_invert = 1, |
194 | }, |
195 | [BOARD_PXI6514] = { |
196 | .name = "pxi-6514" , |
197 | .num_di_ports = 4, |
198 | .num_do_ports = 4, |
199 | .legacy_invert = 1, |
200 | }, |
201 | [BOARD_PCI6515] = { |
202 | .name = "pci-6515" , |
203 | .num_di_ports = 4, |
204 | .num_do_ports = 4, |
205 | .legacy_invert = 1, |
206 | }, |
207 | [BOARD_PXI6515] = { |
208 | .name = "pxi-6515" , |
209 | .num_di_ports = 4, |
210 | .num_do_ports = 4, |
211 | .legacy_invert = 1, |
212 | }, |
213 | [BOARD_PCI6516] = { |
214 | .name = "pci-6516" , |
215 | .num_do_ports = 4, |
216 | .legacy_invert = 1, |
217 | }, |
218 | [BOARD_PCI6517] = { |
219 | .name = "pci-6517" , |
220 | .num_do_ports = 4, |
221 | .legacy_invert = 1, |
222 | }, |
223 | [BOARD_PCI6518] = { |
224 | .name = "pci-6518" , |
225 | .num_di_ports = 2, |
226 | .num_do_ports = 2, |
227 | .legacy_invert = 1, |
228 | }, |
229 | [BOARD_PCI6519] = { |
230 | .name = "pci-6519" , |
231 | .num_di_ports = 2, |
232 | .num_do_ports = 2, |
233 | .legacy_invert = 1, |
234 | }, |
235 | [BOARD_PCI6520] = { |
236 | .name = "pci-6520" , |
237 | .num_di_ports = 1, |
238 | .num_do_ports = 1, |
239 | }, |
240 | [BOARD_PCI6521] = { |
241 | .name = "pci-6521" , |
242 | .num_di_ports = 1, |
243 | .num_do_ports = 1, |
244 | }, |
245 | [BOARD_PXI6521] = { |
246 | .name = "pxi-6521" , |
247 | .num_di_ports = 1, |
248 | .num_do_ports = 1, |
249 | }, |
250 | [BOARD_PCI6528] = { |
251 | .name = "pci-6528" , |
252 | .num_di_ports = 3, |
253 | .num_do_ports = 3, |
254 | }, |
255 | [BOARD_PXI6528] = { |
256 | .name = "pxi-6528" , |
257 | .num_di_ports = 3, |
258 | .num_do_ports = 3, |
259 | }, |
260 | }; |
261 | |
262 | static bool ni_65xx_legacy_invert_outputs; |
263 | module_param_named(legacy_invert_outputs, ni_65xx_legacy_invert_outputs, |
264 | bool, 0444); |
265 | MODULE_PARM_DESC(legacy_invert_outputs, |
266 | "invert outputs of PCI/PXI-6513/6514/6515/6516/6517/6518/6519 for compatibility with old user code" ); |
267 | |
268 | static unsigned int ni_65xx_num_ports(struct comedi_device *dev) |
269 | { |
270 | const struct ni_65xx_board *board = dev->board_ptr; |
271 | |
272 | return board->num_dio_ports + board->num_di_ports + board->num_do_ports; |
273 | } |
274 | |
275 | static void ni_65xx_disable_input_filters(struct comedi_device *dev) |
276 | { |
277 | unsigned int num_ports = ni_65xx_num_ports(dev); |
278 | int i; |
279 | |
280 | /* disable input filtering on all ports */ |
281 | for (i = 0; i < num_ports; ++i) |
282 | writeb(val: 0x00, addr: dev->mmio + NI_65XX_FILTER_ENA(i)); |
283 | |
284 | /* set filter interval to 0 (32bit reg) */ |
285 | writel(val: 0x00000000, addr: dev->mmio + NI_65XX_FILTER_REG); |
286 | } |
287 | |
288 | /* updates edge detection for base_chan to base_chan+31 */ |
289 | static void ni_65xx_update_edge_detection(struct comedi_device *dev, |
290 | unsigned int base_chan, |
291 | unsigned int rising, |
292 | unsigned int falling) |
293 | { |
294 | unsigned int num_ports = ni_65xx_num_ports(dev); |
295 | unsigned int port; |
296 | |
297 | if (base_chan >= NI_65XX_PORT_TO_CHAN(num_ports)) |
298 | return; |
299 | |
300 | for (port = NI_65XX_CHAN_TO_PORT(base_chan); port < num_ports; port++) { |
301 | int bitshift = (int)(NI_65XX_PORT_TO_CHAN(port) - base_chan); |
302 | unsigned int port_mask, port_rising, port_falling; |
303 | |
304 | if (bitshift >= 32) |
305 | break; |
306 | |
307 | if (bitshift >= 0) { |
308 | port_mask = ~0U >> bitshift; |
309 | port_rising = rising >> bitshift; |
310 | port_falling = falling >> bitshift; |
311 | } else { |
312 | port_mask = ~0U << -bitshift; |
313 | port_rising = rising << -bitshift; |
314 | port_falling = falling << -bitshift; |
315 | } |
316 | if (port_mask & 0xff) { |
317 | if (~port_mask & 0xff) { |
318 | port_rising |= |
319 | readb(addr: dev->mmio + |
320 | NI_65XX_RISE_EDGE_ENA_REG(port)) & |
321 | ~port_mask; |
322 | port_falling |= |
323 | readb(addr: dev->mmio + |
324 | NI_65XX_FALL_EDGE_ENA_REG(port)) & |
325 | ~port_mask; |
326 | } |
327 | writeb(val: port_rising & 0xff, |
328 | addr: dev->mmio + NI_65XX_RISE_EDGE_ENA_REG(port)); |
329 | writeb(val: port_falling & 0xff, |
330 | addr: dev->mmio + NI_65XX_FALL_EDGE_ENA_REG(port)); |
331 | } |
332 | } |
333 | } |
334 | |
335 | static void ni_65xx_disable_edge_detection(struct comedi_device *dev) |
336 | { |
337 | /* clear edge detection for channels 0 to 31 */ |
338 | ni_65xx_update_edge_detection(dev, base_chan: 0, rising: 0, falling: 0); |
339 | /* clear edge detection for channels 32 to 63 */ |
340 | ni_65xx_update_edge_detection(dev, base_chan: 32, rising: 0, falling: 0); |
341 | /* clear edge detection for channels 64 to 95 */ |
342 | ni_65xx_update_edge_detection(dev, base_chan: 64, rising: 0, falling: 0); |
343 | } |
344 | |
345 | static int ni_65xx_dio_insn_config(struct comedi_device *dev, |
346 | struct comedi_subdevice *s, |
347 | struct comedi_insn *insn, |
348 | unsigned int *data) |
349 | { |
350 | unsigned long base_port = (unsigned long)s->private; |
351 | unsigned int chan = CR_CHAN(insn->chanspec); |
352 | unsigned int chan_mask = NI_65XX_CHAN_TO_MASK(chan); |
353 | unsigned int port = base_port + NI_65XX_CHAN_TO_PORT(chan); |
354 | unsigned int interval; |
355 | unsigned int val; |
356 | |
357 | switch (data[0]) { |
358 | case INSN_CONFIG_FILTER: |
359 | /* |
360 | * The deglitch filter interval is specified in nanoseconds. |
361 | * The hardware supports intervals in 200ns increments. Round |
362 | * the user values up and return the actual interval. |
363 | */ |
364 | interval = (data[1] + 100) / 200; |
365 | if (interval > 0xfffff) |
366 | interval = 0xfffff; |
367 | data[1] = interval * 200; |
368 | |
369 | /* |
370 | * Enable/disable the channel for deglitch filtering. Note |
371 | * that the filter interval is never set to '0'. This is done |
372 | * because other channels might still be enabled for filtering. |
373 | */ |
374 | val = readb(addr: dev->mmio + NI_65XX_FILTER_ENA(port)); |
375 | if (interval) { |
376 | writel(val: interval, addr: dev->mmio + NI_65XX_FILTER_REG); |
377 | val |= chan_mask; |
378 | } else { |
379 | val &= ~chan_mask; |
380 | } |
381 | writeb(val, addr: dev->mmio + NI_65XX_FILTER_ENA(port)); |
382 | break; |
383 | |
384 | case INSN_CONFIG_DIO_OUTPUT: |
385 | if (s->type != COMEDI_SUBD_DIO) |
386 | return -EINVAL; |
387 | writeb(NI_65XX_IO_SEL_OUTPUT, |
388 | addr: dev->mmio + NI_65XX_IO_SEL_REG(port)); |
389 | break; |
390 | |
391 | case INSN_CONFIG_DIO_INPUT: |
392 | if (s->type != COMEDI_SUBD_DIO) |
393 | return -EINVAL; |
394 | writeb(NI_65XX_IO_SEL_INPUT, |
395 | addr: dev->mmio + NI_65XX_IO_SEL_REG(port)); |
396 | break; |
397 | |
398 | case INSN_CONFIG_DIO_QUERY: |
399 | if (s->type != COMEDI_SUBD_DIO) |
400 | return -EINVAL; |
401 | val = readb(addr: dev->mmio + NI_65XX_IO_SEL_REG(port)); |
402 | data[1] = (val == NI_65XX_IO_SEL_INPUT) ? COMEDI_INPUT |
403 | : COMEDI_OUTPUT; |
404 | break; |
405 | |
406 | default: |
407 | return -EINVAL; |
408 | } |
409 | |
410 | return insn->n; |
411 | } |
412 | |
413 | static int ni_65xx_dio_insn_bits(struct comedi_device *dev, |
414 | struct comedi_subdevice *s, |
415 | struct comedi_insn *insn, |
416 | unsigned int *data) |
417 | { |
418 | unsigned long base_port = (unsigned long)s->private; |
419 | unsigned int base_chan = CR_CHAN(insn->chanspec); |
420 | int last_port_offset = NI_65XX_CHAN_TO_PORT(s->n_chan - 1); |
421 | unsigned int read_bits = 0; |
422 | int port_offset; |
423 | |
424 | for (port_offset = NI_65XX_CHAN_TO_PORT(base_chan); |
425 | port_offset <= last_port_offset; port_offset++) { |
426 | unsigned int port = base_port + port_offset; |
427 | int base_port_channel = NI_65XX_PORT_TO_CHAN(port_offset); |
428 | unsigned int port_mask, port_data, bits; |
429 | int bitshift = base_port_channel - base_chan; |
430 | |
431 | if (bitshift >= 32) |
432 | break; |
433 | port_mask = data[0]; |
434 | port_data = data[1]; |
435 | if (bitshift > 0) { |
436 | port_mask >>= bitshift; |
437 | port_data >>= bitshift; |
438 | } else { |
439 | port_mask <<= -bitshift; |
440 | port_data <<= -bitshift; |
441 | } |
442 | port_mask &= 0xff; |
443 | port_data &= 0xff; |
444 | |
445 | /* update the outputs */ |
446 | if (port_mask) { |
447 | bits = readb(addr: dev->mmio + NI_65XX_IO_DATA_REG(port)); |
448 | bits ^= s->io_bits; /* invert if necessary */ |
449 | bits &= ~port_mask; |
450 | bits |= (port_data & port_mask); |
451 | bits ^= s->io_bits; /* invert back */ |
452 | writeb(val: bits, addr: dev->mmio + NI_65XX_IO_DATA_REG(port)); |
453 | } |
454 | |
455 | /* read back the actual state */ |
456 | bits = readb(addr: dev->mmio + NI_65XX_IO_DATA_REG(port)); |
457 | bits ^= s->io_bits; /* invert if necessary */ |
458 | if (bitshift > 0) |
459 | bits <<= bitshift; |
460 | else |
461 | bits >>= -bitshift; |
462 | |
463 | read_bits |= bits; |
464 | } |
465 | data[1] = read_bits; |
466 | return insn->n; |
467 | } |
468 | |
469 | static irqreturn_t ni_65xx_interrupt(int irq, void *d) |
470 | { |
471 | struct comedi_device *dev = d; |
472 | struct comedi_subdevice *s = dev->read_subdev; |
473 | unsigned int status; |
474 | unsigned short val = 0; |
475 | |
476 | status = readb(addr: dev->mmio + NI_65XX_STATUS_REG); |
477 | if ((status & NI_65XX_STATUS_INT) == 0) |
478 | return IRQ_NONE; |
479 | if ((status & NI_65XX_STATUS_EDGE_INT) == 0) |
480 | return IRQ_NONE; |
481 | |
482 | writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT, |
483 | addr: dev->mmio + NI_65XX_CLR_REG); |
484 | |
485 | comedi_buf_write_samples(s, data: &val, nsamples: 1); |
486 | comedi_handle_events(dev, s); |
487 | |
488 | return IRQ_HANDLED; |
489 | } |
490 | |
491 | static int ni_65xx_intr_cmdtest(struct comedi_device *dev, |
492 | struct comedi_subdevice *s, |
493 | struct comedi_cmd *cmd) |
494 | { |
495 | int err = 0; |
496 | |
497 | /* Step 1 : check if triggers are trivially valid */ |
498 | |
499 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
500 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_OTHER); |
501 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_FOLLOW); |
502 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
503 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT); |
504 | |
505 | if (err) |
506 | return 1; |
507 | |
508 | /* Step 2a : make sure trigger sources are unique */ |
509 | /* Step 2b : and mutually compatible */ |
510 | |
511 | /* Step 3: check if arguments are trivially valid */ |
512 | |
513 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
514 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
515 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
516 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
517 | val: cmd->chanlist_len); |
518 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
519 | |
520 | if (err) |
521 | return 3; |
522 | |
523 | /* Step 4: fix up any arguments */ |
524 | |
525 | /* Step 5: check channel list if it exists */ |
526 | |
527 | return 0; |
528 | } |
529 | |
530 | static int ni_65xx_intr_cmd(struct comedi_device *dev, |
531 | struct comedi_subdevice *s) |
532 | { |
533 | writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT, |
534 | addr: dev->mmio + NI_65XX_CLR_REG); |
535 | writeb(NI_65XX_CTRL_FALL_EDGE_ENA | NI_65XX_CTRL_RISE_EDGE_ENA | |
536 | NI_65XX_CTRL_INT_ENA | NI_65XX_CTRL_EDGE_ENA, |
537 | addr: dev->mmio + NI_65XX_CTRL_REG); |
538 | |
539 | return 0; |
540 | } |
541 | |
542 | static int ni_65xx_intr_cancel(struct comedi_device *dev, |
543 | struct comedi_subdevice *s) |
544 | { |
545 | writeb(val: 0x00, addr: dev->mmio + NI_65XX_CTRL_REG); |
546 | |
547 | return 0; |
548 | } |
549 | |
550 | static int ni_65xx_intr_insn_bits(struct comedi_device *dev, |
551 | struct comedi_subdevice *s, |
552 | struct comedi_insn *insn, |
553 | unsigned int *data) |
554 | { |
555 | data[1] = 0; |
556 | return insn->n; |
557 | } |
558 | |
559 | static int ni_65xx_intr_insn_config(struct comedi_device *dev, |
560 | struct comedi_subdevice *s, |
561 | struct comedi_insn *insn, |
562 | unsigned int *data) |
563 | { |
564 | switch (data[0]) { |
565 | case INSN_CONFIG_CHANGE_NOTIFY: |
566 | /* add instruction to check_insn_config_length() */ |
567 | if (insn->n != 3) |
568 | return -EINVAL; |
569 | |
570 | /* update edge detection for channels 0 to 31 */ |
571 | ni_65xx_update_edge_detection(dev, base_chan: 0, rising: data[1], falling: data[2]); |
572 | /* clear edge detection for channels 32 to 63 */ |
573 | ni_65xx_update_edge_detection(dev, base_chan: 32, rising: 0, falling: 0); |
574 | /* clear edge detection for channels 64 to 95 */ |
575 | ni_65xx_update_edge_detection(dev, base_chan: 64, rising: 0, falling: 0); |
576 | break; |
577 | case INSN_CONFIG_DIGITAL_TRIG: |
578 | /* check trigger number */ |
579 | if (data[1] != 0) |
580 | return -EINVAL; |
581 | /* check digital trigger operation */ |
582 | switch (data[2]) { |
583 | case COMEDI_DIGITAL_TRIG_DISABLE: |
584 | ni_65xx_disable_edge_detection(dev); |
585 | break; |
586 | case COMEDI_DIGITAL_TRIG_ENABLE_EDGES: |
587 | /* |
588 | * update edge detection for channels data[3] |
589 | * to (data[3] + 31) |
590 | */ |
591 | ni_65xx_update_edge_detection(dev, base_chan: data[3], |
592 | rising: data[4], falling: data[5]); |
593 | break; |
594 | default: |
595 | return -EINVAL; |
596 | } |
597 | break; |
598 | default: |
599 | return -EINVAL; |
600 | } |
601 | |
602 | return insn->n; |
603 | } |
604 | |
605 | /* ripped from mite.h and mite_setup2() to avoid mite dependency */ |
606 | #define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */ |
607 | #define WENAB BIT(7) /* window enable */ |
608 | |
609 | static int ni_65xx_mite_init(struct pci_dev *pcidev) |
610 | { |
611 | void __iomem *mite_base; |
612 | u32 main_phys_addr; |
613 | |
614 | /* ioremap the MITE registers (BAR 0) temporarily */ |
615 | mite_base = pci_ioremap_bar(pdev: pcidev, bar: 0); |
616 | if (!mite_base) |
617 | return -ENOMEM; |
618 | |
619 | /* set data window to main registers (BAR 1) */ |
620 | main_phys_addr = pci_resource_start(pcidev, 1); |
621 | writel(val: main_phys_addr | WENAB, addr: mite_base + MITE_IODWBSR); |
622 | |
623 | /* finished with MITE registers */ |
624 | iounmap(addr: mite_base); |
625 | return 0; |
626 | } |
627 | |
628 | static int ni_65xx_auto_attach(struct comedi_device *dev, |
629 | unsigned long context) |
630 | { |
631 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
632 | const struct ni_65xx_board *board = NULL; |
633 | struct comedi_subdevice *s; |
634 | unsigned int i; |
635 | int ret; |
636 | |
637 | if (context < ARRAY_SIZE(ni_65xx_boards)) |
638 | board = &ni_65xx_boards[context]; |
639 | if (!board) |
640 | return -ENODEV; |
641 | dev->board_ptr = board; |
642 | dev->board_name = board->name; |
643 | |
644 | ret = comedi_pci_enable(dev); |
645 | if (ret) |
646 | return ret; |
647 | |
648 | ret = ni_65xx_mite_init(pcidev); |
649 | if (ret) |
650 | return ret; |
651 | |
652 | dev->mmio = pci_ioremap_bar(pdev: pcidev, bar: 1); |
653 | if (!dev->mmio) |
654 | return -ENOMEM; |
655 | |
656 | writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT, |
657 | addr: dev->mmio + NI_65XX_CLR_REG); |
658 | writeb(val: 0x00, addr: dev->mmio + NI_65XX_CTRL_REG); |
659 | |
660 | if (pcidev->irq) { |
661 | ret = request_irq(irq: pcidev->irq, handler: ni_65xx_interrupt, IRQF_SHARED, |
662 | name: dev->board_name, dev); |
663 | if (ret == 0) |
664 | dev->irq = pcidev->irq; |
665 | } |
666 | |
667 | dev_info(dev->class_dev, "board: %s, ID=0x%02x" , dev->board_name, |
668 | readb(dev->mmio + NI_65XX_ID_REG)); |
669 | |
670 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
671 | if (ret) |
672 | return ret; |
673 | |
674 | s = &dev->subdevices[0]; |
675 | if (board->num_di_ports) { |
676 | s->type = COMEDI_SUBD_DI; |
677 | s->subdev_flags = SDF_READABLE; |
678 | s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_di_ports); |
679 | s->maxdata = 1; |
680 | s->range_table = &range_digital; |
681 | s->insn_bits = ni_65xx_dio_insn_bits; |
682 | s->insn_config = ni_65xx_dio_insn_config; |
683 | |
684 | /* the input ports always start at port 0 */ |
685 | s->private = (void *)0; |
686 | } else { |
687 | s->type = COMEDI_SUBD_UNUSED; |
688 | } |
689 | |
690 | s = &dev->subdevices[1]; |
691 | if (board->num_do_ports) { |
692 | s->type = COMEDI_SUBD_DO; |
693 | s->subdev_flags = SDF_WRITABLE; |
694 | s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_do_ports); |
695 | s->maxdata = 1; |
696 | s->range_table = &range_digital; |
697 | s->insn_bits = ni_65xx_dio_insn_bits; |
698 | |
699 | /* the output ports always start after the input ports */ |
700 | s->private = (void *)(unsigned long)board->num_di_ports; |
701 | |
702 | /* |
703 | * Use the io_bits to handle the inverted outputs. Inverted |
704 | * outputs are only supported if the "legacy_invert_outputs" |
705 | * module parameter is set to "true". |
706 | */ |
707 | if (ni_65xx_legacy_invert_outputs && board->legacy_invert) |
708 | s->io_bits = 0xff; |
709 | |
710 | /* reset all output ports to comedi '0' */ |
711 | for (i = 0; i < board->num_do_ports; ++i) { |
712 | writeb(val: s->io_bits, /* inverted if necessary */ |
713 | addr: dev->mmio + |
714 | NI_65XX_IO_DATA_REG(board->num_di_ports + i)); |
715 | } |
716 | } else { |
717 | s->type = COMEDI_SUBD_UNUSED; |
718 | } |
719 | |
720 | s = &dev->subdevices[2]; |
721 | if (board->num_dio_ports) { |
722 | s->type = COMEDI_SUBD_DIO; |
723 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
724 | s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_dio_ports); |
725 | s->maxdata = 1; |
726 | s->range_table = &range_digital; |
727 | s->insn_bits = ni_65xx_dio_insn_bits; |
728 | s->insn_config = ni_65xx_dio_insn_config; |
729 | |
730 | /* the input/output ports always start at port 0 */ |
731 | s->private = (void *)0; |
732 | |
733 | /* configure all ports for input */ |
734 | for (i = 0; i < board->num_dio_ports; ++i) { |
735 | writeb(NI_65XX_IO_SEL_INPUT, |
736 | addr: dev->mmio + NI_65XX_IO_SEL_REG(i)); |
737 | } |
738 | } else { |
739 | s->type = COMEDI_SUBD_UNUSED; |
740 | } |
741 | |
742 | s = &dev->subdevices[3]; |
743 | s->type = COMEDI_SUBD_DI; |
744 | s->subdev_flags = SDF_READABLE; |
745 | s->n_chan = 1; |
746 | s->maxdata = 1; |
747 | s->range_table = &range_digital; |
748 | s->insn_bits = ni_65xx_intr_insn_bits; |
749 | if (dev->irq) { |
750 | dev->read_subdev = s; |
751 | s->subdev_flags |= SDF_CMD_READ; |
752 | s->len_chanlist = 1; |
753 | s->insn_config = ni_65xx_intr_insn_config; |
754 | s->do_cmdtest = ni_65xx_intr_cmdtest; |
755 | s->do_cmd = ni_65xx_intr_cmd; |
756 | s->cancel = ni_65xx_intr_cancel; |
757 | } |
758 | |
759 | ni_65xx_disable_input_filters(dev); |
760 | ni_65xx_disable_edge_detection(dev); |
761 | |
762 | return 0; |
763 | } |
764 | |
765 | static void ni_65xx_detach(struct comedi_device *dev) |
766 | { |
767 | if (dev->mmio) |
768 | writeb(val: 0x00, addr: dev->mmio + NI_65XX_CTRL_REG); |
769 | comedi_pci_detach(dev); |
770 | } |
771 | |
772 | static struct comedi_driver ni_65xx_driver = { |
773 | .driver_name = "ni_65xx" , |
774 | .module = THIS_MODULE, |
775 | .auto_attach = ni_65xx_auto_attach, |
776 | .detach = ni_65xx_detach, |
777 | }; |
778 | |
779 | static int ni_65xx_pci_probe(struct pci_dev *dev, |
780 | const struct pci_device_id *id) |
781 | { |
782 | return comedi_pci_auto_config(pcidev: dev, driver: &ni_65xx_driver, context: id->driver_data); |
783 | } |
784 | |
785 | static const struct pci_device_id ni_65xx_pci_table[] = { |
786 | { PCI_VDEVICE(NI, 0x1710), BOARD_PXI6509 }, |
787 | { PCI_VDEVICE(NI, 0x7085), BOARD_PCI6509 }, |
788 | { PCI_VDEVICE(NI, 0x7086), BOARD_PXI6528 }, |
789 | { PCI_VDEVICE(NI, 0x7087), BOARD_PCI6515 }, |
790 | { PCI_VDEVICE(NI, 0x7088), BOARD_PCI6514 }, |
791 | { PCI_VDEVICE(NI, 0x70a9), BOARD_PCI6528 }, |
792 | { PCI_VDEVICE(NI, 0x70c3), BOARD_PCI6511 }, |
793 | { PCI_VDEVICE(NI, 0x70c8), BOARD_PCI6513 }, |
794 | { PCI_VDEVICE(NI, 0x70c9), BOARD_PXI6515 }, |
795 | { PCI_VDEVICE(NI, 0x70cc), BOARD_PCI6512 }, |
796 | { PCI_VDEVICE(NI, 0x70cd), BOARD_PXI6514 }, |
797 | { PCI_VDEVICE(NI, 0x70d1), BOARD_PXI6513 }, |
798 | { PCI_VDEVICE(NI, 0x70d2), BOARD_PXI6512 }, |
799 | { PCI_VDEVICE(NI, 0x70d3), BOARD_PXI6511 }, |
800 | { PCI_VDEVICE(NI, 0x7124), BOARD_PCI6510 }, |
801 | { PCI_VDEVICE(NI, 0x7125), BOARD_PCI6516 }, |
802 | { PCI_VDEVICE(NI, 0x7126), BOARD_PCI6517 }, |
803 | { PCI_VDEVICE(NI, 0x7127), BOARD_PCI6518 }, |
804 | { PCI_VDEVICE(NI, 0x7128), BOARD_PCI6519 }, |
805 | { PCI_VDEVICE(NI, 0x718b), BOARD_PCI6521 }, |
806 | { PCI_VDEVICE(NI, 0x718c), BOARD_PXI6521 }, |
807 | { PCI_VDEVICE(NI, 0x71c5), BOARD_PCI6520 }, |
808 | { 0 } |
809 | }; |
810 | MODULE_DEVICE_TABLE(pci, ni_65xx_pci_table); |
811 | |
812 | static struct pci_driver ni_65xx_pci_driver = { |
813 | .name = "ni_65xx" , |
814 | .id_table = ni_65xx_pci_table, |
815 | .probe = ni_65xx_pci_probe, |
816 | .remove = comedi_pci_auto_unconfig, |
817 | }; |
818 | module_comedi_pci_driver(ni_65xx_driver, ni_65xx_pci_driver); |
819 | |
820 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
821 | MODULE_DESCRIPTION("Comedi driver for NI PCI-65xx static dio boards" ); |
822 | MODULE_LICENSE("GPL" ); |
823 | |