1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * das16.c |
4 | * DAS16 driver |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
8 | * Copyright (C) 2000 Chris R. Baugher <baugher@enteract.com> |
9 | * Copyright (C) 2001,2002 Frank Mori Hess <fmhess@users.sourceforge.net> |
10 | */ |
11 | |
12 | /* |
13 | * Driver: das16 |
14 | * Description: DAS16 compatible boards |
15 | * Author: Sam Moore, Warren Jasper, ds, Chris Baugher, Frank Hess, Roman Fietze |
16 | * Devices: [Keithley Metrabyte] DAS-16 (das-16), DAS-16G (das-16g), |
17 | * DAS-16F (das-16f), DAS-1201 (das-1201), DAS-1202 (das-1202), |
18 | * DAS-1401 (das-1401), DAS-1402 (das-1402), DAS-1601 (das-1601), |
19 | * DAS-1602 (das-1602), |
20 | * [ComputerBoards] PC104-DAS16/JR (pc104-das16jr), |
21 | * PC104-DAS16JR/16 (pc104-das16jr/16), CIO-DAS16 (cio-das16), |
22 | * CIO-DAS16F (cio-das16/f), CIO-DAS16/JR (cio-das16/jr), |
23 | * CIO-DAS16JR/16 (cio-das16jr/16), CIO-DAS1401/12 (cio-das1401/12), |
24 | * CIO-DAS1402/12 (cio-das1402/12), CIO-DAS1402/16 (cio-das1402/16), |
25 | * CIO-DAS1601/12 (cio-das1601/12), CIO-DAS1602/12 (cio-das1602/12), |
26 | * CIO-DAS1602/16 (cio-das1602/16), CIO-DAS16/330 (cio-das16/330) |
27 | * Status: works |
28 | * Updated: 2003-10-12 |
29 | * |
30 | * A rewrite of the das16 and das1600 drivers. |
31 | * |
32 | * Options: |
33 | * [0] - base io address |
34 | * [1] - irq (does nothing, irq is not used anymore) |
35 | * [2] - dma channel (optional, required for comedi_command support) |
36 | * [3] - master clock speed in MHz (optional, 1 or 10, ignored if |
37 | * board can probe clock, defaults to 1) |
38 | * [4] - analog input range lowest voltage in microvolts (optional, |
39 | * only useful if your board does not have software |
40 | * programmable gain) |
41 | * [5] - analog input range highest voltage in microvolts (optional, |
42 | * only useful if board does not have software programmable |
43 | * gain) |
44 | * [6] - analog output range lowest voltage in microvolts (optional) |
45 | * [7] - analog output range highest voltage in microvolts (optional) |
46 | * |
47 | * Passing a zero for an option is the same as leaving it unspecified. |
48 | */ |
49 | |
50 | /* |
51 | * Testing and debugging help provided by Daniel Koch. |
52 | * |
53 | * Keithley Manuals: |
54 | * 2309.PDF (das16) |
55 | * 4919.PDF (das1400, 1600) |
56 | * 4922.PDF (das-1400) |
57 | * 4923.PDF (das1200, 1400, 1600) |
58 | * |
59 | * Computer boards manuals also available from their website |
60 | * www.measurementcomputing.com |
61 | */ |
62 | |
63 | #include <linux/module.h> |
64 | #include <linux/slab.h> |
65 | #include <linux/interrupt.h> |
66 | #include <linux/comedi/comedidev.h> |
67 | #include <linux/comedi/comedi_8255.h> |
68 | #include <linux/comedi/comedi_8254.h> |
69 | #include <linux/comedi/comedi_isadma.h> |
70 | |
71 | #define DAS16_DMA_SIZE 0xff00 /* size in bytes of allocated dma buffer */ |
72 | |
73 | /* |
74 | * Register I/O map |
75 | */ |
76 | #define DAS16_TRIG_REG 0x00 |
77 | #define DAS16_AI_LSB_REG 0x00 |
78 | #define DAS16_AI_MSB_REG 0x01 |
79 | #define DAS16_MUX_REG 0x02 |
80 | #define DAS16_DIO_REG 0x03 |
81 | #define DAS16_AO_LSB_REG(x) ((x) ? 0x06 : 0x04) |
82 | #define DAS16_AO_MSB_REG(x) ((x) ? 0x07 : 0x05) |
83 | #define DAS16_STATUS_REG 0x08 |
84 | #define DAS16_STATUS_BUSY BIT(7) |
85 | #define DAS16_STATUS_UNIPOLAR BIT(6) |
86 | #define DAS16_STATUS_MUXBIT BIT(5) |
87 | #define DAS16_STATUS_INT BIT(4) |
88 | #define DAS16_CTRL_REG 0x09 |
89 | #define DAS16_CTRL_INTE BIT(7) |
90 | #define DAS16_CTRL_IRQ(x) (((x) & 0x7) << 4) |
91 | #define DAS16_CTRL_DMAE BIT(2) |
92 | #define DAS16_CTRL_PACING_MASK (3 << 0) |
93 | #define DAS16_CTRL_INT_PACER (3 << 0) |
94 | #define DAS16_CTRL_EXT_PACER (2 << 0) |
95 | #define DAS16_CTRL_SOFT_PACER (0 << 0) |
96 | #define DAS16_PACER_REG 0x0a |
97 | #define DAS16_PACER_BURST_LEN(x) (((x) & 0xf) << 4) |
98 | #define DAS16_PACER_CTR0 BIT(1) |
99 | #define DAS16_PACER_TRIG0 BIT(0) |
100 | #define DAS16_GAIN_REG 0x0b |
101 | #define DAS16_TIMER_BASE_REG 0x0c /* to 0x0f */ |
102 | |
103 | #define DAS1600_CONV_REG 0x404 |
104 | #define DAS1600_CONV_DISABLE BIT(6) |
105 | #define DAS1600_BURST_REG 0x405 |
106 | #define DAS1600_BURST_VAL BIT(6) |
107 | #define DAS1600_ENABLE_REG 0x406 |
108 | #define DAS1600_ENABLE_VAL BIT(6) |
109 | #define DAS1600_STATUS_REG 0x407 |
110 | #define DAS1600_STATUS_BME BIT(6) |
111 | #define DAS1600_STATUS_ME BIT(5) |
112 | #define DAS1600_STATUS_CD BIT(4) |
113 | #define DAS1600_STATUS_WS BIT(1) |
114 | #define DAS1600_STATUS_CLK_10MHZ BIT(0) |
115 | |
116 | static const struct comedi_lrange range_das1x01_bip = { |
117 | 4, { |
118 | BIP_RANGE(10), |
119 | BIP_RANGE(1), |
120 | BIP_RANGE(0.1), |
121 | BIP_RANGE(0.01) |
122 | } |
123 | }; |
124 | |
125 | static const struct comedi_lrange range_das1x01_unip = { |
126 | 4, { |
127 | UNI_RANGE(10), |
128 | UNI_RANGE(1), |
129 | UNI_RANGE(0.1), |
130 | UNI_RANGE(0.01) |
131 | } |
132 | }; |
133 | |
134 | static const struct comedi_lrange range_das1x02_bip = { |
135 | 4, { |
136 | BIP_RANGE(10), |
137 | BIP_RANGE(5), |
138 | BIP_RANGE(2.5), |
139 | BIP_RANGE(1.25) |
140 | } |
141 | }; |
142 | |
143 | static const struct comedi_lrange range_das1x02_unip = { |
144 | 4, { |
145 | UNI_RANGE(10), |
146 | UNI_RANGE(5), |
147 | UNI_RANGE(2.5), |
148 | UNI_RANGE(1.25) |
149 | } |
150 | }; |
151 | |
152 | static const struct comedi_lrange range_das16jr = { |
153 | 9, { |
154 | BIP_RANGE(10), |
155 | BIP_RANGE(5), |
156 | BIP_RANGE(2.5), |
157 | BIP_RANGE(1.25), |
158 | BIP_RANGE(0.625), |
159 | UNI_RANGE(10), |
160 | UNI_RANGE(5), |
161 | UNI_RANGE(2.5), |
162 | UNI_RANGE(1.25) |
163 | } |
164 | }; |
165 | |
166 | static const struct comedi_lrange range_das16jr_16 = { |
167 | 8, { |
168 | BIP_RANGE(10), |
169 | BIP_RANGE(5), |
170 | BIP_RANGE(2.5), |
171 | BIP_RANGE(1.25), |
172 | UNI_RANGE(10), |
173 | UNI_RANGE(5), |
174 | UNI_RANGE(2.5), |
175 | UNI_RANGE(1.25) |
176 | } |
177 | }; |
178 | |
179 | static const int das16jr_gainlist[] = { 8, 0, 1, 2, 3, 4, 5, 6, 7 }; |
180 | static const int das16jr_16_gainlist[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; |
181 | static const int das1600_gainlist[] = { 0, 1, 2, 3 }; |
182 | |
183 | enum { |
184 | das16_pg_none = 0, |
185 | das16_pg_16jr, |
186 | das16_pg_16jr_16, |
187 | das16_pg_1601, |
188 | das16_pg_1602, |
189 | }; |
190 | |
191 | static const int *const das16_gainlists[] = { |
192 | NULL, |
193 | das16jr_gainlist, |
194 | das16jr_16_gainlist, |
195 | das1600_gainlist, |
196 | das1600_gainlist, |
197 | }; |
198 | |
199 | static const struct comedi_lrange *const das16_ai_uni_lranges[] = { |
200 | &range_unknown, |
201 | &range_das16jr, |
202 | &range_das16jr_16, |
203 | &range_das1x01_unip, |
204 | &range_das1x02_unip, |
205 | }; |
206 | |
207 | static const struct comedi_lrange *const das16_ai_bip_lranges[] = { |
208 | &range_unknown, |
209 | &range_das16jr, |
210 | &range_das16jr_16, |
211 | &range_das1x01_bip, |
212 | &range_das1x02_bip, |
213 | }; |
214 | |
215 | struct das16_board { |
216 | const char *name; |
217 | unsigned int ai_maxdata; |
218 | unsigned int ai_speed; /* max conversion speed in nanosec */ |
219 | unsigned int ai_pg; |
220 | unsigned int has_ao:1; |
221 | unsigned int has_8255:1; |
222 | |
223 | unsigned int i8255_offset; |
224 | |
225 | unsigned int size; |
226 | unsigned int id; |
227 | }; |
228 | |
229 | static const struct das16_board das16_boards[] = { |
230 | { |
231 | .name = "das-16" , |
232 | .ai_maxdata = 0x0fff, |
233 | .ai_speed = 15000, |
234 | .ai_pg = das16_pg_none, |
235 | .has_ao = 1, |
236 | .has_8255 = 1, |
237 | .i8255_offset = 0x10, |
238 | .size = 0x14, |
239 | .id = 0x00, |
240 | }, { |
241 | .name = "das-16g" , |
242 | .ai_maxdata = 0x0fff, |
243 | .ai_speed = 15000, |
244 | .ai_pg = das16_pg_none, |
245 | .has_ao = 1, |
246 | .has_8255 = 1, |
247 | .i8255_offset = 0x10, |
248 | .size = 0x14, |
249 | .id = 0x00, |
250 | }, { |
251 | .name = "das-16f" , |
252 | .ai_maxdata = 0x0fff, |
253 | .ai_speed = 8500, |
254 | .ai_pg = das16_pg_none, |
255 | .has_ao = 1, |
256 | .has_8255 = 1, |
257 | .i8255_offset = 0x10, |
258 | .size = 0x14, |
259 | .id = 0x00, |
260 | }, { |
261 | .name = "cio-das16" , |
262 | .ai_maxdata = 0x0fff, |
263 | .ai_speed = 20000, |
264 | .ai_pg = das16_pg_none, |
265 | .has_ao = 1, |
266 | .has_8255 = 1, |
267 | .i8255_offset = 0x10, |
268 | .size = 0x14, |
269 | .id = 0x80, |
270 | }, { |
271 | .name = "cio-das16/f" , |
272 | .ai_maxdata = 0x0fff, |
273 | .ai_speed = 10000, |
274 | .ai_pg = das16_pg_none, |
275 | .has_ao = 1, |
276 | .has_8255 = 1, |
277 | .i8255_offset = 0x10, |
278 | .size = 0x14, |
279 | .id = 0x80, |
280 | }, { |
281 | .name = "cio-das16/jr" , |
282 | .ai_maxdata = 0x0fff, |
283 | .ai_speed = 7692, |
284 | .ai_pg = das16_pg_16jr, |
285 | .size = 0x10, |
286 | .id = 0x00, |
287 | }, { |
288 | .name = "pc104-das16jr" , |
289 | .ai_maxdata = 0x0fff, |
290 | .ai_speed = 3300, |
291 | .ai_pg = das16_pg_16jr, |
292 | .size = 0x10, |
293 | .id = 0x00, |
294 | }, { |
295 | .name = "cio-das16jr/16" , |
296 | .ai_maxdata = 0xffff, |
297 | .ai_speed = 10000, |
298 | .ai_pg = das16_pg_16jr_16, |
299 | .size = 0x10, |
300 | .id = 0x00, |
301 | }, { |
302 | .name = "pc104-das16jr/16" , |
303 | .ai_maxdata = 0xffff, |
304 | .ai_speed = 10000, |
305 | .ai_pg = das16_pg_16jr_16, |
306 | .size = 0x10, |
307 | .id = 0x00, |
308 | }, { |
309 | .name = "das-1201" , |
310 | .ai_maxdata = 0x0fff, |
311 | .ai_speed = 20000, |
312 | .ai_pg = das16_pg_none, |
313 | .has_8255 = 1, |
314 | .i8255_offset = 0x400, |
315 | .size = 0x408, |
316 | .id = 0x20, |
317 | }, { |
318 | .name = "das-1202" , |
319 | .ai_maxdata = 0x0fff, |
320 | .ai_speed = 10000, |
321 | .ai_pg = das16_pg_none, |
322 | .has_8255 = 1, |
323 | .i8255_offset = 0x400, |
324 | .size = 0x408, |
325 | .id = 0x20, |
326 | }, { |
327 | .name = "das-1401" , |
328 | .ai_maxdata = 0x0fff, |
329 | .ai_speed = 10000, |
330 | .ai_pg = das16_pg_1601, |
331 | .size = 0x408, |
332 | .id = 0xc0, |
333 | }, { |
334 | .name = "das-1402" , |
335 | .ai_maxdata = 0x0fff, |
336 | .ai_speed = 10000, |
337 | .ai_pg = das16_pg_1602, |
338 | .size = 0x408, |
339 | .id = 0xc0, |
340 | }, { |
341 | .name = "das-1601" , |
342 | .ai_maxdata = 0x0fff, |
343 | .ai_speed = 10000, |
344 | .ai_pg = das16_pg_1601, |
345 | .has_ao = 1, |
346 | .has_8255 = 1, |
347 | .i8255_offset = 0x400, |
348 | .size = 0x408, |
349 | .id = 0xc0, |
350 | }, { |
351 | .name = "das-1602" , |
352 | .ai_maxdata = 0x0fff, |
353 | .ai_speed = 10000, |
354 | .ai_pg = das16_pg_1602, |
355 | .has_ao = 1, |
356 | .has_8255 = 1, |
357 | .i8255_offset = 0x400, |
358 | .size = 0x408, |
359 | .id = 0xc0, |
360 | }, { |
361 | .name = "cio-das1401/12" , |
362 | .ai_maxdata = 0x0fff, |
363 | .ai_speed = 6250, |
364 | .ai_pg = das16_pg_1601, |
365 | .size = 0x408, |
366 | .id = 0xc0, |
367 | }, { |
368 | .name = "cio-das1402/12" , |
369 | .ai_maxdata = 0x0fff, |
370 | .ai_speed = 6250, |
371 | .ai_pg = das16_pg_1602, |
372 | .size = 0x408, |
373 | .id = 0xc0, |
374 | }, { |
375 | .name = "cio-das1402/16" , |
376 | .ai_maxdata = 0xffff, |
377 | .ai_speed = 10000, |
378 | .ai_pg = das16_pg_1602, |
379 | .size = 0x408, |
380 | .id = 0xc0, |
381 | }, { |
382 | .name = "cio-das1601/12" , |
383 | .ai_maxdata = 0x0fff, |
384 | .ai_speed = 6250, |
385 | .ai_pg = das16_pg_1601, |
386 | .has_ao = 1, |
387 | .has_8255 = 1, |
388 | .i8255_offset = 0x400, |
389 | .size = 0x408, |
390 | .id = 0xc0, |
391 | }, { |
392 | .name = "cio-das1602/12" , |
393 | .ai_maxdata = 0x0fff, |
394 | .ai_speed = 10000, |
395 | .ai_pg = das16_pg_1602, |
396 | .has_ao = 1, |
397 | .has_8255 = 1, |
398 | .i8255_offset = 0x400, |
399 | .size = 0x408, |
400 | .id = 0xc0, |
401 | }, { |
402 | .name = "cio-das1602/16" , |
403 | .ai_maxdata = 0xffff, |
404 | .ai_speed = 10000, |
405 | .ai_pg = das16_pg_1602, |
406 | .has_ao = 1, |
407 | .has_8255 = 1, |
408 | .i8255_offset = 0x400, |
409 | .size = 0x408, |
410 | .id = 0xc0, |
411 | }, { |
412 | .name = "cio-das16/330" , |
413 | .ai_maxdata = 0x0fff, |
414 | .ai_speed = 3030, |
415 | .ai_pg = das16_pg_16jr, |
416 | .size = 0x14, |
417 | .id = 0xf0, |
418 | }, |
419 | }; |
420 | |
421 | /* |
422 | * Period for timer interrupt in jiffies. It's a function |
423 | * to deal with possibility of dynamic HZ patches |
424 | */ |
425 | static inline int timer_period(void) |
426 | { |
427 | return HZ / 20; |
428 | } |
429 | |
430 | struct das16_private_struct { |
431 | struct comedi_isadma *dma; |
432 | struct comedi_device *dev; |
433 | unsigned int clockbase; |
434 | unsigned int ctrl_reg; |
435 | unsigned int divisor1; |
436 | unsigned int divisor2; |
437 | struct timer_list timer; |
438 | unsigned long ; |
439 | unsigned int can_burst:1; |
440 | unsigned int timer_running:1; |
441 | }; |
442 | |
443 | static void das16_ai_setup_dma(struct comedi_device *dev, |
444 | struct comedi_subdevice *s, |
445 | unsigned int unread_samples) |
446 | { |
447 | struct das16_private_struct *devpriv = dev->private; |
448 | struct comedi_isadma *dma = devpriv->dma; |
449 | struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; |
450 | unsigned int max_samples = comedi_bytes_to_samples(s, nbytes: desc->maxsize); |
451 | unsigned int nsamples; |
452 | |
453 | /* |
454 | * Determine dma size based on the buffer size plus the number of |
455 | * unread samples and the number of samples remaining in the command. |
456 | */ |
457 | nsamples = comedi_nsamples_left(s, nsamples: max_samples + unread_samples); |
458 | if (nsamples > unread_samples) { |
459 | nsamples -= unread_samples; |
460 | desc->size = comedi_samples_to_bytes(s, nsamples); |
461 | comedi_isadma_program(desc); |
462 | } |
463 | } |
464 | |
465 | static void das16_interrupt(struct comedi_device *dev) |
466 | { |
467 | struct das16_private_struct *devpriv = dev->private; |
468 | struct comedi_subdevice *s = dev->read_subdev; |
469 | struct comedi_async *async = s->async; |
470 | struct comedi_cmd *cmd = &async->cmd; |
471 | struct comedi_isadma *dma = devpriv->dma; |
472 | struct comedi_isadma_desc *desc = &dma->desc[dma->cur_dma]; |
473 | unsigned long spin_flags; |
474 | unsigned int residue; |
475 | unsigned int nbytes; |
476 | unsigned int nsamples; |
477 | |
478 | spin_lock_irqsave(&dev->spinlock, spin_flags); |
479 | if (!(devpriv->ctrl_reg & DAS16_CTRL_DMAE)) { |
480 | spin_unlock_irqrestore(lock: &dev->spinlock, flags: spin_flags); |
481 | return; |
482 | } |
483 | |
484 | /* |
485 | * The pc104-das16jr (at least) has problems if the dma |
486 | * transfer is interrupted in the middle of transferring |
487 | * a 16 bit sample. |
488 | */ |
489 | residue = comedi_isadma_disable_on_sample(dma_chan: desc->chan, |
490 | size: comedi_bytes_per_sample(s)); |
491 | |
492 | /* figure out how many samples to read */ |
493 | if (residue > desc->size) { |
494 | dev_err(dev->class_dev, "residue > transfer size!\n" ); |
495 | async->events |= COMEDI_CB_ERROR; |
496 | nbytes = 0; |
497 | } else { |
498 | nbytes = desc->size - residue; |
499 | } |
500 | nsamples = comedi_bytes_to_samples(s, nbytes); |
501 | |
502 | /* restart DMA if more samples are needed */ |
503 | if (nsamples) { |
504 | dma->cur_dma = 1 - dma->cur_dma; |
505 | das16_ai_setup_dma(dev, s, unread_samples: nsamples); |
506 | } |
507 | |
508 | spin_unlock_irqrestore(lock: &dev->spinlock, flags: spin_flags); |
509 | |
510 | comedi_buf_write_samples(s, data: desc->virt_addr, nsamples); |
511 | |
512 | if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg) |
513 | async->events |= COMEDI_CB_EOA; |
514 | |
515 | comedi_handle_events(dev, s); |
516 | } |
517 | |
518 | static void das16_timer_interrupt(struct timer_list *t) |
519 | { |
520 | struct das16_private_struct *devpriv = from_timer(devpriv, t, timer); |
521 | struct comedi_device *dev = devpriv->dev; |
522 | unsigned long flags; |
523 | |
524 | das16_interrupt(dev); |
525 | |
526 | spin_lock_irqsave(&dev->spinlock, flags); |
527 | if (devpriv->timer_running) |
528 | mod_timer(timer: &devpriv->timer, expires: jiffies + timer_period()); |
529 | spin_unlock_irqrestore(lock: &dev->spinlock, flags); |
530 | } |
531 | |
532 | static void das16_ai_set_mux_range(struct comedi_device *dev, |
533 | unsigned int first_chan, |
534 | unsigned int last_chan, |
535 | unsigned int range) |
536 | { |
537 | const struct das16_board *board = dev->board_ptr; |
538 | |
539 | /* set multiplexer */ |
540 | outb(value: first_chan | (last_chan << 4), port: dev->iobase + DAS16_MUX_REG); |
541 | |
542 | /* some boards do not have programmable gain */ |
543 | if (board->ai_pg == das16_pg_none) |
544 | return; |
545 | |
546 | /* |
547 | * Set gain (this is also burst rate register but according to |
548 | * computer boards manual, burst rate does nothing, even on |
549 | * keithley cards). |
550 | */ |
551 | outb(value: (das16_gainlists[board->ai_pg])[range], |
552 | port: dev->iobase + DAS16_GAIN_REG); |
553 | } |
554 | |
555 | static int das16_ai_check_chanlist(struct comedi_device *dev, |
556 | struct comedi_subdevice *s, |
557 | struct comedi_cmd *cmd) |
558 | { |
559 | unsigned int chan0 = CR_CHAN(cmd->chanlist[0]); |
560 | unsigned int range0 = CR_RANGE(cmd->chanlist[0]); |
561 | int i; |
562 | |
563 | for (i = 1; i < cmd->chanlist_len; i++) { |
564 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); |
565 | unsigned int range = CR_RANGE(cmd->chanlist[i]); |
566 | |
567 | if (chan != ((chan0 + i) % s->n_chan)) { |
568 | dev_dbg(dev->class_dev, |
569 | "entries in chanlist must be consecutive channels, counting upwards\n" ); |
570 | return -EINVAL; |
571 | } |
572 | |
573 | if (range != range0) { |
574 | dev_dbg(dev->class_dev, |
575 | "entries in chanlist must all have the same gain\n" ); |
576 | return -EINVAL; |
577 | } |
578 | } |
579 | |
580 | return 0; |
581 | } |
582 | |
583 | static int das16_cmd_test(struct comedi_device *dev, struct comedi_subdevice *s, |
584 | struct comedi_cmd *cmd) |
585 | { |
586 | const struct das16_board *board = dev->board_ptr; |
587 | struct das16_private_struct *devpriv = dev->private; |
588 | int err = 0; |
589 | unsigned int trig_mask; |
590 | unsigned int arg; |
591 | |
592 | /* Step 1 : check if triggers are trivially valid */ |
593 | |
594 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
595 | |
596 | trig_mask = TRIG_FOLLOW; |
597 | if (devpriv->can_burst) |
598 | trig_mask |= TRIG_TIMER | TRIG_EXT; |
599 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, flags: trig_mask); |
600 | |
601 | trig_mask = TRIG_TIMER | TRIG_EXT; |
602 | if (devpriv->can_burst) |
603 | trig_mask |= TRIG_NOW; |
604 | err |= comedi_check_trigger_src(src: &cmd->convert_src, flags: trig_mask); |
605 | |
606 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
607 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
608 | |
609 | if (err) |
610 | return 1; |
611 | |
612 | /* Step 2a : make sure trigger sources are unique */ |
613 | |
614 | err |= comedi_check_trigger_is_unique(src: cmd->scan_begin_src); |
615 | err |= comedi_check_trigger_is_unique(src: cmd->convert_src); |
616 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
617 | |
618 | /* Step 2b : and mutually compatible */ |
619 | |
620 | /* make sure scan_begin_src and convert_src don't conflict */ |
621 | if (cmd->scan_begin_src == TRIG_FOLLOW && cmd->convert_src == TRIG_NOW) |
622 | err |= -EINVAL; |
623 | if (cmd->scan_begin_src != TRIG_FOLLOW && cmd->convert_src != TRIG_NOW) |
624 | err |= -EINVAL; |
625 | |
626 | if (err) |
627 | return 2; |
628 | |
629 | /* Step 3: check if arguments are trivially valid */ |
630 | |
631 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
632 | |
633 | if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */ |
634 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
635 | |
636 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
637 | val: cmd->chanlist_len); |
638 | |
639 | /* check against maximum frequency */ |
640 | if (cmd->scan_begin_src == TRIG_TIMER) { |
641 | err |= comedi_check_trigger_arg_min(arg: &cmd->scan_begin_arg, |
642 | val: board->ai_speed * |
643 | cmd->chanlist_len); |
644 | } |
645 | |
646 | if (cmd->convert_src == TRIG_TIMER) { |
647 | err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg, |
648 | val: board->ai_speed); |
649 | } |
650 | |
651 | if (cmd->stop_src == TRIG_COUNT) |
652 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
653 | else /* TRIG_NONE */ |
654 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
655 | |
656 | if (err) |
657 | return 3; |
658 | |
659 | /* step 4: fix up arguments */ |
660 | if (cmd->scan_begin_src == TRIG_TIMER) { |
661 | arg = cmd->scan_begin_arg; |
662 | comedi_8254_cascade_ns_to_timer(i8254: dev->pacer, nanosec: &arg, flags: cmd->flags); |
663 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: arg); |
664 | } |
665 | if (cmd->convert_src == TRIG_TIMER) { |
666 | arg = cmd->convert_arg; |
667 | comedi_8254_cascade_ns_to_timer(i8254: dev->pacer, nanosec: &arg, flags: cmd->flags); |
668 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: arg); |
669 | } |
670 | if (err) |
671 | return 4; |
672 | |
673 | /* Step 5: check channel list if it exists */ |
674 | if (cmd->chanlist && cmd->chanlist_len > 0) |
675 | err |= das16_ai_check_chanlist(dev, s, cmd); |
676 | |
677 | if (err) |
678 | return 5; |
679 | |
680 | return 0; |
681 | } |
682 | |
683 | static unsigned int das16_set_pacer(struct comedi_device *dev, unsigned int ns, |
684 | unsigned int flags) |
685 | { |
686 | comedi_8254_cascade_ns_to_timer(i8254: dev->pacer, nanosec: &ns, flags); |
687 | comedi_8254_update_divisors(i8254: dev->pacer); |
688 | comedi_8254_pacer_enable(i8254: dev->pacer, counter1: 1, counter2: 2, enable: true); |
689 | |
690 | return ns; |
691 | } |
692 | |
693 | static int das16_cmd_exec(struct comedi_device *dev, struct comedi_subdevice *s) |
694 | { |
695 | struct das16_private_struct *devpriv = dev->private; |
696 | struct comedi_isadma *dma = devpriv->dma; |
697 | struct comedi_async *async = s->async; |
698 | struct comedi_cmd *cmd = &async->cmd; |
699 | unsigned int first_chan = CR_CHAN(cmd->chanlist[0]); |
700 | unsigned int last_chan = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]); |
701 | unsigned int range = CR_RANGE(cmd->chanlist[0]); |
702 | unsigned int byte; |
703 | unsigned long flags; |
704 | |
705 | if (cmd->flags & CMDF_PRIORITY) { |
706 | dev_err(dev->class_dev, |
707 | "isa dma transfers cannot be performed with CMDF_PRIORITY, aborting\n" ); |
708 | return -1; |
709 | } |
710 | |
711 | if (devpriv->can_burst) |
712 | outb(DAS1600_CONV_DISABLE, port: dev->iobase + DAS1600_CONV_REG); |
713 | |
714 | /* set mux and range for chanlist scan */ |
715 | das16_ai_set_mux_range(dev, first_chan, last_chan, range); |
716 | |
717 | /* set counter mode and counts */ |
718 | cmd->convert_arg = das16_set_pacer(dev, ns: cmd->convert_arg, flags: cmd->flags); |
719 | |
720 | /* enable counters */ |
721 | byte = 0; |
722 | if (devpriv->can_burst) { |
723 | if (cmd->convert_src == TRIG_NOW) { |
724 | outb(DAS1600_BURST_VAL, |
725 | port: dev->iobase + DAS1600_BURST_REG); |
726 | /* set burst length */ |
727 | byte |= DAS16_PACER_BURST_LEN(cmd->chanlist_len - 1); |
728 | } else { |
729 | outb(value: 0, port: dev->iobase + DAS1600_BURST_REG); |
730 | } |
731 | } |
732 | outb(value: byte, port: dev->iobase + DAS16_PACER_REG); |
733 | |
734 | /* set up dma transfer */ |
735 | dma->cur_dma = 0; |
736 | das16_ai_setup_dma(dev, s, unread_samples: 0); |
737 | |
738 | /* set up timer */ |
739 | spin_lock_irqsave(&dev->spinlock, flags); |
740 | devpriv->timer_running = 1; |
741 | devpriv->timer.expires = jiffies + timer_period(); |
742 | add_timer(timer: &devpriv->timer); |
743 | |
744 | /* enable DMA interrupt with external or internal pacing */ |
745 | devpriv->ctrl_reg &= ~(DAS16_CTRL_INTE | DAS16_CTRL_PACING_MASK); |
746 | devpriv->ctrl_reg |= DAS16_CTRL_DMAE; |
747 | if (cmd->convert_src == TRIG_EXT) |
748 | devpriv->ctrl_reg |= DAS16_CTRL_EXT_PACER; |
749 | else |
750 | devpriv->ctrl_reg |= DAS16_CTRL_INT_PACER; |
751 | outb(value: devpriv->ctrl_reg, port: dev->iobase + DAS16_CTRL_REG); |
752 | |
753 | if (devpriv->can_burst) |
754 | outb(value: 0, port: dev->iobase + DAS1600_CONV_REG); |
755 | spin_unlock_irqrestore(lock: &dev->spinlock, flags); |
756 | |
757 | return 0; |
758 | } |
759 | |
760 | static int das16_cancel(struct comedi_device *dev, struct comedi_subdevice *s) |
761 | { |
762 | struct das16_private_struct *devpriv = dev->private; |
763 | struct comedi_isadma *dma = devpriv->dma; |
764 | unsigned long flags; |
765 | |
766 | spin_lock_irqsave(&dev->spinlock, flags); |
767 | |
768 | /* disable interrupts, dma and pacer clocked conversions */ |
769 | devpriv->ctrl_reg &= ~(DAS16_CTRL_INTE | DAS16_CTRL_DMAE | |
770 | DAS16_CTRL_PACING_MASK); |
771 | outb(value: devpriv->ctrl_reg, port: dev->iobase + DAS16_CTRL_REG); |
772 | |
773 | comedi_isadma_disable(dma_chan: dma->chan); |
774 | |
775 | /* disable SW timer */ |
776 | if (devpriv->timer_running) { |
777 | devpriv->timer_running = 0; |
778 | del_timer(timer: &devpriv->timer); |
779 | } |
780 | |
781 | if (devpriv->can_burst) |
782 | outb(value: 0, port: dev->iobase + DAS1600_BURST_REG); |
783 | |
784 | spin_unlock_irqrestore(lock: &dev->spinlock, flags); |
785 | |
786 | return 0; |
787 | } |
788 | |
789 | static void das16_ai_munge(struct comedi_device *dev, |
790 | struct comedi_subdevice *s, void *array, |
791 | unsigned int num_bytes, |
792 | unsigned int start_chan_index) |
793 | { |
794 | unsigned short *data = array; |
795 | unsigned int num_samples = comedi_bytes_to_samples(s, nbytes: num_bytes); |
796 | unsigned int i; |
797 | __le16 *buf = array; |
798 | |
799 | for (i = 0; i < num_samples; i++) { |
800 | data[i] = le16_to_cpu(buf[i]); |
801 | if (s->maxdata == 0x0fff) |
802 | data[i] >>= 4; |
803 | data[i] &= s->maxdata; |
804 | } |
805 | } |
806 | |
807 | static int das16_ai_eoc(struct comedi_device *dev, |
808 | struct comedi_subdevice *s, |
809 | struct comedi_insn *insn, |
810 | unsigned long context) |
811 | { |
812 | unsigned int status; |
813 | |
814 | status = inb(port: dev->iobase + DAS16_STATUS_REG); |
815 | if ((status & DAS16_STATUS_BUSY) == 0) |
816 | return 0; |
817 | return -EBUSY; |
818 | } |
819 | |
820 | static int das16_ai_insn_read(struct comedi_device *dev, |
821 | struct comedi_subdevice *s, |
822 | struct comedi_insn *insn, |
823 | unsigned int *data) |
824 | { |
825 | unsigned int chan = CR_CHAN(insn->chanspec); |
826 | unsigned int range = CR_RANGE(insn->chanspec); |
827 | unsigned int val; |
828 | int ret; |
829 | int i; |
830 | |
831 | /* set mux and range for single channel */ |
832 | das16_ai_set_mux_range(dev, first_chan: chan, last_chan: chan, range); |
833 | |
834 | for (i = 0; i < insn->n; i++) { |
835 | /* trigger conversion */ |
836 | outb_p(value: 0, port: dev->iobase + DAS16_TRIG_REG); |
837 | |
838 | ret = comedi_timeout(dev, s, insn, cb: das16_ai_eoc, context: 0); |
839 | if (ret) |
840 | return ret; |
841 | |
842 | val = inb(port: dev->iobase + DAS16_AI_MSB_REG) << 8; |
843 | val |= inb(port: dev->iobase + DAS16_AI_LSB_REG); |
844 | if (s->maxdata == 0x0fff) |
845 | val >>= 4; |
846 | val &= s->maxdata; |
847 | |
848 | data[i] = val; |
849 | } |
850 | |
851 | return insn->n; |
852 | } |
853 | |
854 | static int das16_ao_insn_write(struct comedi_device *dev, |
855 | struct comedi_subdevice *s, |
856 | struct comedi_insn *insn, |
857 | unsigned int *data) |
858 | { |
859 | unsigned int chan = CR_CHAN(insn->chanspec); |
860 | int i; |
861 | |
862 | for (i = 0; i < insn->n; i++) { |
863 | unsigned int val = data[i]; |
864 | |
865 | s->readback[chan] = val; |
866 | |
867 | val <<= 4; |
868 | |
869 | outb(value: val & 0xff, port: dev->iobase + DAS16_AO_LSB_REG(chan)); |
870 | outb(value: (val >> 8) & 0xff, port: dev->iobase + DAS16_AO_MSB_REG(chan)); |
871 | } |
872 | |
873 | return insn->n; |
874 | } |
875 | |
876 | static int das16_di_insn_bits(struct comedi_device *dev, |
877 | struct comedi_subdevice *s, |
878 | struct comedi_insn *insn, |
879 | unsigned int *data) |
880 | { |
881 | data[1] = inb(port: dev->iobase + DAS16_DIO_REG) & 0xf; |
882 | |
883 | return insn->n; |
884 | } |
885 | |
886 | static int das16_do_insn_bits(struct comedi_device *dev, |
887 | struct comedi_subdevice *s, |
888 | struct comedi_insn *insn, |
889 | unsigned int *data) |
890 | { |
891 | if (comedi_dio_update_state(s, data)) |
892 | outb(value: s->state, port: dev->iobase + DAS16_DIO_REG); |
893 | |
894 | data[1] = s->state; |
895 | |
896 | return insn->n; |
897 | } |
898 | |
899 | static int das16_probe(struct comedi_device *dev, struct comedi_devconfig *it) |
900 | { |
901 | const struct das16_board *board = dev->board_ptr; |
902 | int diobits; |
903 | |
904 | /* diobits indicates boards */ |
905 | diobits = inb(port: dev->iobase + DAS16_DIO_REG) & 0xf0; |
906 | if (board->id != diobits) { |
907 | dev_err(dev->class_dev, |
908 | "requested board's id bits are incorrect (0x%x != 0x%x)\n" , |
909 | board->id, diobits); |
910 | return -EINVAL; |
911 | } |
912 | |
913 | return 0; |
914 | } |
915 | |
916 | static void das16_reset(struct comedi_device *dev) |
917 | { |
918 | outb(value: 0, port: dev->iobase + DAS16_STATUS_REG); |
919 | outb(value: 0, port: dev->iobase + DAS16_CTRL_REG); |
920 | outb(value: 0, port: dev->iobase + DAS16_PACER_REG); |
921 | } |
922 | |
923 | static void das16_alloc_dma(struct comedi_device *dev, unsigned int dma_chan) |
924 | { |
925 | struct das16_private_struct *devpriv = dev->private; |
926 | |
927 | timer_setup(&devpriv->timer, das16_timer_interrupt, 0); |
928 | |
929 | /* only DMA channels 3 and 1 are valid */ |
930 | if (!(dma_chan == 1 || dma_chan == 3)) |
931 | return; |
932 | |
933 | /* DMA uses two buffers */ |
934 | devpriv->dma = comedi_isadma_alloc(dev, n_desc: 2, dma_chan1: dma_chan, dma_chan2: dma_chan, |
935 | DAS16_DMA_SIZE, COMEDI_ISADMA_READ); |
936 | } |
937 | |
938 | static void das16_free_dma(struct comedi_device *dev) |
939 | { |
940 | struct das16_private_struct *devpriv = dev->private; |
941 | |
942 | if (devpriv) { |
943 | del_timer_sync(timer: &devpriv->timer); |
944 | comedi_isadma_free(dma: devpriv->dma); |
945 | } |
946 | } |
947 | |
948 | static const struct comedi_lrange *das16_ai_range(struct comedi_device *dev, |
949 | struct comedi_subdevice *s, |
950 | struct comedi_devconfig *it, |
951 | unsigned int pg_type, |
952 | unsigned int status) |
953 | { |
954 | unsigned int min = it->options[4]; |
955 | unsigned int max = it->options[5]; |
956 | |
957 | /* get any user-defined input range */ |
958 | if (pg_type == das16_pg_none && (min || max)) { |
959 | struct comedi_lrange *lrange; |
960 | struct comedi_krange *krange; |
961 | |
962 | /* allocate single-range range table */ |
963 | lrange = comedi_alloc_spriv(s, |
964 | struct_size(lrange, range, 1)); |
965 | if (!lrange) |
966 | return &range_unknown; |
967 | |
968 | /* initialize ai range */ |
969 | lrange->length = 1; |
970 | krange = lrange->range; |
971 | krange->min = min; |
972 | krange->max = max; |
973 | krange->flags = UNIT_volt; |
974 | |
975 | return lrange; |
976 | } |
977 | |
978 | /* use software programmable range */ |
979 | if (status & DAS16_STATUS_UNIPOLAR) |
980 | return das16_ai_uni_lranges[pg_type]; |
981 | return das16_ai_bip_lranges[pg_type]; |
982 | } |
983 | |
984 | static const struct comedi_lrange *das16_ao_range(struct comedi_device *dev, |
985 | struct comedi_subdevice *s, |
986 | struct comedi_devconfig *it) |
987 | { |
988 | unsigned int min = it->options[6]; |
989 | unsigned int max = it->options[7]; |
990 | |
991 | /* get any user-defined output range */ |
992 | if (min || max) { |
993 | struct comedi_lrange *lrange; |
994 | struct comedi_krange *krange; |
995 | |
996 | /* allocate single-range range table */ |
997 | lrange = comedi_alloc_spriv(s, |
998 | struct_size(lrange, range, 1)); |
999 | if (!lrange) |
1000 | return &range_unknown; |
1001 | |
1002 | /* initialize ao range */ |
1003 | lrange->length = 1; |
1004 | krange = lrange->range; |
1005 | krange->min = min; |
1006 | krange->max = max; |
1007 | krange->flags = UNIT_volt; |
1008 | |
1009 | return lrange; |
1010 | } |
1011 | |
1012 | return &range_unknown; |
1013 | } |
1014 | |
1015 | static int das16_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
1016 | { |
1017 | const struct das16_board *board = dev->board_ptr; |
1018 | struct das16_private_struct *devpriv; |
1019 | struct comedi_subdevice *s; |
1020 | unsigned int osc_base; |
1021 | unsigned int status; |
1022 | int ret; |
1023 | |
1024 | /* check that clock setting is valid */ |
1025 | if (it->options[3]) { |
1026 | if (it->options[3] != 1 && it->options[3] != 10) { |
1027 | dev_err(dev->class_dev, |
1028 | "Invalid option. Master clock must be set to 1 or 10 (MHz)\n" ); |
1029 | return -EINVAL; |
1030 | } |
1031 | } |
1032 | |
1033 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
1034 | if (!devpriv) |
1035 | return -ENOMEM; |
1036 | devpriv->dev = dev; |
1037 | |
1038 | if (board->size < 0x400) { |
1039 | ret = comedi_request_region(dev, start: it->options[0], len: board->size); |
1040 | if (ret) |
1041 | return ret; |
1042 | } else { |
1043 | ret = comedi_request_region(dev, start: it->options[0], len: 0x10); |
1044 | if (ret) |
1045 | return ret; |
1046 | /* Request an additional region for the 8255 */ |
1047 | ret = __comedi_request_region(dev, start: dev->iobase + 0x400, |
1048 | len: board->size & 0x3ff); |
1049 | if (ret) |
1050 | return ret; |
1051 | devpriv->extra_iobase = dev->iobase + 0x400; |
1052 | devpriv->can_burst = 1; |
1053 | } |
1054 | |
1055 | /* probe id bits to make sure they are consistent */ |
1056 | if (das16_probe(dev, it)) |
1057 | return -EINVAL; |
1058 | |
1059 | /* get master clock speed */ |
1060 | osc_base = I8254_OSC_BASE_1MHZ; |
1061 | if (devpriv->can_burst) { |
1062 | status = inb(port: dev->iobase + DAS1600_STATUS_REG); |
1063 | if (status & DAS1600_STATUS_CLK_10MHZ) |
1064 | osc_base = I8254_OSC_BASE_10MHZ; |
1065 | } else { |
1066 | if (it->options[3]) |
1067 | osc_base = I8254_OSC_BASE_1MHZ / it->options[3]; |
1068 | } |
1069 | |
1070 | dev->pacer = comedi_8254_io_alloc(iobase: dev->iobase + DAS16_TIMER_BASE_REG, |
1071 | osc_base, I8254_IO8, regshift: 0); |
1072 | if (IS_ERR(ptr: dev->pacer)) |
1073 | return PTR_ERR(ptr: dev->pacer); |
1074 | |
1075 | das16_alloc_dma(dev, dma_chan: it->options[2]); |
1076 | |
1077 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4 + board->has_8255); |
1078 | if (ret) |
1079 | return ret; |
1080 | |
1081 | status = inb(port: dev->iobase + DAS16_STATUS_REG); |
1082 | |
1083 | /* Analog Input subdevice */ |
1084 | s = &dev->subdevices[0]; |
1085 | s->type = COMEDI_SUBD_AI; |
1086 | s->subdev_flags = SDF_READABLE; |
1087 | if (status & DAS16_STATUS_MUXBIT) { |
1088 | s->subdev_flags |= SDF_GROUND; |
1089 | s->n_chan = 16; |
1090 | } else { |
1091 | s->subdev_flags |= SDF_DIFF; |
1092 | s->n_chan = 8; |
1093 | } |
1094 | s->len_chanlist = s->n_chan; |
1095 | s->maxdata = board->ai_maxdata; |
1096 | s->range_table = das16_ai_range(dev, s, it, pg_type: board->ai_pg, status); |
1097 | s->insn_read = das16_ai_insn_read; |
1098 | if (devpriv->dma) { |
1099 | dev->read_subdev = s; |
1100 | s->subdev_flags |= SDF_CMD_READ; |
1101 | s->do_cmdtest = das16_cmd_test; |
1102 | s->do_cmd = das16_cmd_exec; |
1103 | s->cancel = das16_cancel; |
1104 | s->munge = das16_ai_munge; |
1105 | } |
1106 | |
1107 | /* Analog Output subdevice */ |
1108 | s = &dev->subdevices[1]; |
1109 | if (board->has_ao) { |
1110 | s->type = COMEDI_SUBD_AO; |
1111 | s->subdev_flags = SDF_WRITABLE; |
1112 | s->n_chan = 2; |
1113 | s->maxdata = 0x0fff; |
1114 | s->range_table = das16_ao_range(dev, s, it); |
1115 | s->insn_write = das16_ao_insn_write; |
1116 | |
1117 | ret = comedi_alloc_subdev_readback(s); |
1118 | if (ret) |
1119 | return ret; |
1120 | } else { |
1121 | s->type = COMEDI_SUBD_UNUSED; |
1122 | } |
1123 | |
1124 | /* Digital Input subdevice */ |
1125 | s = &dev->subdevices[2]; |
1126 | s->type = COMEDI_SUBD_DI; |
1127 | s->subdev_flags = SDF_READABLE; |
1128 | s->n_chan = 4; |
1129 | s->maxdata = 1; |
1130 | s->range_table = &range_digital; |
1131 | s->insn_bits = das16_di_insn_bits; |
1132 | |
1133 | /* Digital Output subdevice */ |
1134 | s = &dev->subdevices[3]; |
1135 | s->type = COMEDI_SUBD_DO; |
1136 | s->subdev_flags = SDF_WRITABLE; |
1137 | s->n_chan = 4; |
1138 | s->maxdata = 1; |
1139 | s->range_table = &range_digital; |
1140 | s->insn_bits = das16_do_insn_bits; |
1141 | |
1142 | /* initialize digital output lines */ |
1143 | outb(value: s->state, port: dev->iobase + DAS16_DIO_REG); |
1144 | |
1145 | /* 8255 Digital I/O subdevice */ |
1146 | if (board->has_8255) { |
1147 | s = &dev->subdevices[4]; |
1148 | ret = subdev_8255_io_init(dev, s, regbase: board->i8255_offset); |
1149 | if (ret) |
1150 | return ret; |
1151 | } |
1152 | |
1153 | das16_reset(dev); |
1154 | /* set the interrupt level */ |
1155 | devpriv->ctrl_reg = DAS16_CTRL_IRQ(dev->irq); |
1156 | outb(value: devpriv->ctrl_reg, port: dev->iobase + DAS16_CTRL_REG); |
1157 | |
1158 | if (devpriv->can_burst) { |
1159 | outb(DAS1600_ENABLE_VAL, port: dev->iobase + DAS1600_ENABLE_REG); |
1160 | outb(value: 0, port: dev->iobase + DAS1600_CONV_REG); |
1161 | outb(value: 0, port: dev->iobase + DAS1600_BURST_REG); |
1162 | } |
1163 | |
1164 | return 0; |
1165 | } |
1166 | |
1167 | static void das16_detach(struct comedi_device *dev) |
1168 | { |
1169 | const struct das16_board *board = dev->board_ptr; |
1170 | struct das16_private_struct *devpriv = dev->private; |
1171 | |
1172 | if (devpriv) { |
1173 | if (dev->iobase) |
1174 | das16_reset(dev); |
1175 | das16_free_dma(dev); |
1176 | |
1177 | if (devpriv->extra_iobase) |
1178 | release_region(devpriv->extra_iobase, |
1179 | board->size & 0x3ff); |
1180 | } |
1181 | |
1182 | comedi_legacy_detach(dev); |
1183 | } |
1184 | |
1185 | static struct comedi_driver das16_driver = { |
1186 | .driver_name = "das16" , |
1187 | .module = THIS_MODULE, |
1188 | .attach = das16_attach, |
1189 | .detach = das16_detach, |
1190 | .board_name = &das16_boards[0].name, |
1191 | .num_names = ARRAY_SIZE(das16_boards), |
1192 | .offset = sizeof(das16_boards[0]), |
1193 | }; |
1194 | module_comedi_driver(das16_driver); |
1195 | |
1196 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
1197 | MODULE_DESCRIPTION("Comedi driver for DAS16 compatible boards" ); |
1198 | MODULE_LICENSE("GPL" ); |
1199 | |