1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Comedi driver for NI AT-MIO E series cards |
4 | * |
5 | * COMEDI - Linux Control and Measurement Device Interface |
6 | * Copyright (C) 1997-2001 David A. Schleef <ds@schleef.org> |
7 | */ |
8 | |
9 | /* |
10 | * Driver: ni_atmio |
11 | * Description: National Instruments AT-MIO-E series |
12 | * Author: ds |
13 | * Devices: [National Instruments] AT-MIO-16E-1 (ni_atmio), |
14 | * AT-MIO-16E-2, AT-MIO-16E-10, AT-MIO-16DE-10, AT-MIO-64E-3, |
15 | * AT-MIO-16XE-50, AT-MIO-16XE-10, AT-AI-16XE-10 |
16 | * Status: works |
17 | * Updated: Thu May 1 20:03:02 CDT 2003 |
18 | * |
19 | * The driver has 2.6 kernel isapnp support, and will automatically probe for |
20 | * a supported board if the I/O base is left unspecified with comedi_config. |
21 | * However, many of the isapnp id numbers are unknown. If your board is not |
22 | * recognized, please send the output of 'cat /proc/isapnp' (you may need to |
23 | * modprobe the isa-pnp module for /proc/isapnp to exist) so the id numbers |
24 | * for your board can be added to the driver. |
25 | * |
26 | * Otherwise, you can use the isapnptools package to configure your board. |
27 | * Use isapnp to configure the I/O base and IRQ for the board, and then pass |
28 | * the same values as parameters in comedi_config. A sample isapnp.conf file |
29 | * is included in the etc/ directory of Comedilib. |
30 | * |
31 | * Comedilib includes a utility to autocalibrate these boards. The boards |
32 | * seem to boot into a state where the all calibration DACs are at one |
33 | * extreme of their range, thus the default calibration is terrible. |
34 | * Calibration at boot is strongly encouraged. |
35 | * |
36 | * To use the extended digital I/O on some of the boards, enable the |
37 | * 8255 driver when configuring the Comedi source tree. |
38 | * |
39 | * External triggering is supported for some events. The channel index |
40 | * (scan_begin_arg, etc.) maps to PFI0 - PFI9. |
41 | * |
42 | * Some of the more esoteric triggering possibilities of these boards are |
43 | * not supported. |
44 | */ |
45 | |
46 | /* |
47 | * The real guts of the driver is in ni_mio_common.c, which is included |
48 | * both here and in ni_pcimio.c |
49 | * |
50 | * Interrupt support added by Truxton Fulton <trux@truxton.com> |
51 | * |
52 | * References for specifications: |
53 | * 340747b.pdf Register Level Programmer Manual (obsolete) |
54 | * 340747c.pdf Register Level Programmer Manual (new) |
55 | * DAQ-STC reference manual |
56 | * |
57 | * Other possibly relevant info: |
58 | * 320517c.pdf User manual (obsolete) |
59 | * 320517f.pdf User manual (new) |
60 | * 320889a.pdf delete |
61 | * 320906c.pdf maximum signal ratings |
62 | * 321066a.pdf about 16x |
63 | * 321791a.pdf discontinuation of at-mio-16e-10 rev. c |
64 | * 321808a.pdf about at-mio-16e-10 rev P |
65 | * 321837a.pdf discontinuation of at-mio-16de-10 rev d |
66 | * 321838a.pdf about at-mio-16de-10 rev N |
67 | * |
68 | * ISSUES: |
69 | * - need to deal with external reference for DAC, and other DAC |
70 | * properties in board properties |
71 | * - deal with at-mio-16de-10 revision D to N changes, etc. |
72 | */ |
73 | |
74 | #include <linux/module.h> |
75 | #include <linux/interrupt.h> |
76 | #include <linux/comedi/comedidev.h> |
77 | #include <linux/isapnp.h> |
78 | #include <linux/comedi/comedi_8255.h> |
79 | |
80 | #include "ni_stc.h" |
81 | |
82 | /* AT specific setup */ |
83 | static const struct ni_board_struct ni_boards[] = { |
84 | { |
85 | .name = "at-mio-16e-1" , |
86 | .device_id = 44, |
87 | .isapnp_id = 0x0000, /* XXX unknown */ |
88 | .n_adchan = 16, |
89 | .ai_maxdata = 0x0fff, |
90 | .ai_fifo_depth = 8192, |
91 | .gainlkup = ai_gain_16, |
92 | .ai_speed = 800, |
93 | .n_aochan = 2, |
94 | .ao_maxdata = 0x0fff, |
95 | .ao_fifo_depth = 2048, |
96 | .ao_range_table = &range_ni_E_ao_ext, |
97 | .ao_speed = 1000, |
98 | .caldac = { mb88341 }, |
99 | }, { |
100 | .name = "at-mio-16e-2" , |
101 | .device_id = 25, |
102 | .isapnp_id = 0x1900, |
103 | .n_adchan = 16, |
104 | .ai_maxdata = 0x0fff, |
105 | .ai_fifo_depth = 2048, |
106 | .gainlkup = ai_gain_16, |
107 | .ai_speed = 2000, |
108 | .n_aochan = 2, |
109 | .ao_maxdata = 0x0fff, |
110 | .ao_fifo_depth = 2048, |
111 | .ao_range_table = &range_ni_E_ao_ext, |
112 | .ao_speed = 1000, |
113 | .caldac = { mb88341 }, |
114 | }, { |
115 | .name = "at-mio-16e-10" , |
116 | .device_id = 36, |
117 | .isapnp_id = 0x2400, |
118 | .n_adchan = 16, |
119 | .ai_maxdata = 0x0fff, |
120 | .ai_fifo_depth = 512, |
121 | .gainlkup = ai_gain_16, |
122 | .ai_speed = 10000, |
123 | .n_aochan = 2, |
124 | .ao_maxdata = 0x0fff, |
125 | .ao_range_table = &range_ni_E_ao_ext, |
126 | .ao_speed = 10000, |
127 | .caldac = { ad8804_debug }, |
128 | }, { |
129 | .name = "at-mio-16de-10" , |
130 | .device_id = 37, |
131 | .isapnp_id = 0x2500, |
132 | .n_adchan = 16, |
133 | .ai_maxdata = 0x0fff, |
134 | .ai_fifo_depth = 512, |
135 | .gainlkup = ai_gain_16, |
136 | .ai_speed = 10000, |
137 | .n_aochan = 2, |
138 | .ao_maxdata = 0x0fff, |
139 | .ao_range_table = &range_ni_E_ao_ext, |
140 | .ao_speed = 10000, |
141 | .caldac = { ad8804_debug }, |
142 | .has_8255 = 1, |
143 | }, { |
144 | .name = "at-mio-64e-3" , |
145 | .device_id = 38, |
146 | .isapnp_id = 0x2600, |
147 | .n_adchan = 64, |
148 | .ai_maxdata = 0x0fff, |
149 | .ai_fifo_depth = 2048, |
150 | .gainlkup = ai_gain_16, |
151 | .ai_speed = 2000, |
152 | .n_aochan = 2, |
153 | .ao_maxdata = 0x0fff, |
154 | .ao_fifo_depth = 2048, |
155 | .ao_range_table = &range_ni_E_ao_ext, |
156 | .ao_speed = 1000, |
157 | .caldac = { ad8804_debug }, |
158 | }, { |
159 | .name = "at-mio-16xe-50" , |
160 | .device_id = 39, |
161 | .isapnp_id = 0x2700, |
162 | .n_adchan = 16, |
163 | .ai_maxdata = 0xffff, |
164 | .ai_fifo_depth = 512, |
165 | .alwaysdither = 1, |
166 | .gainlkup = ai_gain_8, |
167 | .ai_speed = 50000, |
168 | .n_aochan = 2, |
169 | .ao_maxdata = 0x0fff, |
170 | .ao_range_table = &range_bipolar10, |
171 | .ao_speed = 50000, |
172 | .caldac = { dac8800, dac8043 }, |
173 | }, { |
174 | .name = "at-mio-16xe-10" , |
175 | .device_id = 50, |
176 | .isapnp_id = 0x0000, /* XXX unknown */ |
177 | .n_adchan = 16, |
178 | .ai_maxdata = 0xffff, |
179 | .ai_fifo_depth = 512, |
180 | .alwaysdither = 1, |
181 | .gainlkup = ai_gain_14, |
182 | .ai_speed = 10000, |
183 | .n_aochan = 2, |
184 | .ao_maxdata = 0xffff, |
185 | .ao_fifo_depth = 2048, |
186 | .ao_range_table = &range_ni_E_ao_ext, |
187 | .ao_speed = 1000, |
188 | .caldac = { dac8800, dac8043, ad8522 }, |
189 | }, { |
190 | .name = "at-ai-16xe-10" , |
191 | .device_id = 51, |
192 | .isapnp_id = 0x0000, /* XXX unknown */ |
193 | .n_adchan = 16, |
194 | .ai_maxdata = 0xffff, |
195 | .ai_fifo_depth = 512, |
196 | .alwaysdither = 1, /* unknown */ |
197 | .gainlkup = ai_gain_14, |
198 | .ai_speed = 10000, |
199 | .caldac = { dac8800, dac8043, ad8522 }, |
200 | }, |
201 | }; |
202 | |
203 | static const int ni_irqpin[] = { |
204 | -1, -1, -1, 0, 1, 2, -1, 3, -1, -1, 4, 5, 6, -1, -1, 7 |
205 | }; |
206 | |
207 | #include "ni_mio_common.c" |
208 | |
209 | static const struct pnp_device_id device_ids[] = { |
210 | {.id = "NIC1900" , .driver_data = 0}, |
211 | {.id = "NIC2400" , .driver_data = 0}, |
212 | {.id = "NIC2500" , .driver_data = 0}, |
213 | {.id = "NIC2600" , .driver_data = 0}, |
214 | {.id = "NIC2700" , .driver_data = 0}, |
215 | {.id = "" } |
216 | }; |
217 | |
218 | MODULE_DEVICE_TABLE(pnp, device_ids); |
219 | |
220 | static int ni_isapnp_find_board(struct pnp_dev **dev) |
221 | { |
222 | struct pnp_dev *isapnp_dev = NULL; |
223 | int i; |
224 | |
225 | for (i = 0; i < ARRAY_SIZE(ni_boards); i++) { |
226 | isapnp_dev = |
227 | pnp_find_dev(NULL, |
228 | ISAPNP_VENDOR('N', 'I', 'C'), |
229 | ISAPNP_FUNCTION(ni_boards[i].isapnp_id), |
230 | NULL); |
231 | |
232 | if (!isapnp_dev || !isapnp_dev->card) |
233 | continue; |
234 | |
235 | if (pnp_device_attach(pnp_dev: isapnp_dev) < 0) |
236 | continue; |
237 | |
238 | if (pnp_activate_dev(dev: isapnp_dev) < 0) { |
239 | pnp_device_detach(pnp_dev: isapnp_dev); |
240 | return -EAGAIN; |
241 | } |
242 | |
243 | if (!pnp_port_valid(dev: isapnp_dev, bar: 0) || |
244 | !pnp_irq_valid(dev: isapnp_dev, bar: 0)) { |
245 | pnp_device_detach(pnp_dev: isapnp_dev); |
246 | return -ENOMEM; |
247 | } |
248 | break; |
249 | } |
250 | if (i == ARRAY_SIZE(ni_boards)) |
251 | return -ENODEV; |
252 | *dev = isapnp_dev; |
253 | return 0; |
254 | } |
255 | |
256 | static const struct ni_board_struct *ni_atmio_probe(struct comedi_device *dev) |
257 | { |
258 | int device_id = ni_read_eeprom(dev, addr: 511); |
259 | int i; |
260 | |
261 | for (i = 0; i < ARRAY_SIZE(ni_boards); i++) { |
262 | const struct ni_board_struct *board = &ni_boards[i]; |
263 | |
264 | if (board->device_id == device_id) |
265 | return board; |
266 | } |
267 | if (device_id == 255) |
268 | dev_err(dev->class_dev, "can't find board\n" ); |
269 | else if (device_id == 0) |
270 | dev_err(dev->class_dev, |
271 | "EEPROM read error (?) or device not found\n" ); |
272 | else |
273 | dev_err(dev->class_dev, |
274 | "unknown device ID %d -- contact author\n" , device_id); |
275 | |
276 | return NULL; |
277 | } |
278 | |
279 | static int ni_atmio_attach(struct comedi_device *dev, |
280 | struct comedi_devconfig *it) |
281 | { |
282 | const struct ni_board_struct *board; |
283 | struct pnp_dev *isapnp_dev; |
284 | int ret; |
285 | unsigned long iobase; |
286 | unsigned int irq; |
287 | |
288 | ret = ni_alloc_private(dev); |
289 | if (ret) |
290 | return ret; |
291 | |
292 | iobase = it->options[0]; |
293 | irq = it->options[1]; |
294 | isapnp_dev = NULL; |
295 | if (iobase == 0) { |
296 | ret = ni_isapnp_find_board(dev: &isapnp_dev); |
297 | if (ret < 0) |
298 | return ret; |
299 | |
300 | iobase = pnp_port_start(dev: isapnp_dev, bar: 0); |
301 | irq = pnp_irq(dev: isapnp_dev, bar: 0); |
302 | comedi_set_hw_dev(dev, hw_dev: &isapnp_dev->dev); |
303 | } |
304 | |
305 | ret = comedi_request_region(dev, start: iobase, len: 0x20); |
306 | if (ret) |
307 | return ret; |
308 | |
309 | board = ni_atmio_probe(dev); |
310 | if (!board) |
311 | return -ENODEV; |
312 | dev->board_ptr = board; |
313 | dev->board_name = board->name; |
314 | |
315 | /* irq stuff */ |
316 | |
317 | if (irq != 0) { |
318 | if (irq > 15 || ni_irqpin[irq] == -1) |
319 | return -EINVAL; |
320 | ret = request_irq(irq, handler: ni_E_interrupt, flags: 0, |
321 | name: dev->board_name, dev); |
322 | if (ret < 0) |
323 | return -EINVAL; |
324 | dev->irq = irq; |
325 | } |
326 | |
327 | /* generic E series stuff in ni_mio_common.c */ |
328 | |
329 | ret = ni_E_init(dev, interrupt_pin: ni_irqpin[dev->irq], irq_polarity: 0); |
330 | if (ret < 0) |
331 | return ret; |
332 | |
333 | return 0; |
334 | } |
335 | |
336 | static void ni_atmio_detach(struct comedi_device *dev) |
337 | { |
338 | struct pnp_dev *isapnp_dev; |
339 | |
340 | mio_common_detach(dev); |
341 | comedi_legacy_detach(dev); |
342 | |
343 | isapnp_dev = dev->hw_dev ? to_pnp_dev(dev->hw_dev) : NULL; |
344 | if (isapnp_dev) |
345 | pnp_device_detach(pnp_dev: isapnp_dev); |
346 | } |
347 | |
348 | static struct comedi_driver ni_atmio_driver = { |
349 | .driver_name = "ni_atmio" , |
350 | .module = THIS_MODULE, |
351 | .attach = ni_atmio_attach, |
352 | .detach = ni_atmio_detach, |
353 | }; |
354 | module_comedi_driver(ni_atmio_driver); |
355 | |
356 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
357 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
358 | MODULE_LICENSE("GPL" ); |
359 | |
360 | |