1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * dt3000.c |
4 | * Data Translation DT3000 series driver |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 1999 David A. Schleef <ds@schleef.org> |
8 | */ |
9 | |
10 | /* |
11 | * Driver: dt3000 |
12 | * Description: Data Translation DT3000 series |
13 | * Devices: [Data Translation] DT3001 (dt3000), DT3001-PGL, DT3002, DT3003, |
14 | * DT3003-PGL, DT3004, DT3005, DT3004-200 |
15 | * Author: ds |
16 | * Updated: Mon, 14 Apr 2008 15:41:24 +0100 |
17 | * Status: works |
18 | * |
19 | * Configuration Options: not applicable, uses PCI auto config |
20 | * |
21 | * There is code to support AI commands, but it may not work. |
22 | * |
23 | * AO commands are not supported. |
24 | */ |
25 | |
26 | /* |
27 | * The DT3000 series is Data Translation's attempt to make a PCI |
28 | * data acquisition board. The design of this series is very nice, |
29 | * since each board has an on-board DSP (Texas Instruments TMS320C52). |
30 | * However, a few details are a little annoying. The boards lack |
31 | * bus-mastering DMA, which eliminates them from serious work. |
32 | * They also are not capable of autocalibration, which is a common |
33 | * feature in modern hardware. The default firmware is pretty bad, |
34 | * making it nearly impossible to write an RT compatible driver. |
35 | * It would make an interesting project to write a decent firmware |
36 | * for these boards. |
37 | * |
38 | * Data Translation originally wanted an NDA for the documentation |
39 | * for the 3k series. However, if you ask nicely, they might send |
40 | * you the docs without one, also. |
41 | */ |
42 | |
43 | #include <linux/module.h> |
44 | #include <linux/delay.h> |
45 | #include <linux/interrupt.h> |
46 | #include <linux/comedi/comedi_pci.h> |
47 | |
48 | /* |
49 | * PCI BAR0 - dual-ported RAM location definitions (dev->mmio) |
50 | */ |
51 | #define DPR_DAC_BUFFER (4 * 0x000) |
52 | #define DPR_ADC_BUFFER (4 * 0x800) |
53 | #define DPR_COMMAND (4 * 0xfd3) |
54 | #define DPR_SUBSYS (4 * 0xfd3) |
55 | #define DPR_SUBSYS_AI 0 |
56 | #define DPR_SUBSYS_AO 1 |
57 | #define DPR_SUBSYS_DIN 2 |
58 | #define DPR_SUBSYS_DOUT 3 |
59 | #define DPR_SUBSYS_MEM 4 |
60 | #define DPR_SUBSYS_CT 5 |
61 | #define DPR_ENCODE (4 * 0xfd4) |
62 | #define DPR_PARAMS(x) (4 * (0xfd5 + (x))) |
63 | #define DPR_TICK_REG_LO (4 * 0xff5) |
64 | #define DPR_TICK_REG_HI (4 * 0xff6) |
65 | #define DPR_DA_BUF_FRONT (4 * 0xff7) |
66 | #define DPR_DA_BUF_REAR (4 * 0xff8) |
67 | #define DPR_AD_BUF_FRONT (4 * 0xff9) |
68 | #define DPR_AD_BUF_REAR (4 * 0xffa) |
69 | #define DPR_INT_MASK (4 * 0xffb) |
70 | #define DPR_INTR_FLAG (4 * 0xffc) |
71 | #define DPR_INTR_CMDONE BIT(7) |
72 | #define DPR_INTR_CTDONE BIT(6) |
73 | #define DPR_INTR_DAHWERR BIT(5) |
74 | #define DPR_INTR_DASWERR BIT(4) |
75 | #define DPR_INTR_DAEMPTY BIT(3) |
76 | #define DPR_INTR_ADHWERR BIT(2) |
77 | #define DPR_INTR_ADSWERR BIT(1) |
78 | #define DPR_INTR_ADFULL BIT(0) |
79 | #define DPR_RESPONSE_MBX (4 * 0xffe) |
80 | #define DPR_CMD_MBX (4 * 0xfff) |
81 | #define DPR_CMD_COMPLETION(x) ((x) << 8) |
82 | #define DPR_CMD_NOTPROCESSED DPR_CMD_COMPLETION(0x00) |
83 | #define DPR_CMD_NOERROR DPR_CMD_COMPLETION(0x55) |
84 | #define DPR_CMD_ERROR DPR_CMD_COMPLETION(0xaa) |
85 | #define DPR_CMD_NOTSUPPORTED DPR_CMD_COMPLETION(0xff) |
86 | #define DPR_CMD_COMPLETION_MASK DPR_CMD_COMPLETION(0xff) |
87 | #define DPR_CMD(x) ((x) << 0) |
88 | #define DPR_CMD_GETBRDINFO DPR_CMD(0) |
89 | #define DPR_CMD_CONFIG DPR_CMD(1) |
90 | #define DPR_CMD_GETCONFIG DPR_CMD(2) |
91 | #define DPR_CMD_START DPR_CMD(3) |
92 | #define DPR_CMD_STOP DPR_CMD(4) |
93 | #define DPR_CMD_READSINGLE DPR_CMD(5) |
94 | #define DPR_CMD_WRITESINGLE DPR_CMD(6) |
95 | #define DPR_CMD_CALCCLOCK DPR_CMD(7) |
96 | #define DPR_CMD_READEVENTS DPR_CMD(8) |
97 | #define DPR_CMD_WRITECTCTRL DPR_CMD(16) |
98 | #define DPR_CMD_READCTCTRL DPR_CMD(17) |
99 | #define DPR_CMD_WRITECT DPR_CMD(18) |
100 | #define DPR_CMD_READCT DPR_CMD(19) |
101 | #define DPR_CMD_WRITEDATA DPR_CMD(32) |
102 | #define DPR_CMD_READDATA DPR_CMD(33) |
103 | #define DPR_CMD_WRITEIO DPR_CMD(34) |
104 | #define DPR_CMD_READIO DPR_CMD(35) |
105 | #define DPR_CMD_WRITECODE DPR_CMD(36) |
106 | #define DPR_CMD_READCODE DPR_CMD(37) |
107 | #define DPR_CMD_EXECUTE DPR_CMD(38) |
108 | #define DPR_CMD_HALT DPR_CMD(48) |
109 | #define DPR_CMD_MASK DPR_CMD(0xff) |
110 | |
111 | #define DPR_PARAM5_AD_TRIG(x) (((x) & 0x7) << 2) |
112 | #define DPR_PARAM5_AD_TRIG_INT DPR_PARAM5_AD_TRIG(0) |
113 | #define DPR_PARAM5_AD_TRIG_EXT DPR_PARAM5_AD_TRIG(1) |
114 | #define DPR_PARAM5_AD_TRIG_INT_RETRIG DPR_PARAM5_AD_TRIG(2) |
115 | #define DPR_PARAM5_AD_TRIG_EXT_RETRIG DPR_PARAM5_AD_TRIG(3) |
116 | #define DPR_PARAM5_AD_TRIG_INT_RETRIG2 DPR_PARAM5_AD_TRIG(4) |
117 | |
118 | #define DPR_PARAM6_AD_DIFF BIT(0) |
119 | |
120 | #define DPR_AI_FIFO_DEPTH 2003 |
121 | #define DPR_AO_FIFO_DEPTH 2048 |
122 | |
123 | #define DPR_EXTERNAL_CLOCK 1 |
124 | #define DPR_RISING_EDGE 2 |
125 | |
126 | #define DPR_TMODE_MASK 0x1c |
127 | |
128 | #define DPR_CMD_TIMEOUT 100 |
129 | |
130 | static const struct comedi_lrange range_dt3000_ai = { |
131 | 4, { |
132 | BIP_RANGE(10), |
133 | BIP_RANGE(5), |
134 | BIP_RANGE(2.5), |
135 | BIP_RANGE(1.25) |
136 | } |
137 | }; |
138 | |
139 | static const struct comedi_lrange range_dt3000_ai_pgl = { |
140 | 4, { |
141 | BIP_RANGE(10), |
142 | BIP_RANGE(1), |
143 | BIP_RANGE(0.1), |
144 | BIP_RANGE(0.02) |
145 | } |
146 | }; |
147 | |
148 | enum dt3k_boardid { |
149 | BOARD_DT3001, |
150 | BOARD_DT3001_PGL, |
151 | BOARD_DT3002, |
152 | BOARD_DT3003, |
153 | BOARD_DT3003_PGL, |
154 | BOARD_DT3004, |
155 | BOARD_DT3005, |
156 | }; |
157 | |
158 | struct dt3k_boardtype { |
159 | const char *name; |
160 | int adchan; |
161 | int ai_speed; |
162 | const struct comedi_lrange *adrange; |
163 | unsigned int ai_is_16bit:1; |
164 | unsigned int has_ao:1; |
165 | }; |
166 | |
167 | static const struct dt3k_boardtype dt3k_boardtypes[] = { |
168 | [BOARD_DT3001] = { |
169 | .name = "dt3001" , |
170 | .adchan = 16, |
171 | .adrange = &range_dt3000_ai, |
172 | .ai_speed = 3000, |
173 | .has_ao = 1, |
174 | }, |
175 | [BOARD_DT3001_PGL] = { |
176 | .name = "dt3001-pgl" , |
177 | .adchan = 16, |
178 | .adrange = &range_dt3000_ai_pgl, |
179 | .ai_speed = 3000, |
180 | .has_ao = 1, |
181 | }, |
182 | [BOARD_DT3002] = { |
183 | .name = "dt3002" , |
184 | .adchan = 32, |
185 | .adrange = &range_dt3000_ai, |
186 | .ai_speed = 3000, |
187 | }, |
188 | [BOARD_DT3003] = { |
189 | .name = "dt3003" , |
190 | .adchan = 64, |
191 | .adrange = &range_dt3000_ai, |
192 | .ai_speed = 3000, |
193 | .has_ao = 1, |
194 | }, |
195 | [BOARD_DT3003_PGL] = { |
196 | .name = "dt3003-pgl" , |
197 | .adchan = 64, |
198 | .adrange = &range_dt3000_ai_pgl, |
199 | .ai_speed = 3000, |
200 | .has_ao = 1, |
201 | }, |
202 | [BOARD_DT3004] = { |
203 | .name = "dt3004" , |
204 | .adchan = 16, |
205 | .adrange = &range_dt3000_ai, |
206 | .ai_speed = 10000, |
207 | .ai_is_16bit = 1, |
208 | .has_ao = 1, |
209 | }, |
210 | [BOARD_DT3005] = { |
211 | .name = "dt3005" , /* a.k.a. 3004-200 */ |
212 | .adchan = 16, |
213 | .adrange = &range_dt3000_ai, |
214 | .ai_speed = 5000, |
215 | .ai_is_16bit = 1, |
216 | .has_ao = 1, |
217 | }, |
218 | }; |
219 | |
220 | struct dt3k_private { |
221 | unsigned int lock; |
222 | unsigned int ai_front; |
223 | unsigned int ai_rear; |
224 | }; |
225 | |
226 | static void dt3k_send_cmd(struct comedi_device *dev, unsigned int cmd) |
227 | { |
228 | int i; |
229 | unsigned int status = 0; |
230 | |
231 | writew(val: cmd, addr: dev->mmio + DPR_CMD_MBX); |
232 | |
233 | for (i = 0; i < DPR_CMD_TIMEOUT; i++) { |
234 | status = readw(addr: dev->mmio + DPR_CMD_MBX); |
235 | status &= DPR_CMD_COMPLETION_MASK; |
236 | if (status != DPR_CMD_NOTPROCESSED) |
237 | break; |
238 | udelay(1); |
239 | } |
240 | |
241 | if (status != DPR_CMD_NOERROR) |
242 | dev_dbg(dev->class_dev, "%s: timeout/error status=0x%04x\n" , |
243 | __func__, status); |
244 | } |
245 | |
246 | static unsigned int dt3k_readsingle(struct comedi_device *dev, |
247 | unsigned int subsys, unsigned int chan, |
248 | unsigned int gain) |
249 | { |
250 | writew(val: subsys, addr: dev->mmio + DPR_SUBSYS); |
251 | |
252 | writew(val: chan, addr: dev->mmio + DPR_PARAMS(0)); |
253 | writew(val: gain, addr: dev->mmio + DPR_PARAMS(1)); |
254 | |
255 | dt3k_send_cmd(dev, DPR_CMD_READSINGLE); |
256 | |
257 | return readw(addr: dev->mmio + DPR_PARAMS(2)); |
258 | } |
259 | |
260 | static void dt3k_writesingle(struct comedi_device *dev, unsigned int subsys, |
261 | unsigned int chan, unsigned int data) |
262 | { |
263 | writew(val: subsys, addr: dev->mmio + DPR_SUBSYS); |
264 | |
265 | writew(val: chan, addr: dev->mmio + DPR_PARAMS(0)); |
266 | writew(val: 0, addr: dev->mmio + DPR_PARAMS(1)); |
267 | writew(val: data, addr: dev->mmio + DPR_PARAMS(2)); |
268 | |
269 | dt3k_send_cmd(dev, DPR_CMD_WRITESINGLE); |
270 | } |
271 | |
272 | static void dt3k_ai_empty_fifo(struct comedi_device *dev, |
273 | struct comedi_subdevice *s) |
274 | { |
275 | struct dt3k_private *devpriv = dev->private; |
276 | int front; |
277 | int rear; |
278 | int count; |
279 | int i; |
280 | unsigned short data; |
281 | |
282 | front = readw(addr: dev->mmio + DPR_AD_BUF_FRONT); |
283 | count = front - devpriv->ai_front; |
284 | if (count < 0) |
285 | count += DPR_AI_FIFO_DEPTH; |
286 | |
287 | rear = devpriv->ai_rear; |
288 | |
289 | for (i = 0; i < count; i++) { |
290 | data = readw(addr: dev->mmio + DPR_ADC_BUFFER + rear); |
291 | comedi_buf_write_samples(s, data: &data, nsamples: 1); |
292 | rear++; |
293 | if (rear >= DPR_AI_FIFO_DEPTH) |
294 | rear = 0; |
295 | } |
296 | |
297 | devpriv->ai_rear = rear; |
298 | writew(val: rear, addr: dev->mmio + DPR_AD_BUF_REAR); |
299 | } |
300 | |
301 | static int dt3k_ai_cancel(struct comedi_device *dev, |
302 | struct comedi_subdevice *s) |
303 | { |
304 | writew(DPR_SUBSYS_AI, addr: dev->mmio + DPR_SUBSYS); |
305 | dt3k_send_cmd(dev, DPR_CMD_STOP); |
306 | |
307 | writew(val: 0, addr: dev->mmio + DPR_INT_MASK); |
308 | |
309 | return 0; |
310 | } |
311 | |
312 | static int debug_n_ints; |
313 | |
314 | /* FIXME! Assumes shared interrupt is for this card. */ |
315 | /* What's this debug_n_ints stuff? Obviously needs some work... */ |
316 | static irqreturn_t dt3k_interrupt(int irq, void *d) |
317 | { |
318 | struct comedi_device *dev = d; |
319 | struct comedi_subdevice *s = dev->read_subdev; |
320 | unsigned int status; |
321 | |
322 | if (!dev->attached) |
323 | return IRQ_NONE; |
324 | |
325 | status = readw(addr: dev->mmio + DPR_INTR_FLAG); |
326 | |
327 | if (status & DPR_INTR_ADFULL) |
328 | dt3k_ai_empty_fifo(dev, s); |
329 | |
330 | if (status & (DPR_INTR_ADSWERR | DPR_INTR_ADHWERR)) |
331 | s->async->events |= COMEDI_CB_ERROR; |
332 | |
333 | debug_n_ints++; |
334 | if (debug_n_ints >= 10) |
335 | s->async->events |= COMEDI_CB_EOA; |
336 | |
337 | comedi_handle_events(dev, s); |
338 | return IRQ_HANDLED; |
339 | } |
340 | |
341 | static int dt3k_ns_to_timer(unsigned int timer_base, unsigned int *nanosec, |
342 | unsigned int flags) |
343 | { |
344 | unsigned int divider, base, prescale; |
345 | |
346 | /* This function needs improvement */ |
347 | /* Don't know if divider==0 works. */ |
348 | |
349 | for (prescale = 0; prescale < 16; prescale++) { |
350 | base = timer_base * (prescale + 1); |
351 | switch (flags & CMDF_ROUND_MASK) { |
352 | case CMDF_ROUND_NEAREST: |
353 | default: |
354 | divider = DIV_ROUND_CLOSEST(*nanosec, base); |
355 | break; |
356 | case CMDF_ROUND_DOWN: |
357 | divider = (*nanosec) / base; |
358 | break; |
359 | case CMDF_ROUND_UP: |
360 | divider = DIV_ROUND_UP(*nanosec, base); |
361 | break; |
362 | } |
363 | if (divider < 65536) { |
364 | *nanosec = divider * base; |
365 | return (prescale << 16) | (divider); |
366 | } |
367 | } |
368 | |
369 | prescale = 15; |
370 | base = timer_base * (prescale + 1); |
371 | divider = 65535; |
372 | *nanosec = divider * base; |
373 | return (prescale << 16) | (divider); |
374 | } |
375 | |
376 | static int dt3k_ai_cmdtest(struct comedi_device *dev, |
377 | struct comedi_subdevice *s, struct comedi_cmd *cmd) |
378 | { |
379 | const struct dt3k_boardtype *board = dev->board_ptr; |
380 | int err = 0; |
381 | unsigned int arg; |
382 | |
383 | /* Step 1 : check if triggers are trivially valid */ |
384 | |
385 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
386 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_TIMER); |
387 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_TIMER); |
388 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
389 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT); |
390 | |
391 | if (err) |
392 | return 1; |
393 | |
394 | /* Step 2a : make sure trigger sources are unique */ |
395 | /* Step 2b : and mutually compatible */ |
396 | |
397 | /* Step 3: check if arguments are trivially valid */ |
398 | |
399 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
400 | |
401 | if (cmd->scan_begin_src == TRIG_TIMER) { |
402 | err |= comedi_check_trigger_arg_min(arg: &cmd->scan_begin_arg, |
403 | val: board->ai_speed); |
404 | err |= comedi_check_trigger_arg_max(arg: &cmd->scan_begin_arg, |
405 | val: 100 * 16 * 65535); |
406 | } |
407 | |
408 | if (cmd->convert_src == TRIG_TIMER) { |
409 | err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg, |
410 | val: board->ai_speed); |
411 | err |= comedi_check_trigger_arg_max(arg: &cmd->convert_arg, |
412 | val: 50 * 16 * 65535); |
413 | } |
414 | |
415 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
416 | val: cmd->chanlist_len); |
417 | |
418 | if (cmd->stop_src == TRIG_COUNT) |
419 | err |= comedi_check_trigger_arg_max(arg: &cmd->stop_arg, val: 0x00ffffff); |
420 | else /* TRIG_NONE */ |
421 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
422 | |
423 | if (err) |
424 | return 3; |
425 | |
426 | /* step 4: fix up any arguments */ |
427 | |
428 | if (cmd->scan_begin_src == TRIG_TIMER) { |
429 | arg = cmd->scan_begin_arg; |
430 | dt3k_ns_to_timer(timer_base: 100, nanosec: &arg, flags: cmd->flags); |
431 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: arg); |
432 | } |
433 | |
434 | if (cmd->convert_src == TRIG_TIMER) { |
435 | arg = cmd->convert_arg; |
436 | dt3k_ns_to_timer(timer_base: 50, nanosec: &arg, flags: cmd->flags); |
437 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg); |
438 | |
439 | if (cmd->scan_begin_src == TRIG_TIMER) { |
440 | arg = cmd->convert_arg * cmd->scan_end_arg; |
441 | err |= comedi_check_trigger_arg_min( |
442 | arg: &cmd->scan_begin_arg, val: arg); |
443 | } |
444 | } |
445 | |
446 | if (err) |
447 | return 4; |
448 | |
449 | return 0; |
450 | } |
451 | |
452 | static int dt3k_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
453 | { |
454 | struct comedi_cmd *cmd = &s->async->cmd; |
455 | int i; |
456 | unsigned int chan, range, aref; |
457 | unsigned int divider; |
458 | unsigned int tscandiv; |
459 | |
460 | for (i = 0; i < cmd->chanlist_len; i++) { |
461 | chan = CR_CHAN(cmd->chanlist[i]); |
462 | range = CR_RANGE(cmd->chanlist[i]); |
463 | |
464 | writew(val: (range << 6) | chan, addr: dev->mmio + DPR_ADC_BUFFER + i); |
465 | } |
466 | aref = CR_AREF(cmd->chanlist[0]); |
467 | |
468 | writew(val: cmd->scan_end_arg, addr: dev->mmio + DPR_PARAMS(0)); |
469 | |
470 | if (cmd->convert_src == TRIG_TIMER) { |
471 | divider = dt3k_ns_to_timer(timer_base: 50, nanosec: &cmd->convert_arg, flags: cmd->flags); |
472 | writew(val: (divider >> 16), addr: dev->mmio + DPR_PARAMS(1)); |
473 | writew(val: (divider & 0xffff), addr: dev->mmio + DPR_PARAMS(2)); |
474 | } |
475 | |
476 | if (cmd->scan_begin_src == TRIG_TIMER) { |
477 | tscandiv = dt3k_ns_to_timer(timer_base: 100, nanosec: &cmd->scan_begin_arg, |
478 | flags: cmd->flags); |
479 | writew(val: (tscandiv >> 16), addr: dev->mmio + DPR_PARAMS(3)); |
480 | writew(val: (tscandiv & 0xffff), addr: dev->mmio + DPR_PARAMS(4)); |
481 | } |
482 | |
483 | writew(DPR_PARAM5_AD_TRIG_INT_RETRIG, addr: dev->mmio + DPR_PARAMS(5)); |
484 | writew(val: (aref == AREF_DIFF) ? DPR_PARAM6_AD_DIFF : 0, |
485 | addr: dev->mmio + DPR_PARAMS(6)); |
486 | |
487 | writew(DPR_AI_FIFO_DEPTH / 2, addr: dev->mmio + DPR_PARAMS(7)); |
488 | |
489 | writew(DPR_SUBSYS_AI, addr: dev->mmio + DPR_SUBSYS); |
490 | dt3k_send_cmd(dev, DPR_CMD_CONFIG); |
491 | |
492 | writew(DPR_INTR_ADFULL | DPR_INTR_ADSWERR | DPR_INTR_ADHWERR, |
493 | addr: dev->mmio + DPR_INT_MASK); |
494 | |
495 | debug_n_ints = 0; |
496 | |
497 | writew(DPR_SUBSYS_AI, addr: dev->mmio + DPR_SUBSYS); |
498 | dt3k_send_cmd(dev, DPR_CMD_START); |
499 | |
500 | return 0; |
501 | } |
502 | |
503 | static int dt3k_ai_insn_read(struct comedi_device *dev, |
504 | struct comedi_subdevice *s, |
505 | struct comedi_insn *insn, |
506 | unsigned int *data) |
507 | { |
508 | int i; |
509 | unsigned int chan, gain; |
510 | |
511 | chan = CR_CHAN(insn->chanspec); |
512 | gain = CR_RANGE(insn->chanspec); |
513 | /* XXX docs don't explain how to select aref */ |
514 | |
515 | for (i = 0; i < insn->n; i++) |
516 | data[i] = dt3k_readsingle(dev, DPR_SUBSYS_AI, chan, gain); |
517 | |
518 | return i; |
519 | } |
520 | |
521 | static int dt3k_ao_insn_write(struct comedi_device *dev, |
522 | struct comedi_subdevice *s, |
523 | struct comedi_insn *insn, |
524 | unsigned int *data) |
525 | { |
526 | unsigned int chan = CR_CHAN(insn->chanspec); |
527 | unsigned int val = s->readback[chan]; |
528 | int i; |
529 | |
530 | for (i = 0; i < insn->n; i++) { |
531 | val = data[i]; |
532 | dt3k_writesingle(dev, DPR_SUBSYS_AO, chan, data: val); |
533 | } |
534 | s->readback[chan] = val; |
535 | |
536 | return insn->n; |
537 | } |
538 | |
539 | static void dt3k_dio_config(struct comedi_device *dev, int bits) |
540 | { |
541 | /* XXX */ |
542 | writew(DPR_SUBSYS_DOUT, addr: dev->mmio + DPR_SUBSYS); |
543 | |
544 | writew(val: bits, addr: dev->mmio + DPR_PARAMS(0)); |
545 | |
546 | /* XXX write 0 to DPR_PARAMS(1) and DPR_PARAMS(2) ? */ |
547 | |
548 | dt3k_send_cmd(dev, DPR_CMD_CONFIG); |
549 | } |
550 | |
551 | static int dt3k_dio_insn_config(struct comedi_device *dev, |
552 | struct comedi_subdevice *s, |
553 | struct comedi_insn *insn, |
554 | unsigned int *data) |
555 | { |
556 | unsigned int chan = CR_CHAN(insn->chanspec); |
557 | unsigned int mask; |
558 | int ret; |
559 | |
560 | if (chan < 4) |
561 | mask = 0x0f; |
562 | else |
563 | mask = 0xf0; |
564 | |
565 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); |
566 | if (ret) |
567 | return ret; |
568 | |
569 | dt3k_dio_config(dev, bits: (s->io_bits & 0x01) | ((s->io_bits & 0x10) >> 3)); |
570 | |
571 | return insn->n; |
572 | } |
573 | |
574 | static int dt3k_dio_insn_bits(struct comedi_device *dev, |
575 | struct comedi_subdevice *s, |
576 | struct comedi_insn *insn, |
577 | unsigned int *data) |
578 | { |
579 | if (comedi_dio_update_state(s, data)) |
580 | dt3k_writesingle(dev, DPR_SUBSYS_DOUT, chan: 0, data: s->state); |
581 | |
582 | data[1] = dt3k_readsingle(dev, DPR_SUBSYS_DIN, chan: 0, gain: 0); |
583 | |
584 | return insn->n; |
585 | } |
586 | |
587 | static int dt3k_mem_insn_read(struct comedi_device *dev, |
588 | struct comedi_subdevice *s, |
589 | struct comedi_insn *insn, |
590 | unsigned int *data) |
591 | { |
592 | unsigned int addr = CR_CHAN(insn->chanspec); |
593 | int i; |
594 | |
595 | for (i = 0; i < insn->n; i++) { |
596 | writew(DPR_SUBSYS_MEM, addr: dev->mmio + DPR_SUBSYS); |
597 | writew(val: addr, addr: dev->mmio + DPR_PARAMS(0)); |
598 | writew(val: 1, addr: dev->mmio + DPR_PARAMS(1)); |
599 | |
600 | dt3k_send_cmd(dev, DPR_CMD_READCODE); |
601 | |
602 | data[i] = readw(addr: dev->mmio + DPR_PARAMS(2)); |
603 | } |
604 | |
605 | return i; |
606 | } |
607 | |
608 | static int dt3000_auto_attach(struct comedi_device *dev, |
609 | unsigned long context) |
610 | { |
611 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
612 | const struct dt3k_boardtype *board = NULL; |
613 | struct dt3k_private *devpriv; |
614 | struct comedi_subdevice *s; |
615 | int ret = 0; |
616 | |
617 | if (context < ARRAY_SIZE(dt3k_boardtypes)) |
618 | board = &dt3k_boardtypes[context]; |
619 | if (!board) |
620 | return -ENODEV; |
621 | dev->board_ptr = board; |
622 | dev->board_name = board->name; |
623 | |
624 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
625 | if (!devpriv) |
626 | return -ENOMEM; |
627 | |
628 | ret = comedi_pci_enable(dev); |
629 | if (ret < 0) |
630 | return ret; |
631 | |
632 | dev->mmio = pci_ioremap_bar(pdev: pcidev, bar: 0); |
633 | if (!dev->mmio) |
634 | return -ENOMEM; |
635 | |
636 | if (pcidev->irq) { |
637 | ret = request_irq(irq: pcidev->irq, handler: dt3k_interrupt, IRQF_SHARED, |
638 | name: dev->board_name, dev); |
639 | if (ret == 0) |
640 | dev->irq = pcidev->irq; |
641 | } |
642 | |
643 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
644 | if (ret) |
645 | return ret; |
646 | |
647 | /* Analog Input subdevice */ |
648 | s = &dev->subdevices[0]; |
649 | s->type = COMEDI_SUBD_AI; |
650 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF; |
651 | s->n_chan = board->adchan; |
652 | s->maxdata = board->ai_is_16bit ? 0xffff : 0x0fff; |
653 | s->range_table = &range_dt3000_ai; /* XXX */ |
654 | s->insn_read = dt3k_ai_insn_read; |
655 | if (dev->irq) { |
656 | dev->read_subdev = s; |
657 | s->subdev_flags |= SDF_CMD_READ; |
658 | s->len_chanlist = 512; |
659 | s->do_cmd = dt3k_ai_cmd; |
660 | s->do_cmdtest = dt3k_ai_cmdtest; |
661 | s->cancel = dt3k_ai_cancel; |
662 | } |
663 | |
664 | /* Analog Output subdevice */ |
665 | s = &dev->subdevices[1]; |
666 | if (board->has_ao) { |
667 | s->type = COMEDI_SUBD_AO; |
668 | s->subdev_flags = SDF_WRITABLE; |
669 | s->n_chan = 2; |
670 | s->maxdata = 0x0fff; |
671 | s->range_table = &range_bipolar10; |
672 | s->insn_write = dt3k_ao_insn_write; |
673 | |
674 | ret = comedi_alloc_subdev_readback(s); |
675 | if (ret) |
676 | return ret; |
677 | |
678 | } else { |
679 | s->type = COMEDI_SUBD_UNUSED; |
680 | } |
681 | |
682 | /* Digital I/O subdevice */ |
683 | s = &dev->subdevices[2]; |
684 | s->type = COMEDI_SUBD_DIO; |
685 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
686 | s->n_chan = 8; |
687 | s->maxdata = 1; |
688 | s->range_table = &range_digital; |
689 | s->insn_config = dt3k_dio_insn_config; |
690 | s->insn_bits = dt3k_dio_insn_bits; |
691 | |
692 | /* Memory subdevice */ |
693 | s = &dev->subdevices[3]; |
694 | s->type = COMEDI_SUBD_MEMORY; |
695 | s->subdev_flags = SDF_READABLE; |
696 | s->n_chan = 0x1000; |
697 | s->maxdata = 0xff; |
698 | s->range_table = &range_unknown; |
699 | s->insn_read = dt3k_mem_insn_read; |
700 | |
701 | return 0; |
702 | } |
703 | |
704 | static struct comedi_driver dt3000_driver = { |
705 | .driver_name = "dt3000" , |
706 | .module = THIS_MODULE, |
707 | .auto_attach = dt3000_auto_attach, |
708 | .detach = comedi_pci_detach, |
709 | }; |
710 | |
711 | static int dt3000_pci_probe(struct pci_dev *dev, |
712 | const struct pci_device_id *id) |
713 | { |
714 | return comedi_pci_auto_config(pcidev: dev, driver: &dt3000_driver, context: id->driver_data); |
715 | } |
716 | |
717 | static const struct pci_device_id dt3000_pci_table[] = { |
718 | { PCI_VDEVICE(DT, 0x0022), BOARD_DT3001 }, |
719 | { PCI_VDEVICE(DT, 0x0023), BOARD_DT3002 }, |
720 | { PCI_VDEVICE(DT, 0x0024), BOARD_DT3003 }, |
721 | { PCI_VDEVICE(DT, 0x0025), BOARD_DT3004 }, |
722 | { PCI_VDEVICE(DT, 0x0026), BOARD_DT3005 }, |
723 | { PCI_VDEVICE(DT, 0x0027), BOARD_DT3001_PGL }, |
724 | { PCI_VDEVICE(DT, 0x0028), BOARD_DT3003_PGL }, |
725 | { 0 } |
726 | }; |
727 | MODULE_DEVICE_TABLE(pci, dt3000_pci_table); |
728 | |
729 | static struct pci_driver dt3000_pci_driver = { |
730 | .name = "dt3000" , |
731 | .id_table = dt3000_pci_table, |
732 | .probe = dt3000_pci_probe, |
733 | .remove = comedi_pci_auto_unconfig, |
734 | }; |
735 | module_comedi_pci_driver(dt3000_driver, dt3000_pci_driver); |
736 | |
737 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
738 | MODULE_DESCRIPTION("Comedi driver for Data Translation DT3000 series boards" ); |
739 | MODULE_LICENSE("GPL" ); |
740 | |