1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * comedi/drivers/das08.c |
4 | * comedi module for common DAS08 support (used by ISA/PCI/PCMCIA drivers) |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
8 | * Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net> |
9 | * Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org> |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/comedi/comedidev.h> |
14 | #include <linux/comedi/comedi_8255.h> |
15 | #include <linux/comedi/comedi_8254.h> |
16 | |
17 | #include "das08.h" |
18 | |
19 | /* |
20 | * Data format of DAS08_AI_LSB_REG and DAS08_AI_MSB_REG depends on |
21 | * 'ai_encoding' member of board structure: |
22 | * |
23 | * das08_encode12 : DATA[11..4] = MSB[7..0], DATA[3..0] = LSB[7..4]. |
24 | * das08_pcm_encode12 : DATA[11..8] = MSB[3..0], DATA[7..9] = LSB[7..0]. |
25 | * das08_encode16 : SIGN = MSB[7], MAGNITUDE[14..8] = MSB[6..0], |
26 | * MAGNITUDE[7..0] = LSB[7..0]. |
27 | * SIGN==0 for negative input, SIGN==1 for positive input. |
28 | * Note: when read a second time after conversion |
29 | * complete, MSB[7] is an "over-range" bit. |
30 | */ |
31 | #define DAS08_AI_LSB_REG 0x00 /* (R) AI least significant bits */ |
32 | #define DAS08_AI_MSB_REG 0x01 /* (R) AI most significant bits */ |
33 | #define DAS08_AI_TRIG_REG 0x01 /* (W) AI software trigger */ |
34 | #define DAS08_STATUS_REG 0x02 /* (R) status */ |
35 | #define DAS08_STATUS_AI_BUSY BIT(7) /* AI conversion in progress */ |
36 | /* |
37 | * The IRQ status bit is set to 1 by a rising edge on the external interrupt |
38 | * input (which may be jumpered to the pacer output). It is cleared by |
39 | * setting the INTE control bit to 0. Not present on "JR" boards. |
40 | */ |
41 | #define DAS08_STATUS_IRQ BIT(3) /* latched interrupt input */ |
42 | /* digital inputs (not "JR" boards) */ |
43 | #define DAS08_STATUS_DI(x) (((x) & 0x70) >> 4) |
44 | #define DAS08_CONTROL_REG 0x02 /* (W) control */ |
45 | /* |
46 | * Note: The AI multiplexor channel can also be read from status register using |
47 | * the same mask. |
48 | */ |
49 | #define DAS08_CONTROL_MUX_MASK 0x7 /* multiplexor channel mask */ |
50 | #define DAS08_CONTROL_MUX(x) ((x) & DAS08_CONTROL_MUX_MASK) /* mux channel */ |
51 | #define DAS08_CONTROL_INTE BIT(3) /* interrupt enable (not "JR" boards) */ |
52 | #define DAS08_CONTROL_DO_MASK 0xf0 /* digital outputs mask (not "JR") */ |
53 | /* digital outputs (not "JR" boards) */ |
54 | #define DAS08_CONTROL_DO(x) (((x) << 4) & DAS08_CONTROL_DO_MASK) |
55 | /* |
56 | * (R/W) programmable AI gain ("PGx" and "AOx" boards): |
57 | * + bits 3..0 (R/W) show/set the gain for the current AI mux channel |
58 | * + bits 6..4 (R) show the current AI mux channel |
59 | * + bit 7 (R) not unused |
60 | */ |
61 | #define DAS08_GAIN_REG 0x03 |
62 | |
63 | #define DAS08JR_DI_REG 0x03 /* (R) digital inputs ("JR" boards) */ |
64 | #define DAS08JR_DO_REG 0x03 /* (W) digital outputs ("JR" boards) */ |
65 | /* (W) analog output l.s.b. registers for 2 channels ("JR" boards) */ |
66 | #define DAS08JR_AO_LSB_REG(x) ((x) ? 0x06 : 0x04) |
67 | /* (W) analog output m.s.b. registers for 2 channels ("JR" boards) */ |
68 | #define DAS08JR_AO_MSB_REG(x) ((x) ? 0x07 : 0x05) |
69 | /* |
70 | * (R) update analog outputs ("JR" boards set for simultaneous output) |
71 | * (same register as digital inputs) |
72 | */ |
73 | #define DAS08JR_AO_UPDATE_REG 0x03 |
74 | |
75 | /* (W) analog output l.s.b. registers for 2 channels ("AOx" boards) */ |
76 | #define DAS08AOX_AO_LSB_REG(x) ((x) ? 0x0a : 0x08) |
77 | /* (W) analog output m.s.b. registers for 2 channels ("AOx" boards) */ |
78 | #define DAS08AOX_AO_MSB_REG(x) ((x) ? 0x0b : 0x09) |
79 | /* |
80 | * (R) update analog outputs ("AOx" boards set for simultaneous output) |
81 | * (any of the analog output registers could be used for this) |
82 | */ |
83 | #define DAS08AOX_AO_UPDATE_REG 0x08 |
84 | |
85 | /* gainlist same as _pgx_ below */ |
86 | |
87 | static const struct comedi_lrange das08_pgl_ai_range = { |
88 | 9, { |
89 | BIP_RANGE(10), |
90 | BIP_RANGE(5), |
91 | BIP_RANGE(2.5), |
92 | BIP_RANGE(1.25), |
93 | BIP_RANGE(0.625), |
94 | UNI_RANGE(10), |
95 | UNI_RANGE(5), |
96 | UNI_RANGE(2.5), |
97 | UNI_RANGE(1.25) |
98 | } |
99 | }; |
100 | |
101 | static const struct comedi_lrange das08_pgh_ai_range = { |
102 | 12, { |
103 | BIP_RANGE(10), |
104 | BIP_RANGE(5), |
105 | BIP_RANGE(1), |
106 | BIP_RANGE(0.5), |
107 | BIP_RANGE(0.1), |
108 | BIP_RANGE(0.05), |
109 | BIP_RANGE(0.01), |
110 | BIP_RANGE(0.005), |
111 | UNI_RANGE(10), |
112 | UNI_RANGE(1), |
113 | UNI_RANGE(0.1), |
114 | UNI_RANGE(0.01) |
115 | } |
116 | }; |
117 | |
118 | static const struct comedi_lrange das08_pgm_ai_range = { |
119 | 9, { |
120 | BIP_RANGE(10), |
121 | BIP_RANGE(5), |
122 | BIP_RANGE(0.5), |
123 | BIP_RANGE(0.05), |
124 | BIP_RANGE(0.01), |
125 | UNI_RANGE(10), |
126 | UNI_RANGE(1), |
127 | UNI_RANGE(0.1), |
128 | UNI_RANGE(0.01) |
129 | } |
130 | }; |
131 | |
132 | static const struct comedi_lrange *const das08_ai_lranges[] = { |
133 | [das08_pg_none] = &range_unknown, |
134 | [das08_bipolar5] = &range_bipolar5, |
135 | [das08_pgh] = &das08_pgh_ai_range, |
136 | [das08_pgl] = &das08_pgl_ai_range, |
137 | [das08_pgm] = &das08_pgm_ai_range, |
138 | }; |
139 | |
140 | static const int das08_pgh_ai_gainlist[] = { |
141 | 8, 0, 10, 2, 12, 4, 14, 6, 1, 3, 5, 7 |
142 | }; |
143 | static const int das08_pgl_ai_gainlist[] = { 8, 0, 2, 4, 6, 1, 3, 5, 7 }; |
144 | static const int das08_pgm_ai_gainlist[] = { 8, 0, 10, 12, 14, 9, 11, 13, 15 }; |
145 | |
146 | static const int *const das08_ai_gainlists[] = { |
147 | [das08_pg_none] = NULL, |
148 | [das08_bipolar5] = NULL, |
149 | [das08_pgh] = das08_pgh_ai_gainlist, |
150 | [das08_pgl] = das08_pgl_ai_gainlist, |
151 | [das08_pgm] = das08_pgm_ai_gainlist, |
152 | }; |
153 | |
154 | static int das08_ai_eoc(struct comedi_device *dev, |
155 | struct comedi_subdevice *s, |
156 | struct comedi_insn *insn, |
157 | unsigned long context) |
158 | { |
159 | unsigned int status; |
160 | |
161 | status = inb(port: dev->iobase + DAS08_STATUS_REG); |
162 | if ((status & DAS08_STATUS_AI_BUSY) == 0) |
163 | return 0; |
164 | return -EBUSY; |
165 | } |
166 | |
167 | static int das08_ai_insn_read(struct comedi_device *dev, |
168 | struct comedi_subdevice *s, |
169 | struct comedi_insn *insn, unsigned int *data) |
170 | { |
171 | const struct das08_board_struct *board = dev->board_ptr; |
172 | struct das08_private_struct *devpriv = dev->private; |
173 | int n; |
174 | int chan; |
175 | int range; |
176 | int lsb, msb; |
177 | int ret; |
178 | |
179 | chan = CR_CHAN(insn->chanspec); |
180 | |
181 | /* clear crap */ |
182 | inb(port: dev->iobase + DAS08_AI_LSB_REG); |
183 | inb(port: dev->iobase + DAS08_AI_MSB_REG); |
184 | |
185 | /* set multiplexer */ |
186 | /* lock to prevent race with digital output */ |
187 | spin_lock(lock: &dev->spinlock); |
188 | devpriv->do_mux_bits &= ~DAS08_CONTROL_MUX_MASK; |
189 | devpriv->do_mux_bits |= DAS08_CONTROL_MUX(chan); |
190 | outb(value: devpriv->do_mux_bits, port: dev->iobase + DAS08_CONTROL_REG); |
191 | spin_unlock(lock: &dev->spinlock); |
192 | |
193 | if (devpriv->pg_gainlist) { |
194 | /* set gain/range */ |
195 | range = CR_RANGE(insn->chanspec); |
196 | outb(value: devpriv->pg_gainlist[range], |
197 | port: dev->iobase + DAS08_GAIN_REG); |
198 | } |
199 | |
200 | for (n = 0; n < insn->n; n++) { |
201 | /* clear over-range bits for 16-bit boards */ |
202 | if (board->ai_nbits == 16) |
203 | if (inb(port: dev->iobase + DAS08_AI_MSB_REG) & 0x80) |
204 | dev_info(dev->class_dev, "over-range\n" ); |
205 | |
206 | /* trigger conversion */ |
207 | outb_p(value: 0, port: dev->iobase + DAS08_AI_TRIG_REG); |
208 | |
209 | ret = comedi_timeout(dev, s, insn, cb: das08_ai_eoc, context: 0); |
210 | if (ret) |
211 | return ret; |
212 | |
213 | msb = inb(port: dev->iobase + DAS08_AI_MSB_REG); |
214 | lsb = inb(port: dev->iobase + DAS08_AI_LSB_REG); |
215 | if (board->ai_encoding == das08_encode12) { |
216 | data[n] = (lsb >> 4) | (msb << 4); |
217 | } else if (board->ai_encoding == das08_pcm_encode12) { |
218 | data[n] = (msb << 8) + lsb; |
219 | } else if (board->ai_encoding == das08_encode16) { |
220 | /* |
221 | * "JR" 16-bit boards are sign-magnitude. |
222 | * |
223 | * XXX The manual seems to imply that 0 is full-scale |
224 | * negative and 65535 is full-scale positive, but the |
225 | * original COMEDI patch to add support for the |
226 | * DAS08/JR/16 and DAS08/JR/16-AO boards have it |
227 | * encoded as sign-magnitude. Assume the original |
228 | * COMEDI code is correct for now. |
229 | */ |
230 | unsigned int magnitude = lsb | ((msb & 0x7f) << 8); |
231 | |
232 | /* |
233 | * MSB bit 7 is 0 for negative, 1 for positive voltage. |
234 | * COMEDI 16-bit bipolar data value for 0V is 0x8000. |
235 | */ |
236 | if (msb & 0x80) |
237 | data[n] = BIT(15) + magnitude; |
238 | else |
239 | data[n] = BIT(15) - magnitude; |
240 | } else { |
241 | dev_err(dev->class_dev, "bug! unknown ai encoding\n" ); |
242 | return -1; |
243 | } |
244 | } |
245 | |
246 | return n; |
247 | } |
248 | |
249 | static int das08_di_insn_bits(struct comedi_device *dev, |
250 | struct comedi_subdevice *s, |
251 | struct comedi_insn *insn, unsigned int *data) |
252 | { |
253 | data[0] = 0; |
254 | data[1] = DAS08_STATUS_DI(inb(dev->iobase + DAS08_STATUS_REG)); |
255 | |
256 | return insn->n; |
257 | } |
258 | |
259 | static int das08_do_insn_bits(struct comedi_device *dev, |
260 | struct comedi_subdevice *s, |
261 | struct comedi_insn *insn, unsigned int *data) |
262 | { |
263 | struct das08_private_struct *devpriv = dev->private; |
264 | |
265 | if (comedi_dio_update_state(s, data)) { |
266 | /* prevent race with setting of analog input mux */ |
267 | spin_lock(lock: &dev->spinlock); |
268 | devpriv->do_mux_bits &= ~DAS08_CONTROL_DO_MASK; |
269 | devpriv->do_mux_bits |= DAS08_CONTROL_DO(s->state); |
270 | outb(value: devpriv->do_mux_bits, port: dev->iobase + DAS08_CONTROL_REG); |
271 | spin_unlock(lock: &dev->spinlock); |
272 | } |
273 | |
274 | data[1] = s->state; |
275 | |
276 | return insn->n; |
277 | } |
278 | |
279 | static int das08jr_di_insn_bits(struct comedi_device *dev, |
280 | struct comedi_subdevice *s, |
281 | struct comedi_insn *insn, unsigned int *data) |
282 | { |
283 | data[0] = 0; |
284 | data[1] = inb(port: dev->iobase + DAS08JR_DI_REG); |
285 | |
286 | return insn->n; |
287 | } |
288 | |
289 | static int das08jr_do_insn_bits(struct comedi_device *dev, |
290 | struct comedi_subdevice *s, |
291 | struct comedi_insn *insn, unsigned int *data) |
292 | { |
293 | if (comedi_dio_update_state(s, data)) |
294 | outb(value: s->state, port: dev->iobase + DAS08JR_DO_REG); |
295 | |
296 | data[1] = s->state; |
297 | |
298 | return insn->n; |
299 | } |
300 | |
301 | static void das08_ao_set_data(struct comedi_device *dev, |
302 | unsigned int chan, unsigned int data) |
303 | { |
304 | const struct das08_board_struct *board = dev->board_ptr; |
305 | unsigned char lsb; |
306 | unsigned char msb; |
307 | |
308 | lsb = data & 0xff; |
309 | msb = (data >> 8) & 0xff; |
310 | if (board->is_jr) { |
311 | outb(value: lsb, port: dev->iobase + DAS08JR_AO_LSB_REG(chan)); |
312 | outb(value: msb, port: dev->iobase + DAS08JR_AO_MSB_REG(chan)); |
313 | /* load DACs */ |
314 | inb(port: dev->iobase + DAS08JR_AO_UPDATE_REG); |
315 | } else { |
316 | outb(value: lsb, port: dev->iobase + DAS08AOX_AO_LSB_REG(chan)); |
317 | outb(value: msb, port: dev->iobase + DAS08AOX_AO_MSB_REG(chan)); |
318 | /* load DACs */ |
319 | inb(port: dev->iobase + DAS08AOX_AO_UPDATE_REG); |
320 | } |
321 | } |
322 | |
323 | static int das08_ao_insn_write(struct comedi_device *dev, |
324 | struct comedi_subdevice *s, |
325 | struct comedi_insn *insn, |
326 | unsigned int *data) |
327 | { |
328 | unsigned int chan = CR_CHAN(insn->chanspec); |
329 | unsigned int val = s->readback[chan]; |
330 | int i; |
331 | |
332 | for (i = 0; i < insn->n; i++) { |
333 | val = data[i]; |
334 | das08_ao_set_data(dev, chan, data: val); |
335 | } |
336 | s->readback[chan] = val; |
337 | |
338 | return insn->n; |
339 | } |
340 | |
341 | int das08_common_attach(struct comedi_device *dev, unsigned long iobase) |
342 | { |
343 | const struct das08_board_struct *board = dev->board_ptr; |
344 | struct das08_private_struct *devpriv = dev->private; |
345 | struct comedi_subdevice *s; |
346 | int ret; |
347 | int i; |
348 | |
349 | dev->iobase = iobase; |
350 | |
351 | dev->board_name = board->name; |
352 | |
353 | ret = comedi_alloc_subdevices(dev, num_subdevices: 6); |
354 | if (ret) |
355 | return ret; |
356 | |
357 | s = &dev->subdevices[0]; |
358 | /* ai */ |
359 | if (board->ai_nbits) { |
360 | s->type = COMEDI_SUBD_AI; |
361 | /* |
362 | * XXX some boards actually have differential |
363 | * inputs instead of single ended. |
364 | * The driver does nothing with arefs though, |
365 | * so it's no big deal. |
366 | */ |
367 | s->subdev_flags = SDF_READABLE | SDF_GROUND; |
368 | s->n_chan = 8; |
369 | s->maxdata = (1 << board->ai_nbits) - 1; |
370 | s->range_table = das08_ai_lranges[board->ai_pg]; |
371 | s->insn_read = das08_ai_insn_read; |
372 | devpriv->pg_gainlist = das08_ai_gainlists[board->ai_pg]; |
373 | } else { |
374 | s->type = COMEDI_SUBD_UNUSED; |
375 | } |
376 | |
377 | s = &dev->subdevices[1]; |
378 | /* ao */ |
379 | if (board->ao_nbits) { |
380 | s->type = COMEDI_SUBD_AO; |
381 | s->subdev_flags = SDF_WRITABLE; |
382 | s->n_chan = 2; |
383 | s->maxdata = (1 << board->ao_nbits) - 1; |
384 | s->range_table = &range_bipolar5; |
385 | s->insn_write = das08_ao_insn_write; |
386 | |
387 | ret = comedi_alloc_subdev_readback(s); |
388 | if (ret) |
389 | return ret; |
390 | |
391 | /* initialize all channels to 0V */ |
392 | for (i = 0; i < s->n_chan; i++) { |
393 | s->readback[i] = s->maxdata / 2; |
394 | das08_ao_set_data(dev, chan: i, data: s->readback[i]); |
395 | } |
396 | } else { |
397 | s->type = COMEDI_SUBD_UNUSED; |
398 | } |
399 | |
400 | s = &dev->subdevices[2]; |
401 | /* di */ |
402 | if (board->di_nchan) { |
403 | s->type = COMEDI_SUBD_DI; |
404 | s->subdev_flags = SDF_READABLE; |
405 | s->n_chan = board->di_nchan; |
406 | s->maxdata = 1; |
407 | s->range_table = &range_digital; |
408 | s->insn_bits = board->is_jr ? das08jr_di_insn_bits : |
409 | das08_di_insn_bits; |
410 | } else { |
411 | s->type = COMEDI_SUBD_UNUSED; |
412 | } |
413 | |
414 | s = &dev->subdevices[3]; |
415 | /* do */ |
416 | if (board->do_nchan) { |
417 | s->type = COMEDI_SUBD_DO; |
418 | s->subdev_flags = SDF_WRITABLE; |
419 | s->n_chan = board->do_nchan; |
420 | s->maxdata = 1; |
421 | s->range_table = &range_digital; |
422 | s->insn_bits = board->is_jr ? das08jr_do_insn_bits : |
423 | das08_do_insn_bits; |
424 | } else { |
425 | s->type = COMEDI_SUBD_UNUSED; |
426 | } |
427 | |
428 | s = &dev->subdevices[4]; |
429 | /* 8255 */ |
430 | if (board->i8255_offset != 0) { |
431 | ret = subdev_8255_io_init(dev, s, regbase: board->i8255_offset); |
432 | if (ret) |
433 | return ret; |
434 | } else { |
435 | s->type = COMEDI_SUBD_UNUSED; |
436 | } |
437 | |
438 | /* Counter subdevice (8254) */ |
439 | s = &dev->subdevices[5]; |
440 | if (board->i8254_offset) { |
441 | dev->pacer = |
442 | comedi_8254_io_alloc(iobase: dev->iobase + board->i8254_offset, |
443 | osc_base: 0, I8254_IO8, regshift: 0); |
444 | if (IS_ERR(ptr: dev->pacer)) |
445 | return PTR_ERR(ptr: dev->pacer); |
446 | |
447 | comedi_8254_subdevice_init(s, i8254: dev->pacer); |
448 | } else { |
449 | s->type = COMEDI_SUBD_UNUSED; |
450 | } |
451 | |
452 | return 0; |
453 | } |
454 | EXPORT_SYMBOL_GPL(das08_common_attach); |
455 | |
456 | static int __init das08_init(void) |
457 | { |
458 | return 0; |
459 | } |
460 | module_init(das08_init); |
461 | |
462 | static void __exit das08_exit(void) |
463 | { |
464 | } |
465 | module_exit(das08_exit); |
466 | |
467 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
468 | MODULE_DESCRIPTION("Comedi common DAS08 support module" ); |
469 | MODULE_LICENSE("GPL" ); |
470 | |