1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * ii_pci20kc.c |
4 | * Driver for Intelligent Instruments PCI-20001C carrier board and modules. |
5 | * |
6 | * Copyright (C) 2000 Markus Kempf <kempf@matsci.uni-sb.de> |
7 | * with suggestions from David Schleef 16.06.2000 |
8 | */ |
9 | |
10 | /* |
11 | * Driver: ii_pci20kc |
12 | * Description: Intelligent Instruments PCI-20001C carrier board |
13 | * Devices: [Intelligent Instrumentation] PCI-20001C (ii_pci20kc) |
14 | * Author: Markus Kempf <kempf@matsci.uni-sb.de> |
15 | * Status: works |
16 | * |
17 | * Supports the PCI-20001C-1a and PCI-20001C-2a carrier boards. The |
18 | * -2a version has 32 on-board DIO channels. Three add-on modules |
19 | * can be added to the carrier board for additional functionality. |
20 | * |
21 | * Supported add-on modules: |
22 | * PCI-20006M-1 1 channel, 16-bit analog output module |
23 | * PCI-20006M-2 2 channel, 16-bit analog output module |
24 | * PCI-20341M-1A 4 channel, 16-bit analog input module |
25 | * |
26 | * Options: |
27 | * 0 Board base address |
28 | * 1 IRQ (not-used) |
29 | */ |
30 | |
31 | #include <linux/module.h> |
32 | #include <linux/io.h> |
33 | #include <linux/comedi/comedidev.h> |
34 | |
35 | /* |
36 | * Register I/O map |
37 | */ |
38 | #define II20K_SIZE 0x400 |
39 | #define II20K_MOD_OFFSET 0x100 |
40 | #define II20K_ID_REG 0x00 |
41 | #define II20K_ID_MOD1_EMPTY BIT(7) |
42 | #define II20K_ID_MOD2_EMPTY BIT(6) |
43 | #define II20K_ID_MOD3_EMPTY BIT(5) |
44 | #define II20K_ID_MASK 0x1f |
45 | #define II20K_ID_PCI20001C_1A 0x1b /* no on-board DIO */ |
46 | #define II20K_ID_PCI20001C_2A 0x1d /* on-board DIO */ |
47 | #define II20K_MOD_STATUS_REG 0x40 |
48 | #define II20K_MOD_STATUS_IRQ_MOD1 BIT(7) |
49 | #define II20K_MOD_STATUS_IRQ_MOD2 BIT(6) |
50 | #define II20K_MOD_STATUS_IRQ_MOD3 BIT(5) |
51 | #define II20K_DIO0_REG 0x80 |
52 | #define II20K_DIO1_REG 0x81 |
53 | #define II20K_DIR_ENA_REG 0x82 |
54 | #define II20K_DIR_DIO3_OUT BIT(7) |
55 | #define II20K_DIR_DIO2_OUT BIT(6) |
56 | #define II20K_BUF_DISAB_DIO3 BIT(5) |
57 | #define II20K_BUF_DISAB_DIO2 BIT(4) |
58 | #define II20K_DIR_DIO1_OUT BIT(3) |
59 | #define II20K_DIR_DIO0_OUT BIT(2) |
60 | #define II20K_BUF_DISAB_DIO1 BIT(1) |
61 | #define II20K_BUF_DISAB_DIO0 BIT(0) |
62 | #define II20K_CTRL01_REG 0x83 |
63 | #define II20K_CTRL01_SET BIT(7) |
64 | #define II20K_CTRL01_DIO0_IN BIT(4) |
65 | #define II20K_CTRL01_DIO1_IN BIT(1) |
66 | #define II20K_DIO2_REG 0xc0 |
67 | #define II20K_DIO3_REG 0xc1 |
68 | #define II20K_CTRL23_REG 0xc3 |
69 | #define II20K_CTRL23_SET BIT(7) |
70 | #define II20K_CTRL23_DIO2_IN BIT(4) |
71 | #define II20K_CTRL23_DIO3_IN BIT(1) |
72 | |
73 | #define II20K_ID_PCI20006M_1 0xe2 /* 1 AO channels */ |
74 | #define II20K_ID_PCI20006M_2 0xe3 /* 2 AO channels */ |
75 | #define II20K_AO_STRB_REG(x) (0x0b + ((x) * 0x08)) |
76 | #define II20K_AO_LSB_REG(x) (0x0d + ((x) * 0x08)) |
77 | #define II20K_AO_MSB_REG(x) (0x0e + ((x) * 0x08)) |
78 | #define II20K_AO_STRB_BOTH_REG 0x1b |
79 | |
80 | #define II20K_ID_PCI20341M_1 0x77 /* 4 AI channels */ |
81 | #define II20K_AI_STATUS_CMD_REG 0x01 |
82 | #define II20K_AI_STATUS_CMD_BUSY BIT(7) |
83 | #define II20K_AI_STATUS_CMD_HW_ENA BIT(1) |
84 | #define II20K_AI_STATUS_CMD_EXT_START BIT(0) |
85 | #define II20K_AI_LSB_REG 0x02 |
86 | #define II20K_AI_MSB_REG 0x03 |
87 | #define II20K_AI_PACER_RESET_REG 0x04 |
88 | #define II20K_AI_16BIT_DATA_REG 0x06 |
89 | #define II20K_AI_CONF_REG 0x10 |
90 | #define II20K_AI_CONF_ENA BIT(2) |
91 | #define II20K_AI_OPT_REG 0x11 |
92 | #define II20K_AI_OPT_TRIG_ENA BIT(5) |
93 | #define II20K_AI_OPT_TRIG_INV BIT(4) |
94 | #define II20K_AI_OPT_TIMEBASE(x) (((x) & 0x3) << 1) |
95 | #define II20K_AI_OPT_BURST_MODE BIT(0) |
96 | #define II20K_AI_STATUS_REG 0x12 |
97 | #define II20K_AI_STATUS_INT BIT(7) |
98 | #define II20K_AI_STATUS_TRIG BIT(6) |
99 | #define II20K_AI_STATUS_TRIG_ENA BIT(5) |
100 | #define II20K_AI_STATUS_PACER_ERR BIT(2) |
101 | #define II20K_AI_STATUS_DATA_ERR BIT(1) |
102 | #define II20K_AI_STATUS_SET_TIME_ERR BIT(0) |
103 | #define II20K_AI_LAST_CHAN_ADDR_REG 0x13 |
104 | #define II20K_AI_CUR_ADDR_REG 0x14 |
105 | #define II20K_AI_SET_TIME_REG 0x15 |
106 | #define II20K_AI_DELAY_LSB_REG 0x16 |
107 | #define II20K_AI_DELAY_MSB_REG 0x17 |
108 | #define II20K_AI_CHAN_ADV_REG 0x18 |
109 | #define II20K_AI_CHAN_RESET_REG 0x19 |
110 | #define II20K_AI_START_TRIG_REG 0x1a |
111 | #define II20K_AI_COUNT_RESET_REG 0x1b |
112 | #define II20K_AI_CHANLIST_REG 0x80 |
113 | #define II20K_AI_CHANLIST_ONBOARD_ONLY BIT(5) |
114 | #define II20K_AI_CHANLIST_GAIN(x) (((x) & 0x3) << 3) |
115 | #define II20K_AI_CHANLIST_MUX_ENA BIT(2) |
116 | #define II20K_AI_CHANLIST_CHAN(x) (((x) & 0x3) << 0) |
117 | #define II20K_AI_CHANLIST_LEN 0x80 |
118 | |
119 | /* the AO range is set by jumpers on the 20006M module */ |
120 | static const struct comedi_lrange ii20k_ao_ranges = { |
121 | 3, { |
122 | BIP_RANGE(5), /* Chan 0 - W1/W3 in Chan 1 - W2/W4 in */ |
123 | UNI_RANGE(10), /* Chan 0 - W1/W3 out Chan 1 - W2/W4 in */ |
124 | BIP_RANGE(10) /* Chan 0 - W1/W3 in Chan 1 - W2/W4 out */ |
125 | } |
126 | }; |
127 | |
128 | static const struct comedi_lrange ii20k_ai_ranges = { |
129 | 4, { |
130 | BIP_RANGE(5), /* gain 1 */ |
131 | BIP_RANGE(0.5), /* gain 10 */ |
132 | BIP_RANGE(0.05), /* gain 100 */ |
133 | BIP_RANGE(0.025) /* gain 200 */ |
134 | }, |
135 | }; |
136 | |
137 | static void __iomem *ii20k_module_iobase(struct comedi_device *dev, |
138 | struct comedi_subdevice *s) |
139 | { |
140 | return dev->mmio + (s->index + 1) * II20K_MOD_OFFSET; |
141 | } |
142 | |
143 | static int ii20k_ao_insn_write(struct comedi_device *dev, |
144 | struct comedi_subdevice *s, |
145 | struct comedi_insn *insn, |
146 | unsigned int *data) |
147 | { |
148 | void __iomem *iobase = ii20k_module_iobase(dev, s); |
149 | unsigned int chan = CR_CHAN(insn->chanspec); |
150 | int i; |
151 | |
152 | for (i = 0; i < insn->n; i++) { |
153 | unsigned int val = data[i]; |
154 | |
155 | s->readback[chan] = val; |
156 | |
157 | /* munge the offset binary data to 2's complement */ |
158 | val = comedi_offset_munge(s, val); |
159 | |
160 | writeb(val: val & 0xff, addr: iobase + II20K_AO_LSB_REG(chan)); |
161 | writeb(val: (val >> 8) & 0xff, addr: iobase + II20K_AO_MSB_REG(chan)); |
162 | writeb(val: 0x00, addr: iobase + II20K_AO_STRB_REG(chan)); |
163 | } |
164 | |
165 | return insn->n; |
166 | } |
167 | |
168 | static int ii20k_ai_eoc(struct comedi_device *dev, |
169 | struct comedi_subdevice *s, |
170 | struct comedi_insn *insn, |
171 | unsigned long context) |
172 | { |
173 | void __iomem *iobase = ii20k_module_iobase(dev, s); |
174 | unsigned char status; |
175 | |
176 | status = readb(addr: iobase + II20K_AI_STATUS_REG); |
177 | if ((status & II20K_AI_STATUS_INT) == 0) |
178 | return 0; |
179 | return -EBUSY; |
180 | } |
181 | |
182 | static void ii20k_ai_setup(struct comedi_device *dev, |
183 | struct comedi_subdevice *s, |
184 | unsigned int chanspec) |
185 | { |
186 | void __iomem *iobase = ii20k_module_iobase(dev, s); |
187 | unsigned int chan = CR_CHAN(chanspec); |
188 | unsigned int range = CR_RANGE(chanspec); |
189 | unsigned char val; |
190 | |
191 | /* initialize module */ |
192 | writeb(II20K_AI_CONF_ENA, addr: iobase + II20K_AI_CONF_REG); |
193 | |
194 | /* software conversion */ |
195 | writeb(val: 0, addr: iobase + II20K_AI_STATUS_CMD_REG); |
196 | |
197 | /* set the time base for the settling time counter based on the gain */ |
198 | val = (range < 3) ? II20K_AI_OPT_TIMEBASE(0) : II20K_AI_OPT_TIMEBASE(2); |
199 | writeb(val, addr: iobase + II20K_AI_OPT_REG); |
200 | |
201 | /* set the settling time counter based on the gain */ |
202 | val = (range < 2) ? 0x58 : (range < 3) ? 0x93 : 0x99; |
203 | writeb(val, addr: iobase + II20K_AI_SET_TIME_REG); |
204 | |
205 | /* set number of input channels */ |
206 | writeb(val: 1, addr: iobase + II20K_AI_LAST_CHAN_ADDR_REG); |
207 | |
208 | /* set the channel list byte */ |
209 | val = II20K_AI_CHANLIST_ONBOARD_ONLY | |
210 | II20K_AI_CHANLIST_MUX_ENA | |
211 | II20K_AI_CHANLIST_GAIN(range) | |
212 | II20K_AI_CHANLIST_CHAN(chan); |
213 | writeb(val, addr: iobase + II20K_AI_CHANLIST_REG); |
214 | |
215 | /* reset settling time counter and trigger delay counter */ |
216 | writeb(val: 0, addr: iobase + II20K_AI_COUNT_RESET_REG); |
217 | |
218 | /* reset channel scanner */ |
219 | writeb(val: 0, addr: iobase + II20K_AI_CHAN_RESET_REG); |
220 | } |
221 | |
222 | static int ii20k_ai_insn_read(struct comedi_device *dev, |
223 | struct comedi_subdevice *s, |
224 | struct comedi_insn *insn, |
225 | unsigned int *data) |
226 | { |
227 | void __iomem *iobase = ii20k_module_iobase(dev, s); |
228 | int ret; |
229 | int i; |
230 | |
231 | ii20k_ai_setup(dev, s, chanspec: insn->chanspec); |
232 | |
233 | for (i = 0; i < insn->n; i++) { |
234 | unsigned int val; |
235 | |
236 | /* generate a software start convert signal */ |
237 | readb(addr: iobase + II20K_AI_PACER_RESET_REG); |
238 | |
239 | ret = comedi_timeout(dev, s, insn, cb: ii20k_ai_eoc, context: 0); |
240 | if (ret) |
241 | return ret; |
242 | |
243 | val = readb(addr: iobase + II20K_AI_LSB_REG); |
244 | val |= (readb(addr: iobase + II20K_AI_MSB_REG) << 8); |
245 | |
246 | /* munge the 2's complement data to offset binary */ |
247 | data[i] = comedi_offset_munge(s, val); |
248 | } |
249 | |
250 | return insn->n; |
251 | } |
252 | |
253 | static void ii20k_dio_config(struct comedi_device *dev, |
254 | struct comedi_subdevice *s) |
255 | { |
256 | unsigned char ctrl01 = 0; |
257 | unsigned char ctrl23 = 0; |
258 | unsigned char dir_ena = 0; |
259 | |
260 | /* port 0 - channels 0-7 */ |
261 | if (s->io_bits & 0x000000ff) { |
262 | /* output port */ |
263 | ctrl01 &= ~II20K_CTRL01_DIO0_IN; |
264 | dir_ena &= ~II20K_BUF_DISAB_DIO0; |
265 | dir_ena |= II20K_DIR_DIO0_OUT; |
266 | } else { |
267 | /* input port */ |
268 | ctrl01 |= II20K_CTRL01_DIO0_IN; |
269 | dir_ena &= ~II20K_DIR_DIO0_OUT; |
270 | } |
271 | |
272 | /* port 1 - channels 8-15 */ |
273 | if (s->io_bits & 0x0000ff00) { |
274 | /* output port */ |
275 | ctrl01 &= ~II20K_CTRL01_DIO1_IN; |
276 | dir_ena &= ~II20K_BUF_DISAB_DIO1; |
277 | dir_ena |= II20K_DIR_DIO1_OUT; |
278 | } else { |
279 | /* input port */ |
280 | ctrl01 |= II20K_CTRL01_DIO1_IN; |
281 | dir_ena &= ~II20K_DIR_DIO1_OUT; |
282 | } |
283 | |
284 | /* port 2 - channels 16-23 */ |
285 | if (s->io_bits & 0x00ff0000) { |
286 | /* output port */ |
287 | ctrl23 &= ~II20K_CTRL23_DIO2_IN; |
288 | dir_ena &= ~II20K_BUF_DISAB_DIO2; |
289 | dir_ena |= II20K_DIR_DIO2_OUT; |
290 | } else { |
291 | /* input port */ |
292 | ctrl23 |= II20K_CTRL23_DIO2_IN; |
293 | dir_ena &= ~II20K_DIR_DIO2_OUT; |
294 | } |
295 | |
296 | /* port 3 - channels 24-31 */ |
297 | if (s->io_bits & 0xff000000) { |
298 | /* output port */ |
299 | ctrl23 &= ~II20K_CTRL23_DIO3_IN; |
300 | dir_ena &= ~II20K_BUF_DISAB_DIO3; |
301 | dir_ena |= II20K_DIR_DIO3_OUT; |
302 | } else { |
303 | /* input port */ |
304 | ctrl23 |= II20K_CTRL23_DIO3_IN; |
305 | dir_ena &= ~II20K_DIR_DIO3_OUT; |
306 | } |
307 | |
308 | ctrl23 |= II20K_CTRL01_SET; |
309 | ctrl23 |= II20K_CTRL23_SET; |
310 | |
311 | /* order is important */ |
312 | writeb(val: ctrl01, addr: dev->mmio + II20K_CTRL01_REG); |
313 | writeb(val: ctrl23, addr: dev->mmio + II20K_CTRL23_REG); |
314 | writeb(val: dir_ena, addr: dev->mmio + II20K_DIR_ENA_REG); |
315 | } |
316 | |
317 | static int ii20k_dio_insn_config(struct comedi_device *dev, |
318 | struct comedi_subdevice *s, |
319 | struct comedi_insn *insn, |
320 | unsigned int *data) |
321 | { |
322 | unsigned int chan = CR_CHAN(insn->chanspec); |
323 | unsigned int mask; |
324 | int ret; |
325 | |
326 | if (chan < 8) |
327 | mask = 0x000000ff; |
328 | else if (chan < 16) |
329 | mask = 0x0000ff00; |
330 | else if (chan < 24) |
331 | mask = 0x00ff0000; |
332 | else |
333 | mask = 0xff000000; |
334 | |
335 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); |
336 | if (ret) |
337 | return ret; |
338 | |
339 | ii20k_dio_config(dev, s); |
340 | |
341 | return insn->n; |
342 | } |
343 | |
344 | static int ii20k_dio_insn_bits(struct comedi_device *dev, |
345 | struct comedi_subdevice *s, |
346 | struct comedi_insn *insn, |
347 | unsigned int *data) |
348 | { |
349 | unsigned int mask; |
350 | |
351 | mask = comedi_dio_update_state(s, data); |
352 | if (mask) { |
353 | if (mask & 0x000000ff) |
354 | writeb(val: (s->state >> 0) & 0xff, |
355 | addr: dev->mmio + II20K_DIO0_REG); |
356 | if (mask & 0x0000ff00) |
357 | writeb(val: (s->state >> 8) & 0xff, |
358 | addr: dev->mmio + II20K_DIO1_REG); |
359 | if (mask & 0x00ff0000) |
360 | writeb(val: (s->state >> 16) & 0xff, |
361 | addr: dev->mmio + II20K_DIO2_REG); |
362 | if (mask & 0xff000000) |
363 | writeb(val: (s->state >> 24) & 0xff, |
364 | addr: dev->mmio + II20K_DIO3_REG); |
365 | } |
366 | |
367 | data[1] = readb(addr: dev->mmio + II20K_DIO0_REG); |
368 | data[1] |= readb(addr: dev->mmio + II20K_DIO1_REG) << 8; |
369 | data[1] |= readb(addr: dev->mmio + II20K_DIO2_REG) << 16; |
370 | data[1] |= readb(addr: dev->mmio + II20K_DIO3_REG) << 24; |
371 | |
372 | return insn->n; |
373 | } |
374 | |
375 | static int ii20k_init_module(struct comedi_device *dev, |
376 | struct comedi_subdevice *s) |
377 | { |
378 | void __iomem *iobase = ii20k_module_iobase(dev, s); |
379 | unsigned char id; |
380 | int ret; |
381 | |
382 | id = readb(addr: iobase + II20K_ID_REG); |
383 | switch (id) { |
384 | case II20K_ID_PCI20006M_1: |
385 | case II20K_ID_PCI20006M_2: |
386 | /* Analog Output subdevice */ |
387 | s->type = COMEDI_SUBD_AO; |
388 | s->subdev_flags = SDF_WRITABLE; |
389 | s->n_chan = (id == II20K_ID_PCI20006M_2) ? 2 : 1; |
390 | s->maxdata = 0xffff; |
391 | s->range_table = &ii20k_ao_ranges; |
392 | s->insn_write = ii20k_ao_insn_write; |
393 | |
394 | ret = comedi_alloc_subdev_readback(s); |
395 | if (ret) |
396 | return ret; |
397 | break; |
398 | case II20K_ID_PCI20341M_1: |
399 | /* Analog Input subdevice */ |
400 | s->type = COMEDI_SUBD_AI; |
401 | s->subdev_flags = SDF_READABLE | SDF_DIFF; |
402 | s->n_chan = 4; |
403 | s->maxdata = 0xffff; |
404 | s->range_table = &ii20k_ai_ranges; |
405 | s->insn_read = ii20k_ai_insn_read; |
406 | break; |
407 | default: |
408 | s->type = COMEDI_SUBD_UNUSED; |
409 | break; |
410 | } |
411 | |
412 | return 0; |
413 | } |
414 | |
415 | static int ii20k_attach(struct comedi_device *dev, |
416 | struct comedi_devconfig *it) |
417 | { |
418 | struct comedi_subdevice *s; |
419 | unsigned int membase; |
420 | unsigned char id; |
421 | bool has_dio; |
422 | int ret; |
423 | |
424 | membase = it->options[0]; |
425 | if (!membase || (membase & ~(0x100000 - II20K_SIZE))) { |
426 | dev_warn(dev->class_dev, |
427 | "%s: invalid memory address specified\n" , |
428 | dev->board_name); |
429 | return -EINVAL; |
430 | } |
431 | |
432 | if (!request_mem_region(membase, II20K_SIZE, dev->board_name)) { |
433 | dev_warn(dev->class_dev, "%s: I/O mem conflict (%#x,%u)\n" , |
434 | dev->board_name, membase, II20K_SIZE); |
435 | return -EIO; |
436 | } |
437 | dev->iobase = membase; /* actually, a memory address */ |
438 | |
439 | dev->mmio = ioremap(offset: membase, II20K_SIZE); |
440 | if (!dev->mmio) |
441 | return -ENOMEM; |
442 | |
443 | id = readb(addr: dev->mmio + II20K_ID_REG); |
444 | switch (id & II20K_ID_MASK) { |
445 | case II20K_ID_PCI20001C_1A: |
446 | has_dio = false; |
447 | break; |
448 | case II20K_ID_PCI20001C_2A: |
449 | has_dio = true; |
450 | break; |
451 | default: |
452 | return -ENODEV; |
453 | } |
454 | |
455 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
456 | if (ret) |
457 | return ret; |
458 | |
459 | s = &dev->subdevices[0]; |
460 | if (id & II20K_ID_MOD1_EMPTY) { |
461 | s->type = COMEDI_SUBD_UNUSED; |
462 | } else { |
463 | ret = ii20k_init_module(dev, s); |
464 | if (ret) |
465 | return ret; |
466 | } |
467 | |
468 | s = &dev->subdevices[1]; |
469 | if (id & II20K_ID_MOD2_EMPTY) { |
470 | s->type = COMEDI_SUBD_UNUSED; |
471 | } else { |
472 | ret = ii20k_init_module(dev, s); |
473 | if (ret) |
474 | return ret; |
475 | } |
476 | |
477 | s = &dev->subdevices[2]; |
478 | if (id & II20K_ID_MOD3_EMPTY) { |
479 | s->type = COMEDI_SUBD_UNUSED; |
480 | } else { |
481 | ret = ii20k_init_module(dev, s); |
482 | if (ret) |
483 | return ret; |
484 | } |
485 | |
486 | /* Digital I/O subdevice */ |
487 | s = &dev->subdevices[3]; |
488 | if (has_dio) { |
489 | s->type = COMEDI_SUBD_DIO; |
490 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
491 | s->n_chan = 32; |
492 | s->maxdata = 1; |
493 | s->range_table = &range_digital; |
494 | s->insn_bits = ii20k_dio_insn_bits; |
495 | s->insn_config = ii20k_dio_insn_config; |
496 | |
497 | /* default all channels to input */ |
498 | ii20k_dio_config(dev, s); |
499 | } else { |
500 | s->type = COMEDI_SUBD_UNUSED; |
501 | } |
502 | |
503 | return 0; |
504 | } |
505 | |
506 | static void ii20k_detach(struct comedi_device *dev) |
507 | { |
508 | if (dev->mmio) |
509 | iounmap(addr: dev->mmio); |
510 | if (dev->iobase) /* actually, a memory address */ |
511 | release_mem_region(dev->iobase, II20K_SIZE); |
512 | } |
513 | |
514 | static struct comedi_driver ii20k_driver = { |
515 | .driver_name = "ii_pci20kc" , |
516 | .module = THIS_MODULE, |
517 | .attach = ii20k_attach, |
518 | .detach = ii20k_detach, |
519 | }; |
520 | module_comedi_driver(ii20k_driver); |
521 | |
522 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
523 | MODULE_DESCRIPTION("Comedi driver for Intelligent Instruments PCI-20001C" ); |
524 | MODULE_LICENSE("GPL" ); |
525 | |