1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * ni_6527.c |
4 | * Comedi driver for National Instruments PCI-6527 |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 1999,2002,2003 David A. Schleef <ds@schleef.org> |
8 | */ |
9 | |
10 | /* |
11 | * Driver: ni_6527 |
12 | * Description: National Instruments 6527 |
13 | * Devices: [National Instruments] PCI-6527 (pci-6527), PXI-6527 (pxi-6527) |
14 | * Author: David A. Schleef <ds@schleef.org> |
15 | * Updated: Sat, 25 Jan 2003 13:24:40 -0800 |
16 | * Status: works |
17 | * |
18 | * Configuration Options: not applicable, uses PCI auto config |
19 | */ |
20 | |
21 | #include <linux/module.h> |
22 | #include <linux/interrupt.h> |
23 | #include <linux/comedi/comedi_pci.h> |
24 | |
25 | /* |
26 | * PCI BAR1 - Register memory map |
27 | * |
28 | * Manuals (available from ftp://ftp.natinst.com/support/manuals) |
29 | * 370106b.pdf 6527 Register Level Programmer Manual |
30 | */ |
31 | #define NI6527_DI_REG(x) (0x00 + (x)) |
32 | #define NI6527_DO_REG(x) (0x03 + (x)) |
33 | #define NI6527_ID_REG 0x06 |
34 | #define NI6527_CLR_REG 0x07 |
35 | #define NI6527_CLR_EDGE BIT(3) |
36 | #define NI6527_CLR_OVERFLOW BIT(2) |
37 | #define NI6527_CLR_FILT BIT(1) |
38 | #define NI6527_CLR_INTERVAL BIT(0) |
39 | #define NI6527_CLR_IRQS (NI6527_CLR_EDGE | NI6527_CLR_OVERFLOW) |
40 | #define NI6527_CLR_RESET_FILT (NI6527_CLR_FILT | NI6527_CLR_INTERVAL) |
41 | #define NI6527_FILT_INTERVAL_REG(x) (0x08 + (x)) |
42 | #define NI6527_FILT_ENA_REG(x) (0x0c + (x)) |
43 | #define NI6527_STATUS_REG 0x14 |
44 | #define NI6527_STATUS_IRQ BIT(2) |
45 | #define NI6527_STATUS_OVERFLOW BIT(1) |
46 | #define NI6527_STATUS_EDGE BIT(0) |
47 | #define NI6527_CTRL_REG 0x15 |
48 | #define NI6527_CTRL_FALLING BIT(4) |
49 | #define NI6527_CTRL_RISING BIT(3) |
50 | #define NI6527_CTRL_IRQ BIT(2) |
51 | #define NI6527_CTRL_OVERFLOW BIT(1) |
52 | #define NI6527_CTRL_EDGE BIT(0) |
53 | #define NI6527_CTRL_DISABLE_IRQS 0 |
54 | #define NI6527_CTRL_ENABLE_IRQS (NI6527_CTRL_FALLING | \ |
55 | NI6527_CTRL_RISING | \ |
56 | NI6527_CTRL_IRQ | NI6527_CTRL_EDGE) |
57 | #define NI6527_RISING_EDGE_REG(x) (0x18 + (x)) |
58 | #define NI6527_FALLING_EDGE_REG(x) (0x20 + (x)) |
59 | |
60 | enum ni6527_boardid { |
61 | BOARD_PCI6527, |
62 | BOARD_PXI6527, |
63 | }; |
64 | |
65 | struct ni6527_board { |
66 | const char *name; |
67 | }; |
68 | |
69 | static const struct ni6527_board ni6527_boards[] = { |
70 | [BOARD_PCI6527] = { |
71 | .name = "pci-6527" , |
72 | }, |
73 | [BOARD_PXI6527] = { |
74 | .name = "pxi-6527" , |
75 | }, |
76 | }; |
77 | |
78 | struct ni6527_private { |
79 | unsigned int filter_interval; |
80 | unsigned int filter_enable; |
81 | }; |
82 | |
83 | static void ni6527_set_filter_interval(struct comedi_device *dev, |
84 | unsigned int val) |
85 | { |
86 | struct ni6527_private *devpriv = dev->private; |
87 | |
88 | if (val != devpriv->filter_interval) { |
89 | writeb(val: val & 0xff, addr: dev->mmio + NI6527_FILT_INTERVAL_REG(0)); |
90 | writeb(val: (val >> 8) & 0xff, |
91 | addr: dev->mmio + NI6527_FILT_INTERVAL_REG(1)); |
92 | writeb(val: (val >> 16) & 0x0f, |
93 | addr: dev->mmio + NI6527_FILT_INTERVAL_REG(2)); |
94 | |
95 | writeb(NI6527_CLR_INTERVAL, addr: dev->mmio + NI6527_CLR_REG); |
96 | |
97 | devpriv->filter_interval = val; |
98 | } |
99 | } |
100 | |
101 | static void ni6527_set_filter_enable(struct comedi_device *dev, |
102 | unsigned int val) |
103 | { |
104 | writeb(val: val & 0xff, addr: dev->mmio + NI6527_FILT_ENA_REG(0)); |
105 | writeb(val: (val >> 8) & 0xff, addr: dev->mmio + NI6527_FILT_ENA_REG(1)); |
106 | writeb(val: (val >> 16) & 0xff, addr: dev->mmio + NI6527_FILT_ENA_REG(2)); |
107 | } |
108 | |
109 | static int ni6527_di_insn_config(struct comedi_device *dev, |
110 | struct comedi_subdevice *s, |
111 | struct comedi_insn *insn, |
112 | unsigned int *data) |
113 | { |
114 | struct ni6527_private *devpriv = dev->private; |
115 | unsigned int chan = CR_CHAN(insn->chanspec); |
116 | unsigned int interval; |
117 | |
118 | switch (data[0]) { |
119 | case INSN_CONFIG_FILTER: |
120 | /* |
121 | * The deglitch filter interval is specified in nanoseconds. |
122 | * The hardware supports intervals in 200ns increments. Round |
123 | * the user values up and return the actual interval. |
124 | */ |
125 | interval = (data[1] + 100) / 200; |
126 | data[1] = interval * 200; |
127 | |
128 | if (interval) { |
129 | ni6527_set_filter_interval(dev, val: interval); |
130 | devpriv->filter_enable |= 1 << chan; |
131 | } else { |
132 | devpriv->filter_enable &= ~(1 << chan); |
133 | } |
134 | ni6527_set_filter_enable(dev, val: devpriv->filter_enable); |
135 | break; |
136 | default: |
137 | return -EINVAL; |
138 | } |
139 | |
140 | return insn->n; |
141 | } |
142 | |
143 | static int ni6527_di_insn_bits(struct comedi_device *dev, |
144 | struct comedi_subdevice *s, |
145 | struct comedi_insn *insn, |
146 | unsigned int *data) |
147 | { |
148 | unsigned int val; |
149 | |
150 | val = readb(addr: dev->mmio + NI6527_DI_REG(0)); |
151 | val |= (readb(addr: dev->mmio + NI6527_DI_REG(1)) << 8); |
152 | val |= (readb(addr: dev->mmio + NI6527_DI_REG(2)) << 16); |
153 | |
154 | data[1] = val; |
155 | |
156 | return insn->n; |
157 | } |
158 | |
159 | static int ni6527_do_insn_bits(struct comedi_device *dev, |
160 | struct comedi_subdevice *s, |
161 | struct comedi_insn *insn, |
162 | unsigned int *data) |
163 | { |
164 | unsigned int mask; |
165 | |
166 | mask = comedi_dio_update_state(s, data); |
167 | if (mask) { |
168 | /* Outputs are inverted */ |
169 | unsigned int val = s->state ^ 0xffffff; |
170 | |
171 | if (mask & 0x0000ff) |
172 | writeb(val: val & 0xff, addr: dev->mmio + NI6527_DO_REG(0)); |
173 | if (mask & 0x00ff00) |
174 | writeb(val: (val >> 8) & 0xff, |
175 | addr: dev->mmio + NI6527_DO_REG(1)); |
176 | if (mask & 0xff0000) |
177 | writeb(val: (val >> 16) & 0xff, |
178 | addr: dev->mmio + NI6527_DO_REG(2)); |
179 | } |
180 | |
181 | data[1] = s->state; |
182 | |
183 | return insn->n; |
184 | } |
185 | |
186 | static irqreturn_t ni6527_interrupt(int irq, void *d) |
187 | { |
188 | struct comedi_device *dev = d; |
189 | struct comedi_subdevice *s = dev->read_subdev; |
190 | unsigned int status; |
191 | |
192 | status = readb(addr: dev->mmio + NI6527_STATUS_REG); |
193 | if (!(status & NI6527_STATUS_IRQ)) |
194 | return IRQ_NONE; |
195 | |
196 | if (status & NI6527_STATUS_EDGE) { |
197 | unsigned short val = 0; |
198 | |
199 | comedi_buf_write_samples(s, data: &val, nsamples: 1); |
200 | comedi_handle_events(dev, s); |
201 | } |
202 | |
203 | writeb(NI6527_CLR_IRQS, addr: dev->mmio + NI6527_CLR_REG); |
204 | |
205 | return IRQ_HANDLED; |
206 | } |
207 | |
208 | static int ni6527_intr_cmdtest(struct comedi_device *dev, |
209 | struct comedi_subdevice *s, |
210 | struct comedi_cmd *cmd) |
211 | { |
212 | int err = 0; |
213 | |
214 | /* Step 1 : check if triggers are trivially valid */ |
215 | |
216 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
217 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_OTHER); |
218 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_FOLLOW); |
219 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
220 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT); |
221 | |
222 | if (err) |
223 | return 1; |
224 | |
225 | /* Step 2a : make sure trigger sources are unique */ |
226 | /* Step 2b : and mutually compatible */ |
227 | |
228 | /* Step 3: check if arguments are trivially valid */ |
229 | |
230 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
231 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
232 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
233 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
234 | val: cmd->chanlist_len); |
235 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
236 | |
237 | if (err) |
238 | return 3; |
239 | |
240 | /* Step 4: fix up any arguments */ |
241 | |
242 | /* Step 5: check channel list if it exists */ |
243 | |
244 | return 0; |
245 | } |
246 | |
247 | static int ni6527_intr_cmd(struct comedi_device *dev, |
248 | struct comedi_subdevice *s) |
249 | { |
250 | writeb(NI6527_CLR_IRQS, addr: dev->mmio + NI6527_CLR_REG); |
251 | writeb(NI6527_CTRL_ENABLE_IRQS, addr: dev->mmio + NI6527_CTRL_REG); |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | static int ni6527_intr_cancel(struct comedi_device *dev, |
257 | struct comedi_subdevice *s) |
258 | { |
259 | writeb(NI6527_CTRL_DISABLE_IRQS, addr: dev->mmio + NI6527_CTRL_REG); |
260 | |
261 | return 0; |
262 | } |
263 | |
264 | static int ni6527_intr_insn_bits(struct comedi_device *dev, |
265 | struct comedi_subdevice *s, |
266 | struct comedi_insn *insn, unsigned int *data) |
267 | { |
268 | data[1] = 0; |
269 | return insn->n; |
270 | } |
271 | |
272 | static void ni6527_set_edge_detection(struct comedi_device *dev, |
273 | unsigned int mask, |
274 | unsigned int rising, |
275 | unsigned int falling) |
276 | { |
277 | unsigned int i; |
278 | |
279 | rising &= mask; |
280 | falling &= mask; |
281 | for (i = 0; i < 2; i++) { |
282 | if (mask & 0xff) { |
283 | if (~mask & 0xff) { |
284 | /* preserve rising-edge detection channels */ |
285 | rising |= readb(addr: dev->mmio + |
286 | NI6527_RISING_EDGE_REG(i)) & |
287 | (~mask & 0xff); |
288 | /* preserve falling-edge detection channels */ |
289 | falling |= readb(addr: dev->mmio + |
290 | NI6527_FALLING_EDGE_REG(i)) & |
291 | (~mask & 0xff); |
292 | } |
293 | /* update rising-edge detection channels */ |
294 | writeb(val: rising & 0xff, |
295 | addr: dev->mmio + NI6527_RISING_EDGE_REG(i)); |
296 | /* update falling-edge detection channels */ |
297 | writeb(val: falling & 0xff, |
298 | addr: dev->mmio + NI6527_FALLING_EDGE_REG(i)); |
299 | } |
300 | rising >>= 8; |
301 | falling >>= 8; |
302 | mask >>= 8; |
303 | } |
304 | } |
305 | |
306 | static int ni6527_intr_insn_config(struct comedi_device *dev, |
307 | struct comedi_subdevice *s, |
308 | struct comedi_insn *insn, |
309 | unsigned int *data) |
310 | { |
311 | unsigned int mask = 0xffffffff; |
312 | unsigned int rising, falling, shift; |
313 | |
314 | switch (data[0]) { |
315 | case INSN_CONFIG_CHANGE_NOTIFY: |
316 | /* check_insn_config_length() does not check this instruction */ |
317 | if (insn->n != 3) |
318 | return -EINVAL; |
319 | rising = data[1]; |
320 | falling = data[2]; |
321 | ni6527_set_edge_detection(dev, mask, rising, falling); |
322 | break; |
323 | case INSN_CONFIG_DIGITAL_TRIG: |
324 | /* check trigger number */ |
325 | if (data[1] != 0) |
326 | return -EINVAL; |
327 | /* check digital trigger operation */ |
328 | switch (data[2]) { |
329 | case COMEDI_DIGITAL_TRIG_DISABLE: |
330 | rising = 0; |
331 | falling = 0; |
332 | break; |
333 | case COMEDI_DIGITAL_TRIG_ENABLE_EDGES: |
334 | /* check shift amount */ |
335 | shift = data[3]; |
336 | if (shift >= 32) { |
337 | mask = 0; |
338 | rising = 0; |
339 | falling = 0; |
340 | } else { |
341 | mask <<= shift; |
342 | rising = data[4] << shift; |
343 | falling = data[5] << shift; |
344 | } |
345 | break; |
346 | default: |
347 | return -EINVAL; |
348 | } |
349 | ni6527_set_edge_detection(dev, mask, rising, falling); |
350 | break; |
351 | default: |
352 | return -EINVAL; |
353 | } |
354 | |
355 | return insn->n; |
356 | } |
357 | |
358 | static void ni6527_reset(struct comedi_device *dev) |
359 | { |
360 | /* disable deglitch filters on all channels */ |
361 | ni6527_set_filter_enable(dev, val: 0); |
362 | |
363 | /* disable edge detection */ |
364 | ni6527_set_edge_detection(dev, mask: 0xffffffff, rising: 0, falling: 0); |
365 | |
366 | writeb(NI6527_CLR_IRQS | NI6527_CLR_RESET_FILT, |
367 | addr: dev->mmio + NI6527_CLR_REG); |
368 | writeb(NI6527_CTRL_DISABLE_IRQS, addr: dev->mmio + NI6527_CTRL_REG); |
369 | } |
370 | |
371 | static int ni6527_auto_attach(struct comedi_device *dev, |
372 | unsigned long context) |
373 | { |
374 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
375 | const struct ni6527_board *board = NULL; |
376 | struct ni6527_private *devpriv; |
377 | struct comedi_subdevice *s; |
378 | int ret; |
379 | |
380 | if (context < ARRAY_SIZE(ni6527_boards)) |
381 | board = &ni6527_boards[context]; |
382 | if (!board) |
383 | return -ENODEV; |
384 | dev->board_ptr = board; |
385 | dev->board_name = board->name; |
386 | |
387 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
388 | if (!devpriv) |
389 | return -ENOMEM; |
390 | |
391 | ret = comedi_pci_enable(dev); |
392 | if (ret) |
393 | return ret; |
394 | |
395 | dev->mmio = pci_ioremap_bar(pdev: pcidev, bar: 1); |
396 | if (!dev->mmio) |
397 | return -ENOMEM; |
398 | |
399 | /* make sure this is actually a 6527 device */ |
400 | if (readb(addr: dev->mmio + NI6527_ID_REG) != 0x27) |
401 | return -ENODEV; |
402 | |
403 | ni6527_reset(dev); |
404 | |
405 | ret = request_irq(irq: pcidev->irq, handler: ni6527_interrupt, IRQF_SHARED, |
406 | name: dev->board_name, dev); |
407 | if (ret == 0) |
408 | dev->irq = pcidev->irq; |
409 | |
410 | ret = comedi_alloc_subdevices(dev, num_subdevices: 3); |
411 | if (ret) |
412 | return ret; |
413 | |
414 | /* Digital Input subdevice */ |
415 | s = &dev->subdevices[0]; |
416 | s->type = COMEDI_SUBD_DI; |
417 | s->subdev_flags = SDF_READABLE; |
418 | s->n_chan = 24; |
419 | s->maxdata = 1; |
420 | s->range_table = &range_digital; |
421 | s->insn_config = ni6527_di_insn_config; |
422 | s->insn_bits = ni6527_di_insn_bits; |
423 | |
424 | /* Digital Output subdevice */ |
425 | s = &dev->subdevices[1]; |
426 | s->type = COMEDI_SUBD_DO; |
427 | s->subdev_flags = SDF_WRITABLE; |
428 | s->n_chan = 24; |
429 | s->maxdata = 1; |
430 | s->range_table = &range_digital; |
431 | s->insn_bits = ni6527_do_insn_bits; |
432 | |
433 | /* Edge detection interrupt subdevice */ |
434 | s = &dev->subdevices[2]; |
435 | if (dev->irq) { |
436 | dev->read_subdev = s; |
437 | s->type = COMEDI_SUBD_DI; |
438 | s->subdev_flags = SDF_READABLE | SDF_CMD_READ; |
439 | s->n_chan = 1; |
440 | s->maxdata = 1; |
441 | s->range_table = &range_digital; |
442 | s->insn_config = ni6527_intr_insn_config; |
443 | s->insn_bits = ni6527_intr_insn_bits; |
444 | s->len_chanlist = 1; |
445 | s->do_cmdtest = ni6527_intr_cmdtest; |
446 | s->do_cmd = ni6527_intr_cmd; |
447 | s->cancel = ni6527_intr_cancel; |
448 | } else { |
449 | s->type = COMEDI_SUBD_UNUSED; |
450 | } |
451 | |
452 | return 0; |
453 | } |
454 | |
455 | static void ni6527_detach(struct comedi_device *dev) |
456 | { |
457 | if (dev->mmio) |
458 | ni6527_reset(dev); |
459 | comedi_pci_detach(dev); |
460 | } |
461 | |
462 | static struct comedi_driver ni6527_driver = { |
463 | .driver_name = "ni_6527" , |
464 | .module = THIS_MODULE, |
465 | .auto_attach = ni6527_auto_attach, |
466 | .detach = ni6527_detach, |
467 | }; |
468 | |
469 | static int ni6527_pci_probe(struct pci_dev *dev, |
470 | const struct pci_device_id *id) |
471 | { |
472 | return comedi_pci_auto_config(pcidev: dev, driver: &ni6527_driver, context: id->driver_data); |
473 | } |
474 | |
475 | static const struct pci_device_id ni6527_pci_table[] = { |
476 | { PCI_VDEVICE(NI, 0x2b10), BOARD_PXI6527 }, |
477 | { PCI_VDEVICE(NI, 0x2b20), BOARD_PCI6527 }, |
478 | { 0 } |
479 | }; |
480 | MODULE_DEVICE_TABLE(pci, ni6527_pci_table); |
481 | |
482 | static struct pci_driver ni6527_pci_driver = { |
483 | .name = "ni_6527" , |
484 | .id_table = ni6527_pci_table, |
485 | .probe = ni6527_pci_probe, |
486 | .remove = comedi_pci_auto_unconfig, |
487 | }; |
488 | module_comedi_pci_driver(ni6527_driver, ni6527_pci_driver); |
489 | |
490 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
491 | MODULE_DESCRIPTION("Comedi driver for National Instruments PCI-6527" ); |
492 | MODULE_LICENSE("GPL" ); |
493 | |