Commit | Line | Data |
---|---|---|
e55c95a3 GG |
1 | /* |
2 | comedi/drivers/me4000.c | |
3 | Source code for the Meilhaus ME-4000 board family. | |
4 | ||
5 | COMEDI - Linux Control and Measurement Device Interface | |
6 | Copyright (C) 2000 David A. Schleef <ds@schleef.org> | |
7 | ||
8 | This program is free software; you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 2 of the License, or | |
11 | (at your option) any later version. | |
12 | ||
13 | This program is distributed in the hope that it will be useful, | |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | GNU General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU General Public License | |
19 | along with this program; if not, write to the Free Software | |
20 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
21 | ||
22 | */ | |
23 | /* | |
24 | Driver: me4000 | |
25 | Description: Meilhaus ME-4000 series boards | |
26 | Devices: [Meilhaus] ME-4650 (me4000), ME-4670i, ME-4680, ME-4680i, ME-4680is | |
27 | Author: gg (Guenter Gebhardt <g.gebhardt@meilhaus.com>) | |
28 | Updated: Mon, 18 Mar 2002 15:34:01 -0800 | |
29 | Status: broken (no support for loading firmware) | |
30 | ||
31 | Supports: | |
32 | ||
33 | - Analog Input | |
34 | - Analog Output | |
35 | - Digital I/O | |
36 | - Counter | |
37 | ||
38 | Configuration Options: | |
39 | ||
40 | [0] - PCI bus number (optional) | |
41 | [1] - PCI slot number (optional) | |
42 | ||
43 | If bus/slot is not specified, the first available PCI | |
44 | device will be used. | |
45 | ||
46 | The firmware required by these boards is available in the | |
47 | comedi_nonfree_firmware tarball available from | |
48 | http://www.comedi.org. However, the driver's support for | |
49 | loading the firmware through comedi_config is currently | |
50 | broken. | |
51 | ||
52 | */ | |
53 | ||
25436dc9 | 54 | #include <linux/interrupt.h> |
e55c95a3 GG |
55 | #include "../comedidev.h" |
56 | ||
57 | #include <linux/delay.h> | |
58 | #include <linux/list.h> | |
59 | #include <linux/spinlock.h> | |
60 | ||
e55c95a3 GG |
61 | #include "me4000.h" |
62 | #if 0 | |
63 | /* file removed due to GPL incompatibility */ | |
64 | #include "me4000_fw.h" | |
65 | #endif | |
66 | ||
27f4caaa | 67 | static const struct me4000_board me4000_boards[] = { |
035d432a HS |
68 | { |
69 | .name = "ME-4650", | |
70 | .device_id = 0x4650, | |
6ba8dfef | 71 | .ai_nchan = 16, |
898f5191 | 72 | .dio_nchan = 32, |
035d432a HS |
73 | }, { |
74 | .name = "ME-4660", | |
75 | .device_id = 0x4660, | |
6ba8dfef HS |
76 | .ai_nchan = 32, |
77 | .ai_diff_nchan = 16, | |
898f5191 | 78 | .dio_nchan = 32, |
eedf4299 | 79 | .has_counter = 1, |
035d432a HS |
80 | }, { |
81 | .name = "ME-4660i", | |
82 | .device_id = 0x4661, | |
6ba8dfef HS |
83 | .ai_nchan = 32, |
84 | .ai_diff_nchan = 16, | |
898f5191 | 85 | .dio_nchan = 32, |
eedf4299 | 86 | .has_counter = 1, |
035d432a HS |
87 | }, { |
88 | .name = "ME-4660s", | |
89 | .device_id = 0x4662, | |
6ba8dfef HS |
90 | .ai_nchan = 32, |
91 | .ai_diff_nchan = 16, | |
92 | .ai_sh_nchan = 8, | |
898f5191 | 93 | .dio_nchan = 32, |
eedf4299 | 94 | .has_counter = 1, |
035d432a HS |
95 | }, { |
96 | .name = "ME-4660is", | |
97 | .device_id = 0x4663, | |
6ba8dfef HS |
98 | .ai_nchan = 32, |
99 | .ai_diff_nchan = 16, | |
100 | .ai_sh_nchan = 8, | |
898f5191 | 101 | .dio_nchan = 32, |
eedf4299 | 102 | .has_counter = 1, |
035d432a HS |
103 | }, { |
104 | .name = "ME-4670", | |
105 | .device_id = 0x4670, | |
106 | .ao = { | |
107 | .count = 4, | |
035d432a | 108 | }, |
6ba8dfef HS |
109 | .ai_nchan = 32, |
110 | .ai_diff_nchan = 16, | |
111 | .ex_trig_analog = 1, | |
898f5191 | 112 | .dio_nchan = 32, |
eedf4299 | 113 | .has_counter = 1, |
035d432a HS |
114 | }, { |
115 | .name = "ME-4670i", | |
116 | .device_id = 0x4671, | |
117 | .ao = { | |
118 | .count = 4, | |
035d432a | 119 | }, |
6ba8dfef HS |
120 | .ai_nchan = 32, |
121 | .ai_diff_nchan = 16, | |
122 | .ex_trig_analog = 1, | |
898f5191 | 123 | .dio_nchan = 32, |
eedf4299 | 124 | .has_counter = 1, |
035d432a HS |
125 | }, { |
126 | .name = "ME-4670s", | |
127 | .device_id = 0x4672, | |
128 | .ao = { | |
129 | .count = 4, | |
035d432a | 130 | }, |
6ba8dfef HS |
131 | .ai_nchan = 32, |
132 | .ai_diff_nchan = 16, | |
133 | .ai_sh_nchan = 8, | |
134 | .ex_trig_analog = 1, | |
898f5191 | 135 | .dio_nchan = 32, |
eedf4299 | 136 | .has_counter = 1, |
035d432a HS |
137 | }, { |
138 | .name = "ME-4670is", | |
139 | .device_id = 0x4673, | |
140 | .ao = { | |
141 | .count = 4, | |
035d432a | 142 | }, |
6ba8dfef HS |
143 | .ai_nchan = 32, |
144 | .ai_diff_nchan = 16, | |
145 | .ai_sh_nchan = 8, | |
146 | .ex_trig_analog = 1, | |
898f5191 | 147 | .dio_nchan = 32, |
eedf4299 | 148 | .has_counter = 1, |
035d432a HS |
149 | }, { |
150 | .name = "ME-4680", | |
151 | .device_id = 0x4680, | |
152 | .ao = { | |
153 | .count = 4, | |
154 | .fifo_count = 4, | |
155 | }, | |
6ba8dfef HS |
156 | .ai_nchan = 32, |
157 | .ai_diff_nchan = 16, | |
158 | .ex_trig_analog = 1, | |
898f5191 | 159 | .dio_nchan = 32, |
eedf4299 | 160 | .has_counter = 1, |
035d432a HS |
161 | }, { |
162 | .name = "ME-4680i", | |
163 | .device_id = 0x4681, | |
164 | .ao = { | |
165 | .count = 4, | |
166 | .fifo_count = 4, | |
167 | }, | |
6ba8dfef HS |
168 | .ai_nchan = 32, |
169 | .ai_diff_nchan = 16, | |
170 | .ex_trig_analog = 1, | |
898f5191 | 171 | .dio_nchan = 32, |
eedf4299 | 172 | .has_counter = 1, |
035d432a HS |
173 | }, { |
174 | .name = "ME-4680s", | |
175 | .device_id = 0x4682, | |
176 | .ao = { | |
177 | .count = 4, | |
178 | .fifo_count = 4, | |
179 | }, | |
6ba8dfef HS |
180 | .ai_nchan = 32, |
181 | .ai_diff_nchan = 16, | |
182 | .ai_sh_nchan = 8, | |
183 | .ex_trig_analog = 1, | |
898f5191 | 184 | .dio_nchan = 32, |
eedf4299 | 185 | .has_counter = 1, |
035d432a HS |
186 | }, { |
187 | .name = "ME-4680is", | |
188 | .device_id = 0x4683, | |
189 | .ao = { | |
190 | .count = 4, | |
191 | .fifo_count = 4, | |
192 | }, | |
6ba8dfef HS |
193 | .ai_nchan = 32, |
194 | .ai_diff_nchan = 16, | |
195 | .ai_sh_nchan = 8, | |
196 | .ex_trig_analog = 1, | |
898f5191 | 197 | .dio_nchan = 32, |
eedf4299 | 198 | .has_counter = 1, |
035d432a | 199 | }, |
e55c95a3 GG |
200 | }; |
201 | ||
e55c95a3 GG |
202 | /*----------------------------------------------------------------------------- |
203 | Meilhaus function prototypes | |
204 | ---------------------------------------------------------------------------*/ | |
71b5f4f1 | 205 | static int get_registers(struct comedi_device *dev, struct pci_dev *pci_dev_p); |
0a85b6f0 MT |
206 | static int init_board_info(struct comedi_device *dev, |
207 | struct pci_dev *pci_dev_p); | |
71b5f4f1 BP |
208 | static int init_ao_context(struct comedi_device *dev); |
209 | static int init_ai_context(struct comedi_device *dev); | |
210 | static int init_dio_context(struct comedi_device *dev); | |
211 | static int init_cnt_context(struct comedi_device *dev); | |
212 | static int xilinx_download(struct comedi_device *dev); | |
213 | static int reset_board(struct comedi_device *dev); | |
214 | ||
71b5f4f1 | 215 | static int ai_write_chanlist(struct comedi_device *dev, |
0a85b6f0 MT |
216 | struct comedi_subdevice *s, |
217 | struct comedi_cmd *cmd); | |
e55c95a3 | 218 | |
9ced1de6 | 219 | static const struct comedi_lrange me4000_ai_range = { |
e55c95a3 GG |
220 | 4, |
221 | { | |
0a85b6f0 MT |
222 | UNI_RANGE(2.5), |
223 | UNI_RANGE(10), | |
224 | BIP_RANGE(2.5), | |
225 | BIP_RANGE(10), | |
226 | } | |
e55c95a3 GG |
227 | }; |
228 | ||
9ced1de6 | 229 | static const struct comedi_lrange me4000_ao_range = { |
e55c95a3 GG |
230 | 1, |
231 | { | |
0a85b6f0 MT |
232 | BIP_RANGE(10), |
233 | } | |
e55c95a3 GG |
234 | }; |
235 | ||
0707bb04 | 236 | static int me4000_probe(struct comedi_device *dev, struct comedi_devconfig *it) |
e55c95a3 | 237 | { |
20fb2280 | 238 | struct pci_dev *pci_device = NULL; |
e55c95a3 | 239 | int result, i; |
27f4caaa | 240 | struct me4000_board *board; |
e55c95a3 | 241 | |
e55c95a3 | 242 | /* Allocate private memory */ |
63aa142a | 243 | if (alloc_private(dev, sizeof(struct me4000_info)) < 0) |
e55c95a3 | 244 | return -ENOMEM; |
82675f35 | 245 | |
e55c95a3 GG |
246 | /* |
247 | * Probe the device to determine what device in the series it is. | |
248 | */ | |
20fb2280 | 249 | for_each_pci_dev(pci_device) { |
e55c95a3 | 250 | if (pci_device->vendor == PCI_VENDOR_ID_MEILHAUS) { |
82933302 | 251 | for (i = 0; i < ARRAY_SIZE(me4000_boards); i++) { |
e55c95a3 | 252 | if (me4000_boards[i].device_id == |
0a85b6f0 | 253 | pci_device->device) { |
b6241fda GS |
254 | /* |
255 | * Was a particular | |
256 | * bus/slot requested? | |
257 | */ | |
e55c95a3 | 258 | if ((it->options[0] != 0) |
0a85b6f0 | 259 | || (it->options[1] != 0)) { |
b6241fda GS |
260 | /* |
261 | * Are we on the wrong | |
262 | * bus/slot? | |
263 | */ | |
e55c95a3 | 264 | if (pci_device->bus->number != |
0a85b6f0 MT |
265 | it->options[0] |
266 | || | |
267 | PCI_SLOT(pci_device->devfn) | |
268 | != it->options[1]) { | |
e55c95a3 GG |
269 | continue; |
270 | } | |
271 | } | |
272 | dev->board_ptr = me4000_boards + i; | |
0a85b6f0 MT |
273 | board = |
274 | (struct me4000_board *) | |
275 | dev->board_ptr; | |
e55c95a3 GG |
276 | info->pci_dev_p = pci_device; |
277 | goto found; | |
278 | } | |
279 | } | |
280 | } | |
281 | } | |
282 | ||
283 | printk(KERN_ERR | |
b6241fda GS |
284 | "comedi%d: me4000: me4000_probe(): " |
285 | "No supported board found (req. bus/slot : %d/%d)\n", | |
0a85b6f0 | 286 | dev->minor, it->options[0], it->options[1]); |
e55c95a3 GG |
287 | return -ENODEV; |
288 | ||
0a85b6f0 | 289 | found: |
e55c95a3 GG |
290 | |
291 | printk(KERN_INFO | |
b6241fda GS |
292 | "comedi%d: me4000: me4000_probe(): " |
293 | "Found %s at PCI bus %d, slot %d\n", | |
0a85b6f0 MT |
294 | dev->minor, me4000_boards[i].name, pci_device->bus->number, |
295 | PCI_SLOT(pci_device->devfn)); | |
e55c95a3 GG |
296 | |
297 | /* Set data in device structure */ | |
298 | dev->board_name = board->name; | |
299 | ||
300 | /* Enable PCI device and request regions */ | |
301 | result = comedi_pci_enable(pci_device, dev->board_name); | |
302 | if (result) { | |
303 | printk(KERN_ERR | |
b6241fda GS |
304 | "comedi%d: me4000: me4000_probe(): Cannot enable PCI " |
305 | "device and request I/O regions\n", dev->minor); | |
e55c95a3 GG |
306 | return result; |
307 | } | |
308 | ||
309 | /* Get the PCI base registers */ | |
310 | result = get_registers(dev, pci_device); | |
311 | if (result) { | |
312 | printk(KERN_ERR | |
b6241fda GS |
313 | "comedi%d: me4000: me4000_probe(): " |
314 | "Cannot get registers\n", dev->minor); | |
e55c95a3 GG |
315 | return result; |
316 | } | |
317 | /* Initialize board info */ | |
318 | result = init_board_info(dev, pci_device); | |
319 | if (result) { | |
320 | printk(KERN_ERR | |
b6241fda GS |
321 | "comedi%d: me4000: me4000_probe(): " |
322 | "Cannot init baord info\n", dev->minor); | |
e55c95a3 GG |
323 | return result; |
324 | } | |
325 | ||
326 | /* Init analog output context */ | |
327 | result = init_ao_context(dev); | |
328 | if (result) { | |
329 | printk(KERN_ERR | |
b6241fda GS |
330 | "comedi%d: me4000: me4000_probe(): " |
331 | "Cannot init ao context\n", dev->minor); | |
e55c95a3 GG |
332 | return result; |
333 | } | |
334 | ||
335 | /* Init analog input context */ | |
336 | result = init_ai_context(dev); | |
337 | if (result) { | |
338 | printk(KERN_ERR | |
b6241fda GS |
339 | "comedi%d: me4000: me4000_probe(): " |
340 | "Cannot init ai context\n", dev->minor); | |
e55c95a3 GG |
341 | return result; |
342 | } | |
343 | ||
344 | /* Init digital I/O context */ | |
345 | result = init_dio_context(dev); | |
346 | if (result) { | |
347 | printk(KERN_ERR | |
b6241fda GS |
348 | "comedi%d: me4000: me4000_probe(): " |
349 | "Cannot init dio context\n", dev->minor); | |
e55c95a3 GG |
350 | return result; |
351 | } | |
352 | ||
353 | /* Init counter context */ | |
354 | result = init_cnt_context(dev); | |
355 | if (result) { | |
356 | printk(KERN_ERR | |
b6241fda GS |
357 | "comedi%d: me4000: me4000_probe(): " |
358 | "Cannot init cnt context\n", dev->minor); | |
e55c95a3 GG |
359 | return result; |
360 | } | |
361 | ||
362 | /* Download the xilinx firmware */ | |
363 | result = xilinx_download(dev); | |
364 | if (result) { | |
365 | printk(KERN_ERR | |
b6241fda GS |
366 | "comedi%d: me4000: me4000_probe(): " |
367 | "Can't download firmware\n", dev->minor); | |
e55c95a3 GG |
368 | return result; |
369 | } | |
370 | ||
371 | /* Make a hardware reset */ | |
372 | result = reset_board(dev); | |
373 | if (result) { | |
374 | printk(KERN_ERR | |
0a85b6f0 MT |
375 | "comedi%d: me4000: me4000_probe(): Can't reset board\n", |
376 | dev->minor); | |
e55c95a3 GG |
377 | return result; |
378 | } | |
379 | ||
380 | return 0; | |
381 | } | |
382 | ||
71b5f4f1 | 383 | static int get_registers(struct comedi_device *dev, struct pci_dev *pci_dev_p) |
e55c95a3 | 384 | { |
b6241fda | 385 | /*--------------------------- plx regbase -------------------------------*/ |
e55c95a3 GG |
386 | |
387 | info->plx_regbase = pci_resource_start(pci_dev_p, 1); | |
388 | if (info->plx_regbase == 0) { | |
389 | printk(KERN_ERR | |
b6241fda GS |
390 | "comedi%d: me4000: get_registers(): " |
391 | "PCI base address 1 is not available\n", dev->minor); | |
e55c95a3 GG |
392 | return -ENODEV; |
393 | } | |
394 | info->plx_regbase_size = pci_resource_len(pci_dev_p, 1); | |
395 | ||
b6241fda | 396 | /*--------------------------- me4000 regbase ----------------------------*/ |
e55c95a3 GG |
397 | |
398 | info->me4000_regbase = pci_resource_start(pci_dev_p, 2); | |
399 | if (info->me4000_regbase == 0) { | |
400 | printk(KERN_ERR | |
b6241fda GS |
401 | "comedi%d: me4000: get_registers(): " |
402 | "PCI base address 2 is not available\n", dev->minor); | |
e55c95a3 GG |
403 | return -ENODEV; |
404 | } | |
405 | info->me4000_regbase_size = pci_resource_len(pci_dev_p, 2); | |
406 | ||
407 | /*--------------------------- timer regbase ------------------------------*/ | |
408 | ||
409 | info->timer_regbase = pci_resource_start(pci_dev_p, 3); | |
410 | if (info->timer_regbase == 0) { | |
411 | printk(KERN_ERR | |
b6241fda GS |
412 | "comedi%d: me4000: get_registers(): " |
413 | "PCI base address 3 is not available\n", dev->minor); | |
e55c95a3 GG |
414 | return -ENODEV; |
415 | } | |
416 | info->timer_regbase_size = pci_resource_len(pci_dev_p, 3); | |
417 | ||
b6241fda | 418 | /*--------------------------- program regbase ----------------------------*/ |
e55c95a3 GG |
419 | |
420 | info->program_regbase = pci_resource_start(pci_dev_p, 5); | |
421 | if (info->program_regbase == 0) { | |
422 | printk(KERN_ERR | |
b6241fda GS |
423 | "comedi%d: me4000: get_registers(): " |
424 | "PCI base address 5 is not available\n", dev->minor); | |
e55c95a3 GG |
425 | return -ENODEV; |
426 | } | |
427 | info->program_regbase_size = pci_resource_len(pci_dev_p, 5); | |
428 | ||
429 | return 0; | |
430 | } | |
431 | ||
71b5f4f1 | 432 | static int init_board_info(struct comedi_device *dev, struct pci_dev *pci_dev_p) |
e55c95a3 GG |
433 | { |
434 | int result; | |
435 | ||
e55c95a3 | 436 | /* Init spin locks */ |
b6c77757 BP |
437 | /* spin_lock_init(&info->preload_lock); */ |
438 | /* spin_lock_init(&info->ai_ctrl_lock); */ | |
e55c95a3 GG |
439 | |
440 | /* Get the serial number */ | |
441 | result = pci_read_config_dword(pci_dev_p, 0x2C, &info->serial_no); | |
82675f35 | 442 | if (result != PCIBIOS_SUCCESSFUL) |
e55c95a3 | 443 | return result; |
e55c95a3 GG |
444 | |
445 | /* Get the hardware revision */ | |
446 | result = pci_read_config_byte(pci_dev_p, 0x08, &info->hw_revision); | |
82675f35 | 447 | if (result != PCIBIOS_SUCCESSFUL) |
e55c95a3 | 448 | return result; |
e55c95a3 GG |
449 | |
450 | /* Get the vendor id */ | |
451 | info->vendor_id = pci_dev_p->vendor; | |
452 | ||
453 | /* Get the device id */ | |
454 | info->device_id = pci_dev_p->device; | |
455 | ||
456 | /* Get the irq assigned to the board */ | |
457 | info->irq = pci_dev_p->irq; | |
458 | ||
459 | return 0; | |
460 | } | |
461 | ||
71b5f4f1 | 462 | static int init_ao_context(struct comedi_device *dev) |
e55c95a3 GG |
463 | { |
464 | int i; | |
465 | ||
e55c95a3 | 466 | for (i = 0; i < thisboard->ao.count; i++) { |
b6c77757 | 467 | /* spin_lock_init(&info->ao_context[i].use_lock); */ |
e55c95a3 GG |
468 | info->ao_context[i].irq = info->irq; |
469 | ||
470 | switch (i) { | |
471 | case 0: | |
472 | info->ao_context[i].ctrl_reg = | |
0a85b6f0 | 473 | info->me4000_regbase + ME4000_AO_00_CTRL_REG; |
e55c95a3 | 474 | info->ao_context[i].status_reg = |
0a85b6f0 | 475 | info->me4000_regbase + ME4000_AO_00_STATUS_REG; |
e55c95a3 | 476 | info->ao_context[i].fifo_reg = |
0a85b6f0 | 477 | info->me4000_regbase + ME4000_AO_00_FIFO_REG; |
e55c95a3 | 478 | info->ao_context[i].single_reg = |
0a85b6f0 | 479 | info->me4000_regbase + ME4000_AO_00_SINGLE_REG; |
e55c95a3 | 480 | info->ao_context[i].timer_reg = |
0a85b6f0 | 481 | info->me4000_regbase + ME4000_AO_00_TIMER_REG; |
e55c95a3 | 482 | info->ao_context[i].irq_status_reg = |
0a85b6f0 | 483 | info->me4000_regbase + ME4000_IRQ_STATUS_REG; |
e55c95a3 | 484 | info->ao_context[i].preload_reg = |
0a85b6f0 | 485 | info->me4000_regbase + ME4000_AO_LOADSETREG_XX; |
e55c95a3 GG |
486 | break; |
487 | case 1: | |
488 | info->ao_context[i].ctrl_reg = | |
0a85b6f0 | 489 | info->me4000_regbase + ME4000_AO_01_CTRL_REG; |
e55c95a3 | 490 | info->ao_context[i].status_reg = |
0a85b6f0 | 491 | info->me4000_regbase + ME4000_AO_01_STATUS_REG; |
e55c95a3 | 492 | info->ao_context[i].fifo_reg = |
0a85b6f0 | 493 | info->me4000_regbase + ME4000_AO_01_FIFO_REG; |
e55c95a3 | 494 | info->ao_context[i].single_reg = |
0a85b6f0 | 495 | info->me4000_regbase + ME4000_AO_01_SINGLE_REG; |
e55c95a3 | 496 | info->ao_context[i].timer_reg = |
0a85b6f0 | 497 | info->me4000_regbase + ME4000_AO_01_TIMER_REG; |
e55c95a3 | 498 | info->ao_context[i].irq_status_reg = |
0a85b6f0 | 499 | info->me4000_regbase + ME4000_IRQ_STATUS_REG; |
e55c95a3 | 500 | info->ao_context[i].preload_reg = |
0a85b6f0 | 501 | info->me4000_regbase + ME4000_AO_LOADSETREG_XX; |
e55c95a3 GG |
502 | break; |
503 | case 2: | |
504 | info->ao_context[i].ctrl_reg = | |
0a85b6f0 | 505 | info->me4000_regbase + ME4000_AO_02_CTRL_REG; |
e55c95a3 | 506 | info->ao_context[i].status_reg = |
0a85b6f0 | 507 | info->me4000_regbase + ME4000_AO_02_STATUS_REG; |
e55c95a3 | 508 | info->ao_context[i].fifo_reg = |
0a85b6f0 | 509 | info->me4000_regbase + ME4000_AO_02_FIFO_REG; |
e55c95a3 | 510 | info->ao_context[i].single_reg = |
0a85b6f0 | 511 | info->me4000_regbase + ME4000_AO_02_SINGLE_REG; |
e55c95a3 | 512 | info->ao_context[i].timer_reg = |
0a85b6f0 | 513 | info->me4000_regbase + ME4000_AO_02_TIMER_REG; |
e55c95a3 | 514 | info->ao_context[i].irq_status_reg = |
0a85b6f0 | 515 | info->me4000_regbase + ME4000_IRQ_STATUS_REG; |
e55c95a3 | 516 | info->ao_context[i].preload_reg = |
0a85b6f0 | 517 | info->me4000_regbase + ME4000_AO_LOADSETREG_XX; |
e55c95a3 GG |
518 | break; |
519 | case 3: | |
520 | info->ao_context[i].ctrl_reg = | |
0a85b6f0 | 521 | info->me4000_regbase + ME4000_AO_03_CTRL_REG; |
e55c95a3 | 522 | info->ao_context[i].status_reg = |
0a85b6f0 | 523 | info->me4000_regbase + ME4000_AO_03_STATUS_REG; |
e55c95a3 | 524 | info->ao_context[i].fifo_reg = |
0a85b6f0 | 525 | info->me4000_regbase + ME4000_AO_03_FIFO_REG; |
e55c95a3 | 526 | info->ao_context[i].single_reg = |
0a85b6f0 | 527 | info->me4000_regbase + ME4000_AO_03_SINGLE_REG; |
e55c95a3 | 528 | info->ao_context[i].timer_reg = |
0a85b6f0 | 529 | info->me4000_regbase + ME4000_AO_03_TIMER_REG; |
e55c95a3 | 530 | info->ao_context[i].irq_status_reg = |
0a85b6f0 | 531 | info->me4000_regbase + ME4000_IRQ_STATUS_REG; |
e55c95a3 | 532 | info->ao_context[i].preload_reg = |
0a85b6f0 | 533 | info->me4000_regbase + ME4000_AO_LOADSETREG_XX; |
e55c95a3 GG |
534 | break; |
535 | default: | |
536 | break; | |
537 | } | |
538 | } | |
539 | ||
540 | return 0; | |
541 | } | |
542 | ||
71b5f4f1 | 543 | static int init_ai_context(struct comedi_device *dev) |
e55c95a3 | 544 | { |
e55c95a3 GG |
545 | info->ai_context.irq = info->irq; |
546 | ||
547 | info->ai_context.ctrl_reg = info->me4000_regbase + ME4000_AI_CTRL_REG; | |
548 | info->ai_context.status_reg = | |
0a85b6f0 | 549 | info->me4000_regbase + ME4000_AI_STATUS_REG; |
e55c95a3 | 550 | info->ai_context.channel_list_reg = |
0a85b6f0 | 551 | info->me4000_regbase + ME4000_AI_CHANNEL_LIST_REG; |
e55c95a3 GG |
552 | info->ai_context.data_reg = info->me4000_regbase + ME4000_AI_DATA_REG; |
553 | info->ai_context.chan_timer_reg = | |
0a85b6f0 | 554 | info->me4000_regbase + ME4000_AI_CHAN_TIMER_REG; |
e55c95a3 | 555 | info->ai_context.chan_pre_timer_reg = |
0a85b6f0 | 556 | info->me4000_regbase + ME4000_AI_CHAN_PRE_TIMER_REG; |
e55c95a3 | 557 | info->ai_context.scan_timer_low_reg = |
0a85b6f0 | 558 | info->me4000_regbase + ME4000_AI_SCAN_TIMER_LOW_REG; |
e55c95a3 | 559 | info->ai_context.scan_timer_high_reg = |
0a85b6f0 | 560 | info->me4000_regbase + ME4000_AI_SCAN_TIMER_HIGH_REG; |
e55c95a3 | 561 | info->ai_context.scan_pre_timer_low_reg = |
0a85b6f0 | 562 | info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_LOW_REG; |
e55c95a3 | 563 | info->ai_context.scan_pre_timer_high_reg = |
0a85b6f0 | 564 | info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_HIGH_REG; |
e55c95a3 GG |
565 | info->ai_context.start_reg = info->me4000_regbase + ME4000_AI_START_REG; |
566 | info->ai_context.irq_status_reg = | |
0a85b6f0 | 567 | info->me4000_regbase + ME4000_IRQ_STATUS_REG; |
e55c95a3 | 568 | info->ai_context.sample_counter_reg = |
0a85b6f0 | 569 | info->me4000_regbase + ME4000_AI_SAMPLE_COUNTER_REG; |
e55c95a3 GG |
570 | |
571 | return 0; | |
572 | } | |
573 | ||
71b5f4f1 | 574 | static int init_dio_context(struct comedi_device *dev) |
e55c95a3 | 575 | { |
e55c95a3 GG |
576 | info->dio_context.dir_reg = info->me4000_regbase + ME4000_DIO_DIR_REG; |
577 | info->dio_context.ctrl_reg = info->me4000_regbase + ME4000_DIO_CTRL_REG; | |
578 | info->dio_context.port_0_reg = | |
0a85b6f0 | 579 | info->me4000_regbase + ME4000_DIO_PORT_0_REG; |
e55c95a3 | 580 | info->dio_context.port_1_reg = |
0a85b6f0 | 581 | info->me4000_regbase + ME4000_DIO_PORT_1_REG; |
e55c95a3 | 582 | info->dio_context.port_2_reg = |
0a85b6f0 | 583 | info->me4000_regbase + ME4000_DIO_PORT_2_REG; |
e55c95a3 | 584 | info->dio_context.port_3_reg = |
0a85b6f0 | 585 | info->me4000_regbase + ME4000_DIO_PORT_3_REG; |
e55c95a3 GG |
586 | |
587 | return 0; | |
588 | } | |
589 | ||
71b5f4f1 | 590 | static int init_cnt_context(struct comedi_device *dev) |
e55c95a3 | 591 | { |
e55c95a3 GG |
592 | info->cnt_context.ctrl_reg = info->timer_regbase + ME4000_CNT_CTRL_REG; |
593 | info->cnt_context.counter_0_reg = | |
0a85b6f0 | 594 | info->timer_regbase + ME4000_CNT_COUNTER_0_REG; |
e55c95a3 | 595 | info->cnt_context.counter_1_reg = |
0a85b6f0 | 596 | info->timer_regbase + ME4000_CNT_COUNTER_1_REG; |
e55c95a3 | 597 | info->cnt_context.counter_2_reg = |
0a85b6f0 | 598 | info->timer_regbase + ME4000_CNT_COUNTER_2_REG; |
e55c95a3 GG |
599 | |
600 | return 0; | |
601 | } | |
602 | ||
603 | #define FIRMWARE_NOT_AVAILABLE 1 | |
604 | #if FIRMWARE_NOT_AVAILABLE | |
605 | extern unsigned char *xilinx_firm; | |
606 | #endif | |
607 | ||
71b5f4f1 | 608 | static int xilinx_download(struct comedi_device *dev) |
e55c95a3 GG |
609 | { |
610 | u32 value = 0; | |
611 | wait_queue_head_t queue; | |
612 | int idx = 0; | |
613 | int size = 0; | |
614 | ||
e55c95a3 GG |
615 | init_waitqueue_head(&queue); |
616 | ||
617 | /* | |
618 | * Set PLX local interrupt 2 polarity to high. | |
619 | * Interrupt is thrown by init pin of xilinx. | |
620 | */ | |
621 | outl(0x10, info->plx_regbase + PLX_INTCSR); | |
622 | ||
623 | /* Set /CS and /WRITE of the Xilinx */ | |
624 | value = inl(info->plx_regbase + PLX_ICR); | |
625 | value |= 0x100; | |
626 | outl(value, info->plx_regbase + PLX_ICR); | |
627 | ||
628 | /* Init Xilinx with CS1 */ | |
629 | inb(info->program_regbase + 0xC8); | |
630 | ||
631 | /* Wait until /INIT pin is set */ | |
632 | udelay(20); | |
d86d3a01 | 633 | if (!(inl(info->plx_regbase + PLX_INTCSR) & 0x20)) { |
e55c95a3 | 634 | printk(KERN_ERR |
b6241fda GS |
635 | "comedi%d: me4000: xilinx_download(): " |
636 | "Can't init Xilinx\n", dev->minor); | |
e55c95a3 GG |
637 | return -EIO; |
638 | } | |
639 | ||
640 | /* Reset /CS and /WRITE of the Xilinx */ | |
641 | value = inl(info->plx_regbase + PLX_ICR); | |
642 | value &= ~0x100; | |
643 | outl(value, info->plx_regbase + PLX_ICR); | |
644 | if (FIRMWARE_NOT_AVAILABLE) { | |
b6241fda GS |
645 | comedi_error(dev, "xilinx firmware unavailable " |
646 | "due to licensing, aborting"); | |
e55c95a3 GG |
647 | return -EIO; |
648 | } else { | |
649 | /* Download Xilinx firmware */ | |
650 | size = (xilinx_firm[0] << 24) + (xilinx_firm[1] << 16) + | |
0a85b6f0 | 651 | (xilinx_firm[2] << 8) + xilinx_firm[3]; |
e55c95a3 GG |
652 | udelay(10); |
653 | ||
654 | for (idx = 0; idx < size; idx++) { | |
655 | outb(xilinx_firm[16 + idx], info->program_regbase); | |
656 | udelay(10); | |
657 | ||
658 | /* Check if BUSY flag is low */ | |
659 | if (inl(info->plx_regbase + PLX_ICR) & 0x20) { | |
660 | printk(KERN_ERR | |
b6241fda GS |
661 | "comedi%d: me4000: xilinx_download(): " |
662 | "Xilinx is still busy (idx = %d)\n", | |
0a85b6f0 | 663 | dev->minor, idx); |
e55c95a3 GG |
664 | return -EIO; |
665 | } | |
666 | } | |
667 | } | |
668 | ||
669 | /* If done flag is high download was successful */ | |
670 | if (inl(info->plx_regbase + PLX_ICR) & 0x4) { | |
671 | } else { | |
672 | printk(KERN_ERR | |
b6241fda GS |
673 | "comedi%d: me4000: xilinx_download(): " |
674 | "DONE flag is not set\n", dev->minor); | |
e55c95a3 | 675 | printk(KERN_ERR |
b6241fda GS |
676 | "comedi%d: me4000: xilinx_download(): " |
677 | "Download not successful\n", dev->minor); | |
e55c95a3 GG |
678 | return -EIO; |
679 | } | |
680 | ||
681 | /* Set /CS and /WRITE */ | |
682 | value = inl(info->plx_regbase + PLX_ICR); | |
683 | value |= 0x100; | |
684 | outl(value, info->plx_regbase + PLX_ICR); | |
685 | ||
686 | return 0; | |
687 | } | |
688 | ||
71b5f4f1 | 689 | static int reset_board(struct comedi_device *dev) |
e55c95a3 GG |
690 | { |
691 | unsigned long icr; | |
692 | ||
e55c95a3 | 693 | /* Make a hardware reset */ |
d6cbe537 | 694 | icr = inl(info->plx_regbase + PLX_ICR); |
e55c95a3 | 695 | icr |= 0x40000000; |
d6cbe537 | 696 | outl(icr, info->plx_regbase + PLX_ICR); |
e55c95a3 | 697 | icr &= ~0x40000000; |
d6cbe537 | 698 | outl(icr, info->plx_regbase + PLX_ICR); |
e55c95a3 GG |
699 | |
700 | /* 0x8000 to the DACs means an output voltage of 0V */ | |
d6cbe537 HS |
701 | outl(0x8000, info->me4000_regbase + ME4000_AO_00_SINGLE_REG); |
702 | outl(0x8000, info->me4000_regbase + ME4000_AO_01_SINGLE_REG); | |
703 | outl(0x8000, info->me4000_regbase + ME4000_AO_02_SINGLE_REG); | |
704 | outl(0x8000, info->me4000_regbase + ME4000_AO_03_SINGLE_REG); | |
e55c95a3 GG |
705 | |
706 | /* Set both stop bits in the analog input control register */ | |
d6cbe537 HS |
707 | outl(ME4000_AI_CTRL_BIT_IMMEDIATE_STOP | ME4000_AI_CTRL_BIT_STOP, |
708 | info->me4000_regbase + ME4000_AI_CTRL_REG); | |
e55c95a3 GG |
709 | |
710 | /* Set both stop bits in the analog output control register */ | |
d6cbe537 HS |
711 | outl(ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, |
712 | info->me4000_regbase + ME4000_AO_00_CTRL_REG); | |
713 | outl(ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, | |
714 | info->me4000_regbase + ME4000_AO_01_CTRL_REG); | |
715 | outl(ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, | |
716 | info->me4000_regbase + ME4000_AO_02_CTRL_REG); | |
717 | outl(ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, | |
718 | info->me4000_regbase + ME4000_AO_03_CTRL_REG); | |
e55c95a3 GG |
719 | |
720 | /* Enable interrupts on the PLX */ | |
d6cbe537 | 721 | outl(0x43, info->plx_regbase + PLX_INTCSR); |
e55c95a3 GG |
722 | |
723 | /* Set the adustment register for AO demux */ | |
d6cbe537 | 724 | outl(ME4000_AO_DEMUX_ADJUST_VALUE, |
0a85b6f0 | 725 | info->me4000_regbase + ME4000_AO_DEMUX_ADJUST_REG); |
e55c95a3 | 726 | |
b6241fda GS |
727 | /* |
728 | * Set digital I/O direction for port 0 | |
729 | * to output on isolated versions | |
730 | */ | |
d6cbe537 HS |
731 | if (!(inl(info->me4000_regbase + ME4000_DIO_DIR_REG) & 0x1)) |
732 | outl(0x1, info->me4000_regbase + ME4000_DIO_CTRL_REG); | |
e55c95a3 GG |
733 | |
734 | return 0; | |
735 | } | |
736 | ||
e55c95a3 GG |
737 | /*============================================================================= |
738 | Analog input section | |
739 | ===========================================================================*/ | |
740 | ||
71b5f4f1 | 741 | static int me4000_ai_insn_read(struct comedi_device *dev, |
0a85b6f0 MT |
742 | struct comedi_subdevice *subdevice, |
743 | struct comedi_insn *insn, unsigned int *data) | |
e55c95a3 GG |
744 | { |
745 | ||
746 | int chan = CR_CHAN(insn->chanspec); | |
747 | int rang = CR_RANGE(insn->chanspec); | |
748 | int aref = CR_AREF(insn->chanspec); | |
749 | ||
750 | unsigned long entry = 0; | |
751 | unsigned long tmp; | |
752 | long lval; | |
753 | ||
e55c95a3 GG |
754 | if (insn->n == 0) { |
755 | return 0; | |
756 | } else if (insn->n > 1) { | |
757 | printk(KERN_ERR | |
b6241fda GS |
758 | "comedi%d: me4000: me4000_ai_insn_read(): " |
759 | "Invalid instruction length %d\n", dev->minor, insn->n); | |
e55c95a3 GG |
760 | return -EINVAL; |
761 | } | |
762 | ||
763 | switch (rang) { | |
764 | case 0: | |
765 | entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5; | |
766 | break; | |
767 | case 1: | |
768 | entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10; | |
769 | break; | |
770 | case 2: | |
771 | entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5; | |
772 | break; | |
773 | case 3: | |
774 | entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10; | |
775 | break; | |
776 | default: | |
777 | printk(KERN_ERR | |
b6241fda GS |
778 | "comedi%d: me4000: me4000_ai_insn_read(): " |
779 | "Invalid range specified\n", dev->minor); | |
e55c95a3 GG |
780 | return -EINVAL; |
781 | } | |
782 | ||
783 | switch (aref) { | |
784 | case AREF_GROUND: | |
785 | case AREF_COMMON: | |
6ba8dfef | 786 | if (chan >= thisboard->ai_nchan) { |
e55c95a3 | 787 | printk(KERN_ERR |
b6241fda GS |
788 | "comedi%d: me4000: me4000_ai_insn_read(): " |
789 | "Analog input is not available\n", dev->minor); | |
e55c95a3 GG |
790 | return -EINVAL; |
791 | } | |
792 | entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED | chan; | |
793 | break; | |
794 | ||
795 | case AREF_DIFF: | |
796 | if (rang == 0 || rang == 1) { | |
797 | printk(KERN_ERR | |
b6241fda GS |
798 | "comedi%d: me4000: me4000_ai_insn_read(): " |
799 | "Range must be bipolar when aref = diff\n", | |
0a85b6f0 | 800 | dev->minor); |
e55c95a3 GG |
801 | return -EINVAL; |
802 | } | |
803 | ||
6ba8dfef | 804 | if (chan >= thisboard->ai_diff_nchan) { |
e55c95a3 | 805 | printk(KERN_ERR |
b6241fda GS |
806 | "comedi%d: me4000: me4000_ai_insn_read(): " |
807 | "Analog input is not available\n", dev->minor); | |
e55c95a3 GG |
808 | return -EINVAL; |
809 | } | |
810 | entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL | chan; | |
811 | break; | |
812 | default: | |
813 | printk(KERN_ERR | |
b6241fda GS |
814 | "comedi%d: me4000: me4000_ai_insn_read(): " |
815 | "Invalid aref specified\n", dev->minor); | |
e55c95a3 GG |
816 | return -EINVAL; |
817 | } | |
818 | ||
819 | entry |= ME4000_AI_LIST_LAST_ENTRY; | |
820 | ||
821 | /* Clear channel list, data fifo and both stop bits */ | |
d6cbe537 | 822 | tmp = inl(info->ai_context.ctrl_reg); |
e55c95a3 | 823 | tmp &= ~(ME4000_AI_CTRL_BIT_CHANNEL_FIFO | |
0a85b6f0 MT |
824 | ME4000_AI_CTRL_BIT_DATA_FIFO | |
825 | ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP); | |
d6cbe537 | 826 | outl(tmp, info->ai_context.ctrl_reg); |
e55c95a3 GG |
827 | |
828 | /* Set the acquisition mode to single */ | |
829 | tmp &= ~(ME4000_AI_CTRL_BIT_MODE_0 | ME4000_AI_CTRL_BIT_MODE_1 | | |
0a85b6f0 | 830 | ME4000_AI_CTRL_BIT_MODE_2); |
d6cbe537 | 831 | outl(tmp, info->ai_context.ctrl_reg); |
e55c95a3 GG |
832 | |
833 | /* Enable channel list and data fifo */ | |
834 | tmp |= ME4000_AI_CTRL_BIT_CHANNEL_FIFO | ME4000_AI_CTRL_BIT_DATA_FIFO; | |
d6cbe537 | 835 | outl(tmp, info->ai_context.ctrl_reg); |
e55c95a3 GG |
836 | |
837 | /* Generate channel list entry */ | |
d6cbe537 | 838 | outl(entry, info->ai_context.channel_list_reg); |
e55c95a3 GG |
839 | |
840 | /* Set the timer to maximum sample rate */ | |
d6cbe537 HS |
841 | outl(ME4000_AI_MIN_TICKS, info->ai_context.chan_timer_reg); |
842 | outl(ME4000_AI_MIN_TICKS, info->ai_context.chan_pre_timer_reg); | |
e55c95a3 GG |
843 | |
844 | /* Start conversion by dummy read */ | |
d6cbe537 | 845 | inl(info->ai_context.start_reg); |
e55c95a3 GG |
846 | |
847 | /* Wait until ready */ | |
848 | udelay(10); | |
d6cbe537 | 849 | if (!(inl(info->ai_context.status_reg) & |
0a85b6f0 | 850 | ME4000_AI_STATUS_BIT_EF_DATA)) { |
e55c95a3 | 851 | printk(KERN_ERR |
b6241fda GS |
852 | "comedi%d: me4000: me4000_ai_insn_read(): " |
853 | "Value not available after wait\n", dev->minor); | |
e55c95a3 GG |
854 | return -EIO; |
855 | } | |
856 | ||
857 | /* Read value from data fifo */ | |
d6cbe537 | 858 | lval = inl(info->ai_context.data_reg) & 0xFFFF; |
e55c95a3 GG |
859 | data[0] = lval ^ 0x8000; |
860 | ||
861 | return 1; | |
862 | } | |
863 | ||
0a85b6f0 MT |
864 | static int me4000_ai_cancel(struct comedi_device *dev, |
865 | struct comedi_subdevice *s) | |
e55c95a3 GG |
866 | { |
867 | unsigned long tmp; | |
868 | ||
e55c95a3 | 869 | /* Stop any running conversion */ |
d6cbe537 | 870 | tmp = inl(info->ai_context.ctrl_reg); |
e55c95a3 | 871 | tmp &= ~(ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP); |
d6cbe537 | 872 | outl(tmp, info->ai_context.ctrl_reg); |
e55c95a3 GG |
873 | |
874 | /* Clear the control register */ | |
d6cbe537 | 875 | outl(0x0, info->ai_context.ctrl_reg); |
e55c95a3 GG |
876 | |
877 | return 0; | |
878 | } | |
879 | ||
71b5f4f1 | 880 | static int ai_check_chanlist(struct comedi_device *dev, |
0a85b6f0 | 881 | struct comedi_subdevice *s, struct comedi_cmd *cmd) |
e55c95a3 GG |
882 | { |
883 | int aref; | |
884 | int i; | |
885 | ||
e55c95a3 GG |
886 | /* Check whether a channel list is available */ |
887 | if (!cmd->chanlist_len) { | |
888 | printk(KERN_ERR | |
b6241fda GS |
889 | "comedi%d: me4000: ai_check_chanlist(): " |
890 | "No channel list available\n", dev->minor); | |
e55c95a3 GG |
891 | return -EINVAL; |
892 | } | |
893 | ||
894 | /* Check the channel list size */ | |
895 | if (cmd->chanlist_len > ME4000_AI_CHANNEL_LIST_COUNT) { | |
896 | printk(KERN_ERR | |
b6241fda GS |
897 | "comedi%d: me4000: ai_check_chanlist(): " |
898 | "Channel list is to large\n", dev->minor); | |
e55c95a3 GG |
899 | return -EINVAL; |
900 | } | |
901 | ||
902 | /* Check the pointer */ | |
903 | if (!cmd->chanlist) { | |
904 | printk(KERN_ERR | |
b6241fda GS |
905 | "comedi%d: me4000: ai_check_chanlist(): " |
906 | "NULL pointer to channel list\n", dev->minor); | |
e55c95a3 GG |
907 | return -EFAULT; |
908 | } | |
909 | ||
910 | /* Check whether aref is equal for all entries */ | |
911 | aref = CR_AREF(cmd->chanlist[0]); | |
912 | for (i = 0; i < cmd->chanlist_len; i++) { | |
913 | if (CR_AREF(cmd->chanlist[i]) != aref) { | |
914 | printk(KERN_ERR | |
b6241fda GS |
915 | "comedi%d: me4000: ai_check_chanlist(): " |
916 | "Mode is not equal for all entries\n", | |
0a85b6f0 | 917 | dev->minor); |
e55c95a3 GG |
918 | return -EINVAL; |
919 | } | |
920 | } | |
921 | ||
922 | /* Check whether channels are available for this ending */ | |
923 | if (aref == SDF_DIFF) { | |
924 | for (i = 0; i < cmd->chanlist_len; i++) { | |
925 | if (CR_CHAN(cmd->chanlist[i]) >= | |
6ba8dfef | 926 | thisboard->ai_diff_nchan) { |
e55c95a3 | 927 | printk(KERN_ERR |
b6241fda GS |
928 | "comedi%d: me4000: ai_check_chanlist():" |
929 | " Channel number to high\n", dev->minor); | |
e55c95a3 GG |
930 | return -EINVAL; |
931 | } | |
932 | } | |
933 | } else { | |
934 | for (i = 0; i < cmd->chanlist_len; i++) { | |
6ba8dfef | 935 | if (CR_CHAN(cmd->chanlist[i]) >= thisboard->ai_nchan) { |
e55c95a3 | 936 | printk(KERN_ERR |
b6241fda GS |
937 | "comedi%d: me4000: ai_check_chanlist(): " |
938 | "Channel number to high\n", dev->minor); | |
e55c95a3 GG |
939 | return -EINVAL; |
940 | } | |
941 | } | |
942 | } | |
943 | ||
944 | /* Check if bipolar is set for all entries when in differential mode */ | |
945 | if (aref == SDF_DIFF) { | |
946 | for (i = 0; i < cmd->chanlist_len; i++) { | |
947 | if (CR_RANGE(cmd->chanlist[i]) != 1 && | |
0a85b6f0 | 948 | CR_RANGE(cmd->chanlist[i]) != 2) { |
e55c95a3 | 949 | printk(KERN_ERR |
b6241fda GS |
950 | "comedi%d: me4000: ai_check_chanlist(): " |
951 | "Bipolar is not selected in " | |
952 | "differential mode\n", | |
0a85b6f0 | 953 | dev->minor); |
e55c95a3 GG |
954 | return -EINVAL; |
955 | } | |
956 | } | |
957 | } | |
958 | ||
959 | return 0; | |
960 | } | |
961 | ||
71b5f4f1 | 962 | static int ai_round_cmd_args(struct comedi_device *dev, |
0a85b6f0 MT |
963 | struct comedi_subdevice *s, |
964 | struct comedi_cmd *cmd, | |
965 | unsigned int *init_ticks, | |
966 | unsigned int *scan_ticks, unsigned int *chan_ticks) | |
e55c95a3 GG |
967 | { |
968 | ||
969 | int rest; | |
970 | ||
e55c95a3 GG |
971 | *init_ticks = 0; |
972 | *scan_ticks = 0; | |
973 | *chan_ticks = 0; | |
974 | ||
e55c95a3 GG |
975 | if (cmd->start_arg) { |
976 | *init_ticks = (cmd->start_arg * 33) / 1000; | |
977 | rest = (cmd->start_arg * 33) % 1000; | |
978 | ||
91211dd1 | 979 | if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_ROUND_NEAREST) { |
82675f35 | 980 | if (rest > 33) |
e55c95a3 | 981 | (*init_ticks)++; |
91211dd1 | 982 | } else if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_ROUND_UP) { |
e55c95a3 GG |
983 | if (rest) |
984 | (*init_ticks)++; | |
985 | } | |
986 | } | |
987 | ||
988 | if (cmd->scan_begin_arg) { | |
989 | *scan_ticks = (cmd->scan_begin_arg * 33) / 1000; | |
990 | rest = (cmd->scan_begin_arg * 33) % 1000; | |
991 | ||
91211dd1 | 992 | if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_ROUND_NEAREST) { |
82675f35 | 993 | if (rest > 33) |
e55c95a3 | 994 | (*scan_ticks)++; |
91211dd1 | 995 | } else if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_ROUND_UP) { |
e55c95a3 GG |
996 | if (rest) |
997 | (*scan_ticks)++; | |
998 | } | |
999 | } | |
1000 | ||
1001 | if (cmd->convert_arg) { | |
1002 | *chan_ticks = (cmd->convert_arg * 33) / 1000; | |
1003 | rest = (cmd->convert_arg * 33) % 1000; | |
1004 | ||
91211dd1 | 1005 | if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_ROUND_NEAREST) { |
82675f35 | 1006 | if (rest > 33) |
e55c95a3 | 1007 | (*chan_ticks)++; |
91211dd1 | 1008 | } else if ((cmd->flags & TRIG_ROUND_MASK) == TRIG_ROUND_UP) { |
e55c95a3 GG |
1009 | if (rest) |
1010 | (*chan_ticks)++; | |
1011 | } | |
1012 | } | |
1013 | ||
e55c95a3 GG |
1014 | return 0; |
1015 | } | |
1016 | ||
71b5f4f1 | 1017 | static void ai_write_timer(struct comedi_device *dev, |
0a85b6f0 MT |
1018 | unsigned int init_ticks, |
1019 | unsigned int scan_ticks, unsigned int chan_ticks) | |
e55c95a3 | 1020 | { |
d6cbe537 HS |
1021 | outl(init_ticks - 1, info->ai_context.scan_pre_timer_low_reg); |
1022 | outl(0x0, info->ai_context.scan_pre_timer_high_reg); | |
e55c95a3 GG |
1023 | |
1024 | if (scan_ticks) { | |
d6cbe537 HS |
1025 | outl(scan_ticks - 1, info->ai_context.scan_timer_low_reg); |
1026 | outl(0x0, info->ai_context.scan_timer_high_reg); | |
e55c95a3 GG |
1027 | } |
1028 | ||
d6cbe537 HS |
1029 | outl(chan_ticks - 1, info->ai_context.chan_pre_timer_reg); |
1030 | outl(chan_ticks - 1, info->ai_context.chan_timer_reg); | |
e55c95a3 GG |
1031 | } |
1032 | ||
71b5f4f1 | 1033 | static int ai_prepare(struct comedi_device *dev, |
0a85b6f0 MT |
1034 | struct comedi_subdevice *s, |
1035 | struct comedi_cmd *cmd, | |
1036 | unsigned int init_ticks, | |
1037 | unsigned int scan_ticks, unsigned int chan_ticks) | |
e55c95a3 GG |
1038 | { |
1039 | ||
1040 | unsigned long tmp = 0; | |
1041 | ||
e55c95a3 GG |
1042 | /* Write timer arguments */ |
1043 | ai_write_timer(dev, init_ticks, scan_ticks, chan_ticks); | |
1044 | ||
1045 | /* Reset control register */ | |
d6cbe537 | 1046 | outl(tmp, info->ai_context.ctrl_reg); |
e55c95a3 GG |
1047 | |
1048 | /* Start sources */ | |
1049 | if ((cmd->start_src == TRIG_EXT && | |
0a85b6f0 MT |
1050 | cmd->scan_begin_src == TRIG_TIMER && |
1051 | cmd->convert_src == TRIG_TIMER) || | |
1052 | (cmd->start_src == TRIG_EXT && | |
1053 | cmd->scan_begin_src == TRIG_FOLLOW && | |
1054 | cmd->convert_src == TRIG_TIMER)) { | |
e55c95a3 | 1055 | tmp = ME4000_AI_CTRL_BIT_MODE_1 | |
0a85b6f0 MT |
1056 | ME4000_AI_CTRL_BIT_CHANNEL_FIFO | |
1057 | ME4000_AI_CTRL_BIT_DATA_FIFO; | |
e55c95a3 | 1058 | } else if (cmd->start_src == TRIG_EXT && |
0a85b6f0 MT |
1059 | cmd->scan_begin_src == TRIG_EXT && |
1060 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 | 1061 | tmp = ME4000_AI_CTRL_BIT_MODE_2 | |
0a85b6f0 MT |
1062 | ME4000_AI_CTRL_BIT_CHANNEL_FIFO | |
1063 | ME4000_AI_CTRL_BIT_DATA_FIFO; | |
e55c95a3 | 1064 | } else if (cmd->start_src == TRIG_EXT && |
0a85b6f0 MT |
1065 | cmd->scan_begin_src == TRIG_EXT && |
1066 | cmd->convert_src == TRIG_EXT) { | |
e55c95a3 | 1067 | tmp = ME4000_AI_CTRL_BIT_MODE_0 | |
0a85b6f0 MT |
1068 | ME4000_AI_CTRL_BIT_MODE_1 | |
1069 | ME4000_AI_CTRL_BIT_CHANNEL_FIFO | | |
1070 | ME4000_AI_CTRL_BIT_DATA_FIFO; | |
e55c95a3 GG |
1071 | } else { |
1072 | tmp = ME4000_AI_CTRL_BIT_MODE_0 | | |
0a85b6f0 MT |
1073 | ME4000_AI_CTRL_BIT_CHANNEL_FIFO | |
1074 | ME4000_AI_CTRL_BIT_DATA_FIFO; | |
e55c95a3 GG |
1075 | } |
1076 | ||
1077 | /* Stop triggers */ | |
1078 | if (cmd->stop_src == TRIG_COUNT) { | |
d6cbe537 | 1079 | outl(cmd->chanlist_len * cmd->stop_arg, |
0a85b6f0 | 1080 | info->ai_context.sample_counter_reg); |
e55c95a3 GG |
1081 | tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ; |
1082 | } else if (cmd->stop_src == TRIG_NONE && | |
0a85b6f0 | 1083 | cmd->scan_end_src == TRIG_COUNT) { |
d6cbe537 | 1084 | outl(cmd->scan_end_arg, |
0a85b6f0 | 1085 | info->ai_context.sample_counter_reg); |
e55c95a3 GG |
1086 | tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ; |
1087 | } else { | |
1088 | tmp |= ME4000_AI_CTRL_BIT_HF_IRQ; | |
1089 | } | |
1090 | ||
1091 | /* Write the setup to the control register */ | |
d6cbe537 | 1092 | outl(tmp, info->ai_context.ctrl_reg); |
e55c95a3 GG |
1093 | |
1094 | /* Write the channel list */ | |
1095 | ai_write_chanlist(dev, s, cmd); | |
1096 | ||
1097 | return 0; | |
1098 | } | |
1099 | ||
71b5f4f1 | 1100 | static int ai_write_chanlist(struct comedi_device *dev, |
0a85b6f0 | 1101 | struct comedi_subdevice *s, struct comedi_cmd *cmd) |
e55c95a3 GG |
1102 | { |
1103 | unsigned int entry; | |
1104 | unsigned int chan; | |
1105 | unsigned int rang; | |
1106 | unsigned int aref; | |
1107 | int i; | |
1108 | ||
e55c95a3 GG |
1109 | for (i = 0; i < cmd->chanlist_len; i++) { |
1110 | chan = CR_CHAN(cmd->chanlist[i]); | |
1111 | rang = CR_RANGE(cmd->chanlist[i]); | |
1112 | aref = CR_AREF(cmd->chanlist[i]); | |
1113 | ||
1114 | entry = chan; | |
1115 | ||
b6241fda | 1116 | if (rang == 0) |
e55c95a3 | 1117 | entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5; |
b6241fda | 1118 | else if (rang == 1) |
e55c95a3 | 1119 | entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10; |
b6241fda | 1120 | else if (rang == 2) |
e55c95a3 | 1121 | entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5; |
b6241fda | 1122 | else |
e55c95a3 | 1123 | entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10; |
e55c95a3 | 1124 | |
b6241fda | 1125 | if (aref == SDF_DIFF) |
e55c95a3 | 1126 | entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL; |
b6241fda | 1127 | else |
e55c95a3 | 1128 | entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED; |
e55c95a3 | 1129 | |
d6cbe537 | 1130 | outl(entry, info->ai_context.channel_list_reg); |
e55c95a3 GG |
1131 | } |
1132 | ||
1133 | return 0; | |
1134 | } | |
1135 | ||
0a85b6f0 MT |
1136 | static int me4000_ai_do_cmd(struct comedi_device *dev, |
1137 | struct comedi_subdevice *s) | |
e55c95a3 GG |
1138 | { |
1139 | int err; | |
1140 | unsigned int init_ticks = 0; | |
1141 | unsigned int scan_ticks = 0; | |
1142 | unsigned int chan_ticks = 0; | |
ea6d0d4c | 1143 | struct comedi_cmd *cmd = &s->async->cmd; |
e55c95a3 | 1144 | |
e55c95a3 GG |
1145 | /* Reset the analog input */ |
1146 | err = me4000_ai_cancel(dev, s); | |
1147 | if (err) | |
1148 | return err; | |
1149 | ||
1150 | /* Round the timer arguments */ | |
1151 | err = ai_round_cmd_args(dev, | |
0a85b6f0 | 1152 | s, cmd, &init_ticks, &scan_ticks, &chan_ticks); |
e55c95a3 GG |
1153 | if (err) |
1154 | return err; | |
1155 | ||
1156 | /* Prepare the AI for acquisition */ | |
1157 | err = ai_prepare(dev, s, cmd, init_ticks, scan_ticks, chan_ticks); | |
1158 | if (err) | |
1159 | return err; | |
1160 | ||
1161 | /* Start acquistion by dummy read */ | |
d6cbe537 | 1162 | inl(info->ai_context.start_reg); |
e55c95a3 GG |
1163 | |
1164 | return 0; | |
1165 | } | |
1166 | ||
1167 | /* | |
1168 | * me4000_ai_do_cmd_test(): | |
1169 | * | |
1170 | * The demo cmd.c in ./comedilib/demo specifies 6 return values: | |
1171 | * - success | |
1172 | * - invalid source | |
1173 | * - source conflict | |
1174 | * - invalid argument | |
1175 | * - argument conflict | |
1176 | * - invalid chanlist | |
1177 | * So I tried to adopt this scheme. | |
1178 | */ | |
71b5f4f1 | 1179 | static int me4000_ai_do_cmd_test(struct comedi_device *dev, |
0a85b6f0 MT |
1180 | struct comedi_subdevice *s, |
1181 | struct comedi_cmd *cmd) | |
e55c95a3 GG |
1182 | { |
1183 | ||
1184 | unsigned int init_ticks; | |
1185 | unsigned int chan_ticks; | |
1186 | unsigned int scan_ticks; | |
1187 | int err = 0; | |
1188 | ||
e55c95a3 GG |
1189 | /* Only rounding flags are implemented */ |
1190 | cmd->flags &= TRIG_ROUND_NEAREST | TRIG_ROUND_UP | TRIG_ROUND_DOWN; | |
1191 | ||
1192 | /* Round the timer arguments */ | |
1193 | ai_round_cmd_args(dev, s, cmd, &init_ticks, &scan_ticks, &chan_ticks); | |
1194 | ||
1195 | /* | |
1196 | * Stage 1. Check if the trigger sources are generally valid. | |
1197 | */ | |
1198 | switch (cmd->start_src) { | |
1199 | case TRIG_NOW: | |
1200 | case TRIG_EXT: | |
1201 | break; | |
1202 | case TRIG_ANY: | |
1203 | cmd->start_src &= TRIG_NOW | TRIG_EXT; | |
1204 | err++; | |
1205 | break; | |
1206 | default: | |
1207 | printk(KERN_ERR | |
b6241fda GS |
1208 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1209 | "Invalid start source\n", dev->minor); | |
e55c95a3 GG |
1210 | cmd->start_src = TRIG_NOW; |
1211 | err++; | |
1212 | } | |
1213 | switch (cmd->scan_begin_src) { | |
1214 | case TRIG_FOLLOW: | |
1215 | case TRIG_TIMER: | |
1216 | case TRIG_EXT: | |
1217 | break; | |
1218 | case TRIG_ANY: | |
1219 | cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT; | |
1220 | err++; | |
1221 | break; | |
1222 | default: | |
1223 | printk(KERN_ERR | |
b6241fda GS |
1224 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1225 | "Invalid scan begin source\n", dev->minor); | |
e55c95a3 GG |
1226 | cmd->scan_begin_src = TRIG_FOLLOW; |
1227 | err++; | |
1228 | } | |
1229 | switch (cmd->convert_src) { | |
1230 | case TRIG_TIMER: | |
1231 | case TRIG_EXT: | |
1232 | break; | |
1233 | case TRIG_ANY: | |
1234 | cmd->convert_src &= TRIG_TIMER | TRIG_EXT; | |
1235 | err++; | |
1236 | break; | |
1237 | default: | |
1238 | printk(KERN_ERR | |
b6241fda GS |
1239 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1240 | "Invalid convert source\n", dev->minor); | |
e55c95a3 GG |
1241 | cmd->convert_src = TRIG_TIMER; |
1242 | err++; | |
1243 | } | |
1244 | switch (cmd->scan_end_src) { | |
1245 | case TRIG_NONE: | |
1246 | case TRIG_COUNT: | |
1247 | break; | |
1248 | case TRIG_ANY: | |
1249 | cmd->scan_end_src &= TRIG_NONE | TRIG_COUNT; | |
1250 | err++; | |
1251 | break; | |
1252 | default: | |
1253 | printk(KERN_ERR | |
b6241fda GS |
1254 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1255 | "Invalid scan end source\n", dev->minor); | |
e55c95a3 GG |
1256 | cmd->scan_end_src = TRIG_NONE; |
1257 | err++; | |
1258 | } | |
1259 | switch (cmd->stop_src) { | |
1260 | case TRIG_NONE: | |
1261 | case TRIG_COUNT: | |
1262 | break; | |
1263 | case TRIG_ANY: | |
1264 | cmd->stop_src &= TRIG_NONE | TRIG_COUNT; | |
1265 | err++; | |
1266 | break; | |
1267 | default: | |
1268 | printk(KERN_ERR | |
b6241fda GS |
1269 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1270 | "Invalid stop source\n", dev->minor); | |
e55c95a3 GG |
1271 | cmd->stop_src = TRIG_NONE; |
1272 | err++; | |
1273 | } | |
82675f35 | 1274 | if (err) |
e55c95a3 | 1275 | return 1; |
e55c95a3 GG |
1276 | |
1277 | /* | |
1278 | * Stage 2. Check for trigger source conflicts. | |
1279 | */ | |
1280 | if (cmd->start_src == TRIG_NOW && | |
0a85b6f0 MT |
1281 | cmd->scan_begin_src == TRIG_TIMER && |
1282 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 | 1283 | } else if (cmd->start_src == TRIG_NOW && |
0a85b6f0 MT |
1284 | cmd->scan_begin_src == TRIG_FOLLOW && |
1285 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 | 1286 | } else if (cmd->start_src == TRIG_EXT && |
0a85b6f0 MT |
1287 | cmd->scan_begin_src == TRIG_TIMER && |
1288 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 | 1289 | } else if (cmd->start_src == TRIG_EXT && |
0a85b6f0 MT |
1290 | cmd->scan_begin_src == TRIG_FOLLOW && |
1291 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 | 1292 | } else if (cmd->start_src == TRIG_EXT && |
0a85b6f0 MT |
1293 | cmd->scan_begin_src == TRIG_EXT && |
1294 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 | 1295 | } else if (cmd->start_src == TRIG_EXT && |
0a85b6f0 MT |
1296 | cmd->scan_begin_src == TRIG_EXT && |
1297 | cmd->convert_src == TRIG_EXT) { | |
e55c95a3 GG |
1298 | } else { |
1299 | printk(KERN_ERR | |
b6241fda GS |
1300 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1301 | "Invalid start trigger combination\n", dev->minor); | |
e55c95a3 GG |
1302 | cmd->start_src = TRIG_NOW; |
1303 | cmd->scan_begin_src = TRIG_FOLLOW; | |
1304 | cmd->convert_src = TRIG_TIMER; | |
1305 | err++; | |
1306 | } | |
1307 | ||
1308 | if (cmd->stop_src == TRIG_NONE && cmd->scan_end_src == TRIG_NONE) { | |
1309 | } else if (cmd->stop_src == TRIG_COUNT && | |
0a85b6f0 | 1310 | cmd->scan_end_src == TRIG_NONE) { |
e55c95a3 | 1311 | } else if (cmd->stop_src == TRIG_NONE && |
0a85b6f0 | 1312 | cmd->scan_end_src == TRIG_COUNT) { |
e55c95a3 | 1313 | } else if (cmd->stop_src == TRIG_COUNT && |
0a85b6f0 | 1314 | cmd->scan_end_src == TRIG_COUNT) { |
e55c95a3 GG |
1315 | } else { |
1316 | printk(KERN_ERR | |
b6241fda GS |
1317 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1318 | "Invalid stop trigger combination\n", dev->minor); | |
e55c95a3 GG |
1319 | cmd->stop_src = TRIG_NONE; |
1320 | cmd->scan_end_src = TRIG_NONE; | |
1321 | err++; | |
1322 | } | |
82675f35 | 1323 | if (err) |
e55c95a3 | 1324 | return 2; |
e55c95a3 GG |
1325 | |
1326 | /* | |
1327 | * Stage 3. Check if arguments are generally valid. | |
1328 | */ | |
1329 | if (cmd->chanlist_len < 1) { | |
1330 | printk(KERN_ERR | |
b6241fda GS |
1331 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1332 | "No channel list\n", dev->minor); | |
e55c95a3 GG |
1333 | cmd->chanlist_len = 1; |
1334 | err++; | |
1335 | } | |
1336 | if (init_ticks < 66) { | |
1337 | printk(KERN_ERR | |
b6241fda GS |
1338 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1339 | "Start arg to low\n", dev->minor); | |
e55c95a3 GG |
1340 | cmd->start_arg = 2000; |
1341 | err++; | |
1342 | } | |
1343 | if (scan_ticks && scan_ticks < 67) { | |
1344 | printk(KERN_ERR | |
b6241fda GS |
1345 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1346 | "Scan begin arg to low\n", dev->minor); | |
e55c95a3 GG |
1347 | cmd->scan_begin_arg = 2031; |
1348 | err++; | |
1349 | } | |
1350 | if (chan_ticks < 66) { | |
1351 | printk(KERN_ERR | |
b6241fda GS |
1352 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1353 | "Convert arg to low\n", dev->minor); | |
e55c95a3 GG |
1354 | cmd->convert_arg = 2000; |
1355 | err++; | |
1356 | } | |
82675f35 BP |
1357 | |
1358 | if (err) | |
e55c95a3 | 1359 | return 3; |
e55c95a3 GG |
1360 | |
1361 | /* | |
1362 | * Stage 4. Check for argument conflicts. | |
1363 | */ | |
1364 | if (cmd->start_src == TRIG_NOW && | |
0a85b6f0 MT |
1365 | cmd->scan_begin_src == TRIG_TIMER && |
1366 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 GG |
1367 | |
1368 | /* Check timer arguments */ | |
1369 | if (init_ticks < ME4000_AI_MIN_TICKS) { | |
1370 | printk(KERN_ERR | |
b6241fda GS |
1371 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1372 | "Invalid start arg\n", dev->minor); | |
b6c77757 | 1373 | cmd->start_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1374 | err++; |
1375 | } | |
1376 | if (chan_ticks < ME4000_AI_MIN_TICKS) { | |
1377 | printk(KERN_ERR | |
b6241fda GS |
1378 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1379 | "Invalid convert arg\n", dev->minor); | |
b6c77757 | 1380 | cmd->convert_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1381 | err++; |
1382 | } | |
1383 | if (scan_ticks <= cmd->chanlist_len * chan_ticks) { | |
1384 | printk(KERN_ERR | |
b6241fda GS |
1385 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1386 | "Invalid scan end arg\n", dev->minor); | |
1387 | ||
1388 | /* At least one tick more */ | |
1389 | cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31; | |
e55c95a3 GG |
1390 | err++; |
1391 | } | |
1392 | } else if (cmd->start_src == TRIG_NOW && | |
0a85b6f0 MT |
1393 | cmd->scan_begin_src == TRIG_FOLLOW && |
1394 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 GG |
1395 | |
1396 | /* Check timer arguments */ | |
1397 | if (init_ticks < ME4000_AI_MIN_TICKS) { | |
1398 | printk(KERN_ERR | |
b6241fda GS |
1399 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1400 | "Invalid start arg\n", dev->minor); | |
b6c77757 | 1401 | cmd->start_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1402 | err++; |
1403 | } | |
1404 | if (chan_ticks < ME4000_AI_MIN_TICKS) { | |
1405 | printk(KERN_ERR | |
b6241fda GS |
1406 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1407 | "Invalid convert arg\n", dev->minor); | |
b6c77757 | 1408 | cmd->convert_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1409 | err++; |
1410 | } | |
1411 | } else if (cmd->start_src == TRIG_EXT && | |
0a85b6f0 MT |
1412 | cmd->scan_begin_src == TRIG_TIMER && |
1413 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 GG |
1414 | |
1415 | /* Check timer arguments */ | |
1416 | if (init_ticks < ME4000_AI_MIN_TICKS) { | |
1417 | printk(KERN_ERR | |
b6241fda GS |
1418 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1419 | "Invalid start arg\n", dev->minor); | |
b6c77757 | 1420 | cmd->start_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1421 | err++; |
1422 | } | |
1423 | if (chan_ticks < ME4000_AI_MIN_TICKS) { | |
1424 | printk(KERN_ERR | |
b6241fda GS |
1425 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1426 | "Invalid convert arg\n", dev->minor); | |
b6c77757 | 1427 | cmd->convert_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1428 | err++; |
1429 | } | |
1430 | if (scan_ticks <= cmd->chanlist_len * chan_ticks) { | |
1431 | printk(KERN_ERR | |
b6241fda GS |
1432 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1433 | "Invalid scan end arg\n", dev->minor); | |
1434 | ||
1435 | /* At least one tick more */ | |
1436 | cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31; | |
e55c95a3 GG |
1437 | err++; |
1438 | } | |
1439 | } else if (cmd->start_src == TRIG_EXT && | |
0a85b6f0 MT |
1440 | cmd->scan_begin_src == TRIG_FOLLOW && |
1441 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 GG |
1442 | |
1443 | /* Check timer arguments */ | |
1444 | if (init_ticks < ME4000_AI_MIN_TICKS) { | |
1445 | printk(KERN_ERR | |
b6241fda GS |
1446 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1447 | "Invalid start arg\n", dev->minor); | |
b6c77757 | 1448 | cmd->start_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1449 | err++; |
1450 | } | |
1451 | if (chan_ticks < ME4000_AI_MIN_TICKS) { | |
1452 | printk(KERN_ERR | |
b6241fda GS |
1453 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1454 | "Invalid convert arg\n", dev->minor); | |
b6c77757 | 1455 | cmd->convert_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1456 | err++; |
1457 | } | |
1458 | } else if (cmd->start_src == TRIG_EXT && | |
0a85b6f0 MT |
1459 | cmd->scan_begin_src == TRIG_EXT && |
1460 | cmd->convert_src == TRIG_TIMER) { | |
e55c95a3 GG |
1461 | |
1462 | /* Check timer arguments */ | |
1463 | if (init_ticks < ME4000_AI_MIN_TICKS) { | |
1464 | printk(KERN_ERR | |
b6241fda GS |
1465 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1466 | "Invalid start arg\n", dev->minor); | |
b6c77757 | 1467 | cmd->start_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1468 | err++; |
1469 | } | |
1470 | if (chan_ticks < ME4000_AI_MIN_TICKS) { | |
1471 | printk(KERN_ERR | |
b6241fda GS |
1472 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1473 | "Invalid convert arg\n", dev->minor); | |
b6c77757 | 1474 | cmd->convert_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1475 | err++; |
1476 | } | |
1477 | } else if (cmd->start_src == TRIG_EXT && | |
0a85b6f0 MT |
1478 | cmd->scan_begin_src == TRIG_EXT && |
1479 | cmd->convert_src == TRIG_EXT) { | |
e55c95a3 GG |
1480 | |
1481 | /* Check timer arguments */ | |
1482 | if (init_ticks < ME4000_AI_MIN_TICKS) { | |
1483 | printk(KERN_ERR | |
b6241fda GS |
1484 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1485 | "Invalid start arg\n", dev->minor); | |
b6c77757 | 1486 | cmd->start_arg = 2000; /* 66 ticks at least */ |
e55c95a3 GG |
1487 | err++; |
1488 | } | |
1489 | } | |
1490 | if (cmd->stop_src == TRIG_COUNT) { | |
1491 | if (cmd->stop_arg == 0) { | |
1492 | printk(KERN_ERR | |
b6241fda GS |
1493 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1494 | "Invalid stop arg\n", dev->minor); | |
e55c95a3 GG |
1495 | cmd->stop_arg = 1; |
1496 | err++; | |
1497 | } | |
1498 | } | |
1499 | if (cmd->scan_end_src == TRIG_COUNT) { | |
1500 | if (cmd->scan_end_arg == 0) { | |
1501 | printk(KERN_ERR | |
b6241fda GS |
1502 | "comedi%d: me4000: me4000_ai_do_cmd_test(): " |
1503 | "Invalid scan end arg\n", dev->minor); | |
e55c95a3 GG |
1504 | cmd->scan_end_arg = 1; |
1505 | err++; | |
1506 | } | |
1507 | } | |
82675f35 BP |
1508 | |
1509 | if (err) | |
e55c95a3 | 1510 | return 4; |
e55c95a3 GG |
1511 | |
1512 | /* | |
1513 | * Stage 5. Check the channel list. | |
1514 | */ | |
1515 | if (ai_check_chanlist(dev, s, cmd)) | |
1516 | return 5; | |
1517 | ||
1518 | return 0; | |
1519 | } | |
1520 | ||
70265d24 | 1521 | static irqreturn_t me4000_ai_isr(int irq, void *dev_id) |
e55c95a3 GG |
1522 | { |
1523 | unsigned int tmp; | |
71b5f4f1 | 1524 | struct comedi_device *dev = dev_id; |
8aaf2717 | 1525 | struct comedi_subdevice *s = &dev->subdevices[0]; |
8b95a0e1 | 1526 | struct me4000_ai_context *ai_context = &info->ai_context; |
e55c95a3 GG |
1527 | int i; |
1528 | int c = 0; | |
1529 | long lval; | |
1530 | ||
ef5bbfcb | 1531 | if (!dev->attached) |
e55c95a3 | 1532 | return IRQ_NONE; |
e55c95a3 GG |
1533 | |
1534 | /* Reset all events */ | |
1535 | s->async->events = 0; | |
1536 | ||
1537 | /* Check if irq number is right */ | |
1538 | if (irq != ai_context->irq) { | |
1539 | printk(KERN_ERR | |
b6241fda GS |
1540 | "comedi%d: me4000: me4000_ai_isr(): " |
1541 | "Incorrect interrupt num: %d\n", dev->minor, irq); | |
e55c95a3 GG |
1542 | return IRQ_HANDLED; |
1543 | } | |
1544 | ||
d6cbe537 | 1545 | if (inl(ai_context->irq_status_reg) & |
0a85b6f0 | 1546 | ME4000_IRQ_STATUS_BIT_AI_HF) { |
e55c95a3 | 1547 | /* Read status register to find out what happened */ |
d6cbe537 | 1548 | tmp = inl(ai_context->ctrl_reg); |
e55c95a3 GG |
1549 | |
1550 | if (!(tmp & ME4000_AI_STATUS_BIT_FF_DATA) && | |
0a85b6f0 MT |
1551 | !(tmp & ME4000_AI_STATUS_BIT_HF_DATA) && |
1552 | (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) { | |
e55c95a3 GG |
1553 | c = ME4000_AI_FIFO_COUNT; |
1554 | ||
b6241fda GS |
1555 | /* |
1556 | * FIFO overflow, so stop conversion | |
1557 | * and disable all interrupts | |
1558 | */ | |
e55c95a3 GG |
1559 | tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; |
1560 | tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | | |
0a85b6f0 | 1561 | ME4000_AI_CTRL_BIT_SC_IRQ); |
d6cbe537 | 1562 | outl(tmp, ai_context->ctrl_reg); |
e55c95a3 GG |
1563 | |
1564 | s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; | |
1565 | ||
1566 | printk(KERN_ERR | |
b6241fda GS |
1567 | "comedi%d: me4000: me4000_ai_isr(): " |
1568 | "FIFO overflow\n", dev->minor); | |
e55c95a3 | 1569 | } else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA) |
0a85b6f0 MT |
1570 | && !(tmp & ME4000_AI_STATUS_BIT_HF_DATA) |
1571 | && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) { | |
e55c95a3 GG |
1572 | s->async->events |= COMEDI_CB_BLOCK; |
1573 | ||
1574 | c = ME4000_AI_FIFO_COUNT / 2; | |
1575 | } else { | |
1576 | printk(KERN_ERR | |
b6241fda GS |
1577 | "comedi%d: me4000: me4000_ai_isr(): " |
1578 | "Can't determine state of fifo\n", dev->minor); | |
e55c95a3 GG |
1579 | c = 0; |
1580 | ||
b6241fda GS |
1581 | /* |
1582 | * Undefined state, so stop conversion | |
1583 | * and disable all interrupts | |
1584 | */ | |
e55c95a3 GG |
1585 | tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; |
1586 | tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | | |
0a85b6f0 | 1587 | ME4000_AI_CTRL_BIT_SC_IRQ); |
d6cbe537 | 1588 | outl(tmp, ai_context->ctrl_reg); |
e55c95a3 GG |
1589 | |
1590 | s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; | |
1591 | ||
1592 | printk(KERN_ERR | |
b6241fda GS |
1593 | "comedi%d: me4000: me4000_ai_isr(): " |
1594 | "Undefined FIFO state\n", dev->minor); | |
e55c95a3 GG |
1595 | } |
1596 | ||
e55c95a3 GG |
1597 | for (i = 0; i < c; i++) { |
1598 | /* Read value from data fifo */ | |
1599 | lval = inl(ai_context->data_reg) & 0xFFFF; | |
1600 | lval ^= 0x8000; | |
1601 | ||
1602 | if (!comedi_buf_put(s->async, lval)) { | |
b6241fda GS |
1603 | /* |
1604 | * Buffer overflow, so stop conversion | |
1605 | * and disable all interrupts | |
1606 | */ | |
e55c95a3 GG |
1607 | tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; |
1608 | tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | | |
0a85b6f0 | 1609 | ME4000_AI_CTRL_BIT_SC_IRQ); |
d6cbe537 | 1610 | outl(tmp, ai_context->ctrl_reg); |
e55c95a3 GG |
1611 | |
1612 | s->async->events |= COMEDI_CB_OVERFLOW; | |
1613 | ||
1614 | printk(KERN_ERR | |
b6241fda GS |
1615 | "comedi%d: me4000: me4000_ai_isr(): " |
1616 | "Buffer overflow\n", dev->minor); | |
e55c95a3 GG |
1617 | |
1618 | break; | |
1619 | } | |
1620 | } | |
1621 | ||
1622 | /* Work is done, so reset the interrupt */ | |
e55c95a3 | 1623 | tmp |= ME4000_AI_CTRL_BIT_HF_IRQ_RESET; |
d6cbe537 | 1624 | outl(tmp, ai_context->ctrl_reg); |
e55c95a3 | 1625 | tmp &= ~ME4000_AI_CTRL_BIT_HF_IRQ_RESET; |
d6cbe537 | 1626 | outl(tmp, ai_context->ctrl_reg); |
e55c95a3 GG |
1627 | } |
1628 | ||
d6cbe537 | 1629 | if (inl(ai_context->irq_status_reg) & ME4000_IRQ_STATUS_BIT_SC) { |
e55c95a3 GG |
1630 | s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOA; |
1631 | ||
b6241fda GS |
1632 | /* |
1633 | * Acquisition is complete, so stop | |
1634 | * conversion and disable all interrupts | |
1635 | */ | |
d6cbe537 | 1636 | tmp = inl(ai_context->ctrl_reg); |
e55c95a3 GG |
1637 | tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; |
1638 | tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ); | |
d6cbe537 | 1639 | outl(tmp, ai_context->ctrl_reg); |
e55c95a3 GG |
1640 | |
1641 | /* Poll data until fifo empty */ | |
1642 | while (inl(ai_context->ctrl_reg) & ME4000_AI_STATUS_BIT_EF_DATA) { | |
1643 | /* Read value from data fifo */ | |
1644 | lval = inl(ai_context->data_reg) & 0xFFFF; | |
1645 | lval ^= 0x8000; | |
1646 | ||
1647 | if (!comedi_buf_put(s->async, lval)) { | |
1648 | printk(KERN_ERR | |
b6241fda GS |
1649 | "comedi%d: me4000: me4000_ai_isr(): " |
1650 | "Buffer overflow\n", dev->minor); | |
e55c95a3 GG |
1651 | s->async->events |= COMEDI_CB_OVERFLOW; |
1652 | break; | |
1653 | } | |
1654 | } | |
1655 | ||
1656 | /* Work is done, so reset the interrupt */ | |
e55c95a3 | 1657 | tmp |= ME4000_AI_CTRL_BIT_SC_IRQ_RESET; |
d6cbe537 | 1658 | outl(tmp, ai_context->ctrl_reg); |
e55c95a3 | 1659 | tmp &= ~ME4000_AI_CTRL_BIT_SC_IRQ_RESET; |
d6cbe537 | 1660 | outl(tmp, ai_context->ctrl_reg); |
e55c95a3 GG |
1661 | } |
1662 | ||
e55c95a3 GG |
1663 | if (s->async->events) |
1664 | comedi_event(dev, s); | |
1665 | ||
1666 | return IRQ_HANDLED; | |
1667 | } | |
1668 | ||
1669 | /*============================================================================= | |
1670 | Analog output section | |
1671 | ===========================================================================*/ | |
1672 | ||
71b5f4f1 | 1673 | static int me4000_ao_insn_write(struct comedi_device *dev, |
0a85b6f0 MT |
1674 | struct comedi_subdevice *s, |
1675 | struct comedi_insn *insn, unsigned int *data) | |
e55c95a3 GG |
1676 | { |
1677 | ||
1678 | int chan = CR_CHAN(insn->chanspec); | |
1679 | int rang = CR_RANGE(insn->chanspec); | |
1680 | int aref = CR_AREF(insn->chanspec); | |
1681 | unsigned long tmp; | |
1682 | ||
e55c95a3 GG |
1683 | if (insn->n == 0) { |
1684 | return 0; | |
1685 | } else if (insn->n > 1) { | |
1686 | printk(KERN_ERR | |
b6241fda GS |
1687 | "comedi%d: me4000: me4000_ao_insn_write(): " |
1688 | "Invalid instruction length %d\n", dev->minor, insn->n); | |
e55c95a3 GG |
1689 | return -EINVAL; |
1690 | } | |
1691 | ||
1692 | if (chan >= thisboard->ao.count) { | |
1693 | printk(KERN_ERR | |
b6241fda GS |
1694 | "comedi%d: me4000: me4000_ao_insn_write(): " |
1695 | "Invalid channel %d\n", dev->minor, insn->n); | |
e55c95a3 GG |
1696 | return -EINVAL; |
1697 | } | |
1698 | ||
1699 | if (rang != 0) { | |
1700 | printk(KERN_ERR | |
b6241fda GS |
1701 | "comedi%d: me4000: me4000_ao_insn_write(): " |
1702 | "Invalid range %d\n", dev->minor, insn->n); | |
e55c95a3 GG |
1703 | return -EINVAL; |
1704 | } | |
1705 | ||
1706 | if (aref != AREF_GROUND && aref != AREF_COMMON) { | |
1707 | printk(KERN_ERR | |
b6241fda GS |
1708 | "comedi%d: me4000: me4000_ao_insn_write(): " |
1709 | "Invalid aref %d\n", dev->minor, insn->n); | |
e55c95a3 GG |
1710 | return -EINVAL; |
1711 | } | |
1712 | ||
1713 | /* Stop any running conversion */ | |
d6cbe537 | 1714 | tmp = inl(info->ao_context[chan].ctrl_reg); |
e55c95a3 | 1715 | tmp |= ME4000_AO_CTRL_BIT_IMMEDIATE_STOP; |
d6cbe537 | 1716 | outl(tmp, info->ao_context[chan].ctrl_reg); |
e55c95a3 GG |
1717 | |
1718 | /* Clear control register and set to single mode */ | |
d6cbe537 | 1719 | outl(0x0, info->ao_context[chan].ctrl_reg); |
e55c95a3 GG |
1720 | |
1721 | /* Write data value */ | |
d6cbe537 | 1722 | outl(data[0], info->ao_context[chan].single_reg); |
e55c95a3 GG |
1723 | |
1724 | /* Store in the mirror */ | |
1725 | info->ao_context[chan].mirror = data[0]; | |
1726 | ||
1727 | return 1; | |
1728 | } | |
1729 | ||
71b5f4f1 | 1730 | static int me4000_ao_insn_read(struct comedi_device *dev, |
0a85b6f0 MT |
1731 | struct comedi_subdevice *s, |
1732 | struct comedi_insn *insn, unsigned int *data) | |
e55c95a3 GG |
1733 | { |
1734 | int chan = CR_CHAN(insn->chanspec); | |
1735 | ||
1736 | if (insn->n == 0) { | |
1737 | return 0; | |
1738 | } else if (insn->n > 1) { | |
0a85b6f0 | 1739 | printk |
b6241fda GS |
1740 | ("comedi%d: me4000: me4000_ao_insn_read(): " |
1741 | "Invalid instruction length\n", dev->minor); | |
e55c95a3 GG |
1742 | return -EINVAL; |
1743 | } | |
1744 | ||
1745 | data[0] = info->ao_context[chan].mirror; | |
1746 | ||
1747 | return 1; | |
1748 | } | |
1749 | ||
1750 | /*============================================================================= | |
1751 | Digital I/O section | |
1752 | ===========================================================================*/ | |
1753 | ||
71b5f4f1 | 1754 | static int me4000_dio_insn_bits(struct comedi_device *dev, |
0a85b6f0 MT |
1755 | struct comedi_subdevice *s, |
1756 | struct comedi_insn *insn, unsigned int *data) | |
e55c95a3 | 1757 | { |
e55c95a3 GG |
1758 | /* |
1759 | * The insn data consists of a mask in data[0] and the new data | |
1760 | * in data[1]. The mask defines which bits we are concerning about. | |
1761 | * The new data must be anded with the mask. | |
1762 | * Each channel corresponds to a bit. | |
1763 | */ | |
1764 | if (data[0]) { | |
1765 | /* Check if requested ports are configured for output */ | |
1766 | if ((s->io_bits & data[0]) != data[0]) | |
1767 | return -EIO; | |
1768 | ||
1769 | s->state &= ~data[0]; | |
1770 | s->state |= data[0] & data[1]; | |
1771 | ||
1772 | /* Write out the new digital output lines */ | |
d6cbe537 | 1773 | outl((s->state >> 0) & 0xFF, |
0a85b6f0 | 1774 | info->dio_context.port_0_reg); |
d6cbe537 | 1775 | outl((s->state >> 8) & 0xFF, |
0a85b6f0 | 1776 | info->dio_context.port_1_reg); |
d6cbe537 | 1777 | outl((s->state >> 16) & 0xFF, |
0a85b6f0 | 1778 | info->dio_context.port_2_reg); |
d6cbe537 | 1779 | outl((s->state >> 24) & 0xFF, |
0a85b6f0 | 1780 | info->dio_context.port_3_reg); |
e55c95a3 GG |
1781 | } |
1782 | ||
1783 | /* On return, data[1] contains the value of | |
1784 | the digital input and output lines. */ | |
d6cbe537 HS |
1785 | data[1] = ((inl(info->dio_context.port_0_reg) & 0xFF) << 0) | |
1786 | ((inl(info->dio_context.port_1_reg) & 0xFF) << 8) | | |
1787 | ((inl(info->dio_context.port_2_reg) & 0xFF) << 16) | | |
1788 | ((inl(info->dio_context.port_3_reg) & 0xFF) << 24); | |
e55c95a3 | 1789 | |
a2714e3e | 1790 | return insn->n; |
e55c95a3 GG |
1791 | } |
1792 | ||
71b5f4f1 | 1793 | static int me4000_dio_insn_config(struct comedi_device *dev, |
0a85b6f0 MT |
1794 | struct comedi_subdevice *s, |
1795 | struct comedi_insn *insn, unsigned int *data) | |
e55c95a3 GG |
1796 | { |
1797 | unsigned long tmp; | |
1798 | int chan = CR_CHAN(insn->chanspec); | |
1799 | ||
f3445c1e IA |
1800 | switch (data[0]) { |
1801 | default: | |
1802 | return -EINVAL; | |
1803 | case INSN_CONFIG_DIO_QUERY: | |
e55c95a3 | 1804 | data[1] = |
0a85b6f0 | 1805 | (s->io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; |
e55c95a3 | 1806 | return insn->n; |
f3445c1e IA |
1807 | case INSN_CONFIG_DIO_INPUT: |
1808 | case INSN_CONFIG_DIO_OUTPUT: | |
1809 | break; | |
e55c95a3 GG |
1810 | } |
1811 | ||
1812 | /* | |
1813 | * The input or output configuration of each digital line is | |
1814 | * configured by a special insn_config instruction. chanspec | |
1815 | * contains the channel to be changed, and data[0] contains the | |
f3445c1e | 1816 | * value INSN_CONFIG_DIO_INPUT or INSN_CONFIG_DIO_OUTPUT. |
e55c95a3 GG |
1817 | * On the ME-4000 it is only possible to switch port wise (8 bit) |
1818 | */ | |
1819 | ||
d6cbe537 | 1820 | tmp = inl(info->dio_context.ctrl_reg); |
e55c95a3 | 1821 | |
f3445c1e | 1822 | if (data[0] == INSN_CONFIG_DIO_OUTPUT) { |
e55c95a3 GG |
1823 | if (chan < 8) { |
1824 | s->io_bits |= 0xFF; | |
1825 | tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 | | |
0a85b6f0 | 1826 | ME4000_DIO_CTRL_BIT_MODE_1); |
e55c95a3 GG |
1827 | tmp |= ME4000_DIO_CTRL_BIT_MODE_0; |
1828 | } else if (chan < 16) { | |
1829 | /* | |
b6241fda GS |
1830 | * Chech for optoisolated ME-4000 version. |
1831 | * If one the first port is a fixed output | |
1832 | * port and the second is a fixed input port. | |
e55c95a3 | 1833 | */ |
d6cbe537 | 1834 | if (!inl(info->dio_context.dir_reg)) |
e55c95a3 GG |
1835 | return -ENODEV; |
1836 | ||
1837 | s->io_bits |= 0xFF00; | |
1838 | tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 | | |
0a85b6f0 | 1839 | ME4000_DIO_CTRL_BIT_MODE_3); |
e55c95a3 GG |
1840 | tmp |= ME4000_DIO_CTRL_BIT_MODE_2; |
1841 | } else if (chan < 24) { | |
1842 | s->io_bits |= 0xFF0000; | |
1843 | tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_4 | | |
0a85b6f0 | 1844 | ME4000_DIO_CTRL_BIT_MODE_5); |
e55c95a3 GG |
1845 | tmp |= ME4000_DIO_CTRL_BIT_MODE_4; |
1846 | } else if (chan < 32) { | |
1847 | s->io_bits |= 0xFF000000; | |
1848 | tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_6 | | |
0a85b6f0 | 1849 | ME4000_DIO_CTRL_BIT_MODE_7); |
e55c95a3 GG |
1850 | tmp |= ME4000_DIO_CTRL_BIT_MODE_6; |
1851 | } else { | |
1852 | return -EINVAL; | |
1853 | } | |
1854 | } else { | |
1855 | if (chan < 8) { | |
1856 | /* | |
b6241fda GS |
1857 | * Chech for optoisolated ME-4000 version. |
1858 | * If one the first port is a fixed output | |
1859 | * port and the second is a fixed input port. | |
e55c95a3 | 1860 | */ |
d6cbe537 | 1861 | if (!inl(info->dio_context.dir_reg)) |
e55c95a3 GG |
1862 | return -ENODEV; |
1863 | ||
1864 | s->io_bits &= ~0xFF; | |
1865 | tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 | | |
0a85b6f0 | 1866 | ME4000_DIO_CTRL_BIT_MODE_1); |
e55c95a3 GG |
1867 | } else if (chan < 16) { |
1868 | s->io_bits &= ~0xFF00; | |
1869 | tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 | | |
0a85b6f0 | 1870 | ME4000_DIO_CTRL_BIT_MODE_3); |
e55c95a3 GG |
1871 | } else if (chan < 24) { |
1872 | s->io_bits &= ~0xFF0000; | |
1873 | tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_4 | | |
0a85b6f0 | 1874 | ME4000_DIO_CTRL_BIT_MODE_5); |
e55c95a3 GG |
1875 | } else if (chan < 32) { |
1876 | s->io_bits &= ~0xFF000000; | |
1877 | tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_6 | | |
0a85b6f0 | 1878 | ME4000_DIO_CTRL_BIT_MODE_7); |
e55c95a3 GG |
1879 | } else { |
1880 | return -EINVAL; | |
1881 | } | |
1882 | } | |
1883 | ||
d6cbe537 | 1884 | outl(tmp, info->dio_context.ctrl_reg); |
e55c95a3 GG |
1885 | |
1886 | return 1; | |
1887 | } | |
1888 | ||
1889 | /*============================================================================= | |
1890 | Counter section | |
1891 | ===========================================================================*/ | |
1892 | ||
71b5f4f1 | 1893 | static int cnt_reset(struct comedi_device *dev, unsigned int channel) |
e55c95a3 | 1894 | { |
e55c95a3 GG |
1895 | switch (channel) { |
1896 | case 0: | |
d6cbe537 HS |
1897 | outb(0x30, info->cnt_context.ctrl_reg); |
1898 | outb(0x00, info->cnt_context.counter_0_reg); | |
1899 | outb(0x00, info->cnt_context.counter_0_reg); | |
e55c95a3 GG |
1900 | break; |
1901 | case 1: | |
d6cbe537 HS |
1902 | outb(0x70, info->cnt_context.ctrl_reg); |
1903 | outb(0x00, info->cnt_context.counter_1_reg); | |
1904 | outb(0x00, info->cnt_context.counter_1_reg); | |
e55c95a3 GG |
1905 | break; |
1906 | case 2: | |
d6cbe537 HS |
1907 | outb(0xB0, info->cnt_context.ctrl_reg); |
1908 | outb(0x00, info->cnt_context.counter_2_reg); | |
1909 | outb(0x00, info->cnt_context.counter_2_reg); | |
e55c95a3 GG |
1910 | break; |
1911 | default: | |
1912 | printk(KERN_ERR | |
0a85b6f0 MT |
1913 | "comedi%d: me4000: cnt_reset(): Invalid channel\n", |
1914 | dev->minor); | |
e55c95a3 GG |
1915 | return -EINVAL; |
1916 | } | |
1917 | ||
1918 | return 0; | |
1919 | } | |
1920 | ||
71b5f4f1 | 1921 | static int cnt_config(struct comedi_device *dev, unsigned int channel, |
0a85b6f0 | 1922 | unsigned int mode) |
e55c95a3 GG |
1923 | { |
1924 | int tmp = 0; | |
1925 | ||
e55c95a3 GG |
1926 | switch (channel) { |
1927 | case 0: | |
1928 | tmp |= ME4000_CNT_COUNTER_0; | |
1929 | break; | |
1930 | case 1: | |
1931 | tmp |= ME4000_CNT_COUNTER_1; | |
1932 | break; | |
1933 | case 2: | |
1934 | tmp |= ME4000_CNT_COUNTER_2; | |
1935 | break; | |
1936 | default: | |
1937 | printk(KERN_ERR | |
0a85b6f0 MT |
1938 | "comedi%d: me4000: cnt_config(): Invalid channel\n", |
1939 | dev->minor); | |
e55c95a3 GG |
1940 | return -EINVAL; |
1941 | } | |
1942 | ||
1943 | switch (mode) { | |
1944 | case 0: | |
1945 | tmp |= ME4000_CNT_MODE_0; | |
1946 | break; | |
1947 | case 1: | |
1948 | tmp |= ME4000_CNT_MODE_1; | |
1949 | break; | |
1950 | case 2: | |
1951 | tmp |= ME4000_CNT_MODE_2; | |
1952 | break; | |
1953 | case 3: | |
1954 | tmp |= ME4000_CNT_MODE_3; | |
1955 | break; | |
1956 | case 4: | |
1957 | tmp |= ME4000_CNT_MODE_4; | |
1958 | break; | |
1959 | case 5: | |
1960 | tmp |= ME4000_CNT_MODE_5; | |
1961 | break; | |
1962 | default: | |
1963 | printk(KERN_ERR | |
0a85b6f0 MT |
1964 | "comedi%d: me4000: cnt_config(): Invalid counter mode\n", |
1965 | dev->minor); | |
e55c95a3 GG |
1966 | return -EINVAL; |
1967 | } | |
1968 | ||
1969 | /* Write the control word */ | |
1970 | tmp |= 0x30; | |
d6cbe537 | 1971 | outb(tmp, info->cnt_context.ctrl_reg); |
e55c95a3 GG |
1972 | |
1973 | return 0; | |
1974 | } | |
1975 | ||
71b5f4f1 | 1976 | static int me4000_cnt_insn_config(struct comedi_device *dev, |
0a85b6f0 MT |
1977 | struct comedi_subdevice *s, |
1978 | struct comedi_insn *insn, unsigned int *data) | |
e55c95a3 GG |
1979 | { |
1980 | ||
1981 | int err; | |
1982 | ||
e55c95a3 GG |
1983 | switch (data[0]) { |
1984 | case GPCT_RESET: | |
1985 | if (insn->n != 1) { | |
1986 | printk(KERN_ERR | |
b6241fda GS |
1987 | "comedi%d: me4000: me4000_cnt_insn_config(): " |
1988 | "Invalid instruction length%d\n", | |
0a85b6f0 | 1989 | dev->minor, insn->n); |
e55c95a3 GG |
1990 | return -EINVAL; |
1991 | } | |
1992 | ||
1993 | err = cnt_reset(dev, insn->chanspec); | |
1994 | if (err) | |
1995 | return err; | |
1996 | break; | |
1997 | case GPCT_SET_OPERATION: | |
1998 | if (insn->n != 2) { | |
1999 | printk(KERN_ERR | |
b6241fda GS |
2000 | "comedi%d: me4000: me4000_cnt_insn_config(): " |
2001 | "Invalid instruction length%d\n", | |
0a85b6f0 | 2002 | dev->minor, insn->n); |
e55c95a3 GG |
2003 | return -EINVAL; |
2004 | } | |
2005 | ||
2006 | err = cnt_config(dev, insn->chanspec, data[1]); | |
2007 | if (err) | |
2008 | return err; | |
2009 | break; | |
2010 | default: | |
2011 | printk(KERN_ERR | |
b6241fda GS |
2012 | "comedi%d: me4000: me4000_cnt_insn_config(): " |
2013 | "Invalid instruction\n", dev->minor); | |
e55c95a3 GG |
2014 | return -EINVAL; |
2015 | } | |
2016 | ||
2017 | return 2; | |
2018 | } | |
2019 | ||
71b5f4f1 | 2020 | static int me4000_cnt_insn_read(struct comedi_device *dev, |
0a85b6f0 MT |
2021 | struct comedi_subdevice *s, |
2022 | struct comedi_insn *insn, unsigned int *data) | |
e55c95a3 GG |
2023 | { |
2024 | ||
2025 | unsigned short tmp; | |
2026 | ||
82675f35 | 2027 | if (insn->n == 0) |
e55c95a3 | 2028 | return 0; |
82675f35 | 2029 | |
e55c95a3 GG |
2030 | if (insn->n > 1) { |
2031 | printk(KERN_ERR | |
b6241fda GS |
2032 | "comedi%d: me4000: me4000_cnt_insn_read(): " |
2033 | "Invalid instruction length %d\n", | |
0a85b6f0 | 2034 | dev->minor, insn->n); |
e55c95a3 GG |
2035 | return -EINVAL; |
2036 | } | |
2037 | ||
2038 | switch (insn->chanspec) { | |
2039 | case 0: | |
d6cbe537 | 2040 | tmp = inb(info->cnt_context.counter_0_reg); |
e55c95a3 | 2041 | data[0] = tmp; |
d6cbe537 | 2042 | tmp = inb(info->cnt_context.counter_0_reg); |
e55c95a3 GG |
2043 | data[0] |= tmp << 8; |
2044 | break; | |
2045 | case 1: | |
d6cbe537 | 2046 | tmp = inb(info->cnt_context.counter_1_reg); |
e55c95a3 | 2047 | data[0] = tmp; |
d6cbe537 | 2048 | tmp = inb(info->cnt_context.counter_1_reg); |
e55c95a3 GG |
2049 | data[0] |= tmp << 8; |
2050 | break; | |
2051 | case 2: | |
d6cbe537 | 2052 | tmp = inb(info->cnt_context.counter_2_reg); |
e55c95a3 | 2053 | data[0] = tmp; |
d6cbe537 | 2054 | tmp = inb(info->cnt_context.counter_2_reg); |
e55c95a3 GG |
2055 | data[0] |= tmp << 8; |
2056 | break; | |
2057 | default: | |
2058 | printk(KERN_ERR | |
b6241fda GS |
2059 | "comedi%d: me4000: me4000_cnt_insn_read(): " |
2060 | "Invalid channel %d\n", | |
0a85b6f0 | 2061 | dev->minor, insn->chanspec); |
e55c95a3 GG |
2062 | return -EINVAL; |
2063 | } | |
2064 | ||
2065 | return 1; | |
2066 | } | |
2067 | ||
71b5f4f1 | 2068 | static int me4000_cnt_insn_write(struct comedi_device *dev, |
0a85b6f0 MT |
2069 | struct comedi_subdevice *s, |
2070 | struct comedi_insn *insn, unsigned int *data) | |
e55c95a3 GG |
2071 | { |
2072 | ||
2073 | unsigned short tmp; | |
2074 | ||
e55c95a3 GG |
2075 | if (insn->n == 0) { |
2076 | return 0; | |
2077 | } else if (insn->n > 1) { | |
2078 | printk(KERN_ERR | |
b6241fda GS |
2079 | "comedi%d: me4000: me4000_cnt_insn_write(): " |
2080 | "Invalid instruction length %d\n", | |
0a85b6f0 | 2081 | dev->minor, insn->n); |
e55c95a3 GG |
2082 | return -EINVAL; |
2083 | } | |
2084 | ||
2085 | switch (insn->chanspec) { | |
2086 | case 0: | |
2087 | tmp = data[0] & 0xFF; | |
d6cbe537 | 2088 | outb(tmp, info->cnt_context.counter_0_reg); |
e55c95a3 | 2089 | tmp = (data[0] >> 8) & 0xFF; |
d6cbe537 | 2090 | outb(tmp, info->cnt_context.counter_0_reg); |
e55c95a3 GG |
2091 | break; |
2092 | case 1: | |
2093 | tmp = data[0] & 0xFF; | |
d6cbe537 | 2094 | outb(tmp, info->cnt_context.counter_1_reg); |
e55c95a3 | 2095 | tmp = (data[0] >> 8) & 0xFF; |
d6cbe537 | 2096 | outb(tmp, info->cnt_context.counter_1_reg); |
e55c95a3 GG |
2097 | break; |
2098 | case 2: | |
2099 | tmp = data[0] & 0xFF; | |
d6cbe537 | 2100 | outb(tmp, info->cnt_context.counter_2_reg); |
e55c95a3 | 2101 | tmp = (data[0] >> 8) & 0xFF; |
d6cbe537 | 2102 | outb(tmp, info->cnt_context.counter_2_reg); |
e55c95a3 GG |
2103 | break; |
2104 | default: | |
2105 | printk(KERN_ERR | |
b6241fda GS |
2106 | "comedi%d: me4000: me4000_cnt_insn_write(): " |
2107 | "Invalid channel %d\n", | |
0a85b6f0 | 2108 | dev->minor, insn->chanspec); |
e55c95a3 GG |
2109 | return -EINVAL; |
2110 | } | |
2111 | ||
2112 | return 1; | |
2113 | } | |
2114 | ||
3af09830 HS |
2115 | static int me4000_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
2116 | { | |
2117 | struct comedi_subdevice *s; | |
2118 | int result; | |
2119 | ||
3af09830 HS |
2120 | result = me4000_probe(dev, it); |
2121 | if (result) | |
2122 | return result; | |
2123 | ||
8b6c5694 HS |
2124 | result = comedi_alloc_subdevices(dev, 4); |
2125 | if (result) | |
2126 | return result; | |
3af09830 HS |
2127 | |
2128 | /*========================================================================= | |
2129 | Analog input subdevice | |
2130 | ========================================================================*/ | |
2131 | ||
8aaf2717 | 2132 | s = &dev->subdevices[0]; |
3af09830 | 2133 | |
6ba8dfef | 2134 | if (thisboard->ai_nchan) { |
3af09830 HS |
2135 | s->type = COMEDI_SUBD_AI; |
2136 | s->subdev_flags = | |
2137 | SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; | |
6ba8dfef | 2138 | s->n_chan = thisboard->ai_nchan; |
3af09830 HS |
2139 | s->maxdata = 0xFFFF; /* 16 bit ADC */ |
2140 | s->len_chanlist = ME4000_AI_CHANNEL_LIST_COUNT; | |
2141 | s->range_table = &me4000_ai_range; | |
2142 | s->insn_read = me4000_ai_insn_read; | |
2143 | ||
2144 | if (info->irq > 0) { | |
2145 | if (request_irq(info->irq, me4000_ai_isr, | |
2146 | IRQF_SHARED, "ME-4000", dev)) { | |
2147 | printk | |
2148 | ("comedi%d: me4000: me4000_attach(): " | |
2149 | "Unable to allocate irq\n", dev->minor); | |
2150 | } else { | |
2151 | dev->read_subdev = s; | |
2152 | s->subdev_flags |= SDF_CMD_READ; | |
2153 | s->cancel = me4000_ai_cancel; | |
2154 | s->do_cmdtest = me4000_ai_do_cmd_test; | |
2155 | s->do_cmd = me4000_ai_do_cmd; | |
2156 | } | |
2157 | } else { | |
2158 | printk(KERN_WARNING | |
2159 | "comedi%d: me4000: me4000_attach(): " | |
2160 | "No interrupt available\n", dev->minor); | |
2161 | } | |
2162 | } else { | |
2163 | s->type = COMEDI_SUBD_UNUSED; | |
2164 | } | |
2165 | ||
2166 | /*========================================================================= | |
2167 | Analog output subdevice | |
2168 | ========================================================================*/ | |
2169 | ||
8aaf2717 | 2170 | s = &dev->subdevices[1]; |
3af09830 HS |
2171 | |
2172 | if (thisboard->ao.count) { | |
2173 | s->type = COMEDI_SUBD_AO; | |
2174 | s->subdev_flags = SDF_WRITEABLE | SDF_COMMON | SDF_GROUND; | |
2175 | s->n_chan = thisboard->ao.count; | |
2176 | s->maxdata = 0xFFFF; /* 16 bit DAC */ | |
2177 | s->range_table = &me4000_ao_range; | |
2178 | s->insn_write = me4000_ao_insn_write; | |
2179 | s->insn_read = me4000_ao_insn_read; | |
2180 | } else { | |
2181 | s->type = COMEDI_SUBD_UNUSED; | |
2182 | } | |
2183 | ||
2184 | /*========================================================================= | |
2185 | Digital I/O subdevice | |
2186 | ========================================================================*/ | |
2187 | ||
8aaf2717 | 2188 | s = &dev->subdevices[2]; |
3af09830 | 2189 | |
898f5191 | 2190 | if (thisboard->dio_nchan) { |
3af09830 HS |
2191 | s->type = COMEDI_SUBD_DIO; |
2192 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
898f5191 | 2193 | s->n_chan = thisboard->dio_nchan; |
3af09830 HS |
2194 | s->maxdata = 1; |
2195 | s->range_table = &range_digital; | |
2196 | s->insn_bits = me4000_dio_insn_bits; | |
2197 | s->insn_config = me4000_dio_insn_config; | |
2198 | } else { | |
2199 | s->type = COMEDI_SUBD_UNUSED; | |
2200 | } | |
2201 | ||
2202 | /* | |
2203 | * Check for optoisolated ME-4000 version. If one the first | |
2204 | * port is a fixed output port and the second is a fixed input port. | |
2205 | */ | |
d6cbe537 | 2206 | if (!inl(info->dio_context.dir_reg)) { |
3af09830 | 2207 | s->io_bits |= 0xFF; |
d6cbe537 | 2208 | outl(ME4000_DIO_CTRL_BIT_MODE_0, info->dio_context.dir_reg); |
3af09830 HS |
2209 | } |
2210 | ||
2211 | /*========================================================================= | |
2212 | Counter subdevice | |
2213 | ========================================================================*/ | |
2214 | ||
8aaf2717 | 2215 | s = &dev->subdevices[3]; |
3af09830 | 2216 | |
eedf4299 | 2217 | if (thisboard->has_counter) { |
3af09830 HS |
2218 | s->type = COMEDI_SUBD_COUNTER; |
2219 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; | |
eedf4299 | 2220 | s->n_chan = 3; |
3af09830 HS |
2221 | s->maxdata = 0xFFFF; /* 16 bit counters */ |
2222 | s->insn_read = me4000_cnt_insn_read; | |
2223 | s->insn_write = me4000_cnt_insn_write; | |
2224 | s->insn_config = me4000_cnt_insn_config; | |
2225 | } else { | |
2226 | s->type = COMEDI_SUBD_UNUSED; | |
2227 | } | |
2228 | ||
2229 | return 0; | |
2230 | } | |
2231 | ||
484ecc95 | 2232 | static void me4000_detach(struct comedi_device *dev) |
3af09830 | 2233 | { |
3af09830 HS |
2234 | if (info) { |
2235 | if (info->pci_dev_p) { | |
2236 | reset_board(dev); | |
2237 | if (info->plx_regbase) | |
2238 | comedi_pci_disable(info->pci_dev_p); | |
2239 | pci_dev_put(info->pci_dev_p); | |
2240 | } | |
2241 | } | |
3af09830 HS |
2242 | } |
2243 | ||
75e6301b | 2244 | static struct comedi_driver me4000_driver = { |
3af09830 HS |
2245 | .driver_name = "me4000", |
2246 | .module = THIS_MODULE, | |
2247 | .attach = me4000_attach, | |
2248 | .detach = me4000_detach, | |
2249 | }; | |
2250 | ||
75e6301b HS |
2251 | static int __devinit me4000_pci_probe(struct pci_dev *dev, |
2252 | const struct pci_device_id *ent) | |
727b286b | 2253 | { |
75e6301b | 2254 | return comedi_pci_auto_config(dev, &me4000_driver); |
727b286b AT |
2255 | } |
2256 | ||
75e6301b | 2257 | static void __devexit me4000_pci_remove(struct pci_dev *dev) |
727b286b AT |
2258 | { |
2259 | comedi_pci_auto_unconfig(dev); | |
2260 | } | |
2261 | ||
3af09830 HS |
2262 | static DEFINE_PCI_DEVICE_TABLE(me4000_pci_table) = { |
2263 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4650) }, | |
2264 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4660) }, | |
2265 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4661) }, | |
2266 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4662) }, | |
2267 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4663) }, | |
2268 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4670) }, | |
2269 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4671) }, | |
2270 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4672) }, | |
2271 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4673) }, | |
2272 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4680) }, | |
2273 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4681) }, | |
2274 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4682) }, | |
2275 | { PCI_DEVICE(PCI_VENDOR_ID_MEILHAUS, 0x4683) }, | |
2276 | { 0 } | |
2277 | }; | |
2278 | MODULE_DEVICE_TABLE(pci, me4000_pci_table); | |
2279 | ||
75e6301b HS |
2280 | static struct pci_driver me4000_pci_driver = { |
2281 | .name = "me4000", | |
3af09830 | 2282 | .id_table = me4000_pci_table, |
75e6301b HS |
2283 | .probe = me4000_pci_probe, |
2284 | .remove = __devexit_p(me4000_pci_remove), | |
727b286b | 2285 | }; |
75e6301b | 2286 | module_comedi_pci_driver(me4000_driver, me4000_pci_driver); |
90f703d3 AT |
2287 | |
2288 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
2289 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
2290 | MODULE_LICENSE("GPL"); |