Commit | Line | Data |
---|---|---|
85acac61 MH |
1 | /* |
2 | ||
3 | comedi/drivers/me_daq.c | |
4 | ||
5 | Hardware driver for Meilhaus data acquisition cards: | |
6 | ||
7 | ME-2000i, ME-2600i, ME-3000vm1 | |
8 | ||
9 | Copyright (C) 2002 Michael Hillmann <hillmann@syscongroup.de> | |
10 | ||
11 | This program is free software; you can redistribute it and/or modify | |
12 | it under the terms of the GNU General Public License as published by | |
13 | the Free Software Foundation; either version 2 of the License, or | |
14 | (at your option) any later version. | |
15 | ||
16 | This program is distributed in the hope that it will be useful, | |
17 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | GNU General Public License for more details. | |
20 | ||
21 | You should have received a copy of the GNU General Public License | |
22 | along with this program; if not, write to the Free Software | |
23 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |
24 | */ | |
25 | ||
26 | /* | |
27 | Driver: me_daq | |
28 | Description: Meilhaus PCI data acquisition cards | |
29 | Author: Michael Hillmann <hillmann@syscongroup.de> | |
30 | Devices: [Meilhaus] ME-2600i (me_daq), ME-2000i | |
31 | Status: experimental | |
32 | ||
33 | Supports: | |
34 | ||
35 | Analog Output | |
36 | ||
37 | Configuration options: | |
38 | ||
39 | [0] - PCI bus number (optional) | |
40 | [1] - PCI slot number (optional) | |
41 | ||
42 | If bus/slot is not specified, the first available PCI | |
43 | device will be used. | |
44 | ||
45 | The 2600 requires a firmware upload, which can be accomplished | |
46 | using the -i or --init-data option of comedi_config. | |
47 | The firmware can be | |
48 | found in the comedi_nonfree_firmware tarball available | |
49 | from http://www.comedi.org | |
50 | ||
51 | */ | |
52 | ||
25436dc9 | 53 | #include <linux/interrupt.h> |
4377a026 | 54 | #include <linux/sched.h> |
85acac61 MH |
55 | #include "../comedidev.h" |
56 | ||
57 | #include "comedi_pci.h" | |
58 | ||
2ce411b5 GKH |
59 | /*#include "me2600_fw.h" */ |
60 | ||
61 | #define ME_DRIVER_NAME "me_daq" | |
62 | ||
3ca88dd5 | 63 | #define PCI_VENDOR_ID_MEILHAUS 0x1402 |
2ce411b5 GKH |
64 | #define ME2000_DEVICE_ID 0x2000 |
65 | #define ME2600_DEVICE_ID 0x2600 | |
66 | ||
67 | #define PLX_INTCSR 0x4C /* PLX interrupt status register */ | |
68 | #define XILINX_DOWNLOAD_RESET 0x42 /* Xilinx registers */ | |
69 | ||
70 | #define ME_CONTROL_1 0x0000 /* - | W */ | |
71 | #define INTERRUPT_ENABLE (1<<15) | |
72 | #define COUNTER_B_IRQ (1<<12) | |
73 | #define COUNTER_A_IRQ (1<<11) | |
74 | #define CHANLIST_READY_IRQ (1<<10) | |
75 | #define EXT_IRQ (1<<9) | |
76 | #define ADFIFO_HALFFULL_IRQ (1<<8) | |
77 | #define SCAN_COUNT_ENABLE (1<<5) | |
78 | #define SIMULTANEOUS_ENABLE (1<<4) | |
79 | #define TRIGGER_FALLING_EDGE (1<<3) | |
80 | #define CONTINUOUS_MODE (1<<2) | |
81 | #define DISABLE_ADC (0<<0) | |
82 | #define SOFTWARE_TRIGGERED_ADC (1<<0) | |
83 | #define SCAN_TRIGGERED_ADC (2<<0) | |
84 | #define EXT_TRIGGERED_ADC (3<<0) | |
85 | #define ME_ADC_START 0x0000 /* R | - */ | |
86 | #define ME_CONTROL_2 0x0002 /* - | W */ | |
87 | #define ENABLE_ADFIFO (1<<10) | |
88 | #define ENABLE_CHANLIST (1<<9) | |
89 | #define ENABLE_PORT_B (1<<7) | |
90 | #define ENABLE_PORT_A (1<<6) | |
91 | #define ENABLE_COUNTER_B (1<<4) | |
92 | #define ENABLE_COUNTER_A (1<<3) | |
93 | #define ENABLE_DAC (1<<1) | |
94 | #define BUFFERED_DAC (1<<0) | |
95 | #define ME_DAC_UPDATE 0x0002 /* R | - */ | |
96 | #define ME_STATUS 0x0004 /* R | - */ | |
97 | #define COUNTER_B_IRQ_PENDING (1<<12) | |
98 | #define COUNTER_A_IRQ_PENDING (1<<11) | |
99 | #define CHANLIST_READY_IRQ_PENDING (1<<10) | |
100 | #define EXT_IRQ_PENDING (1<<9) | |
101 | #define ADFIFO_HALFFULL_IRQ_PENDING (1<<8) | |
102 | #define ADFIFO_FULL (1<<4) | |
103 | #define ADFIFO_HALFFULL (1<<3) | |
104 | #define ADFIFO_EMPTY (1<<2) | |
105 | #define CHANLIST_FULL (1<<1) | |
106 | #define FST_ACTIVE (1<<0) | |
107 | #define ME_RESET_INTERRUPT 0x0004 /* - | W */ | |
108 | #define ME_DIO_PORT_A 0x0006 /* R | W */ | |
109 | #define ME_DIO_PORT_B 0x0008 /* R | W */ | |
110 | #define ME_TIMER_DATA_0 0x000A /* - | W */ | |
111 | #define ME_TIMER_DATA_1 0x000C /* - | W */ | |
112 | #define ME_TIMER_DATA_2 0x000E /* - | W */ | |
113 | #define ME_CHANNEL_LIST 0x0010 /* - | W */ | |
114 | #define ADC_UNIPOLAR (1<<6) | |
115 | #define ADC_GAIN_0 (0<<4) | |
116 | #define ADC_GAIN_1 (1<<4) | |
117 | #define ADC_GAIN_2 (2<<4) | |
118 | #define ADC_GAIN_3 (3<<4) | |
119 | #define ME_READ_AD_FIFO 0x0010 /* R | - */ | |
120 | #define ME_DAC_CONTROL 0x0012 /* - | W */ | |
121 | #define DAC_UNIPOLAR_D (0<<4) | |
122 | #define DAC_BIPOLAR_D (1<<4) | |
123 | #define DAC_UNIPOLAR_C (0<<5) | |
124 | #define DAC_BIPOLAR_C (1<<5) | |
125 | #define DAC_UNIPOLAR_B (0<<6) | |
126 | #define DAC_BIPOLAR_B (1<<6) | |
127 | #define DAC_UNIPOLAR_A (0<<7) | |
128 | #define DAC_BIPOLAR_A (1<<7) | |
129 | #define DAC_GAIN_0_D (0<<8) | |
130 | #define DAC_GAIN_1_D (1<<8) | |
131 | #define DAC_GAIN_0_C (0<<9) | |
132 | #define DAC_GAIN_1_C (1<<9) | |
133 | #define DAC_GAIN_0_B (0<<10) | |
134 | #define DAC_GAIN_1_B (1<<10) | |
135 | #define DAC_GAIN_0_A (0<<11) | |
136 | #define DAC_GAIN_1_A (1<<11) | |
137 | #define ME_DAC_CONTROL_UPDATE 0x0012 /* R | - */ | |
138 | #define ME_DAC_DATA_A 0x0014 /* - | W */ | |
139 | #define ME_DAC_DATA_B 0x0016 /* - | W */ | |
140 | #define ME_DAC_DATA_C 0x0018 /* - | W */ | |
141 | #define ME_DAC_DATA_D 0x001A /* - | W */ | |
142 | #define ME_COUNTER_ENDDATA_A 0x001C /* - | W */ | |
143 | #define ME_COUNTER_ENDDATA_B 0x001E /* - | W */ | |
144 | #define ME_COUNTER_STARTDATA_A 0x0020 /* - | W */ | |
145 | #define ME_COUNTER_VALUE_A 0x0020 /* R | - */ | |
146 | #define ME_COUNTER_STARTDATA_B 0x0022 /* - | W */ | |
147 | #define ME_COUNTER_VALUE_B 0x0022 /* R | - */ | |
148 | ||
149 | /* Function prototypes */ | |
0707bb04 | 150 | static int me_attach(struct comedi_device *dev, struct comedi_devconfig *it); |
71b5f4f1 | 151 | static int me_detach(struct comedi_device *dev); |
85acac61 | 152 | |
9ced1de6 | 153 | static const struct comedi_lrange me2000_ai_range = { |
85acac61 MH |
154 | 8, |
155 | { | |
0a85b6f0 MT |
156 | BIP_RANGE(10), |
157 | BIP_RANGE(5), | |
158 | BIP_RANGE(2.5), | |
159 | BIP_RANGE(1.25), | |
160 | UNI_RANGE(10), | |
161 | UNI_RANGE(5), | |
162 | UNI_RANGE(2.5), | |
163 | UNI_RANGE(1.25) | |
164 | } | |
85acac61 MH |
165 | }; |
166 | ||
9ced1de6 | 167 | static const struct comedi_lrange me2600_ai_range = { |
85acac61 MH |
168 | 8, |
169 | { | |
0a85b6f0 MT |
170 | BIP_RANGE(10), |
171 | BIP_RANGE(5), | |
172 | BIP_RANGE(2.5), | |
173 | BIP_RANGE(1.25), | |
174 | UNI_RANGE(10), | |
175 | UNI_RANGE(5), | |
176 | UNI_RANGE(2.5), | |
177 | UNI_RANGE(1.25) | |
178 | } | |
85acac61 MH |
179 | }; |
180 | ||
9ced1de6 | 181 | static const struct comedi_lrange me2600_ao_range = { |
85acac61 MH |
182 | 3, |
183 | { | |
0a85b6f0 MT |
184 | BIP_RANGE(10), |
185 | BIP_RANGE(5), | |
186 | UNI_RANGE(10) | |
187 | } | |
85acac61 MH |
188 | }; |
189 | ||
190 | static DEFINE_PCI_DEVICE_TABLE(me_pci_table) = { | |
0a85b6f0 MT |
191 | { |
192 | PCI_VENDOR_ID_MEILHAUS, ME2600_DEVICE_ID, PCI_ANY_ID, | |
193 | PCI_ANY_ID, 0, 0, 0}, { | |
194 | PCI_VENDOR_ID_MEILHAUS, ME2000_DEVICE_ID, PCI_ANY_ID, | |
195 | PCI_ANY_ID, 0, 0, 0}, { | |
196 | 0} | |
85acac61 MH |
197 | }; |
198 | ||
199 | MODULE_DEVICE_TABLE(pci, me_pci_table); | |
200 | ||
2ce411b5 | 201 | /* Board specification structure */ |
5e08c198 | 202 | struct me_board { |
2ce411b5 | 203 | const char *name; /* driver name */ |
85acac61 | 204 | int device_id; |
2ce411b5 | 205 | int ao_channel_nbr; /* DA config */ |
85acac61 MH |
206 | int ao_resolution; |
207 | int ao_resolution_mask; | |
9ced1de6 | 208 | const struct comedi_lrange *ao_range_list; |
2ce411b5 | 209 | int ai_channel_nbr; /* AD config */ |
85acac61 MH |
210 | int ai_resolution; |
211 | int ai_resolution_mask; | |
9ced1de6 | 212 | const struct comedi_lrange *ai_range_list; |
2ce411b5 | 213 | int dio_channel_nbr; /* DIO config */ |
5e08c198 | 214 | }; |
85acac61 | 215 | |
5e08c198 | 216 | static const struct me_board me_boards[] = { |
2ce411b5 | 217 | { |
0a85b6f0 MT |
218 | /* -- ME-2600i -- */ |
219 | .name = ME_DRIVER_NAME, | |
220 | .device_id = ME2600_DEVICE_ID, | |
221 | /* Analog Output */ | |
222 | .ao_channel_nbr = 4, | |
223 | .ao_resolution = 12, | |
224 | .ao_resolution_mask = 0x0fff, | |
225 | .ao_range_list = &me2600_ao_range, | |
226 | .ai_channel_nbr = 16, | |
227 | /* Analog Input */ | |
228 | .ai_resolution = 12, | |
229 | .ai_resolution_mask = 0x0fff, | |
230 | .ai_range_list = &me2600_ai_range, | |
231 | .dio_channel_nbr = 32, | |
232 | }, | |
2ce411b5 | 233 | { |
0a85b6f0 MT |
234 | /* -- ME-2000i -- */ |
235 | .name = ME_DRIVER_NAME, | |
236 | .device_id = ME2000_DEVICE_ID, | |
237 | /* Analog Output */ | |
238 | .ao_channel_nbr = 0, | |
239 | .ao_resolution = 0, | |
240 | .ao_resolution_mask = 0, | |
241 | .ao_range_list = NULL, | |
242 | .ai_channel_nbr = 16, | |
243 | /* Analog Input */ | |
244 | .ai_resolution = 12, | |
245 | .ai_resolution_mask = 0x0fff, | |
246 | .ai_range_list = &me2000_ai_range, | |
247 | .dio_channel_nbr = 32, | |
248 | } | |
85acac61 MH |
249 | }; |
250 | ||
5e08c198 | 251 | #define me_board_nbr (sizeof(me_boards)/sizeof(struct me_board)) |
85acac61 | 252 | |
139dfbdf | 253 | static struct comedi_driver me_driver = { |
0a85b6f0 MT |
254 | .driver_name = ME_DRIVER_NAME, |
255 | .module = THIS_MODULE, | |
256 | .attach = me_attach, | |
257 | .detach = me_detach, | |
85acac61 | 258 | }; |
0a85b6f0 | 259 | |
727b286b AT |
260 | static int __devinit me_driver_pci_probe(struct pci_dev *dev, |
261 | const struct pci_device_id *ent) | |
262 | { | |
263 | return comedi_pci_auto_config(dev, me_driver.driver_name); | |
264 | } | |
265 | ||
266 | static void __devexit me_driver_pci_remove(struct pci_dev *dev) | |
267 | { | |
268 | comedi_pci_auto_unconfig(dev); | |
269 | } | |
270 | ||
271 | static struct pci_driver me_driver_pci_driver = { | |
272 | .id_table = me_pci_table, | |
273 | .probe = &me_driver_pci_probe, | |
274 | .remove = __devexit_p(&me_driver_pci_remove) | |
275 | }; | |
276 | ||
277 | static int __init me_driver_init_module(void) | |
278 | { | |
279 | int retval; | |
280 | ||
281 | retval = comedi_driver_register(&me_driver); | |
282 | if (retval < 0) | |
283 | return retval; | |
284 | ||
285 | me_driver_pci_driver.name = (char *)me_driver.driver_name; | |
286 | return pci_register_driver(&me_driver_pci_driver); | |
287 | } | |
288 | ||
289 | static void __exit me_driver_cleanup_module(void) | |
290 | { | |
291 | pci_unregister_driver(&me_driver_pci_driver); | |
292 | comedi_driver_unregister(&me_driver); | |
293 | } | |
294 | ||
295 | module_init(me_driver_init_module); | |
296 | module_exit(me_driver_cleanup_module); | |
85acac61 | 297 | |
2ce411b5 | 298 | /* Private data structure */ |
5e08c198 | 299 | struct me_private_data { |
85acac61 | 300 | struct pci_dev *pci_device; |
39eca2a0 GKH |
301 | void __iomem *plx_regbase; /* PLX configuration base address */ |
302 | void __iomem *me_regbase; /* Base address of the Meilhaus card */ | |
2ce411b5 GKH |
303 | unsigned long plx_regbase_size; /* Size of PLX configuration space */ |
304 | unsigned long me_regbase_size; /* Size of Meilhaus space */ | |
85acac61 | 305 | |
2ce411b5 GKH |
306 | unsigned short control_1; /* Mirror of CONTROL_1 register */ |
307 | unsigned short control_2; /* Mirror of CONTROL_2 register */ | |
308 | unsigned short dac_control; /* Mirror of the DAC_CONTROL register */ | |
309 | int ao_readback[4]; /* Mirror of analog output data */ | |
5e08c198 | 310 | }; |
85acac61 | 311 | |
5e08c198 | 312 | #define dev_private ((struct me_private_data *)dev->private) |
85acac61 | 313 | |
2ce411b5 GKH |
314 | /* |
315 | * ------------------------------------------------------------------ | |
316 | * | |
317 | * Helpful functions | |
318 | * | |
319 | * ------------------------------------------------------------------ | |
320 | */ | |
321 | static inline void sleep(unsigned sec) | |
85acac61 MH |
322 | { |
323 | current->state = TASK_INTERRUPTIBLE; | |
324 | schedule_timeout(sec * HZ); | |
325 | } | |
326 | ||
2ce411b5 GKH |
327 | /* |
328 | * ------------------------------------------------------------------ | |
329 | * | |
330 | * DIGITAL INPUT/OUTPUT SECTION | |
331 | * | |
332 | * ------------------------------------------------------------------ | |
333 | */ | |
0a85b6f0 MT |
334 | static int me_dio_insn_config(struct comedi_device *dev, |
335 | struct comedi_subdevice *s, | |
90035c08 | 336 | struct comedi_insn *insn, unsigned int *data) |
85acac61 MH |
337 | { |
338 | int bits; | |
339 | int mask = 1 << CR_CHAN(insn->chanspec); | |
340 | ||
341 | /* calculate port */ | |
342 | if (mask & 0x0000ffff) { /* Port A in use */ | |
343 | bits = 0x0000ffff; | |
344 | ||
345 | /* Enable Port A */ | |
346 | dev_private->control_2 |= ENABLE_PORT_A; | |
347 | writew(dev_private->control_2, | |
0a85b6f0 | 348 | dev_private->me_regbase + ME_CONTROL_2); |
85acac61 MH |
349 | } else { /* Port B in use */ |
350 | ||
351 | bits = 0xffff0000; | |
352 | ||
353 | /* Enable Port B */ | |
354 | dev_private->control_2 |= ENABLE_PORT_B; | |
355 | writew(dev_private->control_2, | |
0a85b6f0 | 356 | dev_private->me_regbase + ME_CONTROL_2); |
85acac61 MH |
357 | } |
358 | ||
2ce411b5 GKH |
359 | if (data[0]) { |
360 | /* Config port as output */ | |
85acac61 | 361 | s->io_bits |= bits; |
2ce411b5 GKH |
362 | } else { |
363 | /* Config port as input */ | |
85acac61 MH |
364 | s->io_bits &= ~bits; |
365 | } | |
366 | ||
367 | return 1; | |
368 | } | |
369 | ||
2ce411b5 | 370 | /* Digital instant input/outputs */ |
0a85b6f0 MT |
371 | static int me_dio_insn_bits(struct comedi_device *dev, |
372 | struct comedi_subdevice *s, | |
90035c08 | 373 | struct comedi_insn *insn, unsigned int *data) |
85acac61 MH |
374 | { |
375 | unsigned int mask = data[0]; | |
376 | s->state &= ~mask; | |
377 | s->state |= (mask & data[1]); | |
378 | ||
379 | mask &= s->io_bits; | |
380 | if (mask & 0x0000ffff) { /* Port A */ | |
381 | writew((s->state & 0xffff), | |
0a85b6f0 | 382 | dev_private->me_regbase + ME_DIO_PORT_A); |
85acac61 MH |
383 | } else { |
384 | data[1] &= ~0x0000ffff; | |
385 | data[1] |= readw(dev_private->me_regbase + ME_DIO_PORT_A); | |
386 | } | |
387 | ||
388 | if (mask & 0xffff0000) { /* Port B */ | |
389 | writew(((s->state >> 16) & 0xffff), | |
0a85b6f0 | 390 | dev_private->me_regbase + ME_DIO_PORT_B); |
85acac61 MH |
391 | } else { |
392 | data[1] &= ~0xffff0000; | |
393 | data[1] |= readw(dev_private->me_regbase + ME_DIO_PORT_B) << 16; | |
394 | } | |
395 | ||
396 | return 2; | |
397 | } | |
398 | ||
2ce411b5 GKH |
399 | /* |
400 | * ------------------------------------------------------------------ | |
401 | * | |
402 | * ANALOG INPUT SECTION | |
403 | * | |
404 | * ------------------------------------------------------------------ | |
405 | */ | |
406 | ||
407 | /* Analog instant input */ | |
0a85b6f0 MT |
408 | static int me_ai_insn_read(struct comedi_device *dev, |
409 | struct comedi_subdevice *subdevice, | |
90035c08 | 410 | struct comedi_insn *insn, unsigned int *data) |
85acac61 MH |
411 | { |
412 | unsigned short value; | |
413 | int chan = CR_CHAN((&insn->chanspec)[0]); | |
414 | int rang = CR_RANGE((&insn->chanspec)[0]); | |
415 | int aref = CR_AREF((&insn->chanspec)[0]); | |
416 | int i; | |
417 | ||
418 | /* stop any running conversion */ | |
419 | dev_private->control_1 &= 0xFFFC; | |
420 | writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); | |
421 | ||
422 | /* clear chanlist and ad fifo */ | |
423 | dev_private->control_2 &= ~(ENABLE_ADFIFO | ENABLE_CHANLIST); | |
424 | writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); | |
425 | ||
426 | /* reset any pending interrupt */ | |
427 | writew(0x00, dev_private->me_regbase + ME_RESET_INTERRUPT); | |
428 | ||
429 | /* enable the chanlist and ADC fifo */ | |
430 | dev_private->control_2 |= (ENABLE_ADFIFO | ENABLE_CHANLIST); | |
431 | writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); | |
432 | ||
433 | /* write to channel list fifo */ | |
2ce411b5 GKH |
434 | /* b3:b0 are the channel number */ |
435 | value = chan & 0x0f; | |
436 | /* b5:b4 are the channel gain */ | |
437 | value |= (rang & 0x03) << 4; | |
438 | /* b6 channel polarity */ | |
439 | value |= (rang & 0x04) << 4; | |
440 | /* b7 single or differential */ | |
441 | value |= ((aref & AREF_DIFF) ? 0x80 : 0); | |
85acac61 MH |
442 | writew(value & 0xff, dev_private->me_regbase + ME_CHANNEL_LIST); |
443 | ||
444 | /* set ADC mode to software trigger */ | |
445 | dev_private->control_1 |= SOFTWARE_TRIGGERED_ADC; | |
446 | writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); | |
447 | ||
448 | /* start conversion by reading from ADC_START */ | |
449 | readw(dev_private->me_regbase + ME_ADC_START); | |
450 | ||
451 | /* wait for ADC fifo not empty flag */ | |
2ce411b5 GKH |
452 | for (i = 100000; i > 0; i--) |
453 | if (!(readw(dev_private->me_regbase + ME_STATUS) & 0x0004)) | |
85acac61 | 454 | break; |
85acac61 MH |
455 | |
456 | /* get value from ADC fifo */ | |
457 | if (i) { | |
458 | data[0] = | |
0a85b6f0 MT |
459 | (readw(dev_private->me_regbase + |
460 | ME_READ_AD_FIFO) ^ 0x800) & 0x0FFF; | |
85acac61 | 461 | } else { |
2ce411b5 GKH |
462 | printk(KERN_ERR "comedi%d: Cannot get single value\n", |
463 | dev->minor); | |
85acac61 MH |
464 | return -EIO; |
465 | } | |
466 | ||
467 | /* stop any running conversion */ | |
468 | dev_private->control_1 &= 0xFFFC; | |
469 | writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); | |
470 | ||
471 | return 1; | |
472 | } | |
473 | ||
2ce411b5 GKH |
474 | /* |
475 | * ------------------------------------------------------------------ | |
476 | * | |
477 | * HARDWARE TRIGGERED ANALOG INPUT SECTION | |
478 | * | |
479 | * ------------------------------------------------------------------ | |
480 | */ | |
481 | ||
482 | /* Cancel analog input autoscan */ | |
34c43922 | 483 | static int me_ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s) |
85acac61 MH |
484 | { |
485 | /* disable interrupts */ | |
486 | ||
487 | /* stop any running conversion */ | |
488 | dev_private->control_1 &= 0xFFFC; | |
489 | writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); | |
490 | ||
491 | return 0; | |
492 | } | |
493 | ||
2ce411b5 | 494 | /* Test analog input command */ |
0a85b6f0 MT |
495 | static int me_ai_do_cmd_test(struct comedi_device *dev, |
496 | struct comedi_subdevice *s, struct comedi_cmd *cmd) | |
85acac61 MH |
497 | { |
498 | return 0; | |
499 | } | |
500 | ||
2ce411b5 | 501 | /* Analog input command */ |
0a85b6f0 MT |
502 | static int me_ai_do_cmd(struct comedi_device *dev, |
503 | struct comedi_subdevice *subdevice) | |
85acac61 MH |
504 | { |
505 | return 0; | |
506 | } | |
507 | ||
2ce411b5 GKH |
508 | /* |
509 | * ------------------------------------------------------------------ | |
510 | * | |
511 | * ANALOG OUTPUT SECTION | |
512 | * | |
513 | * ------------------------------------------------------------------ | |
514 | */ | |
515 | ||
516 | /* Analog instant output */ | |
0a85b6f0 MT |
517 | static int me_ao_insn_write(struct comedi_device *dev, |
518 | struct comedi_subdevice *s, | |
90035c08 | 519 | struct comedi_insn *insn, unsigned int *data) |
85acac61 MH |
520 | { |
521 | int chan; | |
522 | int rang; | |
523 | int i; | |
524 | ||
525 | /* Enable all DAC */ | |
526 | dev_private->control_2 |= ENABLE_DAC; | |
527 | writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); | |
528 | ||
529 | /* and set DAC to "buffered" mode */ | |
530 | dev_private->control_2 |= BUFFERED_DAC; | |
531 | writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); | |
532 | ||
533 | /* Set dac-control register */ | |
534 | for (i = 0; i < insn->n; i++) { | |
535 | chan = CR_CHAN((&insn->chanspec)[i]); | |
536 | rang = CR_RANGE((&insn->chanspec)[i]); | |
537 | ||
2ce411b5 GKH |
538 | /* clear bits for this channel */ |
539 | dev_private->dac_control &= ~(0x0880 >> chan); | |
85acac61 MH |
540 | if (rang == 0) |
541 | dev_private->dac_control |= | |
0a85b6f0 | 542 | ((DAC_BIPOLAR_A | DAC_GAIN_1_A) >> chan); |
85acac61 MH |
543 | else if (rang == 1) |
544 | dev_private->dac_control |= | |
0a85b6f0 | 545 | ((DAC_BIPOLAR_A | DAC_GAIN_0_A) >> chan); |
85acac61 MH |
546 | } |
547 | writew(dev_private->dac_control, | |
0a85b6f0 | 548 | dev_private->me_regbase + ME_DAC_CONTROL); |
85acac61 MH |
549 | |
550 | /* Update dac-control register */ | |
551 | readw(dev_private->me_regbase + ME_DAC_CONTROL_UPDATE); | |
552 | ||
553 | /* Set data register */ | |
554 | for (i = 0; i < insn->n; i++) { | |
555 | chan = CR_CHAN((&insn->chanspec)[i]); | |
556 | writew((data[0] & s->maxdata), | |
0a85b6f0 | 557 | dev_private->me_regbase + ME_DAC_DATA_A + (chan << 1)); |
85acac61 MH |
558 | dev_private->ao_readback[chan] = (data[0] & s->maxdata); |
559 | } | |
560 | ||
561 | /* Update dac with data registers */ | |
562 | readw(dev_private->me_regbase + ME_DAC_UPDATE); | |
563 | ||
564 | return i; | |
565 | } | |
566 | ||
2ce411b5 | 567 | /* Analog output readback */ |
0a85b6f0 MT |
568 | static int me_ao_insn_read(struct comedi_device *dev, |
569 | struct comedi_subdevice *s, struct comedi_insn *insn, | |
570 | unsigned int *data) | |
85acac61 MH |
571 | { |
572 | int i; | |
573 | ||
574 | for (i = 0; i < insn->n; i++) { | |
575 | data[i] = | |
0a85b6f0 | 576 | dev_private->ao_readback[CR_CHAN((&insn->chanspec)[i])]; |
85acac61 MH |
577 | } |
578 | ||
579 | return 1; | |
580 | } | |
581 | ||
2ce411b5 GKH |
582 | /* |
583 | * ------------------------------------------------------------------ | |
584 | * | |
585 | * INITIALISATION SECTION | |
586 | * | |
587 | * ------------------------------------------------------------------ | |
588 | */ | |
589 | ||
590 | /* Xilinx firmware download for card: ME-2600i */ | |
71b5f4f1 | 591 | static int me2600_xilinx_download(struct comedi_device *dev, |
2ce411b5 GKH |
592 | unsigned char *me2600_firmware, |
593 | unsigned int length) | |
85acac61 MH |
594 | { |
595 | unsigned int value; | |
596 | unsigned int file_length; | |
597 | unsigned int i; | |
598 | ||
599 | /* disable irq's on PLX */ | |
600 | writel(0x00, dev_private->plx_regbase + PLX_INTCSR); | |
601 | ||
602 | /* First, make a dummy read to reset xilinx */ | |
603 | value = readw(dev_private->me_regbase + XILINX_DOWNLOAD_RESET); | |
604 | ||
605 | /* Wait until reset is over */ | |
606 | sleep(1); | |
607 | ||
608 | /* Write a dummy value to Xilinx */ | |
609 | writeb(0x00, dev_private->me_regbase + 0x0); | |
610 | sleep(1); | |
611 | ||
612 | /* | |
613 | * Format of the firmware | |
614 | * Build longs from the byte-wise coded header | |
615 | * Byte 1-3: length of the array | |
616 | * Byte 4-7: version | |
617 | * Byte 8-11: date | |
618 | * Byte 12-15: reserved | |
619 | */ | |
620 | if (length < 16) | |
621 | return -EINVAL; | |
2ce411b5 | 622 | file_length = (((unsigned int)me2600_firmware[0] & 0xff) << 24) + |
0a85b6f0 MT |
623 | (((unsigned int)me2600_firmware[1] & 0xff) << 16) + |
624 | (((unsigned int)me2600_firmware[2] & 0xff) << 8) + | |
625 | ((unsigned int)me2600_firmware[3] & 0xff); | |
85acac61 MH |
626 | |
627 | /* | |
628 | * Loop for writing firmware byte by byte to xilinx | |
629 | * Firmware data start at offfset 16 | |
630 | */ | |
2ce411b5 | 631 | for (i = 0; i < file_length; i++) |
85acac61 | 632 | writeb((me2600_firmware[16 + i] & 0xff), |
0a85b6f0 | 633 | dev_private->me_regbase + 0x0); |
85acac61 MH |
634 | |
635 | /* Write 5 dummy values to xilinx */ | |
2ce411b5 | 636 | for (i = 0; i < 5; i++) |
85acac61 | 637 | writeb(0x00, dev_private->me_regbase + 0x0); |
85acac61 MH |
638 | |
639 | /* Test if there was an error during download -> INTB was thrown */ | |
640 | value = readl(dev_private->plx_regbase + PLX_INTCSR); | |
641 | if (value & 0x20) { | |
642 | /* Disable interrupt */ | |
643 | writel(0x00, dev_private->plx_regbase + PLX_INTCSR); | |
2ce411b5 GKH |
644 | printk(KERN_ERR "comedi%d: Xilinx download failed\n", |
645 | dev->minor); | |
85acac61 MH |
646 | return -EIO; |
647 | } | |
648 | ||
649 | /* Wait until the Xilinx is ready for real work */ | |
650 | sleep(1); | |
651 | ||
652 | /* Enable PLX-Interrupts */ | |
653 | writel(0x43, dev_private->plx_regbase + PLX_INTCSR); | |
654 | ||
655 | return 0; | |
656 | } | |
657 | ||
2ce411b5 | 658 | /* Reset device */ |
71b5f4f1 | 659 | static int me_reset(struct comedi_device *dev) |
85acac61 MH |
660 | { |
661 | /* Reset board */ | |
662 | writew(0x00, dev_private->me_regbase + ME_CONTROL_1); | |
663 | writew(0x00, dev_private->me_regbase + ME_CONTROL_2); | |
664 | writew(0x00, dev_private->me_regbase + ME_RESET_INTERRUPT); | |
665 | writew(0x00, dev_private->me_regbase + ME_DAC_CONTROL); | |
666 | ||
667 | /* Save values in the board context */ | |
668 | dev_private->dac_control = 0; | |
669 | dev_private->control_1 = 0; | |
670 | dev_private->control_2 = 0; | |
671 | ||
672 | return 0; | |
673 | } | |
674 | ||
2ce411b5 GKH |
675 | /* |
676 | * Attach | |
677 | * | |
678 | * - Register PCI device | |
679 | * - Declare device driver capability | |
680 | */ | |
0707bb04 | 681 | static int me_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
85acac61 | 682 | { |
20fb2280 | 683 | struct pci_dev *pci_device = NULL; |
34c43922 | 684 | struct comedi_subdevice *subdevice; |
5e08c198 | 685 | struct me_board *board; |
85acac61 MH |
686 | resource_size_t plx_regbase_tmp; |
687 | unsigned long plx_regbase_size_tmp; | |
688 | resource_size_t me_regbase_tmp; | |
689 | unsigned long me_regbase_size_tmp; | |
690 | resource_size_t swap_regbase_tmp; | |
691 | unsigned long swap_regbase_size_tmp; | |
692 | resource_size_t regbase_tmp; | |
693 | int result, error, i; | |
694 | ||
2ce411b5 | 695 | /* Allocate private memory */ |
5e08c198 | 696 | if (alloc_private(dev, sizeof(struct me_private_data)) < 0) |
85acac61 | 697 | return -ENOMEM; |
2ce411b5 GKH |
698 | |
699 | /* Probe the device to determine what device in the series it is. */ | |
20fb2280 | 700 | for_each_pci_dev(pci_device) { |
85acac61 MH |
701 | if (pci_device->vendor == PCI_VENDOR_ID_MEILHAUS) { |
702 | for (i = 0; i < me_board_nbr; i++) { | |
703 | if (me_boards[i].device_id == | |
0a85b6f0 | 704 | pci_device->device) { |
2ce411b5 GKH |
705 | /* |
706 | * was a particular bus/slot requested? | |
707 | */ | |
85acac61 | 708 | if ((it->options[0] != 0) |
0a85b6f0 | 709 | || (it->options[1] != 0)) { |
2ce411b5 GKH |
710 | /* |
711 | * are we on the wrong bus/slot? | |
712 | */ | |
85acac61 | 713 | if (pci_device->bus->number != |
0a85b6f0 MT |
714 | it->options[0] |
715 | || | |
716 | PCI_SLOT(pci_device->devfn) | |
717 | != it->options[1]) { | |
85acac61 MH |
718 | continue; |
719 | } | |
720 | } | |
721 | ||
722 | dev->board_ptr = me_boards + i; | |
0a85b6f0 MT |
723 | board = |
724 | (struct me_board *)dev->board_ptr; | |
85acac61 MH |
725 | dev_private->pci_device = pci_device; |
726 | goto found; | |
727 | } | |
728 | } | |
729 | } | |
730 | } | |
731 | ||
2ce411b5 GKH |
732 | printk(KERN_ERR |
733 | "comedi%d: no supported board found! (req. bus/slot : %d/%d)\n", | |
734 | dev->minor, it->options[0], it->options[1]); | |
85acac61 MH |
735 | return -EIO; |
736 | ||
2ce411b5 GKH |
737 | found: |
738 | printk(KERN_INFO "comedi%d: found %s at PCI bus %d, slot %d\n", | |
0a85b6f0 MT |
739 | dev->minor, me_boards[i].name, |
740 | pci_device->bus->number, PCI_SLOT(pci_device->devfn)); | |
85acac61 | 741 | |
2ce411b5 | 742 | /* Enable PCI device and request PCI regions */ |
85acac61 | 743 | if (comedi_pci_enable(pci_device, ME_DRIVER_NAME) < 0) { |
2ce411b5 GKH |
744 | printk(KERN_ERR "comedi%d: Failed to enable PCI device and " |
745 | "request regions\n", dev->minor); | |
85acac61 MH |
746 | return -EIO; |
747 | } | |
85acac61 | 748 | |
2ce411b5 | 749 | /* Set data in device structure */ |
85acac61 MH |
750 | dev->board_name = board->name; |
751 | ||
2ce411b5 | 752 | /* Read PLX register base address [PCI_BASE_ADDRESS #0]. */ |
85acac61 MH |
753 | plx_regbase_tmp = pci_resource_start(pci_device, 0); |
754 | plx_regbase_size_tmp = pci_resource_len(pci_device, 0); | |
755 | dev_private->plx_regbase = | |
0a85b6f0 | 756 | ioremap(plx_regbase_tmp, plx_regbase_size_tmp); |
85acac61 MH |
757 | dev_private->plx_regbase_size = plx_regbase_size_tmp; |
758 | if (!dev_private->plx_regbase) { | |
759 | printk("comedi%d: Failed to remap I/O memory\n", dev->minor); | |
760 | return -ENOMEM; | |
761 | } | |
2ce411b5 GKH |
762 | |
763 | /* Read Swap base address [PCI_BASE_ADDRESS #5]. */ | |
85acac61 MH |
764 | |
765 | swap_regbase_tmp = pci_resource_start(pci_device, 5); | |
766 | swap_regbase_size_tmp = pci_resource_len(pci_device, 5); | |
767 | ||
2ce411b5 GKH |
768 | if (!swap_regbase_tmp) |
769 | printk(KERN_ERR "comedi%d: Swap not present\n", dev->minor); | |
85acac61 | 770 | |
2ce411b5 | 771 | /*---------------------------------------------- Workaround start ---*/ |
85acac61 | 772 | if (plx_regbase_tmp & 0x0080) { |
2ce411b5 | 773 | printk(KERN_ERR "comedi%d: PLX-Bug detected\n", dev->minor); |
85acac61 MH |
774 | |
775 | if (swap_regbase_tmp) { | |
776 | regbase_tmp = plx_regbase_tmp; | |
777 | plx_regbase_tmp = swap_regbase_tmp; | |
778 | swap_regbase_tmp = regbase_tmp; | |
779 | ||
780 | result = pci_write_config_dword(pci_device, | |
0a85b6f0 MT |
781 | PCI_BASE_ADDRESS_0, |
782 | plx_regbase_tmp); | |
85acac61 MH |
783 | if (result != PCIBIOS_SUCCESSFUL) |
784 | return -EIO; | |
785 | ||
786 | result = pci_write_config_dword(pci_device, | |
0a85b6f0 MT |
787 | PCI_BASE_ADDRESS_5, |
788 | swap_regbase_tmp); | |
85acac61 MH |
789 | if (result != PCIBIOS_SUCCESSFUL) |
790 | return -EIO; | |
791 | } else { | |
792 | plx_regbase_tmp -= 0x80; | |
793 | result = pci_write_config_dword(pci_device, | |
0a85b6f0 MT |
794 | PCI_BASE_ADDRESS_0, |
795 | plx_regbase_tmp); | |
85acac61 MH |
796 | if (result != PCIBIOS_SUCCESSFUL) |
797 | return -EIO; | |
798 | } | |
799 | } | |
2ce411b5 | 800 | /*--------------------------------------------- Workaround end -----*/ |
85acac61 | 801 | |
2ce411b5 | 802 | /* Read Meilhaus register base address [PCI_BASE_ADDRESS #2]. */ |
85acac61 MH |
803 | |
804 | me_regbase_tmp = pci_resource_start(pci_device, 2); | |
805 | me_regbase_size_tmp = pci_resource_len(pci_device, 2); | |
806 | dev_private->me_regbase_size = me_regbase_size_tmp; | |
807 | dev_private->me_regbase = ioremap(me_regbase_tmp, me_regbase_size_tmp); | |
808 | if (!dev_private->me_regbase) { | |
2ce411b5 GKH |
809 | printk(KERN_ERR "comedi%d: Failed to remap I/O memory\n", |
810 | dev->minor); | |
85acac61 MH |
811 | return -ENOMEM; |
812 | } | |
2ce411b5 | 813 | /* Download firmware and reset card */ |
85acac61 MH |
814 | if (board->device_id == ME2600_DEVICE_ID) { |
815 | unsigned char *aux_data; | |
816 | int aux_len; | |
817 | ||
818 | aux_data = comedi_aux_data(it->options, 0); | |
819 | aux_len = it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]; | |
820 | ||
821 | if (!aux_data || aux_len < 1) { | |
2ce411b5 GKH |
822 | comedi_error(dev, "You must provide me2600 firmware " |
823 | "using the --init-data option of " | |
824 | "comedi_config"); | |
85acac61 MH |
825 | return -EINVAL; |
826 | } | |
827 | me2600_xilinx_download(dev, aux_data, aux_len); | |
828 | } | |
829 | ||
830 | me_reset(dev); | |
831 | ||
2ce411b5 GKH |
832 | /* device driver capabilities */ |
833 | error = alloc_subdevices(dev, 3); | |
834 | if (error < 0) | |
85acac61 MH |
835 | return error; |
836 | ||
837 | subdevice = dev->subdevices + 0; | |
838 | subdevice->type = COMEDI_SUBD_AI; | |
839 | subdevice->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_CMD_READ; | |
840 | subdevice->n_chan = board->ai_channel_nbr; | |
841 | subdevice->maxdata = board->ai_resolution_mask; | |
842 | subdevice->len_chanlist = board->ai_channel_nbr; | |
843 | subdevice->range_table = board->ai_range_list; | |
844 | subdevice->cancel = me_ai_cancel; | |
845 | subdevice->insn_read = me_ai_insn_read; | |
846 | subdevice->do_cmdtest = me_ai_do_cmd_test; | |
847 | subdevice->do_cmd = me_ai_do_cmd; | |
848 | ||
849 | subdevice = dev->subdevices + 1; | |
850 | subdevice->type = COMEDI_SUBD_AO; | |
851 | subdevice->subdev_flags = SDF_WRITEABLE | SDF_COMMON; | |
852 | subdevice->n_chan = board->ao_channel_nbr; | |
853 | subdevice->maxdata = board->ao_resolution_mask; | |
854 | subdevice->len_chanlist = board->ao_channel_nbr; | |
855 | subdevice->range_table = board->ao_range_list; | |
856 | subdevice->insn_read = me_ao_insn_read; | |
857 | subdevice->insn_write = me_ao_insn_write; | |
858 | ||
859 | subdevice = dev->subdevices + 2; | |
860 | subdevice->type = COMEDI_SUBD_DIO; | |
861 | subdevice->subdev_flags = SDF_READABLE | SDF_WRITEABLE; | |
862 | subdevice->n_chan = board->dio_channel_nbr; | |
863 | subdevice->maxdata = 1; | |
864 | subdevice->len_chanlist = board->dio_channel_nbr; | |
865 | subdevice->range_table = &range_digital; | |
866 | subdevice->insn_bits = me_dio_insn_bits; | |
867 | subdevice->insn_config = me_dio_insn_config; | |
868 | subdevice->io_bits = 0; | |
869 | ||
0a85b6f0 MT |
870 | printk(KERN_INFO "comedi%d: " ME_DRIVER_NAME " attached.\n", |
871 | dev->minor); | |
85acac61 MH |
872 | return 0; |
873 | } | |
874 | ||
2ce411b5 | 875 | /* Detach */ |
71b5f4f1 | 876 | static int me_detach(struct comedi_device *dev) |
85acac61 MH |
877 | { |
878 | if (dev_private) { | |
879 | if (dev_private->me_regbase) { | |
880 | me_reset(dev); | |
881 | iounmap(dev_private->me_regbase); | |
882 | } | |
883 | if (dev_private->plx_regbase) | |
884 | iounmap(dev_private->plx_regbase); | |
885 | if (dev_private->pci_device) { | |
2ce411b5 | 886 | if (dev_private->plx_regbase_size) |
85acac61 | 887 | comedi_pci_disable(dev_private->pci_device); |
2ce411b5 | 888 | |
85acac61 MH |
889 | pci_dev_put(dev_private->pci_device); |
890 | } | |
891 | } | |
892 | return 0; | |
893 | } | |
90f703d3 AT |
894 | |
895 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
896 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
897 | MODULE_LICENSE("GPL"); |