1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * comedi/drivers/rti800.c |
4 | * Hardware driver for Analog Devices RTI-800/815 board |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 1998 David A. Schleef <ds@schleef.org> |
8 | */ |
9 | |
10 | /* |
11 | * Driver: rti800 |
12 | * Description: Analog Devices RTI-800/815 |
13 | * Devices: [Analog Devices] RTI-800 (rti800), RTI-815 (rti815) |
14 | * Author: David A. Schleef <ds@schleef.org> |
15 | * Status: unknown |
16 | * Updated: Fri, 05 Sep 2008 14:50:44 +0100 |
17 | * |
18 | * Configuration options: |
19 | * [0] - I/O port base address |
20 | * [1] - IRQ (not supported / unused) |
21 | * [2] - A/D mux/reference (number of channels) |
22 | * 0 = differential |
23 | * 1 = pseudodifferential (common) |
24 | * 2 = single-ended |
25 | * [3] - A/D range |
26 | * 0 = [-10,10] |
27 | * 1 = [-5,5] |
28 | * 2 = [0,10] |
29 | * [4] - A/D encoding |
30 | * 0 = two's complement |
31 | * 1 = straight binary |
32 | * [5] - DAC 0 range |
33 | * 0 = [-10,10] |
34 | * 1 = [0,10] |
35 | * [6] - DAC 0 encoding |
36 | * 0 = two's complement |
37 | * 1 = straight binary |
38 | * [7] - DAC 1 range (same as DAC 0) |
39 | * [8] - DAC 1 encoding (same as DAC 0) |
40 | */ |
41 | |
42 | #include <linux/module.h> |
43 | #include <linux/delay.h> |
44 | #include <linux/interrupt.h> |
45 | #include <linux/comedi/comedidev.h> |
46 | |
47 | /* |
48 | * Register map |
49 | */ |
50 | #define RTI800_CSR 0x00 |
51 | #define RTI800_CSR_BUSY BIT(7) |
52 | #define RTI800_CSR_DONE BIT(6) |
53 | #define RTI800_CSR_OVERRUN BIT(5) |
54 | #define RTI800_CSR_TCR BIT(4) |
55 | #define RTI800_CSR_DMA_ENAB BIT(3) |
56 | #define RTI800_CSR_INTR_TC BIT(2) |
57 | #define RTI800_CSR_INTR_EC BIT(1) |
58 | #define RTI800_CSR_INTR_OVRN BIT(0) |
59 | #define RTI800_MUXGAIN 0x01 |
60 | #define RTI800_CONVERT 0x02 |
61 | #define RTI800_ADCLO 0x03 |
62 | #define RTI800_ADCHI 0x04 |
63 | #define RTI800_DAC0LO 0x05 |
64 | #define RTI800_DAC0HI 0x06 |
65 | #define RTI800_DAC1LO 0x07 |
66 | #define RTI800_DAC1HI 0x08 |
67 | #define RTI800_CLRFLAGS 0x09 |
68 | #define RTI800_DI 0x0a |
69 | #define RTI800_DO 0x0b |
70 | #define RTI800_9513A_DATA 0x0c |
71 | #define RTI800_9513A_CNTRL 0x0d |
72 | #define RTI800_9513A_STATUS 0x0d |
73 | |
74 | static const struct comedi_lrange range_rti800_ai_10_bipolar = { |
75 | 4, { |
76 | BIP_RANGE(10), |
77 | BIP_RANGE(1), |
78 | BIP_RANGE(0.1), |
79 | BIP_RANGE(0.02) |
80 | } |
81 | }; |
82 | |
83 | static const struct comedi_lrange range_rti800_ai_5_bipolar = { |
84 | 4, { |
85 | BIP_RANGE(5), |
86 | BIP_RANGE(0.5), |
87 | BIP_RANGE(0.05), |
88 | BIP_RANGE(0.01) |
89 | } |
90 | }; |
91 | |
92 | static const struct comedi_lrange range_rti800_ai_unipolar = { |
93 | 4, { |
94 | UNI_RANGE(10), |
95 | UNI_RANGE(1), |
96 | UNI_RANGE(0.1), |
97 | UNI_RANGE(0.02) |
98 | } |
99 | }; |
100 | |
101 | static const struct comedi_lrange *const rti800_ai_ranges[] = { |
102 | &range_rti800_ai_10_bipolar, |
103 | &range_rti800_ai_5_bipolar, |
104 | &range_rti800_ai_unipolar, |
105 | }; |
106 | |
107 | static const struct comedi_lrange *const rti800_ao_ranges[] = { |
108 | &range_bipolar10, |
109 | &range_unipolar10, |
110 | }; |
111 | |
112 | struct rti800_board { |
113 | const char *name; |
114 | int has_ao; |
115 | }; |
116 | |
117 | static const struct rti800_board rti800_boardtypes[] = { |
118 | { |
119 | .name = "rti800" , |
120 | }, { |
121 | .name = "rti815" , |
122 | .has_ao = 1, |
123 | }, |
124 | }; |
125 | |
126 | struct rti800_private { |
127 | bool adc_2comp; |
128 | bool dac_2comp[2]; |
129 | const struct comedi_lrange *ao_range_type_list[2]; |
130 | unsigned char muxgain_bits; |
131 | }; |
132 | |
133 | static int rti800_ai_eoc(struct comedi_device *dev, |
134 | struct comedi_subdevice *s, |
135 | struct comedi_insn *insn, |
136 | unsigned long context) |
137 | { |
138 | unsigned char status; |
139 | |
140 | status = inb(port: dev->iobase + RTI800_CSR); |
141 | if (status & RTI800_CSR_OVERRUN) { |
142 | outb(value: 0, port: dev->iobase + RTI800_CLRFLAGS); |
143 | return -EOVERFLOW; |
144 | } |
145 | if (status & RTI800_CSR_DONE) |
146 | return 0; |
147 | return -EBUSY; |
148 | } |
149 | |
150 | static int rti800_ai_insn_read(struct comedi_device *dev, |
151 | struct comedi_subdevice *s, |
152 | struct comedi_insn *insn, |
153 | unsigned int *data) |
154 | { |
155 | struct rti800_private *devpriv = dev->private; |
156 | unsigned int chan = CR_CHAN(insn->chanspec); |
157 | unsigned int gain = CR_RANGE(insn->chanspec); |
158 | unsigned char muxgain_bits; |
159 | int ret; |
160 | int i; |
161 | |
162 | inb(port: dev->iobase + RTI800_ADCHI); |
163 | outb(value: 0, port: dev->iobase + RTI800_CLRFLAGS); |
164 | |
165 | muxgain_bits = chan | (gain << 5); |
166 | if (muxgain_bits != devpriv->muxgain_bits) { |
167 | devpriv->muxgain_bits = muxgain_bits; |
168 | outb(value: devpriv->muxgain_bits, port: dev->iobase + RTI800_MUXGAIN); |
169 | /* |
170 | * Without a delay here, the RTI_CSR_OVERRUN bit |
171 | * gets set, and you will have an error. |
172 | */ |
173 | if (insn->n > 0) { |
174 | int delay = (gain == 0) ? 10 : |
175 | (gain == 1) ? 20 : |
176 | (gain == 2) ? 40 : 80; |
177 | |
178 | udelay(delay); |
179 | } |
180 | } |
181 | |
182 | for (i = 0; i < insn->n; i++) { |
183 | unsigned int val; |
184 | |
185 | outb(value: 0, port: dev->iobase + RTI800_CONVERT); |
186 | |
187 | ret = comedi_timeout(dev, s, insn, cb: rti800_ai_eoc, context: 0); |
188 | if (ret) |
189 | return ret; |
190 | |
191 | val = inb(port: dev->iobase + RTI800_ADCLO); |
192 | val |= (inb(port: dev->iobase + RTI800_ADCHI) & 0xf) << 8; |
193 | |
194 | if (devpriv->adc_2comp) |
195 | val = comedi_offset_munge(s, val); |
196 | |
197 | data[i] = val; |
198 | } |
199 | |
200 | return insn->n; |
201 | } |
202 | |
203 | static int rti800_ao_insn_write(struct comedi_device *dev, |
204 | struct comedi_subdevice *s, |
205 | struct comedi_insn *insn, |
206 | unsigned int *data) |
207 | { |
208 | struct rti800_private *devpriv = dev->private; |
209 | unsigned int chan = CR_CHAN(insn->chanspec); |
210 | int reg_lo = chan ? RTI800_DAC1LO : RTI800_DAC0LO; |
211 | int reg_hi = chan ? RTI800_DAC1HI : RTI800_DAC0HI; |
212 | int i; |
213 | |
214 | for (i = 0; i < insn->n; i++) { |
215 | unsigned int val = data[i]; |
216 | |
217 | s->readback[chan] = val; |
218 | |
219 | if (devpriv->dac_2comp[chan]) |
220 | val = comedi_offset_munge(s, val); |
221 | |
222 | outb(value: val & 0xff, port: dev->iobase + reg_lo); |
223 | outb(value: (val >> 8) & 0xff, port: dev->iobase + reg_hi); |
224 | } |
225 | |
226 | return insn->n; |
227 | } |
228 | |
229 | static int rti800_di_insn_bits(struct comedi_device *dev, |
230 | struct comedi_subdevice *s, |
231 | struct comedi_insn *insn, |
232 | unsigned int *data) |
233 | { |
234 | data[1] = inb(port: dev->iobase + RTI800_DI); |
235 | return insn->n; |
236 | } |
237 | |
238 | static int rti800_do_insn_bits(struct comedi_device *dev, |
239 | struct comedi_subdevice *s, |
240 | struct comedi_insn *insn, |
241 | unsigned int *data) |
242 | { |
243 | if (comedi_dio_update_state(s, data)) { |
244 | /* Outputs are inverted... */ |
245 | outb(value: s->state ^ 0xff, port: dev->iobase + RTI800_DO); |
246 | } |
247 | |
248 | data[1] = s->state; |
249 | |
250 | return insn->n; |
251 | } |
252 | |
253 | static int rti800_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
254 | { |
255 | const struct rti800_board *board = dev->board_ptr; |
256 | struct rti800_private *devpriv; |
257 | struct comedi_subdevice *s; |
258 | int ret; |
259 | |
260 | ret = comedi_request_region(dev, start: it->options[0], len: 0x10); |
261 | if (ret) |
262 | return ret; |
263 | |
264 | outb(value: 0, port: dev->iobase + RTI800_CSR); |
265 | inb(port: dev->iobase + RTI800_ADCHI); |
266 | outb(value: 0, port: dev->iobase + RTI800_CLRFLAGS); |
267 | |
268 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
269 | if (!devpriv) |
270 | return -ENOMEM; |
271 | |
272 | devpriv->adc_2comp = (it->options[4] == 0); |
273 | devpriv->dac_2comp[0] = (it->options[6] == 0); |
274 | devpriv->dac_2comp[1] = (it->options[8] == 0); |
275 | /* invalid, forces the MUXGAIN register to be set when first used */ |
276 | devpriv->muxgain_bits = 0xff; |
277 | |
278 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
279 | if (ret) |
280 | return ret; |
281 | |
282 | s = &dev->subdevices[0]; |
283 | /* ai subdevice */ |
284 | s->type = COMEDI_SUBD_AI; |
285 | s->subdev_flags = SDF_READABLE | SDF_GROUND; |
286 | s->n_chan = (it->options[2] ? 16 : 8); |
287 | s->insn_read = rti800_ai_insn_read; |
288 | s->maxdata = 0x0fff; |
289 | s->range_table = (it->options[3] < ARRAY_SIZE(rti800_ai_ranges)) |
290 | ? rti800_ai_ranges[it->options[3]] |
291 | : &range_unknown; |
292 | |
293 | s = &dev->subdevices[1]; |
294 | if (board->has_ao) { |
295 | /* ao subdevice (only on rti815) */ |
296 | s->type = COMEDI_SUBD_AO; |
297 | s->subdev_flags = SDF_WRITABLE; |
298 | s->n_chan = 2; |
299 | s->maxdata = 0x0fff; |
300 | s->range_table_list = devpriv->ao_range_type_list; |
301 | devpriv->ao_range_type_list[0] = |
302 | (it->options[5] < ARRAY_SIZE(rti800_ao_ranges)) |
303 | ? rti800_ao_ranges[it->options[5]] |
304 | : &range_unknown; |
305 | devpriv->ao_range_type_list[1] = |
306 | (it->options[7] < ARRAY_SIZE(rti800_ao_ranges)) |
307 | ? rti800_ao_ranges[it->options[7]] |
308 | : &range_unknown; |
309 | s->insn_write = rti800_ao_insn_write; |
310 | |
311 | ret = comedi_alloc_subdev_readback(s); |
312 | if (ret) |
313 | return ret; |
314 | } else { |
315 | s->type = COMEDI_SUBD_UNUSED; |
316 | } |
317 | |
318 | s = &dev->subdevices[2]; |
319 | /* di */ |
320 | s->type = COMEDI_SUBD_DI; |
321 | s->subdev_flags = SDF_READABLE; |
322 | s->n_chan = 8; |
323 | s->insn_bits = rti800_di_insn_bits; |
324 | s->maxdata = 1; |
325 | s->range_table = &range_digital; |
326 | |
327 | s = &dev->subdevices[3]; |
328 | /* do */ |
329 | s->type = COMEDI_SUBD_DO; |
330 | s->subdev_flags = SDF_WRITABLE; |
331 | s->n_chan = 8; |
332 | s->insn_bits = rti800_do_insn_bits; |
333 | s->maxdata = 1; |
334 | s->range_table = &range_digital; |
335 | |
336 | /* |
337 | * There is also an Am9513 timer on these boards. This subdevice |
338 | * is not currently supported. |
339 | */ |
340 | |
341 | return 0; |
342 | } |
343 | |
344 | static struct comedi_driver rti800_driver = { |
345 | .driver_name = "rti800" , |
346 | .module = THIS_MODULE, |
347 | .attach = rti800_attach, |
348 | .detach = comedi_legacy_detach, |
349 | .num_names = ARRAY_SIZE(rti800_boardtypes), |
350 | .board_name = &rti800_boardtypes[0].name, |
351 | .offset = sizeof(struct rti800_board), |
352 | }; |
353 | module_comedi_driver(rti800_driver); |
354 | |
355 | MODULE_DESCRIPTION("Comedi: RTI-800 Multifunction Analog/Digital board" ); |
356 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
357 | MODULE_LICENSE("GPL" ); |
358 | |