1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * dt282x.c |
4 | * Comedi driver for Data Translation DT2821 series |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org> |
8 | */ |
9 | |
10 | /* |
11 | * Driver: dt282x |
12 | * Description: Data Translation DT2821 series (including DT-EZ) |
13 | * Author: ds |
14 | * Devices: [Data Translation] DT2821 (dt2821), DT2821-F-16SE (dt2821-f), |
15 | * DT2821-F-8DI (dt2821-f), DT2821-G-16SE (dt2821-g), |
16 | * DT2821-G-8DI (dt2821-g), DT2823 (dt2823), DT2824-PGH (dt2824-pgh), |
17 | * DT2824-PGL (dt2824-pgl), DT2825 (dt2825), DT2827 (dt2827), |
18 | * DT2828 (dt2828), DT2928 (dt2829), DT21-EZ (dt21-ez), DT23-EZ (dt23-ez), |
19 | * DT24-EZ (dt24-ez), DT24-EZ-PGL (dt24-ez-pgl) |
20 | * Status: complete |
21 | * Updated: Wed, 22 Aug 2001 17:11:34 -0700 |
22 | * |
23 | * Configuration options: |
24 | * [0] - I/O port base address |
25 | * [1] - IRQ (optional, required for async command support) |
26 | * [2] - DMA 1 (optional, required for async command support) |
27 | * [3] - DMA 2 (optional, required for async command support) |
28 | * [4] - AI jumpered for 0=single ended, 1=differential |
29 | * [5] - AI jumpered for 0=straight binary, 1=2's complement |
30 | * [6] - AO 0 data format (deprecated, see below) |
31 | * [7] - AO 1 data format (deprecated, see below) |
32 | * [8] - AI jumpered for 0=[-10,10]V, 1=[0,10], 2=[-5,5], 3=[0,5] |
33 | * [9] - AO channel 0 range (deprecated, see below) |
34 | * [10]- AO channel 1 range (deprecated, see below) |
35 | * |
36 | * Notes: |
37 | * - AO commands might be broken. |
38 | * - If you try to run a command on both the AI and AO subdevices |
39 | * simultaneously, bad things will happen. The driver needs to |
40 | * be fixed to check for this situation and return an error. |
41 | * - AO range is not programmable. The AO subdevice has a range_table |
42 | * containing all the possible analog output ranges. Use the range |
43 | * that matches your board configuration to convert between data |
44 | * values and physical units. The format of the data written to the |
45 | * board is handled automatically based on the unipolar/bipolar |
46 | * range that is selected. |
47 | */ |
48 | |
49 | #include <linux/module.h> |
50 | #include <linux/delay.h> |
51 | #include <linux/gfp.h> |
52 | #include <linux/interrupt.h> |
53 | #include <linux/io.h> |
54 | #include <linux/comedi/comedidev.h> |
55 | #include <linux/comedi/comedi_isadma.h> |
56 | |
57 | /* |
58 | * Register map |
59 | */ |
60 | #define DT2821_ADCSR_REG 0x00 |
61 | #define DT2821_ADCSR_ADERR BIT(15) |
62 | #define DT2821_ADCSR_ADCLK BIT(9) |
63 | #define DT2821_ADCSR_MUXBUSY BIT(8) |
64 | #define DT2821_ADCSR_ADDONE BIT(7) |
65 | #define DT2821_ADCSR_IADDONE BIT(6) |
66 | #define DT2821_ADCSR_GS(x) (((x) & 0x3) << 4) |
67 | #define DT2821_ADCSR_CHAN(x) (((x) & 0xf) << 0) |
68 | #define DT2821_CHANCSR_REG 0x02 |
69 | #define DT2821_CHANCSR_LLE BIT(15) |
70 | #define DT2821_CHANCSR_TO_PRESLA(x) (((x) >> 8) & 0xf) |
71 | #define DT2821_CHANCSR_NUMB(x) ((((x) - 1) & 0xf) << 0) |
72 | #define DT2821_ADDAT_REG 0x04 |
73 | #define DT2821_DACSR_REG 0x06 |
74 | #define DT2821_DACSR_DAERR BIT(15) |
75 | #define DT2821_DACSR_YSEL(x) ((x) << 9) |
76 | #define DT2821_DACSR_SSEL BIT(8) |
77 | #define DT2821_DACSR_DACRDY BIT(7) |
78 | #define DT2821_DACSR_IDARDY BIT(6) |
79 | #define DT2821_DACSR_DACLK BIT(5) |
80 | #define DT2821_DACSR_HBOE BIT(1) |
81 | #define DT2821_DACSR_LBOE BIT(0) |
82 | #define DT2821_DADAT_REG 0x08 |
83 | #define DT2821_DIODAT_REG 0x0a |
84 | #define DT2821_SUPCSR_REG 0x0c |
85 | #define DT2821_SUPCSR_DMAD BIT(15) |
86 | #define DT2821_SUPCSR_ERRINTEN BIT(14) |
87 | #define DT2821_SUPCSR_CLRDMADNE BIT(13) |
88 | #define DT2821_SUPCSR_DDMA BIT(12) |
89 | #define DT2821_SUPCSR_DS(x) (((x) & 0x3) << 10) |
90 | #define DT2821_SUPCSR_DS_PIO DT2821_SUPCSR_DS(0) |
91 | #define DT2821_SUPCSR_DS_AD_CLK DT2821_SUPCSR_DS(1) |
92 | #define DT2821_SUPCSR_DS_DA_CLK DT2821_SUPCSR_DS(2) |
93 | #define DT2821_SUPCSR_DS_AD_TRIG DT2821_SUPCSR_DS(3) |
94 | #define DT2821_SUPCSR_BUFFB BIT(9) |
95 | #define DT2821_SUPCSR_SCDN BIT(8) |
96 | #define DT2821_SUPCSR_DACON BIT(7) |
97 | #define DT2821_SUPCSR_ADCINIT BIT(6) |
98 | #define DT2821_SUPCSR_DACINIT BIT(5) |
99 | #define DT2821_SUPCSR_PRLD BIT(4) |
100 | #define DT2821_SUPCSR_STRIG BIT(3) |
101 | #define DT2821_SUPCSR_XTRIG BIT(2) |
102 | #define DT2821_SUPCSR_XCLK BIT(1) |
103 | #define DT2821_SUPCSR_BDINIT BIT(0) |
104 | #define DT2821_TMRCTR_REG 0x0e |
105 | #define DT2821_TMRCTR_PRESCALE(x) (((x) & 0xf) << 8) |
106 | #define DT2821_TMRCTR_DIVIDER(x) ((255 - ((x) & 0xff)) << 0) |
107 | |
108 | /* Pacer Clock */ |
109 | #define DT2821_OSC_BASE 250 /* 4 MHz (in nanoseconds) */ |
110 | #define DT2821_PRESCALE(x) BIT(x) |
111 | #define DT2821_PRESCALE_MAX 15 |
112 | #define DT2821_DIVIDER_MAX 255 |
113 | #define DT2821_OSC_MAX (DT2821_OSC_BASE * \ |
114 | DT2821_PRESCALE(DT2821_PRESCALE_MAX) * \ |
115 | DT2821_DIVIDER_MAX) |
116 | |
117 | static const struct comedi_lrange range_dt282x_ai_lo_bipolar = { |
118 | 4, { |
119 | BIP_RANGE(10), |
120 | BIP_RANGE(5), |
121 | BIP_RANGE(2.5), |
122 | BIP_RANGE(1.25) |
123 | } |
124 | }; |
125 | |
126 | static const struct comedi_lrange range_dt282x_ai_lo_unipolar = { |
127 | 4, { |
128 | UNI_RANGE(10), |
129 | UNI_RANGE(5), |
130 | UNI_RANGE(2.5), |
131 | UNI_RANGE(1.25) |
132 | } |
133 | }; |
134 | |
135 | static const struct comedi_lrange range_dt282x_ai_5_bipolar = { |
136 | 4, { |
137 | BIP_RANGE(5), |
138 | BIP_RANGE(2.5), |
139 | BIP_RANGE(1.25), |
140 | BIP_RANGE(0.625) |
141 | } |
142 | }; |
143 | |
144 | static const struct comedi_lrange range_dt282x_ai_5_unipolar = { |
145 | 4, { |
146 | UNI_RANGE(5), |
147 | UNI_RANGE(2.5), |
148 | UNI_RANGE(1.25), |
149 | UNI_RANGE(0.625) |
150 | } |
151 | }; |
152 | |
153 | static const struct comedi_lrange range_dt282x_ai_hi_bipolar = { |
154 | 4, { |
155 | BIP_RANGE(10), |
156 | BIP_RANGE(1), |
157 | BIP_RANGE(0.1), |
158 | BIP_RANGE(0.02) |
159 | } |
160 | }; |
161 | |
162 | static const struct comedi_lrange range_dt282x_ai_hi_unipolar = { |
163 | 4, { |
164 | UNI_RANGE(10), |
165 | UNI_RANGE(1), |
166 | UNI_RANGE(0.1), |
167 | UNI_RANGE(0.02) |
168 | } |
169 | }; |
170 | |
171 | /* |
172 | * The Analog Output range is set per-channel using jumpers on the board. |
173 | * All of these ranges may not be available on some DT2821 series boards. |
174 | * The default jumper setting has both channels set for +/-10V output. |
175 | */ |
176 | static const struct comedi_lrange dt282x_ao_range = { |
177 | 5, { |
178 | BIP_RANGE(10), |
179 | BIP_RANGE(5), |
180 | BIP_RANGE(2.5), |
181 | UNI_RANGE(10), |
182 | UNI_RANGE(5), |
183 | } |
184 | }; |
185 | |
186 | struct dt282x_board { |
187 | const char *name; |
188 | unsigned int ai_maxdata; |
189 | int adchan_se; |
190 | int adchan_di; |
191 | int ai_speed; |
192 | int ispgl; |
193 | int dachan; |
194 | unsigned int ao_maxdata; |
195 | }; |
196 | |
197 | static const struct dt282x_board boardtypes[] = { |
198 | { |
199 | .name = "dt2821" , |
200 | .ai_maxdata = 0x0fff, |
201 | .adchan_se = 16, |
202 | .adchan_di = 8, |
203 | .ai_speed = 20000, |
204 | .dachan = 2, |
205 | .ao_maxdata = 0x0fff, |
206 | }, { |
207 | .name = "dt2821-f" , |
208 | .ai_maxdata = 0x0fff, |
209 | .adchan_se = 16, |
210 | .adchan_di = 8, |
211 | .ai_speed = 6500, |
212 | .dachan = 2, |
213 | .ao_maxdata = 0x0fff, |
214 | }, { |
215 | .name = "dt2821-g" , |
216 | .ai_maxdata = 0x0fff, |
217 | .adchan_se = 16, |
218 | .adchan_di = 8, |
219 | .ai_speed = 4000, |
220 | .dachan = 2, |
221 | .ao_maxdata = 0x0fff, |
222 | }, { |
223 | .name = "dt2823" , |
224 | .ai_maxdata = 0xffff, |
225 | .adchan_di = 4, |
226 | .ai_speed = 10000, |
227 | .dachan = 2, |
228 | .ao_maxdata = 0xffff, |
229 | }, { |
230 | .name = "dt2824-pgh" , |
231 | .ai_maxdata = 0x0fff, |
232 | .adchan_se = 16, |
233 | .adchan_di = 8, |
234 | .ai_speed = 20000, |
235 | }, { |
236 | .name = "dt2824-pgl" , |
237 | .ai_maxdata = 0x0fff, |
238 | .adchan_se = 16, |
239 | .adchan_di = 8, |
240 | .ai_speed = 20000, |
241 | .ispgl = 1, |
242 | }, { |
243 | .name = "dt2825" , |
244 | .ai_maxdata = 0x0fff, |
245 | .adchan_se = 16, |
246 | .adchan_di = 8, |
247 | .ai_speed = 20000, |
248 | .ispgl = 1, |
249 | .dachan = 2, |
250 | .ao_maxdata = 0x0fff, |
251 | }, { |
252 | .name = "dt2827" , |
253 | .ai_maxdata = 0xffff, |
254 | .adchan_di = 4, |
255 | .ai_speed = 10000, |
256 | .dachan = 2, |
257 | .ao_maxdata = 0x0fff, |
258 | }, { |
259 | .name = "dt2828" , |
260 | .ai_maxdata = 0x0fff, |
261 | .adchan_se = 4, |
262 | .ai_speed = 10000, |
263 | .dachan = 2, |
264 | .ao_maxdata = 0x0fff, |
265 | }, { |
266 | .name = "dt2829" , |
267 | .ai_maxdata = 0xffff, |
268 | .adchan_se = 8, |
269 | .ai_speed = 33250, |
270 | .dachan = 2, |
271 | .ao_maxdata = 0xffff, |
272 | }, { |
273 | .name = "dt21-ez" , |
274 | .ai_maxdata = 0x0fff, |
275 | .adchan_se = 16, |
276 | .adchan_di = 8, |
277 | .ai_speed = 10000, |
278 | .dachan = 2, |
279 | .ao_maxdata = 0x0fff, |
280 | }, { |
281 | .name = "dt23-ez" , |
282 | .ai_maxdata = 0xffff, |
283 | .adchan_se = 16, |
284 | .adchan_di = 8, |
285 | .ai_speed = 10000, |
286 | }, { |
287 | .name = "dt24-ez" , |
288 | .ai_maxdata = 0x0fff, |
289 | .adchan_se = 16, |
290 | .adchan_di = 8, |
291 | .ai_speed = 10000, |
292 | }, { |
293 | .name = "dt24-ez-pgl" , |
294 | .ai_maxdata = 0x0fff, |
295 | .adchan_se = 16, |
296 | .adchan_di = 8, |
297 | .ai_speed = 10000, |
298 | .ispgl = 1, |
299 | }, |
300 | }; |
301 | |
302 | struct dt282x_private { |
303 | struct comedi_isadma *dma; |
304 | unsigned int ad_2scomp:1; |
305 | unsigned int divisor; |
306 | int dacsr; /* software copies of registers */ |
307 | int adcsr; |
308 | int supcsr; |
309 | int ntrig; |
310 | int nread; |
311 | int dma_dir; |
312 | }; |
313 | |
314 | static int dt282x_prep_ai_dma(struct comedi_device *dev, int dma_index, int n) |
315 | { |
316 | struct dt282x_private *devpriv = dev->private; |
317 | struct comedi_isadma *dma = devpriv->dma; |
318 | struct comedi_isadma_desc *desc = &dma->desc[dma_index]; |
319 | |
320 | if (!devpriv->ntrig) |
321 | return 0; |
322 | |
323 | if (n == 0) |
324 | n = desc->maxsize; |
325 | if (n > devpriv->ntrig * 2) |
326 | n = devpriv->ntrig * 2; |
327 | devpriv->ntrig -= n / 2; |
328 | |
329 | desc->size = n; |
330 | comedi_isadma_set_mode(desc, dma_dir: devpriv->dma_dir); |
331 | |
332 | comedi_isadma_program(desc); |
333 | |
334 | return n; |
335 | } |
336 | |
337 | static int dt282x_prep_ao_dma(struct comedi_device *dev, int dma_index, int n) |
338 | { |
339 | struct dt282x_private *devpriv = dev->private; |
340 | struct comedi_isadma *dma = devpriv->dma; |
341 | struct comedi_isadma_desc *desc = &dma->desc[dma_index]; |
342 | |
343 | desc->size = n; |
344 | comedi_isadma_set_mode(desc, dma_dir: devpriv->dma_dir); |
345 | |
346 | comedi_isadma_program(desc); |
347 | |
348 | return n; |
349 | } |
350 | |
351 | static void dt282x_disable_dma(struct comedi_device *dev) |
352 | { |
353 | struct dt282x_private *devpriv = dev->private; |
354 | struct comedi_isadma *dma = devpriv->dma; |
355 | struct comedi_isadma_desc *desc; |
356 | int i; |
357 | |
358 | for (i = 0; i < 2; i++) { |
359 | desc = &dma->desc[i]; |
360 | comedi_isadma_disable(dma_chan: desc->chan); |
361 | } |
362 | } |
363 | |
364 | static unsigned int dt282x_ns_to_timer(unsigned int *ns, unsigned int flags) |
365 | { |
366 | unsigned int prescale, base, divider; |
367 | |
368 | for (prescale = 0; prescale <= DT2821_PRESCALE_MAX; prescale++) { |
369 | if (prescale == 1) /* 0 and 1 are both divide by 1 */ |
370 | continue; |
371 | base = DT2821_OSC_BASE * DT2821_PRESCALE(prescale); |
372 | switch (flags & CMDF_ROUND_MASK) { |
373 | case CMDF_ROUND_NEAREST: |
374 | default: |
375 | divider = DIV_ROUND_CLOSEST(*ns, base); |
376 | break; |
377 | case CMDF_ROUND_DOWN: |
378 | divider = (*ns) / base; |
379 | break; |
380 | case CMDF_ROUND_UP: |
381 | divider = DIV_ROUND_UP(*ns, base); |
382 | break; |
383 | } |
384 | if (divider <= DT2821_DIVIDER_MAX) |
385 | break; |
386 | } |
387 | if (divider > DT2821_DIVIDER_MAX) { |
388 | prescale = DT2821_PRESCALE_MAX; |
389 | divider = DT2821_DIVIDER_MAX; |
390 | base = DT2821_OSC_BASE * DT2821_PRESCALE(prescale); |
391 | } |
392 | *ns = divider * base; |
393 | return DT2821_TMRCTR_PRESCALE(prescale) | |
394 | DT2821_TMRCTR_DIVIDER(divider); |
395 | } |
396 | |
397 | static void dt282x_munge(struct comedi_device *dev, |
398 | struct comedi_subdevice *s, |
399 | unsigned short *buf, |
400 | unsigned int nbytes) |
401 | { |
402 | struct dt282x_private *devpriv = dev->private; |
403 | unsigned int val; |
404 | int i; |
405 | |
406 | if (nbytes % 2) |
407 | dev_err(dev->class_dev, |
408 | "bug! odd number of bytes from dma xfer\n" ); |
409 | |
410 | for (i = 0; i < nbytes / 2; i++) { |
411 | val = buf[i]; |
412 | val &= s->maxdata; |
413 | if (devpriv->ad_2scomp) |
414 | val = comedi_offset_munge(s, val); |
415 | |
416 | buf[i] = val; |
417 | } |
418 | } |
419 | |
420 | static unsigned int dt282x_ao_setup_dma(struct comedi_device *dev, |
421 | struct comedi_subdevice *s, |
422 | int cur_dma) |
423 | { |
424 | struct dt282x_private *devpriv = dev->private; |
425 | struct comedi_isadma *dma = devpriv->dma; |
426 | struct comedi_isadma_desc *desc = &dma->desc[cur_dma]; |
427 | unsigned int nsamples = comedi_bytes_to_samples(s, nbytes: desc->maxsize); |
428 | unsigned int nbytes; |
429 | |
430 | nbytes = comedi_buf_read_samples(s, data: desc->virt_addr, nsamples); |
431 | if (nbytes) |
432 | dt282x_prep_ao_dma(dev, dma_index: cur_dma, n: nbytes); |
433 | else |
434 | dev_err(dev->class_dev, "AO underrun\n" ); |
435 | |
436 | return nbytes; |
437 | } |
438 | |
439 | static void dt282x_ao_dma_interrupt(struct comedi_device *dev, |
440 | struct comedi_subdevice *s) |
441 | { |
442 | struct dt282x_private *devpriv = dev->private; |
443 | struct comedi_isadma *dma = devpriv->dma; |
444 | struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; |
445 | |
446 | outw(value: devpriv->supcsr | DT2821_SUPCSR_CLRDMADNE, |
447 | port: dev->iobase + DT2821_SUPCSR_REG); |
448 | |
449 | comedi_isadma_disable(dma_chan: desc->chan); |
450 | |
451 | if (!dt282x_ao_setup_dma(dev, s, cur_dma: dma->cur_dma)) |
452 | s->async->events |= COMEDI_CB_OVERFLOW; |
453 | |
454 | dma->cur_dma = 1 - dma->cur_dma; |
455 | } |
456 | |
457 | static void dt282x_ai_dma_interrupt(struct comedi_device *dev, |
458 | struct comedi_subdevice *s) |
459 | { |
460 | struct dt282x_private *devpriv = dev->private; |
461 | struct comedi_isadma *dma = devpriv->dma; |
462 | struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; |
463 | unsigned int nsamples = comedi_bytes_to_samples(s, nbytes: desc->size); |
464 | int ret; |
465 | |
466 | outw(value: devpriv->supcsr | DT2821_SUPCSR_CLRDMADNE, |
467 | port: dev->iobase + DT2821_SUPCSR_REG); |
468 | |
469 | comedi_isadma_disable(dma_chan: desc->chan); |
470 | |
471 | dt282x_munge(dev, s, buf: desc->virt_addr, nbytes: desc->size); |
472 | ret = comedi_buf_write_samples(s, data: desc->virt_addr, nsamples); |
473 | if (ret != desc->size) |
474 | return; |
475 | |
476 | devpriv->nread -= nsamples; |
477 | if (devpriv->nread < 0) { |
478 | dev_info(dev->class_dev, "nread off by one\n" ); |
479 | devpriv->nread = 0; |
480 | } |
481 | if (!devpriv->nread) { |
482 | s->async->events |= COMEDI_CB_EOA; |
483 | return; |
484 | } |
485 | |
486 | /* restart the channel */ |
487 | dt282x_prep_ai_dma(dev, dma_index: dma->cur_dma, n: 0); |
488 | |
489 | dma->cur_dma = 1 - dma->cur_dma; |
490 | } |
491 | |
492 | static irqreturn_t dt282x_interrupt(int irq, void *d) |
493 | { |
494 | struct comedi_device *dev = d; |
495 | struct dt282x_private *devpriv = dev->private; |
496 | struct comedi_subdevice *s = dev->read_subdev; |
497 | struct comedi_subdevice *s_ao = dev->write_subdev; |
498 | unsigned int supcsr, adcsr, dacsr; |
499 | int handled = 0; |
500 | |
501 | if (!dev->attached) { |
502 | dev_err(dev->class_dev, "spurious interrupt\n" ); |
503 | return IRQ_HANDLED; |
504 | } |
505 | |
506 | adcsr = inw(port: dev->iobase + DT2821_ADCSR_REG); |
507 | dacsr = inw(port: dev->iobase + DT2821_DACSR_REG); |
508 | supcsr = inw(port: dev->iobase + DT2821_SUPCSR_REG); |
509 | if (supcsr & DT2821_SUPCSR_DMAD) { |
510 | if (devpriv->dma_dir == COMEDI_ISADMA_READ) |
511 | dt282x_ai_dma_interrupt(dev, s); |
512 | else |
513 | dt282x_ao_dma_interrupt(dev, s: s_ao); |
514 | handled = 1; |
515 | } |
516 | if (adcsr & DT2821_ADCSR_ADERR) { |
517 | if (devpriv->nread != 0) { |
518 | dev_err(dev->class_dev, "A/D error\n" ); |
519 | s->async->events |= COMEDI_CB_ERROR; |
520 | } |
521 | handled = 1; |
522 | } |
523 | if (dacsr & DT2821_DACSR_DAERR) { |
524 | dev_err(dev->class_dev, "D/A error\n" ); |
525 | s_ao->async->events |= COMEDI_CB_ERROR; |
526 | handled = 1; |
527 | } |
528 | |
529 | comedi_handle_events(dev, s); |
530 | if (s_ao) |
531 | comedi_handle_events(dev, s: s_ao); |
532 | |
533 | return IRQ_RETVAL(handled); |
534 | } |
535 | |
536 | static void dt282x_load_changain(struct comedi_device *dev, int n, |
537 | unsigned int *chanlist) |
538 | { |
539 | struct dt282x_private *devpriv = dev->private; |
540 | int i; |
541 | |
542 | outw(DT2821_CHANCSR_LLE | DT2821_CHANCSR_NUMB(n), |
543 | port: dev->iobase + DT2821_CHANCSR_REG); |
544 | for (i = 0; i < n; i++) { |
545 | unsigned int chan = CR_CHAN(chanlist[i]); |
546 | unsigned int range = CR_RANGE(chanlist[i]); |
547 | |
548 | outw(value: devpriv->adcsr | |
549 | DT2821_ADCSR_GS(range) | |
550 | DT2821_ADCSR_CHAN(chan), |
551 | port: dev->iobase + DT2821_ADCSR_REG); |
552 | } |
553 | outw(DT2821_CHANCSR_NUMB(n), port: dev->iobase + DT2821_CHANCSR_REG); |
554 | } |
555 | |
556 | static int dt282x_ai_timeout(struct comedi_device *dev, |
557 | struct comedi_subdevice *s, |
558 | struct comedi_insn *insn, |
559 | unsigned long context) |
560 | { |
561 | unsigned int status; |
562 | |
563 | status = inw(port: dev->iobase + DT2821_ADCSR_REG); |
564 | switch (context) { |
565 | case DT2821_ADCSR_MUXBUSY: |
566 | if ((status & DT2821_ADCSR_MUXBUSY) == 0) |
567 | return 0; |
568 | break; |
569 | case DT2821_ADCSR_ADDONE: |
570 | if (status & DT2821_ADCSR_ADDONE) |
571 | return 0; |
572 | break; |
573 | default: |
574 | return -EINVAL; |
575 | } |
576 | return -EBUSY; |
577 | } |
578 | |
579 | /* |
580 | * Performs a single A/D conversion. |
581 | * - Put channel/gain into channel-gain list |
582 | * - preload multiplexer |
583 | * - trigger conversion and wait for it to finish |
584 | */ |
585 | static int dt282x_ai_insn_read(struct comedi_device *dev, |
586 | struct comedi_subdevice *s, |
587 | struct comedi_insn *insn, |
588 | unsigned int *data) |
589 | { |
590 | struct dt282x_private *devpriv = dev->private; |
591 | unsigned int val; |
592 | int ret; |
593 | int i; |
594 | |
595 | /* XXX should we really be enabling the ad clock here? */ |
596 | devpriv->adcsr = DT2821_ADCSR_ADCLK; |
597 | outw(value: devpriv->adcsr, port: dev->iobase + DT2821_ADCSR_REG); |
598 | |
599 | dt282x_load_changain(dev, n: 1, chanlist: &insn->chanspec); |
600 | |
601 | outw(value: devpriv->supcsr | DT2821_SUPCSR_PRLD, |
602 | port: dev->iobase + DT2821_SUPCSR_REG); |
603 | ret = comedi_timeout(dev, s, insn, |
604 | cb: dt282x_ai_timeout, DT2821_ADCSR_MUXBUSY); |
605 | if (ret) |
606 | return ret; |
607 | |
608 | for (i = 0; i < insn->n; i++) { |
609 | outw(value: devpriv->supcsr | DT2821_SUPCSR_STRIG, |
610 | port: dev->iobase + DT2821_SUPCSR_REG); |
611 | |
612 | ret = comedi_timeout(dev, s, insn, |
613 | cb: dt282x_ai_timeout, DT2821_ADCSR_ADDONE); |
614 | if (ret) |
615 | return ret; |
616 | |
617 | val = inw(port: dev->iobase + DT2821_ADDAT_REG); |
618 | val &= s->maxdata; |
619 | if (devpriv->ad_2scomp) |
620 | val = comedi_offset_munge(s, val); |
621 | |
622 | data[i] = val; |
623 | } |
624 | |
625 | return i; |
626 | } |
627 | |
628 | static int dt282x_ai_cmdtest(struct comedi_device *dev, |
629 | struct comedi_subdevice *s, |
630 | struct comedi_cmd *cmd) |
631 | { |
632 | const struct dt282x_board *board = dev->board_ptr; |
633 | struct dt282x_private *devpriv = dev->private; |
634 | int err = 0; |
635 | unsigned int arg; |
636 | |
637 | /* Step 1 : check if triggers are trivially valid */ |
638 | |
639 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
640 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, |
641 | TRIG_FOLLOW | TRIG_EXT); |
642 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_TIMER); |
643 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
644 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
645 | |
646 | if (err) |
647 | return 1; |
648 | |
649 | /* Step 2a : make sure trigger sources are unique */ |
650 | |
651 | err |= comedi_check_trigger_is_unique(src: cmd->scan_begin_src); |
652 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
653 | |
654 | /* Step 2b : and mutually compatible */ |
655 | |
656 | if (err) |
657 | return 2; |
658 | |
659 | /* Step 3: check if arguments are trivially valid */ |
660 | |
661 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
662 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
663 | err |= comedi_check_trigger_arg_max(arg: &cmd->convert_arg, DT2821_OSC_MAX); |
664 | err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg, val: board->ai_speed); |
665 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
666 | val: cmd->chanlist_len); |
667 | |
668 | if (cmd->stop_src == TRIG_COUNT) |
669 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
670 | else /* TRIG_EXT | TRIG_NONE */ |
671 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
672 | |
673 | if (err) |
674 | return 3; |
675 | |
676 | /* step 4: fix up any arguments */ |
677 | |
678 | arg = cmd->convert_arg; |
679 | devpriv->divisor = dt282x_ns_to_timer(ns: &arg, flags: cmd->flags); |
680 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg); |
681 | |
682 | if (err) |
683 | return 4; |
684 | |
685 | return 0; |
686 | } |
687 | |
688 | static int dt282x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
689 | { |
690 | struct dt282x_private *devpriv = dev->private; |
691 | struct comedi_isadma *dma = devpriv->dma; |
692 | struct comedi_cmd *cmd = &s->async->cmd; |
693 | int ret; |
694 | |
695 | dt282x_disable_dma(dev); |
696 | |
697 | outw(value: devpriv->divisor, port: dev->iobase + DT2821_TMRCTR_REG); |
698 | |
699 | devpriv->supcsr = DT2821_SUPCSR_ERRINTEN; |
700 | if (cmd->scan_begin_src == TRIG_FOLLOW) |
701 | devpriv->supcsr = DT2821_SUPCSR_DS_AD_CLK; |
702 | else |
703 | devpriv->supcsr = DT2821_SUPCSR_DS_AD_TRIG; |
704 | outw(value: devpriv->supcsr | |
705 | DT2821_SUPCSR_CLRDMADNE | |
706 | DT2821_SUPCSR_BUFFB | |
707 | DT2821_SUPCSR_ADCINIT, |
708 | port: dev->iobase + DT2821_SUPCSR_REG); |
709 | |
710 | devpriv->ntrig = cmd->stop_arg * cmd->scan_end_arg; |
711 | devpriv->nread = devpriv->ntrig; |
712 | |
713 | devpriv->dma_dir = COMEDI_ISADMA_READ; |
714 | dma->cur_dma = 0; |
715 | dt282x_prep_ai_dma(dev, dma_index: 0, n: 0); |
716 | if (devpriv->ntrig) { |
717 | dt282x_prep_ai_dma(dev, dma_index: 1, n: 0); |
718 | devpriv->supcsr |= DT2821_SUPCSR_DDMA; |
719 | outw(value: devpriv->supcsr, port: dev->iobase + DT2821_SUPCSR_REG); |
720 | } |
721 | |
722 | devpriv->adcsr = 0; |
723 | |
724 | dt282x_load_changain(dev, n: cmd->chanlist_len, chanlist: cmd->chanlist); |
725 | |
726 | devpriv->adcsr = DT2821_ADCSR_ADCLK | DT2821_ADCSR_IADDONE; |
727 | outw(value: devpriv->adcsr, port: dev->iobase + DT2821_ADCSR_REG); |
728 | |
729 | outw(value: devpriv->supcsr | DT2821_SUPCSR_PRLD, |
730 | port: dev->iobase + DT2821_SUPCSR_REG); |
731 | ret = comedi_timeout(dev, s, NULL, |
732 | cb: dt282x_ai_timeout, DT2821_ADCSR_MUXBUSY); |
733 | if (ret) |
734 | return ret; |
735 | |
736 | if (cmd->scan_begin_src == TRIG_FOLLOW) { |
737 | outw(value: devpriv->supcsr | DT2821_SUPCSR_STRIG, |
738 | port: dev->iobase + DT2821_SUPCSR_REG); |
739 | } else { |
740 | devpriv->supcsr |= DT2821_SUPCSR_XTRIG; |
741 | outw(value: devpriv->supcsr, port: dev->iobase + DT2821_SUPCSR_REG); |
742 | } |
743 | |
744 | return 0; |
745 | } |
746 | |
747 | static int dt282x_ai_cancel(struct comedi_device *dev, |
748 | struct comedi_subdevice *s) |
749 | { |
750 | struct dt282x_private *devpriv = dev->private; |
751 | |
752 | dt282x_disable_dma(dev); |
753 | |
754 | devpriv->adcsr = 0; |
755 | outw(value: devpriv->adcsr, port: dev->iobase + DT2821_ADCSR_REG); |
756 | |
757 | devpriv->supcsr = 0; |
758 | outw(value: devpriv->supcsr | DT2821_SUPCSR_ADCINIT, |
759 | port: dev->iobase + DT2821_SUPCSR_REG); |
760 | |
761 | return 0; |
762 | } |
763 | |
764 | static int dt282x_ao_insn_write(struct comedi_device *dev, |
765 | struct comedi_subdevice *s, |
766 | struct comedi_insn *insn, |
767 | unsigned int *data) |
768 | { |
769 | struct dt282x_private *devpriv = dev->private; |
770 | unsigned int chan = CR_CHAN(insn->chanspec); |
771 | unsigned int range = CR_RANGE(insn->chanspec); |
772 | int i; |
773 | |
774 | devpriv->dacsr |= DT2821_DACSR_SSEL | DT2821_DACSR_YSEL(chan); |
775 | |
776 | for (i = 0; i < insn->n; i++) { |
777 | unsigned int val = data[i]; |
778 | |
779 | s->readback[chan] = val; |
780 | |
781 | if (comedi_range_is_bipolar(s, range)) |
782 | val = comedi_offset_munge(s, val); |
783 | |
784 | outw(value: devpriv->dacsr, port: dev->iobase + DT2821_DACSR_REG); |
785 | |
786 | outw(value: val, port: dev->iobase + DT2821_DADAT_REG); |
787 | |
788 | outw(value: devpriv->supcsr | DT2821_SUPCSR_DACON, |
789 | port: dev->iobase + DT2821_SUPCSR_REG); |
790 | } |
791 | |
792 | return insn->n; |
793 | } |
794 | |
795 | static int dt282x_ao_cmdtest(struct comedi_device *dev, |
796 | struct comedi_subdevice *s, |
797 | struct comedi_cmd *cmd) |
798 | { |
799 | struct dt282x_private *devpriv = dev->private; |
800 | int err = 0; |
801 | unsigned int arg; |
802 | |
803 | /* Step 1 : check if triggers are trivially valid */ |
804 | |
805 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_INT); |
806 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_TIMER); |
807 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_NOW); |
808 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
809 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
810 | |
811 | if (err) |
812 | return 1; |
813 | |
814 | /* Step 2a : make sure trigger sources are unique */ |
815 | |
816 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
817 | |
818 | /* Step 2b : and mutually compatible */ |
819 | |
820 | if (err) |
821 | return 2; |
822 | |
823 | /* Step 3: check if arguments are trivially valid */ |
824 | |
825 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
826 | err |= comedi_check_trigger_arg_min(arg: &cmd->scan_begin_arg, val: 5000); |
827 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
828 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
829 | val: cmd->chanlist_len); |
830 | |
831 | if (cmd->stop_src == TRIG_COUNT) |
832 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
833 | else /* TRIG_EXT | TRIG_NONE */ |
834 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
835 | |
836 | if (err) |
837 | return 3; |
838 | |
839 | /* step 4: fix up any arguments */ |
840 | |
841 | arg = cmd->scan_begin_arg; |
842 | devpriv->divisor = dt282x_ns_to_timer(ns: &arg, flags: cmd->flags); |
843 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: arg); |
844 | |
845 | if (err) |
846 | return 4; |
847 | |
848 | return 0; |
849 | } |
850 | |
851 | static int dt282x_ao_inttrig(struct comedi_device *dev, |
852 | struct comedi_subdevice *s, |
853 | unsigned int trig_num) |
854 | { |
855 | struct dt282x_private *devpriv = dev->private; |
856 | struct comedi_cmd *cmd = &s->async->cmd; |
857 | |
858 | if (trig_num != cmd->start_src) |
859 | return -EINVAL; |
860 | |
861 | if (!dt282x_ao_setup_dma(dev, s, cur_dma: 0)) |
862 | return -EPIPE; |
863 | |
864 | if (!dt282x_ao_setup_dma(dev, s, cur_dma: 1)) |
865 | return -EPIPE; |
866 | |
867 | outw(value: devpriv->supcsr | DT2821_SUPCSR_STRIG, |
868 | port: dev->iobase + DT2821_SUPCSR_REG); |
869 | s->async->inttrig = NULL; |
870 | |
871 | return 1; |
872 | } |
873 | |
874 | static int dt282x_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
875 | { |
876 | struct dt282x_private *devpriv = dev->private; |
877 | struct comedi_isadma *dma = devpriv->dma; |
878 | struct comedi_cmd *cmd = &s->async->cmd; |
879 | |
880 | dt282x_disable_dma(dev); |
881 | |
882 | devpriv->supcsr = DT2821_SUPCSR_ERRINTEN | |
883 | DT2821_SUPCSR_DS_DA_CLK | |
884 | DT2821_SUPCSR_DDMA; |
885 | outw(value: devpriv->supcsr | |
886 | DT2821_SUPCSR_CLRDMADNE | |
887 | DT2821_SUPCSR_BUFFB | |
888 | DT2821_SUPCSR_DACINIT, |
889 | port: dev->iobase + DT2821_SUPCSR_REG); |
890 | |
891 | devpriv->ntrig = cmd->stop_arg * cmd->chanlist_len; |
892 | devpriv->nread = devpriv->ntrig; |
893 | |
894 | devpriv->dma_dir = COMEDI_ISADMA_WRITE; |
895 | dma->cur_dma = 0; |
896 | |
897 | outw(value: devpriv->divisor, port: dev->iobase + DT2821_TMRCTR_REG); |
898 | |
899 | /* clear all bits but the DIO direction bits */ |
900 | devpriv->dacsr &= (DT2821_DACSR_LBOE | DT2821_DACSR_HBOE); |
901 | |
902 | devpriv->dacsr |= (DT2821_DACSR_SSEL | |
903 | DT2821_DACSR_DACLK | |
904 | DT2821_DACSR_IDARDY); |
905 | outw(value: devpriv->dacsr, port: dev->iobase + DT2821_DACSR_REG); |
906 | |
907 | s->async->inttrig = dt282x_ao_inttrig; |
908 | |
909 | return 0; |
910 | } |
911 | |
912 | static int dt282x_ao_cancel(struct comedi_device *dev, |
913 | struct comedi_subdevice *s) |
914 | { |
915 | struct dt282x_private *devpriv = dev->private; |
916 | |
917 | dt282x_disable_dma(dev); |
918 | |
919 | /* clear all bits but the DIO direction bits */ |
920 | devpriv->dacsr &= (DT2821_DACSR_LBOE | DT2821_DACSR_HBOE); |
921 | |
922 | outw(value: devpriv->dacsr, port: dev->iobase + DT2821_DACSR_REG); |
923 | |
924 | devpriv->supcsr = 0; |
925 | outw(value: devpriv->supcsr | DT2821_SUPCSR_DACINIT, |
926 | port: dev->iobase + DT2821_SUPCSR_REG); |
927 | |
928 | return 0; |
929 | } |
930 | |
931 | static int dt282x_dio_insn_bits(struct comedi_device *dev, |
932 | struct comedi_subdevice *s, |
933 | struct comedi_insn *insn, |
934 | unsigned int *data) |
935 | { |
936 | if (comedi_dio_update_state(s, data)) |
937 | outw(value: s->state, port: dev->iobase + DT2821_DIODAT_REG); |
938 | |
939 | data[1] = inw(port: dev->iobase + DT2821_DIODAT_REG); |
940 | |
941 | return insn->n; |
942 | } |
943 | |
944 | static int dt282x_dio_insn_config(struct comedi_device *dev, |
945 | struct comedi_subdevice *s, |
946 | struct comedi_insn *insn, |
947 | unsigned int *data) |
948 | { |
949 | struct dt282x_private *devpriv = dev->private; |
950 | unsigned int chan = CR_CHAN(insn->chanspec); |
951 | unsigned int mask; |
952 | int ret; |
953 | |
954 | if (chan < 8) |
955 | mask = 0x00ff; |
956 | else |
957 | mask = 0xff00; |
958 | |
959 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); |
960 | if (ret) |
961 | return ret; |
962 | |
963 | devpriv->dacsr &= ~(DT2821_DACSR_LBOE | DT2821_DACSR_HBOE); |
964 | if (s->io_bits & 0x00ff) |
965 | devpriv->dacsr |= DT2821_DACSR_LBOE; |
966 | if (s->io_bits & 0xff00) |
967 | devpriv->dacsr |= DT2821_DACSR_HBOE; |
968 | |
969 | outw(value: devpriv->dacsr, port: dev->iobase + DT2821_DACSR_REG); |
970 | |
971 | return insn->n; |
972 | } |
973 | |
974 | static const struct comedi_lrange *const ai_range_table[] = { |
975 | &range_dt282x_ai_lo_bipolar, |
976 | &range_dt282x_ai_lo_unipolar, |
977 | &range_dt282x_ai_5_bipolar, |
978 | &range_dt282x_ai_5_unipolar |
979 | }; |
980 | |
981 | static const struct comedi_lrange *const ai_range_pgl_table[] = { |
982 | &range_dt282x_ai_hi_bipolar, |
983 | &range_dt282x_ai_hi_unipolar |
984 | }; |
985 | |
986 | static const struct comedi_lrange *opt_ai_range_lkup(int ispgl, int x) |
987 | { |
988 | if (ispgl) { |
989 | if (x < 0 || x >= 2) |
990 | x = 0; |
991 | return ai_range_pgl_table[x]; |
992 | } |
993 | |
994 | if (x < 0 || x >= 4) |
995 | x = 0; |
996 | return ai_range_table[x]; |
997 | } |
998 | |
999 | static void dt282x_alloc_dma(struct comedi_device *dev, |
1000 | struct comedi_devconfig *it) |
1001 | { |
1002 | struct dt282x_private *devpriv = dev->private; |
1003 | unsigned int irq_num = it->options[1]; |
1004 | unsigned int dma_chan[2]; |
1005 | |
1006 | if (it->options[2] < it->options[3]) { |
1007 | dma_chan[0] = it->options[2]; |
1008 | dma_chan[1] = it->options[3]; |
1009 | } else { |
1010 | dma_chan[0] = it->options[3]; |
1011 | dma_chan[1] = it->options[2]; |
1012 | } |
1013 | |
1014 | if (!irq_num || dma_chan[0] == dma_chan[1] || |
1015 | dma_chan[0] < 5 || dma_chan[0] > 7 || |
1016 | dma_chan[1] < 5 || dma_chan[1] > 7) |
1017 | return; |
1018 | |
1019 | if (request_irq(irq: irq_num, handler: dt282x_interrupt, flags: 0, name: dev->board_name, dev)) |
1020 | return; |
1021 | |
1022 | /* DMA uses two 4K buffers with separate DMA channels */ |
1023 | devpriv->dma = comedi_isadma_alloc(dev, n_desc: 2, dma_chan1: dma_chan[0], dma_chan2: dma_chan[1], |
1024 | PAGE_SIZE, dma_dir: 0); |
1025 | if (!devpriv->dma) |
1026 | free_irq(irq_num, dev); |
1027 | else |
1028 | dev->irq = irq_num; |
1029 | } |
1030 | |
1031 | static void dt282x_free_dma(struct comedi_device *dev) |
1032 | { |
1033 | struct dt282x_private *devpriv = dev->private; |
1034 | |
1035 | if (devpriv) |
1036 | comedi_isadma_free(dma: devpriv->dma); |
1037 | } |
1038 | |
1039 | static int dt282x_initialize(struct comedi_device *dev) |
1040 | { |
1041 | /* Initialize board */ |
1042 | outw(DT2821_SUPCSR_BDINIT, port: dev->iobase + DT2821_SUPCSR_REG); |
1043 | inw(port: dev->iobase + DT2821_ADCSR_REG); |
1044 | |
1045 | /* |
1046 | * At power up, some registers are in a well-known state. |
1047 | * Check them to see if a DT2821 series board is present. |
1048 | */ |
1049 | if (((inw(port: dev->iobase + DT2821_ADCSR_REG) & 0xfff0) != 0x7c00) || |
1050 | ((inw(port: dev->iobase + DT2821_CHANCSR_REG) & 0xf0f0) != 0x70f0) || |
1051 | ((inw(port: dev->iobase + DT2821_DACSR_REG) & 0x7c93) != 0x7c90) || |
1052 | ((inw(port: dev->iobase + DT2821_SUPCSR_REG) & 0xf8ff) != 0x0000) || |
1053 | ((inw(port: dev->iobase + DT2821_TMRCTR_REG) & 0xff00) != 0xf000)) { |
1054 | dev_err(dev->class_dev, "board not found\n" ); |
1055 | return -EIO; |
1056 | } |
1057 | return 0; |
1058 | } |
1059 | |
1060 | static int dt282x_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
1061 | { |
1062 | const struct dt282x_board *board = dev->board_ptr; |
1063 | struct dt282x_private *devpriv; |
1064 | struct comedi_subdevice *s; |
1065 | int ret; |
1066 | |
1067 | ret = comedi_request_region(dev, start: it->options[0], len: 0x10); |
1068 | if (ret) |
1069 | return ret; |
1070 | |
1071 | ret = dt282x_initialize(dev); |
1072 | if (ret) |
1073 | return ret; |
1074 | |
1075 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
1076 | if (!devpriv) |
1077 | return -ENOMEM; |
1078 | |
1079 | /* an IRQ and 2 DMA channels are required for async command support */ |
1080 | dt282x_alloc_dma(dev, it); |
1081 | |
1082 | ret = comedi_alloc_subdevices(dev, num_subdevices: 3); |
1083 | if (ret) |
1084 | return ret; |
1085 | |
1086 | /* Analog Input subdevice */ |
1087 | s = &dev->subdevices[0]; |
1088 | s->type = COMEDI_SUBD_AI; |
1089 | s->subdev_flags = SDF_READABLE; |
1090 | if ((it->options[4] && board->adchan_di) || board->adchan_se == 0) { |
1091 | s->subdev_flags |= SDF_DIFF; |
1092 | s->n_chan = board->adchan_di; |
1093 | } else { |
1094 | s->subdev_flags |= SDF_COMMON; |
1095 | s->n_chan = board->adchan_se; |
1096 | } |
1097 | s->maxdata = board->ai_maxdata; |
1098 | |
1099 | s->range_table = opt_ai_range_lkup(ispgl: board->ispgl, x: it->options[8]); |
1100 | devpriv->ad_2scomp = it->options[5] ? 1 : 0; |
1101 | |
1102 | s->insn_read = dt282x_ai_insn_read; |
1103 | if (dev->irq) { |
1104 | dev->read_subdev = s; |
1105 | s->subdev_flags |= SDF_CMD_READ; |
1106 | s->len_chanlist = s->n_chan; |
1107 | s->do_cmdtest = dt282x_ai_cmdtest; |
1108 | s->do_cmd = dt282x_ai_cmd; |
1109 | s->cancel = dt282x_ai_cancel; |
1110 | } |
1111 | |
1112 | /* Analog Output subdevice */ |
1113 | s = &dev->subdevices[1]; |
1114 | if (board->dachan) { |
1115 | s->type = COMEDI_SUBD_AO; |
1116 | s->subdev_flags = SDF_WRITABLE; |
1117 | s->n_chan = board->dachan; |
1118 | s->maxdata = board->ao_maxdata; |
1119 | /* ranges are per-channel, set by jumpers on the board */ |
1120 | s->range_table = &dt282x_ao_range; |
1121 | s->insn_write = dt282x_ao_insn_write; |
1122 | if (dev->irq) { |
1123 | dev->write_subdev = s; |
1124 | s->subdev_flags |= SDF_CMD_WRITE; |
1125 | s->len_chanlist = s->n_chan; |
1126 | s->do_cmdtest = dt282x_ao_cmdtest; |
1127 | s->do_cmd = dt282x_ao_cmd; |
1128 | s->cancel = dt282x_ao_cancel; |
1129 | } |
1130 | |
1131 | ret = comedi_alloc_subdev_readback(s); |
1132 | if (ret) |
1133 | return ret; |
1134 | } else { |
1135 | s->type = COMEDI_SUBD_UNUSED; |
1136 | } |
1137 | |
1138 | /* Digital I/O subdevice */ |
1139 | s = &dev->subdevices[2]; |
1140 | s->type = COMEDI_SUBD_DIO; |
1141 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
1142 | s->n_chan = 16; |
1143 | s->maxdata = 1; |
1144 | s->range_table = &range_digital; |
1145 | s->insn_bits = dt282x_dio_insn_bits; |
1146 | s->insn_config = dt282x_dio_insn_config; |
1147 | |
1148 | return 0; |
1149 | } |
1150 | |
1151 | static void dt282x_detach(struct comedi_device *dev) |
1152 | { |
1153 | dt282x_free_dma(dev); |
1154 | comedi_legacy_detach(dev); |
1155 | } |
1156 | |
1157 | static struct comedi_driver dt282x_driver = { |
1158 | .driver_name = "dt282x" , |
1159 | .module = THIS_MODULE, |
1160 | .attach = dt282x_attach, |
1161 | .detach = dt282x_detach, |
1162 | .board_name = &boardtypes[0].name, |
1163 | .num_names = ARRAY_SIZE(boardtypes), |
1164 | .offset = sizeof(struct dt282x_board), |
1165 | }; |
1166 | module_comedi_driver(dt282x_driver); |
1167 | |
1168 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
1169 | MODULE_DESCRIPTION("Comedi driver for Data Translation DT2821 series" ); |
1170 | MODULE_LICENSE("GPL" ); |
1171 | |