1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Comedi driver for National Instruments AT-MIO16D board |
4 | * Copyright (C) 2000 Chris R. Baugher <baugher@enteract.com> |
5 | */ |
6 | |
7 | /* |
8 | * Driver: ni_atmio16d |
9 | * Description: National Instruments AT-MIO-16D |
10 | * Author: Chris R. Baugher <baugher@enteract.com> |
11 | * Status: unknown |
12 | * Devices: [National Instruments] AT-MIO-16 (atmio16), AT-MIO-16D (atmio16d) |
13 | * |
14 | * Configuration options: |
15 | * [0] - I/O port |
16 | * [1] - MIO irq (0 == no irq; or 3,4,5,6,7,9,10,11,12,14,15) |
17 | * [2] - DIO irq (0 == no irq; or 3,4,5,6,7,9) |
18 | * [3] - DMA1 channel (0 == no DMA; or 5,6,7) |
19 | * [4] - DMA2 channel (0 == no DMA; or 5,6,7) |
20 | * [5] - a/d mux (0=differential; 1=single) |
21 | * [6] - a/d range (0=bipolar10; 1=bipolar5; 2=unipolar10) |
22 | * [7] - dac0 range (0=bipolar; 1=unipolar) |
23 | * [8] - dac0 reference (0=internal; 1=external) |
24 | * [9] - dac0 coding (0=2's comp; 1=straight binary) |
25 | * [10] - dac1 range (same as dac0 options) |
26 | * [11] - dac1 reference (same as dac0 options) |
27 | * [12] - dac1 coding (same as dac0 options) |
28 | */ |
29 | |
30 | /* |
31 | * I must give credit here to Michal Dobes <dobes@tesnet.cz> who |
32 | * wrote the driver for Advantec's pcl812 boards. I used the interrupt |
33 | * handling code from his driver as an example for this one. |
34 | * |
35 | * Chris Baugher |
36 | * 5/1/2000 |
37 | * |
38 | */ |
39 | |
40 | #include <linux/module.h> |
41 | #include <linux/interrupt.h> |
42 | #include <linux/comedi/comedidev.h> |
43 | #include <linux/comedi/comedi_8255.h> |
44 | |
45 | /* Configuration and Status Registers */ |
46 | #define COM_REG_1 0x00 /* wo 16 */ |
47 | #define STAT_REG 0x00 /* ro 16 */ |
48 | #define COM_REG_2 0x02 /* wo 16 */ |
49 | /* Event Strobe Registers */ |
50 | #define START_CONVERT_REG 0x08 /* wo 16 */ |
51 | #define START_DAQ_REG 0x0A /* wo 16 */ |
52 | #define AD_CLEAR_REG 0x0C /* wo 16 */ |
53 | #define EXT_STROBE_REG 0x0E /* wo 16 */ |
54 | /* Analog Output Registers */ |
55 | #define DAC0_REG 0x10 /* wo 16 */ |
56 | #define DAC1_REG 0x12 /* wo 16 */ |
57 | #define INT2CLR_REG 0x14 /* wo 16 */ |
58 | /* Analog Input Registers */ |
59 | #define MUX_CNTR_REG 0x04 /* wo 16 */ |
60 | #define MUX_GAIN_REG 0x06 /* wo 16 */ |
61 | #define AD_FIFO_REG 0x16 /* ro 16 */ |
62 | #define DMA_TC_INT_CLR_REG 0x16 /* wo 16 */ |
63 | /* AM9513A Counter/Timer Registers */ |
64 | #define AM9513A_DATA_REG 0x18 /* rw 16 */ |
65 | #define AM9513A_COM_REG 0x1A /* wo 16 */ |
66 | #define AM9513A_STAT_REG 0x1A /* ro 16 */ |
67 | /* MIO-16 Digital I/O Registers */ |
68 | #define MIO_16_DIG_IN_REG 0x1C /* ro 16 */ |
69 | #define MIO_16_DIG_OUT_REG 0x1C /* wo 16 */ |
70 | /* RTSI Switch Registers */ |
71 | #define RTSI_SW_SHIFT_REG 0x1E /* wo 8 */ |
72 | #define RTSI_SW_STROBE_REG 0x1F /* wo 8 */ |
73 | /* DIO-24 Registers */ |
74 | #define DIO_24_PORTA_REG 0x00 /* rw 8 */ |
75 | #define DIO_24_PORTB_REG 0x01 /* rw 8 */ |
76 | #define DIO_24_PORTC_REG 0x02 /* rw 8 */ |
77 | #define DIO_24_CNFG_REG 0x03 /* wo 8 */ |
78 | |
79 | /* Command Register bits */ |
80 | #define COMREG1_2SCADC 0x0001 |
81 | #define COMREG1_1632CNT 0x0002 |
82 | #define COMREG1_SCANEN 0x0008 |
83 | #define COMREG1_DAQEN 0x0010 |
84 | #define COMREG1_DMAEN 0x0020 |
85 | #define COMREG1_CONVINTEN 0x0080 |
86 | #define COMREG2_SCN2 0x0010 |
87 | #define COMREG2_INTEN 0x0080 |
88 | #define COMREG2_DOUTEN0 0x0100 |
89 | #define COMREG2_DOUTEN1 0x0200 |
90 | /* Status Register bits */ |
91 | #define STAT_AD_OVERRUN 0x0100 |
92 | #define STAT_AD_OVERFLOW 0x0200 |
93 | #define STAT_AD_DAQPROG 0x0800 |
94 | #define STAT_AD_CONVAVAIL 0x2000 |
95 | #define STAT_AD_DAQSTOPINT 0x4000 |
96 | /* AM9513A Counter/Timer defines */ |
97 | #define CLOCK_1_MHZ 0x8B25 |
98 | #define CLOCK_100_KHZ 0x8C25 |
99 | #define CLOCK_10_KHZ 0x8D25 |
100 | #define CLOCK_1_KHZ 0x8E25 |
101 | #define CLOCK_100_HZ 0x8F25 |
102 | |
103 | struct atmio16_board_t { |
104 | const char *name; |
105 | int has_8255; |
106 | }; |
107 | |
108 | /* range structs */ |
109 | static const struct comedi_lrange range_atmio16d_ai_10_bipolar = { |
110 | 4, { |
111 | BIP_RANGE(10), |
112 | BIP_RANGE(1), |
113 | BIP_RANGE(0.1), |
114 | BIP_RANGE(0.02) |
115 | } |
116 | }; |
117 | |
118 | static const struct comedi_lrange range_atmio16d_ai_5_bipolar = { |
119 | 4, { |
120 | BIP_RANGE(5), |
121 | BIP_RANGE(0.5), |
122 | BIP_RANGE(0.05), |
123 | BIP_RANGE(0.01) |
124 | } |
125 | }; |
126 | |
127 | static const struct comedi_lrange range_atmio16d_ai_unipolar = { |
128 | 4, { |
129 | UNI_RANGE(10), |
130 | UNI_RANGE(1), |
131 | UNI_RANGE(0.1), |
132 | UNI_RANGE(0.02) |
133 | } |
134 | }; |
135 | |
136 | /* private data struct */ |
137 | struct atmio16d_private { |
138 | enum { adc_diff, adc_singleended } adc_mux; |
139 | enum { adc_bipolar10, adc_bipolar5, adc_unipolar10 } adc_range; |
140 | enum { adc_2comp, adc_straight } adc_coding; |
141 | enum { dac_bipolar, dac_unipolar } dac0_range, dac1_range; |
142 | enum { dac_internal, dac_external } dac0_reference, dac1_reference; |
143 | enum { dac_2comp, dac_straight } dac0_coding, dac1_coding; |
144 | const struct comedi_lrange *ao_range_type_list[2]; |
145 | unsigned int com_reg_1_state; /* current state of command register 1 */ |
146 | unsigned int com_reg_2_state; /* current state of command register 2 */ |
147 | }; |
148 | |
149 | static void reset_counters(struct comedi_device *dev) |
150 | { |
151 | /* Counter 2 */ |
152 | outw(value: 0xFFC2, port: dev->iobase + AM9513A_COM_REG); |
153 | outw(value: 0xFF02, port: dev->iobase + AM9513A_COM_REG); |
154 | outw(value: 0x4, port: dev->iobase + AM9513A_DATA_REG); |
155 | outw(value: 0xFF0A, port: dev->iobase + AM9513A_COM_REG); |
156 | outw(value: 0x3, port: dev->iobase + AM9513A_DATA_REG); |
157 | outw(value: 0xFF42, port: dev->iobase + AM9513A_COM_REG); |
158 | outw(value: 0xFF42, port: dev->iobase + AM9513A_COM_REG); |
159 | /* Counter 3 */ |
160 | outw(value: 0xFFC4, port: dev->iobase + AM9513A_COM_REG); |
161 | outw(value: 0xFF03, port: dev->iobase + AM9513A_COM_REG); |
162 | outw(value: 0x4, port: dev->iobase + AM9513A_DATA_REG); |
163 | outw(value: 0xFF0B, port: dev->iobase + AM9513A_COM_REG); |
164 | outw(value: 0x3, port: dev->iobase + AM9513A_DATA_REG); |
165 | outw(value: 0xFF44, port: dev->iobase + AM9513A_COM_REG); |
166 | outw(value: 0xFF44, port: dev->iobase + AM9513A_COM_REG); |
167 | /* Counter 4 */ |
168 | outw(value: 0xFFC8, port: dev->iobase + AM9513A_COM_REG); |
169 | outw(value: 0xFF04, port: dev->iobase + AM9513A_COM_REG); |
170 | outw(value: 0x4, port: dev->iobase + AM9513A_DATA_REG); |
171 | outw(value: 0xFF0C, port: dev->iobase + AM9513A_COM_REG); |
172 | outw(value: 0x3, port: dev->iobase + AM9513A_DATA_REG); |
173 | outw(value: 0xFF48, port: dev->iobase + AM9513A_COM_REG); |
174 | outw(value: 0xFF48, port: dev->iobase + AM9513A_COM_REG); |
175 | /* Counter 5 */ |
176 | outw(value: 0xFFD0, port: dev->iobase + AM9513A_COM_REG); |
177 | outw(value: 0xFF05, port: dev->iobase + AM9513A_COM_REG); |
178 | outw(value: 0x4, port: dev->iobase + AM9513A_DATA_REG); |
179 | outw(value: 0xFF0D, port: dev->iobase + AM9513A_COM_REG); |
180 | outw(value: 0x3, port: dev->iobase + AM9513A_DATA_REG); |
181 | outw(value: 0xFF50, port: dev->iobase + AM9513A_COM_REG); |
182 | outw(value: 0xFF50, port: dev->iobase + AM9513A_COM_REG); |
183 | |
184 | outw(value: 0, port: dev->iobase + AD_CLEAR_REG); |
185 | } |
186 | |
187 | static void reset_atmio16d(struct comedi_device *dev) |
188 | { |
189 | struct atmio16d_private *devpriv = dev->private; |
190 | int i; |
191 | |
192 | /* now we need to initialize the board */ |
193 | outw(value: 0, port: dev->iobase + COM_REG_1); |
194 | outw(value: 0, port: dev->iobase + COM_REG_2); |
195 | outw(value: 0, port: dev->iobase + MUX_GAIN_REG); |
196 | /* init AM9513A timer */ |
197 | outw(value: 0xFFFF, port: dev->iobase + AM9513A_COM_REG); |
198 | outw(value: 0xFFEF, port: dev->iobase + AM9513A_COM_REG); |
199 | outw(value: 0xFF17, port: dev->iobase + AM9513A_COM_REG); |
200 | outw(value: 0xF000, port: dev->iobase + AM9513A_DATA_REG); |
201 | for (i = 1; i <= 5; ++i) { |
202 | outw(value: 0xFF00 + i, port: dev->iobase + AM9513A_COM_REG); |
203 | outw(value: 0x0004, port: dev->iobase + AM9513A_DATA_REG); |
204 | outw(value: 0xFF08 + i, port: dev->iobase + AM9513A_COM_REG); |
205 | outw(value: 0x3, port: dev->iobase + AM9513A_DATA_REG); |
206 | } |
207 | outw(value: 0xFF5F, port: dev->iobase + AM9513A_COM_REG); |
208 | /* timer init done */ |
209 | outw(value: 0, port: dev->iobase + AD_CLEAR_REG); |
210 | outw(value: 0, port: dev->iobase + INT2CLR_REG); |
211 | /* select straight binary mode for Analog Input */ |
212 | devpriv->com_reg_1_state |= 1; |
213 | outw(value: devpriv->com_reg_1_state, port: dev->iobase + COM_REG_1); |
214 | devpriv->adc_coding = adc_straight; |
215 | /* zero the analog outputs */ |
216 | outw(value: 2048, port: dev->iobase + DAC0_REG); |
217 | outw(value: 2048, port: dev->iobase + DAC1_REG); |
218 | } |
219 | |
220 | static irqreturn_t atmio16d_interrupt(int irq, void *d) |
221 | { |
222 | struct comedi_device *dev = d; |
223 | struct comedi_subdevice *s = dev->read_subdev; |
224 | unsigned short val; |
225 | |
226 | val = inw(port: dev->iobase + AD_FIFO_REG); |
227 | comedi_buf_write_samples(s, data: &val, nsamples: 1); |
228 | comedi_handle_events(dev, s); |
229 | |
230 | return IRQ_HANDLED; |
231 | } |
232 | |
233 | static int atmio16d_ai_cmdtest(struct comedi_device *dev, |
234 | struct comedi_subdevice *s, |
235 | struct comedi_cmd *cmd) |
236 | { |
237 | int err = 0; |
238 | |
239 | /* Step 1 : check if triggers are trivially valid */ |
240 | |
241 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
242 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, |
243 | TRIG_FOLLOW | TRIG_TIMER); |
244 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_TIMER); |
245 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
246 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
247 | |
248 | if (err) |
249 | return 1; |
250 | |
251 | /* Step 2a : make sure trigger sources are unique */ |
252 | |
253 | err |= comedi_check_trigger_is_unique(src: cmd->scan_begin_src); |
254 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
255 | |
256 | /* Step 2b : and mutually compatible */ |
257 | |
258 | if (err) |
259 | return 2; |
260 | |
261 | /* Step 3: check if arguments are trivially valid */ |
262 | |
263 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
264 | |
265 | if (cmd->scan_begin_src == TRIG_FOLLOW) { |
266 | /* internal trigger */ |
267 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
268 | } |
269 | |
270 | err |= comedi_check_trigger_arg_min(arg: &cmd->convert_arg, val: 10000); |
271 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
272 | val: cmd->chanlist_len); |
273 | |
274 | if (cmd->stop_src == TRIG_COUNT) |
275 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
276 | else /* TRIG_NONE */ |
277 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
278 | |
279 | if (err) |
280 | return 3; |
281 | |
282 | return 0; |
283 | } |
284 | |
285 | static int atmio16d_ai_cmd(struct comedi_device *dev, |
286 | struct comedi_subdevice *s) |
287 | { |
288 | struct atmio16d_private *devpriv = dev->private; |
289 | struct comedi_cmd *cmd = &s->async->cmd; |
290 | unsigned int timer, base_clock; |
291 | unsigned int sample_count, tmp, chan, gain; |
292 | int i; |
293 | |
294 | /* |
295 | * This is slowly becoming a working command interface. |
296 | * It is still uber-experimental |
297 | */ |
298 | |
299 | reset_counters(dev); |
300 | |
301 | /* check if scanning multiple channels */ |
302 | if (cmd->chanlist_len < 2) { |
303 | devpriv->com_reg_1_state &= ~COMREG1_SCANEN; |
304 | outw(value: devpriv->com_reg_1_state, port: dev->iobase + COM_REG_1); |
305 | } else { |
306 | devpriv->com_reg_1_state |= COMREG1_SCANEN; |
307 | devpriv->com_reg_2_state |= COMREG2_SCN2; |
308 | outw(value: devpriv->com_reg_1_state, port: dev->iobase + COM_REG_1); |
309 | outw(value: devpriv->com_reg_2_state, port: dev->iobase + COM_REG_2); |
310 | } |
311 | |
312 | /* Setup the Mux-Gain Counter */ |
313 | for (i = 0; i < cmd->chanlist_len; ++i) { |
314 | chan = CR_CHAN(cmd->chanlist[i]); |
315 | gain = CR_RANGE(cmd->chanlist[i]); |
316 | outw(value: i, port: dev->iobase + MUX_CNTR_REG); |
317 | tmp = chan | (gain << 6); |
318 | if (i == cmd->scan_end_arg - 1) |
319 | tmp |= 0x0010; /* set LASTONE bit */ |
320 | outw(value: tmp, port: dev->iobase + MUX_GAIN_REG); |
321 | } |
322 | |
323 | /* |
324 | * Now program the sample interval timer. |
325 | * Figure out which clock to use then get an appropriate timer value. |
326 | */ |
327 | if (cmd->convert_arg < 65536000) { |
328 | base_clock = CLOCK_1_MHZ; |
329 | timer = cmd->convert_arg / 1000; |
330 | } else if (cmd->convert_arg < 655360000) { |
331 | base_clock = CLOCK_100_KHZ; |
332 | timer = cmd->convert_arg / 10000; |
333 | } else /* cmd->convert_arg < 6553600000 */ { |
334 | base_clock = CLOCK_10_KHZ; |
335 | timer = cmd->convert_arg / 100000; |
336 | } |
337 | outw(value: 0xFF03, port: dev->iobase + AM9513A_COM_REG); |
338 | outw(value: base_clock, port: dev->iobase + AM9513A_DATA_REG); |
339 | outw(value: 0xFF0B, port: dev->iobase + AM9513A_COM_REG); |
340 | outw(value: 0x2, port: dev->iobase + AM9513A_DATA_REG); |
341 | outw(value: 0xFF44, port: dev->iobase + AM9513A_COM_REG); |
342 | outw(value: 0xFFF3, port: dev->iobase + AM9513A_COM_REG); |
343 | outw(value: timer, port: dev->iobase + AM9513A_DATA_REG); |
344 | outw(value: 0xFF24, port: dev->iobase + AM9513A_COM_REG); |
345 | |
346 | /* Now figure out how many samples to get */ |
347 | /* and program the sample counter */ |
348 | sample_count = cmd->stop_arg * cmd->scan_end_arg; |
349 | outw(value: 0xFF04, port: dev->iobase + AM9513A_COM_REG); |
350 | outw(value: 0x1025, port: dev->iobase + AM9513A_DATA_REG); |
351 | outw(value: 0xFF0C, port: dev->iobase + AM9513A_COM_REG); |
352 | if (sample_count < 65536) { |
353 | /* use only Counter 4 */ |
354 | outw(value: sample_count, port: dev->iobase + AM9513A_DATA_REG); |
355 | outw(value: 0xFF48, port: dev->iobase + AM9513A_COM_REG); |
356 | outw(value: 0xFFF4, port: dev->iobase + AM9513A_COM_REG); |
357 | outw(value: 0xFF28, port: dev->iobase + AM9513A_COM_REG); |
358 | devpriv->com_reg_1_state &= ~COMREG1_1632CNT; |
359 | outw(value: devpriv->com_reg_1_state, port: dev->iobase + COM_REG_1); |
360 | } else { |
361 | /* Counter 4 and 5 are needed */ |
362 | |
363 | tmp = sample_count & 0xFFFF; |
364 | if (tmp) |
365 | outw(value: tmp - 1, port: dev->iobase + AM9513A_DATA_REG); |
366 | else |
367 | outw(value: 0xFFFF, port: dev->iobase + AM9513A_DATA_REG); |
368 | |
369 | outw(value: 0xFF48, port: dev->iobase + AM9513A_COM_REG); |
370 | outw(value: 0, port: dev->iobase + AM9513A_DATA_REG); |
371 | outw(value: 0xFF28, port: dev->iobase + AM9513A_COM_REG); |
372 | outw(value: 0xFF05, port: dev->iobase + AM9513A_COM_REG); |
373 | outw(value: 0x25, port: dev->iobase + AM9513A_DATA_REG); |
374 | outw(value: 0xFF0D, port: dev->iobase + AM9513A_COM_REG); |
375 | tmp = sample_count & 0xFFFF; |
376 | if ((tmp == 0) || (tmp == 1)) { |
377 | outw(value: (sample_count >> 16) & 0xFFFF, |
378 | port: dev->iobase + AM9513A_DATA_REG); |
379 | } else { |
380 | outw(value: ((sample_count >> 16) & 0xFFFF) + 1, |
381 | port: dev->iobase + AM9513A_DATA_REG); |
382 | } |
383 | outw(value: 0xFF70, port: dev->iobase + AM9513A_COM_REG); |
384 | devpriv->com_reg_1_state |= COMREG1_1632CNT; |
385 | outw(value: devpriv->com_reg_1_state, port: dev->iobase + COM_REG_1); |
386 | } |
387 | |
388 | /* |
389 | * Program the scan interval timer ONLY IF SCANNING IS ENABLED. |
390 | * Figure out which clock to use then get an appropriate timer value. |
391 | */ |
392 | if (cmd->chanlist_len > 1) { |
393 | if (cmd->scan_begin_arg < 65536000) { |
394 | base_clock = CLOCK_1_MHZ; |
395 | timer = cmd->scan_begin_arg / 1000; |
396 | } else if (cmd->scan_begin_arg < 655360000) { |
397 | base_clock = CLOCK_100_KHZ; |
398 | timer = cmd->scan_begin_arg / 10000; |
399 | } else /* cmd->scan_begin_arg < 6553600000 */ { |
400 | base_clock = CLOCK_10_KHZ; |
401 | timer = cmd->scan_begin_arg / 100000; |
402 | } |
403 | outw(value: 0xFF02, port: dev->iobase + AM9513A_COM_REG); |
404 | outw(value: base_clock, port: dev->iobase + AM9513A_DATA_REG); |
405 | outw(value: 0xFF0A, port: dev->iobase + AM9513A_COM_REG); |
406 | outw(value: 0x2, port: dev->iobase + AM9513A_DATA_REG); |
407 | outw(value: 0xFF42, port: dev->iobase + AM9513A_COM_REG); |
408 | outw(value: 0xFFF2, port: dev->iobase + AM9513A_COM_REG); |
409 | outw(value: timer, port: dev->iobase + AM9513A_DATA_REG); |
410 | outw(value: 0xFF22, port: dev->iobase + AM9513A_COM_REG); |
411 | } |
412 | |
413 | /* Clear the A/D FIFO and reset the MUX counter */ |
414 | outw(value: 0, port: dev->iobase + AD_CLEAR_REG); |
415 | outw(value: 0, port: dev->iobase + MUX_CNTR_REG); |
416 | outw(value: 0, port: dev->iobase + INT2CLR_REG); |
417 | /* enable this acquisition operation */ |
418 | devpriv->com_reg_1_state |= COMREG1_DAQEN; |
419 | outw(value: devpriv->com_reg_1_state, port: dev->iobase + COM_REG_1); |
420 | /* enable interrupts for conversion completion */ |
421 | devpriv->com_reg_1_state |= COMREG1_CONVINTEN; |
422 | devpriv->com_reg_2_state |= COMREG2_INTEN; |
423 | outw(value: devpriv->com_reg_1_state, port: dev->iobase + COM_REG_1); |
424 | outw(value: devpriv->com_reg_2_state, port: dev->iobase + COM_REG_2); |
425 | /* apply a trigger. this starts the counters! */ |
426 | outw(value: 0, port: dev->iobase + START_DAQ_REG); |
427 | |
428 | return 0; |
429 | } |
430 | |
431 | /* This will cancel a running acquisition operation */ |
432 | static int atmio16d_ai_cancel(struct comedi_device *dev, |
433 | struct comedi_subdevice *s) |
434 | { |
435 | reset_atmio16d(dev); |
436 | |
437 | return 0; |
438 | } |
439 | |
440 | static int atmio16d_ai_eoc(struct comedi_device *dev, |
441 | struct comedi_subdevice *s, |
442 | struct comedi_insn *insn, |
443 | unsigned long context) |
444 | { |
445 | unsigned int status; |
446 | |
447 | status = inw(port: dev->iobase + STAT_REG); |
448 | if (status & STAT_AD_CONVAVAIL) |
449 | return 0; |
450 | if (status & STAT_AD_OVERFLOW) { |
451 | outw(value: 0, port: dev->iobase + AD_CLEAR_REG); |
452 | return -EOVERFLOW; |
453 | } |
454 | return -EBUSY; |
455 | } |
456 | |
457 | static int atmio16d_ai_insn_read(struct comedi_device *dev, |
458 | struct comedi_subdevice *s, |
459 | struct comedi_insn *insn, unsigned int *data) |
460 | { |
461 | struct atmio16d_private *devpriv = dev->private; |
462 | int i; |
463 | int chan; |
464 | int gain; |
465 | int ret; |
466 | |
467 | chan = CR_CHAN(insn->chanspec); |
468 | gain = CR_RANGE(insn->chanspec); |
469 | |
470 | /* reset the Analog input circuitry */ |
471 | /* outw( 0, dev->iobase+AD_CLEAR_REG ); */ |
472 | /* reset the Analog Input MUX Counter to 0 */ |
473 | /* outw( 0, dev->iobase+MUX_CNTR_REG ); */ |
474 | |
475 | /* set the Input MUX gain */ |
476 | outw(value: chan | (gain << 6), port: dev->iobase + MUX_GAIN_REG); |
477 | |
478 | for (i = 0; i < insn->n; i++) { |
479 | /* start the conversion */ |
480 | outw(value: 0, port: dev->iobase + START_CONVERT_REG); |
481 | |
482 | /* wait for it to finish */ |
483 | ret = comedi_timeout(dev, s, insn, cb: atmio16d_ai_eoc, context: 0); |
484 | if (ret) |
485 | return ret; |
486 | |
487 | /* read the data now */ |
488 | data[i] = inw(port: dev->iobase + AD_FIFO_REG); |
489 | /* change to two's complement if need be */ |
490 | if (devpriv->adc_coding == adc_2comp) |
491 | data[i] ^= 0x800; |
492 | } |
493 | |
494 | return i; |
495 | } |
496 | |
497 | static int atmio16d_ao_insn_write(struct comedi_device *dev, |
498 | struct comedi_subdevice *s, |
499 | struct comedi_insn *insn, |
500 | unsigned int *data) |
501 | { |
502 | struct atmio16d_private *devpriv = dev->private; |
503 | unsigned int chan = CR_CHAN(insn->chanspec); |
504 | unsigned int reg = (chan) ? DAC1_REG : DAC0_REG; |
505 | bool munge = false; |
506 | int i; |
507 | |
508 | if (chan == 0 && devpriv->dac0_coding == dac_2comp) |
509 | munge = true; |
510 | if (chan == 1 && devpriv->dac1_coding == dac_2comp) |
511 | munge = true; |
512 | |
513 | for (i = 0; i < insn->n; i++) { |
514 | unsigned int val = data[i]; |
515 | |
516 | s->readback[chan] = val; |
517 | |
518 | if (munge) |
519 | val ^= 0x800; |
520 | |
521 | outw(value: val, port: dev->iobase + reg); |
522 | } |
523 | |
524 | return insn->n; |
525 | } |
526 | |
527 | static int atmio16d_dio_insn_bits(struct comedi_device *dev, |
528 | struct comedi_subdevice *s, |
529 | struct comedi_insn *insn, |
530 | unsigned int *data) |
531 | { |
532 | if (comedi_dio_update_state(s, data)) |
533 | outw(value: s->state, port: dev->iobase + MIO_16_DIG_OUT_REG); |
534 | |
535 | data[1] = inw(port: dev->iobase + MIO_16_DIG_IN_REG); |
536 | |
537 | return insn->n; |
538 | } |
539 | |
540 | static int atmio16d_dio_insn_config(struct comedi_device *dev, |
541 | struct comedi_subdevice *s, |
542 | struct comedi_insn *insn, |
543 | unsigned int *data) |
544 | { |
545 | struct atmio16d_private *devpriv = dev->private; |
546 | unsigned int chan = CR_CHAN(insn->chanspec); |
547 | unsigned int mask; |
548 | int ret; |
549 | |
550 | if (chan < 4) |
551 | mask = 0x0f; |
552 | else |
553 | mask = 0xf0; |
554 | |
555 | ret = comedi_dio_insn_config(dev, s, insn, data, mask); |
556 | if (ret) |
557 | return ret; |
558 | |
559 | devpriv->com_reg_2_state &= ~(COMREG2_DOUTEN0 | COMREG2_DOUTEN1); |
560 | if (s->io_bits & 0x0f) |
561 | devpriv->com_reg_2_state |= COMREG2_DOUTEN0; |
562 | if (s->io_bits & 0xf0) |
563 | devpriv->com_reg_2_state |= COMREG2_DOUTEN1; |
564 | outw(value: devpriv->com_reg_2_state, port: dev->iobase + COM_REG_2); |
565 | |
566 | return insn->n; |
567 | } |
568 | |
569 | static int atmio16d_attach(struct comedi_device *dev, |
570 | struct comedi_devconfig *it) |
571 | { |
572 | const struct atmio16_board_t *board = dev->board_ptr; |
573 | struct atmio16d_private *devpriv; |
574 | struct comedi_subdevice *s; |
575 | int ret; |
576 | |
577 | ret = comedi_request_region(dev, start: it->options[0], len: 0x20); |
578 | if (ret) |
579 | return ret; |
580 | |
581 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
582 | if (ret) |
583 | return ret; |
584 | |
585 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
586 | if (!devpriv) |
587 | return -ENOMEM; |
588 | |
589 | /* reset the atmio16d hardware */ |
590 | reset_atmio16d(dev); |
591 | |
592 | if (it->options[1]) { |
593 | ret = request_irq(irq: it->options[1], handler: atmio16d_interrupt, flags: 0, |
594 | name: dev->board_name, dev); |
595 | if (ret == 0) |
596 | dev->irq = it->options[1]; |
597 | } |
598 | |
599 | /* set device options */ |
600 | devpriv->adc_mux = it->options[5]; |
601 | devpriv->adc_range = it->options[6]; |
602 | |
603 | devpriv->dac0_range = it->options[7]; |
604 | devpriv->dac0_reference = it->options[8]; |
605 | devpriv->dac0_coding = it->options[9]; |
606 | devpriv->dac1_range = it->options[10]; |
607 | devpriv->dac1_reference = it->options[11]; |
608 | devpriv->dac1_coding = it->options[12]; |
609 | |
610 | /* setup sub-devices */ |
611 | s = &dev->subdevices[0]; |
612 | /* ai subdevice */ |
613 | s->type = COMEDI_SUBD_AI; |
614 | s->subdev_flags = SDF_READABLE | SDF_GROUND; |
615 | s->n_chan = (devpriv->adc_mux ? 16 : 8); |
616 | s->insn_read = atmio16d_ai_insn_read; |
617 | s->maxdata = 0xfff; /* 4095 decimal */ |
618 | switch (devpriv->adc_range) { |
619 | case adc_bipolar10: |
620 | s->range_table = &range_atmio16d_ai_10_bipolar; |
621 | break; |
622 | case adc_bipolar5: |
623 | s->range_table = &range_atmio16d_ai_5_bipolar; |
624 | break; |
625 | case adc_unipolar10: |
626 | s->range_table = &range_atmio16d_ai_unipolar; |
627 | break; |
628 | } |
629 | if (dev->irq) { |
630 | dev->read_subdev = s; |
631 | s->subdev_flags |= SDF_CMD_READ; |
632 | s->len_chanlist = 16; |
633 | s->do_cmdtest = atmio16d_ai_cmdtest; |
634 | s->do_cmd = atmio16d_ai_cmd; |
635 | s->cancel = atmio16d_ai_cancel; |
636 | } |
637 | |
638 | /* ao subdevice */ |
639 | s = &dev->subdevices[1]; |
640 | s->type = COMEDI_SUBD_AO; |
641 | s->subdev_flags = SDF_WRITABLE; |
642 | s->n_chan = 2; |
643 | s->maxdata = 0xfff; /* 4095 decimal */ |
644 | s->range_table_list = devpriv->ao_range_type_list; |
645 | switch (devpriv->dac0_range) { |
646 | case dac_bipolar: |
647 | devpriv->ao_range_type_list[0] = &range_bipolar10; |
648 | break; |
649 | case dac_unipolar: |
650 | devpriv->ao_range_type_list[0] = &range_unipolar10; |
651 | break; |
652 | } |
653 | switch (devpriv->dac1_range) { |
654 | case dac_bipolar: |
655 | devpriv->ao_range_type_list[1] = &range_bipolar10; |
656 | break; |
657 | case dac_unipolar: |
658 | devpriv->ao_range_type_list[1] = &range_unipolar10; |
659 | break; |
660 | } |
661 | s->insn_write = atmio16d_ao_insn_write; |
662 | |
663 | ret = comedi_alloc_subdev_readback(s); |
664 | if (ret) |
665 | return ret; |
666 | |
667 | /* Digital I/O */ |
668 | s = &dev->subdevices[2]; |
669 | s->type = COMEDI_SUBD_DIO; |
670 | s->subdev_flags = SDF_WRITABLE | SDF_READABLE; |
671 | s->n_chan = 8; |
672 | s->insn_bits = atmio16d_dio_insn_bits; |
673 | s->insn_config = atmio16d_dio_insn_config; |
674 | s->maxdata = 1; |
675 | s->range_table = &range_digital; |
676 | |
677 | /* 8255 subdevice */ |
678 | s = &dev->subdevices[3]; |
679 | if (board->has_8255) { |
680 | ret = subdev_8255_io_init(dev, s, regbase: 0x00); |
681 | if (ret) |
682 | return ret; |
683 | } else { |
684 | s->type = COMEDI_SUBD_UNUSED; |
685 | } |
686 | |
687 | /* don't yet know how to deal with counter/timers */ |
688 | #if 0 |
689 | s = &dev->subdevices[4]; |
690 | /* do */ |
691 | s->type = COMEDI_SUBD_TIMER; |
692 | s->n_chan = 0; |
693 | s->maxdata = 0 |
694 | #endif |
695 | |
696 | return 0; |
697 | } |
698 | |
699 | static void atmio16d_detach(struct comedi_device *dev) |
700 | { |
701 | reset_atmio16d(dev); |
702 | comedi_legacy_detach(dev); |
703 | } |
704 | |
705 | static const struct atmio16_board_t atmio16_boards[] = { |
706 | { |
707 | .name = "atmio16" , |
708 | .has_8255 = 0, |
709 | }, { |
710 | .name = "atmio16d" , |
711 | .has_8255 = 1, |
712 | }, |
713 | }; |
714 | |
715 | static struct comedi_driver atmio16d_driver = { |
716 | .driver_name = "atmio16" , |
717 | .module = THIS_MODULE, |
718 | .attach = atmio16d_attach, |
719 | .detach = atmio16d_detach, |
720 | .board_name = &atmio16_boards[0].name, |
721 | .num_names = ARRAY_SIZE(atmio16_boards), |
722 | .offset = sizeof(struct atmio16_board_t), |
723 | }; |
724 | module_comedi_driver(atmio16d_driver); |
725 | |
726 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
727 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
728 | MODULE_LICENSE("GPL" ); |
729 | |