Commit | Line | Data |
---|---|---|
48a4d521 | 1 | /* |
12cba5c9 AT |
2 | * comedi/drivers/daqboard2000.c |
3 | * hardware driver for IOtech DAQboard/2000 | |
4 | * | |
5 | * COMEDI - Linux Control and Measurement Device Interface | |
6 | * Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se> | |
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. | |
48a4d521 AB |
17 | */ |
18 | /* | |
12cba5c9 AT |
19 | * Driver: daqboard2000 |
20 | * Description: IOTech DAQBoard/2000 | |
21 | * Author: Anders Blomdell <anders.blomdell@control.lth.se> | |
22 | * Status: works | |
23 | * Updated: Mon, 14 Apr 2008 15:28:52 +0100 | |
24 | * Devices: [IOTech] DAQBoard/2000 (daqboard2000) | |
25 | * | |
26 | * Much of the functionality of this driver was determined from reading | |
27 | * the source code for the Windows driver. | |
28 | * | |
bad7de74 | 29 | * The FPGA on the board requires firmware, which is available from |
12cba5c9 AT |
30 | * http://www.comedi.org in the comedi_nonfree_firmware tarball. |
31 | * | |
32 | * Configuration options: not applicable, uses PCI auto config | |
33 | */ | |
48a4d521 | 34 | /* |
12cba5c9 AT |
35 | * This card was obviously never intended to leave the Windows world, |
36 | * since it lacked all kind of hardware documentation (except for cable | |
37 | * pinouts, plug and pray has something to catch up with yet). | |
38 | * | |
39 | * With some help from our swedish distributor, we got the Windows sourcecode | |
40 | * for the card, and here are the findings so far. | |
41 | * | |
42 | * 1. A good document that describes the PCI interface chip is 9080db-106.pdf | |
43 | * available from http://www.plxtech.com/products/io/pci9080 | |
44 | * | |
45 | * 2. The initialization done so far is: | |
46 | * a. program the FPGA (windows code sans a lot of error messages) | |
47 | * b. | |
48 | * | |
49 | * 3. Analog out seems to work OK with DAC's disabled, if DAC's are enabled, | |
50 | * you have to output values to all enabled DAC's until result appears, I | |
51 | * guess that it has something to do with pacer clocks, but the source | |
52 | * gives me no clues. I'll keep it simple so far. | |
53 | * | |
54 | * 4. Analog in. | |
55 | * Each channel in the scanlist seems to be controlled by four | |
56 | * control words: | |
57 | * | |
58 | * Word0: | |
59 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
60 | * ! | | | ! | | | ! | | | ! | | | ! | |
61 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
62 | * | |
63 | * Word1: | |
64 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
65 | * ! | | | ! | | | ! | | | ! | | | ! | |
66 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
67 | * | | | | | | | | |
68 | * +------+------+ | | | | +-- Digital input (??) | |
69 | * | | | | +---- 10 us settling time | |
70 | * | | | +------ Suspend acquisition (last to scan) | |
71 | * | | +-------- Simultaneous sample and hold | |
72 | * | +---------- Signed data format | |
73 | * +------------------------- Correction offset low | |
74 | * | |
75 | * Word2: | |
76 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
77 | * ! | | | ! | | | ! | | | ! | | | ! | |
78 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
79 | * | | | | | | | | | | | |
80 | * +-----+ +--+--+ +++ +++ +--+--+ | |
81 | * | | | | +----- Expansion channel | |
82 | * | | | +----------- Expansion gain | |
83 | * | | +--------------- Channel (low) | |
84 | * | +--------------------- Correction offset high | |
85 | * +----------------------------- Correction gain low | |
86 | * Word3: | |
87 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
88 | * ! | | | ! | | | ! | | | ! | | | ! | |
89 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
90 | * | | | | | | | | | | |
91 | * +------+------+ | | +-+-+ | | +-- Low bank enable | |
92 | * | | | | | +---- High bank enable | |
93 | * | | | | +------ Hi/low select | |
94 | * | | | +---------- Gain (1,?,2,4,8,16,32,64) | |
95 | * | | +-------------- differential/single ended | |
96 | * | +---------------- Unipolar | |
97 | * +------------------------- Correction gain high | |
98 | * | |
99 | * 999. The card seems to have an incredible amount of capabilities, but | |
100 | * trying to reverse engineer them from the Windows source is beyond my | |
101 | * patience. | |
102 | * | |
48a4d521 AB |
103 | */ |
104 | ||
ce157f80 | 105 | #include <linux/module.h> |
48a4d521 | 106 | #include <linux/delay.h> |
25436dc9 | 107 | #include <linux/interrupt.h> |
48a4d521 | 108 | |
dde50ca4 | 109 | #include "../comedi_pci.h" |
33782dd5 | 110 | |
48a4d521 AB |
111 | #include "8255.h" |
112 | ||
63ad597e HS |
113 | #define DAQBOARD2000_FIRMWARE "daqboard2000_firmware.bin" |
114 | ||
6691844c HS |
115 | #define DAQBOARD2000_SUBSYSTEM_IDS2 0x0002 /* Daqboard/2000 - 2 Dacs */ |
116 | #define DAQBOARD2000_SUBSYSTEM_IDS4 0x0004 /* Daqboard/2000 - 4 Dacs */ | |
48a4d521 | 117 | |
2696fb57 | 118 | /* Initialization bits for the Serial EEPROM Control Register */ |
ff2ca4f0 IA |
119 | #define DB2K_SECR_PROG_PIN_HI 0x8001767e |
120 | #define DB2K_SECR_PROG_PIN_LO 0x8000767e | |
121 | #define DB2K_SECR_LOCAL_BUS_HI 0xc000767e | |
122 | #define DB2K_SECR_LOCAL_BUS_LO 0x8000767e | |
123 | #define DB2K_SECR_RELOAD_HI 0xa000767e | |
124 | #define DB2K_SECR_RELOAD_LO 0x8000767e | |
48a4d521 | 125 | |
2696fb57 | 126 | /* SECR status bits */ |
48a4d521 AB |
127 | #define DAQBOARD2000_EEPROM_PRESENT 0x10000000 |
128 | ||
2696fb57 | 129 | /* CPLD status bits */ |
6691844c HS |
130 | #define DAQBOARD2000_CPLD_INIT 0x0002 |
131 | #define DAQBOARD2000_CPLD_DONE 0x0004 | |
48a4d521 | 132 | |
1387d4b7 HS |
133 | static const struct comedi_lrange range_daqboard2000_ai = { |
134 | 13, { | |
135 | BIP_RANGE(10), | |
136 | BIP_RANGE(5), | |
137 | BIP_RANGE(2.5), | |
138 | BIP_RANGE(1.25), | |
139 | BIP_RANGE(0.625), | |
140 | BIP_RANGE(0.3125), | |
141 | BIP_RANGE(0.156), | |
142 | UNI_RANGE(10), | |
143 | UNI_RANGE(5), | |
144 | UNI_RANGE(2.5), | |
145 | UNI_RANGE(1.25), | |
146 | UNI_RANGE(0.625), | |
147 | UNI_RANGE(0.3125) | |
148 | } | |
48a4d521 AB |
149 | }; |
150 | ||
f657b14a HS |
151 | /* |
152 | * Register Memory Map | |
153 | */ | |
34d49fc0 IA |
154 | #define DB2K_REG_ACQ_CONTROL 0x00 /* u16 (w) */ |
155 | #define DB2K_REG_ACQ_STATUS 0x00 /* u16 (r) */ | |
1e335385 IA |
156 | #define DB2K_REG_ACQ_SCAN_LIST_FIFO 0x02 /* u16 */ |
157 | #define DB2K_REG_ACQ_PACER_CLOCK_DIV_LOW 0x04 /* u32 */ | |
158 | #define DB2K_REG_ACQ_SCAN_COUNTER 0x08 /* u16 */ | |
159 | #define DB2K_REG_ACQ_PACER_CLOCK_DIV_HIGH 0x0a /* u16 */ | |
160 | #define DB2K_REG_ACQ_TRIGGER_COUNT 0x0c /* u16 */ | |
161 | #define DB2K_REG_ACQ_RESULTS_FIFO 0x10 /* u16 */ | |
162 | #define DB2K_REG_ACQ_RESULTS_SHADOW 0x14 /* u16 */ | |
163 | #define DB2K_REG_ACQ_ADC_RESULT 0x18 /* u16 */ | |
164 | #define DB2K_REG_DAC_SCAN_COUNTER 0x1c /* u16 */ | |
944f0d7f IA |
165 | #define DB2K_REG_DAC_CONTROL 0x20 /* u16 (w) */ |
166 | #define DB2K_REG_DAC_STATUS 0x20 /* u16 (r) */ | |
1e335385 IA |
167 | #define DB2K_REG_DAC_FIFO 0x24 /* s16 */ |
168 | #define DB2K_REG_DAC_PACER_CLOCK_DIV 0x2a /* u16 */ | |
169 | #define DB2K_REG_REF_DACS 0x2c /* u16 */ | |
170 | #define DB2K_REG_DIO_CONTROL 0x30 /* u16 */ | |
171 | #define DB2K_REG_P3_HSIO_DATA 0x32 /* s16 */ | |
172 | #define DB2K_REG_P3_CONTROL 0x34 /* u16 */ | |
173 | #define DB2K_REG_CAL_EEPROM_CONTROL 0x36 /* u16 */ | |
174 | #define DB2K_REG_DAC_SETTING(x) (0x38 + (x) * 2) /* s16 */ | |
175 | #define DB2K_REG_DIO_P2_EXP_IO_8_BIT 0x40 /* s16 */ | |
176 | #define DB2K_REG_COUNTER_TIMER_CONTROL 0x80 /* u16 */ | |
177 | #define DB2K_REG_COUNTER_INPUT(x) (0x88 + (x) * 2) /* s16 */ | |
178 | #define DB2K_REG_TIMER_DIV(x) (0xa0 + (x) * 2) /* u16 */ | |
179 | #define DB2K_REG_DMA_CONTROL 0xb0 /* u16 */ | |
180 | #define DB2K_REG_TRIG_CONTROL 0xb2 /* u16 */ | |
181 | #define DB2K_REG_CAL_EEPROM 0xb8 /* u16 */ | |
182 | #define DB2K_REG_ACQ_DIGITAL_MARK 0xba /* u16 */ | |
183 | #define DB2K_REG_TRIG_DACS 0xbc /* u16 */ | |
184 | #define DB2K_REG_DIO_P2_EXP_IO_16_BIT(x) (0xc0 + (x) * 2) /* s16 */ | |
48a4d521 AB |
185 | |
186 | /* Scan Sequencer programming */ | |
f1ee5d86 IA |
187 | #define DB2K_ACQ_CONTROL_SEQ_START_SCAN_LIST 0x0011 |
188 | #define DB2K_ACQ_CONTROL_SEQ_STOP_SCAN_LIST 0x0010 | |
48a4d521 | 189 | |
2696fb57 | 190 | /* Prepare for acquisition */ |
f1ee5d86 IA |
191 | #define DB2K_ACQ_CONTROL_RESET_SCAN_LIST_FIFO 0x0004 |
192 | #define DB2K_ACQ_CONTROL_RESET_RESULTS_FIFO 0x0002 | |
193 | #define DB2K_ACQ_CONTROL_RESET_CONFIG_PIPE 0x0001 | |
48a4d521 | 194 | |
48a4d521 | 195 | /* Pacer Clock Control */ |
f1ee5d86 IA |
196 | #define DB2K_ACQ_CONTROL_ADC_PACER_INTERNAL 0x0030 |
197 | #define DB2K_ACQ_CONTROL_ADC_PACER_EXTERNAL 0x0032 | |
198 | #define DB2K_ACQ_CONTROL_ADC_PACER_ENABLE 0x0031 | |
199 | #define DB2K_ACQ_CONTROL_ADC_PACER_ENABLE_DAC_PACER 0x0034 | |
200 | #define DB2K_ACQ_CONTROL_ADC_PACER_DISABLE 0x0030 | |
201 | #define DB2K_ACQ_CONTROL_ADC_PACER_NORMAL_MODE 0x0060 | |
202 | #define DB2K_ACQ_CONTROL_ADC_PACER_COMPATIBILITY_MODE 0x0061 | |
203 | #define DB2K_ACQ_CONTROL_ADC_PACER_INTERNAL_OUT_ENABLE 0x0008 | |
204 | #define DB2K_ACQ_CONTROL_ADC_PACER_EXTERNAL_RISING 0x0100 | |
48a4d521 | 205 | |
34d49fc0 IA |
206 | /* Acquisition status bits */ |
207 | #define DB2K_ACQ_STATUS_RESULTS_FIFO_MORE_1_SAMPLE 0x0001 | |
208 | #define DB2K_ACQ_STATUS_RESULTS_FIFO_HAS_DATA 0x0002 | |
209 | #define DB2K_ACQ_STATUS_RESULTS_FIFO_OVERRUN 0x0004 | |
210 | #define DB2K_ACQ_STATUS_LOGIC_SCANNING 0x0008 | |
211 | #define DB2K_ACQ_STATUS_CONFIG_PIPE_FULL 0x0010 | |
212 | #define DB2K_ACQ_STATUS_SCAN_LIST_FIFO_EMPTY 0x0020 | |
213 | #define DB2K_ACQ_STATUS_ADC_NOT_READY 0x0040 | |
214 | #define DB2K_ACQ_STATUS_ARBITRATION_FAILURE 0x0080 | |
215 | #define DB2K_ACQ_STATUS_ADC_PACER_OVERRUN 0x0100 | |
216 | #define DB2K_ACQ_STATUS_DAC_PACER_OVERRUN 0x0200 | |
217 | ||
2696fb57 | 218 | /* DAC status */ |
944f0d7f IA |
219 | #define DB2K_DAC_STATUS_DAC_FULL 0x0001 |
220 | #define DB2K_DAC_STATUS_REF_BUSY 0x0002 | |
221 | #define DB2K_DAC_STATUS_TRIG_BUSY 0x0004 | |
222 | #define DB2K_DAC_STATUS_CAL_BUSY 0x0008 | |
223 | #define DB2K_DAC_STATUS_DAC_BUSY(x) (0x0010 << (x)) | |
48a4d521 | 224 | |
2696fb57 | 225 | /* DAC control */ |
ab8a4dc8 IA |
226 | #define DB2K_DAC_CONTROL_ENABLE_BIT 0x0001 |
227 | #define DB2K_DAC_CONTROL_DATA_IS_SIGNED 0x0002 | |
228 | #define DB2K_DAC_CONTROL_RESET_FIFO 0x0004 | |
229 | #define DB2K_DAC_CONTROL_DAC_DISABLE(x) (0x0020 + ((x) << 4)) | |
230 | #define DB2K_DAC_CONTROL_DAC_ENABLE(x) (0x0021 + ((x) << 4)) | |
231 | #define DB2K_DAC_CONTROL_PATTERN_DISABLE 0x0060 | |
232 | #define DB2K_DAC_CONTROL_PATTERN_ENABLE 0x0061 | |
48a4d521 AB |
233 | |
234 | /* Trigger Control */ | |
77b9634c IA |
235 | #define DB2K_TRIG_CONTROL_TYPE_ANALOG 0x0000 |
236 | #define DB2K_TRIG_CONTROL_TYPE_TTL 0x0010 | |
237 | #define DB2K_TRIG_CONTROL_EDGE_HI_LO 0x0004 | |
238 | #define DB2K_TRIG_CONTROL_EDGE_LO_HI 0x0000 | |
239 | #define DB2K_TRIG_CONTROL_LEVEL_ABOVE 0x0000 | |
240 | #define DB2K_TRIG_CONTROL_LEVEL_BELOW 0x0004 | |
241 | #define DB2K_TRIG_CONTROL_SENSE_LEVEL 0x0002 | |
242 | #define DB2K_TRIG_CONTROL_SENSE_EDGE 0x0000 | |
243 | #define DB2K_TRIG_CONTROL_ENABLE 0x0001 | |
244 | #define DB2K_TRIG_CONTROL_DISABLE 0x0000 | |
48a4d521 | 245 | |
2696fb57 | 246 | /* Reference Dac Selection */ |
2c7aab27 IA |
247 | #define DB2K_REF_DACS_SET 0x0080 |
248 | #define DB2K_REF_DACS_SELECT_POS_REF 0x0100 | |
249 | #define DB2K_REF_DACS_SELECT_NEG_REF 0x0000 | |
48a4d521 | 250 | |
9da18ff8 | 251 | struct daq200_boardtype { |
48a4d521 AB |
252 | const char *name; |
253 | int id; | |
9da18ff8 | 254 | }; |
4044ceb4 | 255 | |
9da18ff8 | 256 | static const struct daq200_boardtype boardtypes[] = { |
48a4d521 AB |
257 | {"ids2", DAQBOARD2000_SUBSYSTEM_IDS2}, |
258 | {"ids4", DAQBOARD2000_SUBSYSTEM_IDS4}, | |
259 | }; | |
260 | ||
b3653681 | 261 | struct daqboard2000_private { |
48a4d521 AB |
262 | enum { |
263 | card_daqboard_2000 | |
264 | } card; | |
d73805ce | 265 | void __iomem *plx; |
b3653681 | 266 | }; |
48a4d521 | 267 | |
0ef61347 IA |
268 | static void daqboard2000_write_acq_scan_list_entry(struct comedi_device *dev, |
269 | u16 entry) | |
48a4d521 | 270 | { |
1e335385 IA |
271 | writew(entry & 0x00ff, dev->mmio + DB2K_REG_ACQ_SCAN_LIST_FIFO); |
272 | writew((entry >> 8) & 0x00ff, | |
273 | dev->mmio + DB2K_REG_ACQ_SCAN_LIST_FIFO); | |
48a4d521 AB |
274 | } |
275 | ||
0ef61347 IA |
276 | static void daqboard2000_setup_sampling(struct comedi_device *dev, int chan, |
277 | int gain) | |
48a4d521 AB |
278 | { |
279 | u16 word0, word1, word2, word3; | |
280 | ||
281 | /* Channel 0-7 diff, channel 8-23 single ended */ | |
282 | word0 = 0; | |
283 | word1 = 0x0004; /* Last scan */ | |
284 | word2 = (chan << 6) & 0x00c0; | |
285 | switch (chan / 4) { | |
286 | case 0: | |
287 | word3 = 0x0001; | |
288 | break; | |
289 | case 1: | |
290 | word3 = 0x0002; | |
291 | break; | |
292 | case 2: | |
293 | word3 = 0x0005; | |
294 | break; | |
295 | case 3: | |
296 | word3 = 0x0006; | |
297 | break; | |
298 | case 4: | |
299 | word3 = 0x0041; | |
300 | break; | |
301 | case 5: | |
302 | word3 = 0x0042; | |
303 | break; | |
304 | default: | |
305 | word3 = 0; | |
306 | break; | |
307 | } | |
48a4d521 | 308 | /* These should be read from EEPROM */ |
269452dc IA |
309 | word2 |= 0x0800; /* offset */ |
310 | word3 |= 0xc000; /* gain */ | |
0ef61347 IA |
311 | daqboard2000_write_acq_scan_list_entry(dev, word0); |
312 | daqboard2000_write_acq_scan_list_entry(dev, word1); | |
313 | daqboard2000_write_acq_scan_list_entry(dev, word2); | |
314 | daqboard2000_write_acq_scan_list_entry(dev, word3); | |
48a4d521 AB |
315 | } |
316 | ||
304d2e4c HS |
317 | static int daqboard2000_ai_status(struct comedi_device *dev, |
318 | struct comedi_subdevice *s, | |
319 | struct comedi_insn *insn, | |
320 | unsigned long context) | |
321 | { | |
304d2e4c HS |
322 | unsigned int status; |
323 | ||
34d49fc0 | 324 | status = readw(dev->mmio + DB2K_REG_ACQ_STATUS); |
304d2e4c HS |
325 | if (status & context) |
326 | return 0; | |
327 | return -EBUSY; | |
328 | } | |
329 | ||
0a85b6f0 MT |
330 | static int daqboard2000_ai_insn_read(struct comedi_device *dev, |
331 | struct comedi_subdevice *s, | |
332 | struct comedi_insn *insn, | |
333 | unsigned int *data) | |
48a4d521 | 334 | { |
304d2e4c HS |
335 | int gain, chan; |
336 | int ret; | |
f657b14a | 337 | int i; |
48a4d521 | 338 | |
f1ee5d86 IA |
339 | writew(DB2K_ACQ_CONTROL_RESET_SCAN_LIST_FIFO | |
340 | DB2K_ACQ_CONTROL_RESET_RESULTS_FIFO | | |
341 | DB2K_ACQ_CONTROL_RESET_CONFIG_PIPE, | |
1e335385 | 342 | dev->mmio + DB2K_REG_ACQ_CONTROL); |
48a4d521 | 343 | |
ac971c94 RKM |
344 | /* |
345 | * If pacer clock is not set to some high value (> 10 us), we | |
346 | * risk multiple samples to be put into the result FIFO. | |
347 | */ | |
348 | /* 1 second, should be long enough */ | |
1e335385 IA |
349 | writel(1000000, dev->mmio + DB2K_REG_ACQ_PACER_CLOCK_DIV_LOW); |
350 | writew(0, dev->mmio + DB2K_REG_ACQ_PACER_CLOCK_DIV_HIGH); | |
48a4d521 AB |
351 | |
352 | gain = CR_RANGE(insn->chanspec); | |
353 | chan = CR_CHAN(insn->chanspec); | |
354 | ||
77a574ec IA |
355 | /* |
356 | * This doesn't look efficient. I decided to take the conservative | |
48a4d521 AB |
357 | * approach when I did the insn conversion. Perhaps it would be |
358 | * better to have broken it completely, then someone would have been | |
77a574ec IA |
359 | * forced to fix it. --ds |
360 | */ | |
48a4d521 | 361 | for (i = 0; i < insn->n; i++) { |
0ef61347 | 362 | daqboard2000_setup_sampling(dev, chan, gain); |
48a4d521 | 363 | /* Enable reading from the scanlist FIFO */ |
f1ee5d86 | 364 | writew(DB2K_ACQ_CONTROL_SEQ_START_SCAN_LIST, |
1e335385 | 365 | dev->mmio + DB2K_REG_ACQ_CONTROL); |
304d2e4c HS |
366 | |
367 | ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status, | |
34d49fc0 | 368 | DB2K_ACQ_STATUS_CONFIG_PIPE_FULL); |
304d2e4c HS |
369 | if (ret) |
370 | return ret; | |
371 | ||
f1ee5d86 | 372 | writew(DB2K_ACQ_CONTROL_ADC_PACER_ENABLE, |
1e335385 | 373 | dev->mmio + DB2K_REG_ACQ_CONTROL); |
304d2e4c HS |
374 | |
375 | ret = comedi_timeout(dev, s, insn, daqboard2000_ai_status, | |
34d49fc0 | 376 | DB2K_ACQ_STATUS_LOGIC_SCANNING); |
304d2e4c HS |
377 | if (ret) |
378 | return ret; | |
379 | ||
34d49fc0 IA |
380 | ret = |
381 | comedi_timeout(dev, s, insn, daqboard2000_ai_status, | |
382 | DB2K_ACQ_STATUS_RESULTS_FIFO_HAS_DATA); | |
304d2e4c HS |
383 | if (ret) |
384 | return ret; | |
385 | ||
1e335385 | 386 | data[i] = readw(dev->mmio + DB2K_REG_ACQ_RESULTS_FIFO); |
f1ee5d86 | 387 | writew(DB2K_ACQ_CONTROL_ADC_PACER_DISABLE, |
1e335385 | 388 | dev->mmio + DB2K_REG_ACQ_CONTROL); |
f1ee5d86 | 389 | writew(DB2K_ACQ_CONTROL_SEQ_STOP_SCAN_LIST, |
1e335385 | 390 | dev->mmio + DB2K_REG_ACQ_CONTROL); |
48a4d521 AB |
391 | } |
392 | ||
393 | return i; | |
394 | } | |
395 | ||
304d2e4c HS |
396 | static int daqboard2000_ao_eoc(struct comedi_device *dev, |
397 | struct comedi_subdevice *s, | |
398 | struct comedi_insn *insn, | |
399 | unsigned long context) | |
400 | { | |
304d2e4c HS |
401 | unsigned int chan = CR_CHAN(insn->chanspec); |
402 | unsigned int status; | |
403 | ||
944f0d7f IA |
404 | status = readw(dev->mmio + DB2K_REG_DAC_STATUS); |
405 | if ((status & DB2K_DAC_STATUS_DAC_BUSY(chan)) == 0) | |
304d2e4c HS |
406 | return 0; |
407 | return -EBUSY; | |
408 | } | |
409 | ||
0a85b6f0 MT |
410 | static int daqboard2000_ao_insn_write(struct comedi_device *dev, |
411 | struct comedi_subdevice *s, | |
412 | struct comedi_insn *insn, | |
413 | unsigned int *data) | |
48a4d521 | 414 | { |
15aba0d2 | 415 | unsigned int chan = CR_CHAN(insn->chanspec); |
f657b14a | 416 | int i; |
48a4d521 AB |
417 | |
418 | for (i = 0; i < insn->n; i++) { | |
15aba0d2 HS |
419 | unsigned int val = data[i]; |
420 | int ret; | |
421 | ||
1e335385 | 422 | writew(val, dev->mmio + DB2K_REG_DAC_SETTING(chan)); |
304d2e4c HS |
423 | |
424 | ret = comedi_timeout(dev, s, insn, daqboard2000_ao_eoc, 0); | |
425 | if (ret) | |
426 | return ret; | |
427 | ||
15aba0d2 | 428 | s->readback[chan] = val; |
48a4d521 AB |
429 | } |
430 | ||
15aba0d2 | 431 | return insn->n; |
48a4d521 AB |
432 | } |
433 | ||
0ef61347 | 434 | static void daqboard2000_reset_local_bus(struct comedi_device *dev) |
48a4d521 | 435 | { |
ad375f77 HS |
436 | struct daqboard2000_private *devpriv = dev->private; |
437 | ||
ff2ca4f0 | 438 | writel(DB2K_SECR_LOCAL_BUS_HI, devpriv->plx + 0x6c); |
4623c3e0 | 439 | mdelay(10); |
ff2ca4f0 | 440 | writel(DB2K_SECR_LOCAL_BUS_LO, devpriv->plx + 0x6c); |
4623c3e0 | 441 | mdelay(10); |
48a4d521 AB |
442 | } |
443 | ||
0ef61347 | 444 | static void daqboard2000_reload_plx(struct comedi_device *dev) |
48a4d521 | 445 | { |
ad375f77 HS |
446 | struct daqboard2000_private *devpriv = dev->private; |
447 | ||
ff2ca4f0 | 448 | writel(DB2K_SECR_RELOAD_LO, devpriv->plx + 0x6c); |
4623c3e0 | 449 | mdelay(10); |
ff2ca4f0 | 450 | writel(DB2K_SECR_RELOAD_HI, devpriv->plx + 0x6c); |
4623c3e0 | 451 | mdelay(10); |
ff2ca4f0 | 452 | writel(DB2K_SECR_RELOAD_LO, devpriv->plx + 0x6c); |
4623c3e0 | 453 | mdelay(10); |
48a4d521 AB |
454 | } |
455 | ||
0ef61347 | 456 | static void daqboard2000_pulse_prog_pin(struct comedi_device *dev) |
48a4d521 | 457 | { |
ad375f77 HS |
458 | struct daqboard2000_private *devpriv = dev->private; |
459 | ||
ff2ca4f0 | 460 | writel(DB2K_SECR_PROG_PIN_HI, devpriv->plx + 0x6c); |
4623c3e0 | 461 | mdelay(10); |
ff2ca4f0 | 462 | writel(DB2K_SECR_PROG_PIN_LO, devpriv->plx + 0x6c); |
4623c3e0 | 463 | mdelay(10); /* Not in the original code, but I like symmetry... */ |
48a4d521 AB |
464 | } |
465 | ||
0ef61347 | 466 | static int daqboard2000_poll_cpld(struct comedi_device *dev, int mask) |
48a4d521 AB |
467 | { |
468 | int result = 0; | |
469 | int i; | |
470 | int cpld; | |
471 | ||
472 | /* timeout after 50 tries -> 5ms */ | |
473 | for (i = 0; i < 50; i++) { | |
1d149995 | 474 | cpld = readw(dev->mmio + 0x1000); |
48a4d521 AB |
475 | if ((cpld & mask) == mask) { |
476 | result = 1; | |
477 | break; | |
478 | } | |
cda23150 | 479 | usleep_range(100, 1000); |
48a4d521 | 480 | } |
5f74ea14 | 481 | udelay(5); |
48a4d521 AB |
482 | return result; |
483 | } | |
484 | ||
0ef61347 | 485 | static int daqboard2000_write_cpld(struct comedi_device *dev, int data) |
48a4d521 AB |
486 | { |
487 | int result = 0; | |
488 | ||
cda23150 | 489 | usleep_range(10, 20); |
1d149995 HS |
490 | writew(data, dev->mmio + 0x1000); |
491 | if ((readw(dev->mmio + 0x1000) & DAQBOARD2000_CPLD_INIT) == | |
0a85b6f0 | 492 | DAQBOARD2000_CPLD_INIT) { |
48a4d521 AB |
493 | result = 1; |
494 | } | |
495 | return result; | |
496 | } | |
497 | ||
0ef61347 IA |
498 | static int daqboard2000_load_firmware(struct comedi_device *dev, |
499 | const u8 *cpld_array, size_t len, | |
500 | unsigned long context) | |
48a4d521 | 501 | { |
ad375f77 | 502 | struct daqboard2000_private *devpriv = dev->private; |
48a4d521 AB |
503 | int result = -EIO; |
504 | /* Read the serial EEPROM control register */ | |
505 | int secr; | |
506 | int retry; | |
63ad597e | 507 | size_t i; |
48a4d521 AB |
508 | |
509 | /* Check to make sure the serial eeprom is present on the board */ | |
510 | secr = readl(devpriv->plx + 0x6c); | |
d8b5ad68 | 511 | if (!(secr & DAQBOARD2000_EEPROM_PRESENT)) |
48a4d521 | 512 | return -EIO; |
48a4d521 AB |
513 | |
514 | for (retry = 0; retry < 3; retry++) { | |
0ef61347 IA |
515 | daqboard2000_reset_local_bus(dev); |
516 | daqboard2000_reload_plx(dev); | |
517 | daqboard2000_pulse_prog_pin(dev); | |
518 | if (daqboard2000_poll_cpld(dev, DAQBOARD2000_CPLD_INIT)) { | |
48a4d521 | 519 | for (i = 0; i < len; i++) { |
d8b5ad68 HS |
520 | if (cpld_array[i] == 0xff && |
521 | cpld_array[i + 1] == 0x20) | |
48a4d521 | 522 | break; |
48a4d521 AB |
523 | } |
524 | for (; i < len; i += 2) { | |
525 | int data = | |
0a85b6f0 | 526 | (cpld_array[i] << 8) + cpld_array[i + 1]; |
0ef61347 | 527 | if (!daqboard2000_write_cpld(dev, data)) |
48a4d521 | 528 | break; |
48a4d521 AB |
529 | } |
530 | if (i >= len) { | |
0ef61347 IA |
531 | daqboard2000_reset_local_bus(dev); |
532 | daqboard2000_reload_plx(dev); | |
48a4d521 AB |
533 | result = 0; |
534 | break; | |
535 | } | |
536 | } | |
537 | } | |
538 | return result; | |
539 | } | |
540 | ||
0ef61347 | 541 | static void daqboard2000_adc_stop_dma_transfer(struct comedi_device *dev) |
48a4d521 | 542 | { |
48a4d521 AB |
543 | } |
544 | ||
0ef61347 | 545 | static void daqboard2000_adc_disarm(struct comedi_device *dev) |
48a4d521 | 546 | { |
48a4d521 | 547 | /* Disable hardware triggers */ |
5f74ea14 | 548 | udelay(2); |
77b9634c | 549 | writew(DB2K_TRIG_CONTROL_TYPE_ANALOG | DB2K_TRIG_CONTROL_DISABLE, |
1e335385 | 550 | dev->mmio + DB2K_REG_TRIG_CONTROL); |
5f74ea14 | 551 | udelay(2); |
77b9634c | 552 | writew(DB2K_TRIG_CONTROL_TYPE_TTL | DB2K_TRIG_CONTROL_DISABLE, |
1e335385 | 553 | dev->mmio + DB2K_REG_TRIG_CONTROL); |
48a4d521 AB |
554 | |
555 | /* Stop the scan list FIFO from loading the configuration pipe */ | |
5f74ea14 | 556 | udelay(2); |
f1ee5d86 | 557 | writew(DB2K_ACQ_CONTROL_SEQ_STOP_SCAN_LIST, |
1e335385 | 558 | dev->mmio + DB2K_REG_ACQ_CONTROL); |
48a4d521 AB |
559 | |
560 | /* Stop the pacer clock */ | |
5f74ea14 | 561 | udelay(2); |
f1ee5d86 | 562 | writew(DB2K_ACQ_CONTROL_ADC_PACER_DISABLE, |
1e335385 | 563 | dev->mmio + DB2K_REG_ACQ_CONTROL); |
48a4d521 AB |
564 | |
565 | /* Stop the input dma (abort channel 1) */ | |
0ef61347 | 566 | daqboard2000_adc_stop_dma_transfer(dev); |
48a4d521 AB |
567 | } |
568 | ||
0ef61347 | 569 | static void daqboard2000_activate_reference_dacs(struct comedi_device *dev) |
48a4d521 | 570 | { |
f657b14a | 571 | unsigned int val; |
48a4d521 AB |
572 | int timeout; |
573 | ||
2696fb57 | 574 | /* Set the + reference dac value in the FPGA */ |
2c7aab27 | 575 | writew(DB2K_REF_DACS_SET | DB2K_REF_DACS_SELECT_POS_REF, |
1e335385 | 576 | dev->mmio + DB2K_REG_REF_DACS); |
48a4d521 | 577 | for (timeout = 0; timeout < 20; timeout++) { |
944f0d7f IA |
578 | val = readw(dev->mmio + DB2K_REG_DAC_STATUS); |
579 | if ((val & DB2K_DAC_STATUS_REF_BUSY) == 0) | |
48a4d521 | 580 | break; |
5f74ea14 | 581 | udelay(2); |
48a4d521 | 582 | } |
48a4d521 | 583 | |
2696fb57 | 584 | /* Set the - reference dac value in the FPGA */ |
2c7aab27 | 585 | writew(DB2K_REF_DACS_SET | DB2K_REF_DACS_SELECT_NEG_REF, |
1e335385 | 586 | dev->mmio + DB2K_REG_REF_DACS); |
48a4d521 | 587 | for (timeout = 0; timeout < 20; timeout++) { |
944f0d7f IA |
588 | val = readw(dev->mmio + DB2K_REG_DAC_STATUS); |
589 | if ((val & DB2K_DAC_STATUS_REF_BUSY) == 0) | |
48a4d521 | 590 | break; |
5f74ea14 | 591 | udelay(2); |
48a4d521 | 592 | } |
48a4d521 AB |
593 | } |
594 | ||
0ef61347 | 595 | static void daqboard2000_initialize_ctrs(struct comedi_device *dev) |
48a4d521 | 596 | { |
48a4d521 AB |
597 | } |
598 | ||
0ef61347 | 599 | static void daqboard2000_initialize_tmrs(struct comedi_device *dev) |
48a4d521 | 600 | { |
48a4d521 AB |
601 | } |
602 | ||
0ef61347 | 603 | static void daqboard2000_dac_disarm(struct comedi_device *dev) |
48a4d521 | 604 | { |
48a4d521 AB |
605 | } |
606 | ||
0ef61347 | 607 | static void daqboard2000_initialize_adc(struct comedi_device *dev) |
48a4d521 | 608 | { |
0ef61347 IA |
609 | daqboard2000_adc_disarm(dev); |
610 | daqboard2000_activate_reference_dacs(dev); | |
611 | daqboard2000_initialize_ctrs(dev); | |
612 | daqboard2000_initialize_tmrs(dev); | |
48a4d521 AB |
613 | } |
614 | ||
0ef61347 | 615 | static void daqboard2000_initialize_dac(struct comedi_device *dev) |
48a4d521 | 616 | { |
0ef61347 | 617 | daqboard2000_dac_disarm(dev); |
48a4d521 AB |
618 | } |
619 | ||
09d6dd74 HS |
620 | static int daqboard2000_8255_cb(struct comedi_device *dev, |
621 | int dir, int port, int data, | |
f4e29703 | 622 | unsigned long iobase) |
48a4d521 | 623 | { |
48a4d521 | 624 | if (dir) { |
f4e29703 | 625 | writew(data, dev->mmio + iobase + port * 2); |
f657b14a | 626 | return 0; |
48a4d521 | 627 | } |
f4e29703 | 628 | return readw(dev->mmio + iobase + port * 2); |
48a4d521 AB |
629 | } |
630 | ||
33214ce1 HS |
631 | static const void *daqboard2000_find_boardinfo(struct comedi_device *dev, |
632 | struct pci_dev *pcidev) | |
48a4d521 | 633 | { |
33214ce1 | 634 | const struct daq200_boardtype *board; |
f34ec800 | 635 | int i; |
48a4d521 | 636 | |
80e162ee | 637 | if (pcidev->subsystem_vendor != PCI_VENDOR_ID_IOTECH) |
33214ce1 HS |
638 | return NULL; |
639 | ||
640 | for (i = 0; i < ARRAY_SIZE(boardtypes); i++) { | |
641 | board = &boardtypes[i]; | |
642 | if (pcidev->subsystem_device == board->id) | |
643 | return board; | |
48a4d521 | 644 | } |
f34ec800 | 645 | return NULL; |
6896ddfa HS |
646 | } |
647 | ||
a690b7e5 | 648 | static int daqboard2000_auto_attach(struct comedi_device *dev, |
6c7d2c8b | 649 | unsigned long context_unused) |
6896ddfa | 650 | { |
750af5e5 | 651 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
33214ce1 | 652 | const struct daq200_boardtype *board; |
ad375f77 | 653 | struct daqboard2000_private *devpriv; |
6896ddfa | 654 | struct comedi_subdevice *s; |
6896ddfa HS |
655 | int result; |
656 | ||
33214ce1 HS |
657 | board = daqboard2000_find_boardinfo(dev, pcidev); |
658 | if (!board) | |
659 | return -ENODEV; | |
660 | dev->board_ptr = board; | |
661 | dev->board_name = board->name; | |
662 | ||
0bdab509 | 663 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
c34fa261 HS |
664 | if (!devpriv) |
665 | return -ENOMEM; | |
6896ddfa | 666 | |
818f569f HS |
667 | result = comedi_pci_enable(dev); |
668 | if (result) | |
fefb09e8 | 669 | return result; |
f55354b0 | 670 | |
10e6b555 | 671 | devpriv->plx = pci_ioremap_bar(pcidev, 0); |
1d149995 HS |
672 | dev->mmio = pci_ioremap_bar(pcidev, 2); |
673 | if (!devpriv->plx || !dev->mmio) | |
48a4d521 | 674 | return -ENOMEM; |
48a4d521 | 675 | |
2f0b9d08 | 676 | result = comedi_alloc_subdevices(dev, 3); |
8b6c5694 HS |
677 | if (result) |
678 | return result; | |
48a4d521 AB |
679 | |
680 | readl(devpriv->plx + 0x6c); | |
681 | ||
41278f33 HS |
682 | result = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev, |
683 | DAQBOARD2000_FIRMWARE, | |
0ef61347 | 684 | daqboard2000_load_firmware, 0); |
48a4d521 | 685 | if (result < 0) |
63ad597e HS |
686 | return result; |
687 | ||
0ef61347 IA |
688 | daqboard2000_initialize_adc(dev); |
689 | daqboard2000_initialize_dac(dev); | |
48a4d521 | 690 | |
b8153577 | 691 | s = &dev->subdevices[0]; |
48a4d521 AB |
692 | /* ai subdevice */ |
693 | s->type = COMEDI_SUBD_AI; | |
694 | s->subdev_flags = SDF_READABLE | SDF_GROUND; | |
695 | s->n_chan = 24; | |
696 | s->maxdata = 0xffff; | |
697 | s->insn_read = daqboard2000_ai_insn_read; | |
698 | s->range_table = &range_daqboard2000_ai; | |
699 | ||
b8153577 | 700 | s = &dev->subdevices[1]; |
48a4d521 AB |
701 | /* ao subdevice */ |
702 | s->type = COMEDI_SUBD_AO; | |
703 | s->subdev_flags = SDF_WRITABLE; | |
704 | s->n_chan = 2; | |
705 | s->maxdata = 0xffff; | |
48a4d521 | 706 | s->insn_write = daqboard2000_ao_insn_write; |
9e794ee4 | 707 | s->range_table = &range_bipolar10; |
48a4d521 | 708 | |
15aba0d2 HS |
709 | result = comedi_alloc_subdev_readback(s); |
710 | if (result) | |
711 | return result; | |
712 | ||
b8153577 | 713 | s = &dev->subdevices[2]; |
9aed0669 | 714 | return subdev_8255_init(dev, s, daqboard2000_8255_cb, |
1e335385 | 715 | DB2K_REG_DIO_P2_EXP_IO_8_BIT); |
48a4d521 AB |
716 | } |
717 | ||
484ecc95 | 718 | static void daqboard2000_detach(struct comedi_device *dev) |
48a4d521 | 719 | { |
ad375f77 | 720 | struct daqboard2000_private *devpriv = dev->private; |
ad196be7 | 721 | |
aac307f9 HS |
722 | if (devpriv && devpriv->plx) |
723 | iounmap(devpriv->plx); | |
724 | comedi_pci_detach(dev); | |
48a4d521 AB |
725 | } |
726 | ||
ace44dc8 HS |
727 | static struct comedi_driver daqboard2000_driver = { |
728 | .driver_name = "daqboard2000", | |
729 | .module = THIS_MODULE, | |
750af5e5 | 730 | .auto_attach = daqboard2000_auto_attach, |
ace44dc8 HS |
731 | .detach = daqboard2000_detach, |
732 | }; | |
733 | ||
a690b7e5 | 734 | static int daqboard2000_pci_probe(struct pci_dev *dev, |
b8f4ac23 | 735 | const struct pci_device_id *id) |
727b286b | 736 | { |
b8f4ac23 HS |
737 | return comedi_pci_auto_config(dev, &daqboard2000_driver, |
738 | id->driver_data); | |
727b286b AT |
739 | } |
740 | ||
41e043fc | 741 | static const struct pci_device_id daqboard2000_pci_table[] = { |
f34ec800 | 742 | { PCI_DEVICE(PCI_VENDOR_ID_IOTECH, 0x0409) }, |
ace44dc8 | 743 | { 0 } |
727b286b | 744 | }; |
ace44dc8 | 745 | MODULE_DEVICE_TABLE(pci, daqboard2000_pci_table); |
727b286b | 746 | |
ace44dc8 HS |
747 | static struct pci_driver daqboard2000_pci_driver = { |
748 | .name = "daqboard2000", | |
749 | .id_table = daqboard2000_pci_table, | |
750 | .probe = daqboard2000_pci_probe, | |
9901a4d7 | 751 | .remove = comedi_pci_auto_unconfig, |
ace44dc8 HS |
752 | }; |
753 | module_comedi_pci_driver(daqboard2000_driver, daqboard2000_pci_driver); | |
90f703d3 AT |
754 | |
755 | MODULE_AUTHOR("Comedi http://www.comedi.org"); | |
756 | MODULE_DESCRIPTION("Comedi low-level driver"); | |
757 | MODULE_LICENSE("GPL"); | |
63ad597e | 758 | MODULE_FIRMWARE(DAQBOARD2000_FIRMWARE); |