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