1 /*
2    comedi/drivers/pcl711.c
3    hardware driver for PC-LabCard PCL-711 and AdSys ACL-8112
4    and compatibles
5 
6    COMEDI - Linux Control and Measurement Device Interface
7    Copyright (C) 1998 David A. Schleef <ds@schleef.org>
8    Janne Jalkanen <jalkanen@cs.hut.fi>
9    Eric Bunn <ebu@cs.hut.fi>
10 
11    This program is free software; you can redistribute it and/or modify
12    it under the terms of the GNU General Public License as published by
13    the Free Software Foundation; either version 2 of the License, or
14    (at your option) any later version.
15 
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20 
21    You should have received a copy of the GNU General Public License
22    along with this program; if not, write to the Free Software
23    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 
25  */
26 /*
27 Driver: pcl711
28 Description: Advantech PCL-711 and 711b, ADLink ACL-8112
29 Author: ds, Janne Jalkanen <jalkanen@cs.hut.fi>, Eric Bunn <ebu@cs.hut.fi>
30 Status: mostly complete
31 Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
32   [AdLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
33 
34 Since these boards do not have DMA or FIFOs, only immediate mode is
35 supported.
36 
37 */
38 
39 /*
40    Dave Andruczyk <dave@tech.buffalostate.edu> also wrote a
41    driver for the PCL-711.  I used a few ideas from his driver
42    here.  His driver also has more comments, if you are
43    interested in understanding how this driver works.
44    http://tech.buffalostate.edu/~dave/driver/
45 
46    The ACL-8112 driver was hacked from the sources of the PCL-711
47    driver (the 744 chip used on the 8112 is almost the same as
48    the 711b chip, but it has more I/O channels) by
49    Janne Jalkanen (jalkanen@cs.hut.fi) and
50    Erik Bunn (ebu@cs.hut.fi).  Remerged with the PCL-711 driver
51    by ds.
52 
53    [acl-8112]
54    This driver supports both TRIGNOW and TRIGCLK,
55    but does not yet support DMA transfers.  It also supports
56    both high (HG) and low (DG) versions of the card, though
57    the HG version has been untested.
58 
59  */
60 
61 #include <linux/interrupt.h>
62 #include "../comedidev.h"
63 
64 #include <linux/ioport.h>
65 #include <linux/delay.h>
66 
67 #include "8253.h"
68 
69 #define PCL711_SIZE 16
70 
71 #define PCL711_CTR0 0
72 #define PCL711_CTR1 1
73 #define PCL711_CTR2 2
74 #define PCL711_CTRCTL 3
75 #define PCL711_AD_LO 4
76 #define PCL711_DA0_LO 4
77 #define PCL711_AD_HI 5
78 #define PCL711_DA0_HI 5
79 #define PCL711_DI_LO 6
80 #define PCL711_DA1_LO 6
81 #define PCL711_DI_HI 7
82 #define PCL711_DA1_HI 7
83 #define PCL711_CLRINTR 8
84 #define PCL711_GAIN 9
85 #define PCL711_MUX 10
86 #define PCL711_MODE 11
87 #define PCL711_SOFTTRIG 12
88 #define PCL711_DO_LO 13
89 #define PCL711_DO_HI 14
90 
91 static const struct comedi_lrange range_pcl711b_ai = { 5, {
92 							   BIP_RANGE(5),
93 							   BIP_RANGE(2.5),
94 							   BIP_RANGE(1.25),
95 							   BIP_RANGE(0.625),
96 							   BIP_RANGE(0.3125)
97 							   }
98 };
99 
100 static const struct comedi_lrange range_acl8112hg_ai = { 12, {
101 							      BIP_RANGE(5),
102 							      BIP_RANGE(0.5),
103 							      BIP_RANGE(0.05),
104 							      BIP_RANGE(0.005),
105 							      UNI_RANGE(10),
106 							      UNI_RANGE(1),
107 							      UNI_RANGE(0.1),
108 							      UNI_RANGE(0.01),
109 							      BIP_RANGE(10),
110 							      BIP_RANGE(1),
111 							      BIP_RANGE(0.1),
112 							      BIP_RANGE(0.01)
113 							      }
114 };
115 
116 static const struct comedi_lrange range_acl8112dg_ai = { 9, {
117 							     BIP_RANGE(5),
118 							     BIP_RANGE(2.5),
119 							     BIP_RANGE(1.25),
120 							     BIP_RANGE(0.625),
121 							     UNI_RANGE(10),
122 							     UNI_RANGE(5),
123 							     UNI_RANGE(2.5),
124 							     UNI_RANGE(1.25),
125 							     BIP_RANGE(10)
126 							     }
127 };
128 
129 /*
130  * flags
131  */
132 
133 #define PCL711_TIMEOUT 100
134 #define PCL711_DRDY 0x10
135 
136 static const int i8253_osc_base = 500;	/* 2 Mhz */
137 
138 struct pcl711_board {
139 
140 	const char *name;
141 	int is_pcl711b;
142 	int is_8112;
143 	int is_dg;
144 	int n_ranges;
145 	int n_aichan;
146 	int n_aochan;
147 	int maxirq;
148 	const struct comedi_lrange *ai_range_type;
149 };
150 
151 static const struct pcl711_board boardtypes[] = {
152 	{"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5},
153 	{"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai},
154 	{"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai},
155 	{"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai},
156 };
157 
158 #define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl711_board))
159 #define this_board ((const struct pcl711_board *)dev->board_ptr)
160 
161 static int pcl711_attach(struct comedi_device *dev,
162 			 struct comedi_devconfig *it);
163 static int pcl711_detach(struct comedi_device *dev);
164 static struct comedi_driver driver_pcl711 = {
165 	.driver_name = "pcl711",
166 	.module = THIS_MODULE,
167 	.attach = pcl711_attach,
168 	.detach = pcl711_detach,
169 	.board_name = &boardtypes[0].name,
170 	.num_names = n_boardtypes,
171 	.offset = sizeof(struct pcl711_board),
172 };
173 
driver_pcl711_init_module(void)174 static int __init driver_pcl711_init_module(void)
175 {
176 	return comedi_driver_register(&driver_pcl711);
177 }
178 
driver_pcl711_cleanup_module(void)179 static void __exit driver_pcl711_cleanup_module(void)
180 {
181 	comedi_driver_unregister(&driver_pcl711);
182 }
183 
184 module_init(driver_pcl711_init_module);
185 module_exit(driver_pcl711_cleanup_module);
186 
187 struct pcl711_private {
188 
189 	int board;
190 	int adchan;
191 	int ntrig;
192 	int aip[8];
193 	int mode;
194 	unsigned int ao_readback[2];
195 	unsigned int divisor1;
196 	unsigned int divisor2;
197 };
198 
199 #define devpriv ((struct pcl711_private *)dev->private)
200 
pcl711_interrupt(int irq,void * d)201 static irqreturn_t pcl711_interrupt(int irq, void *d)
202 {
203 	int lo, hi;
204 	int data;
205 	struct comedi_device *dev = d;
206 	struct comedi_subdevice *s = dev->subdevices + 0;
207 
208 	if (!dev->attached) {
209 		comedi_error(dev, "spurious interrupt");
210 		return IRQ_HANDLED;
211 	}
212 
213 	hi = inb(dev->iobase + PCL711_AD_HI);
214 	lo = inb(dev->iobase + PCL711_AD_LO);
215 	outb(0, dev->iobase + PCL711_CLRINTR);
216 
217 	data = (hi << 8) | lo;
218 
219 	/* FIXME! Nothing else sets ntrig! */
220 	if (!(--devpriv->ntrig)) {
221 		if (this_board->is_8112)
222 			outb(1, dev->iobase + PCL711_MODE);
223 		else
224 			outb(0, dev->iobase + PCL711_MODE);
225 
226 		s->async->events |= COMEDI_CB_EOA;
227 	}
228 	comedi_event(dev, s);
229 	return IRQ_HANDLED;
230 }
231 
pcl711_set_changain(struct comedi_device * dev,int chan)232 static void pcl711_set_changain(struct comedi_device *dev, int chan)
233 {
234 	int chan_register;
235 
236 	outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
237 
238 	chan_register = CR_CHAN(chan);
239 
240 	if (this_board->is_8112) {
241 
242 		/*
243 		 *  Set the correct channel.  The two channel banks are switched
244 		 *  using the mask value.
245 		 *  NB: To use differential channels, you should use
246 		 *  mask = 0x30, but I haven't written the support for this
247 		 *  yet. /JJ
248 		 */
249 
250 		if (chan_register >= 8)
251 			chan_register = 0x20 | (chan_register & 0x7);
252 		else
253 			chan_register |= 0x10;
254 	} else {
255 		outb(chan_register, dev->iobase + PCL711_MUX);
256 	}
257 }
258 
pcl711_ai_insn(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)259 static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
260 			  struct comedi_insn *insn, unsigned int *data)
261 {
262 	int i, n;
263 	int hi, lo;
264 
265 	pcl711_set_changain(dev, insn->chanspec);
266 
267 	for (n = 0; n < insn->n; n++) {
268 		/*
269 		 *  Write the correct mode (software polling) and start polling
270 		 *  by writing to the trigger register
271 		 */
272 		outb(1, dev->iobase + PCL711_MODE);
273 
274 		if (!this_board->is_8112)
275 			outb(0, dev->iobase + PCL711_SOFTTRIG);
276 
277 		i = PCL711_TIMEOUT;
278 		while (--i) {
279 			hi = inb(dev->iobase + PCL711_AD_HI);
280 			if (!(hi & PCL711_DRDY))
281 				goto ok;
282 			udelay(1);
283 		}
284 		printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor);
285 		return -ETIME;
286 
287 ok:
288 		lo = inb(dev->iobase + PCL711_AD_LO);
289 
290 		data[n] = ((hi & 0xf) << 8) | lo;
291 	}
292 
293 	return n;
294 }
295 
pcl711_ai_cmdtest(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_cmd * cmd)296 static int pcl711_ai_cmdtest(struct comedi_device *dev,
297 			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
298 {
299 	int tmp;
300 	int err = 0;
301 
302 	/* step 1 */
303 	tmp = cmd->start_src;
304 	cmd->start_src &= TRIG_NOW;
305 	if (!cmd->start_src || tmp != cmd->start_src)
306 		err++;
307 
308 	tmp = cmd->scan_begin_src;
309 	cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT;
310 	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
311 		err++;
312 
313 	tmp = cmd->convert_src;
314 	cmd->convert_src &= TRIG_NOW;
315 	if (!cmd->convert_src || tmp != cmd->convert_src)
316 		err++;
317 
318 	tmp = cmd->scan_end_src;
319 	cmd->scan_end_src &= TRIG_COUNT;
320 	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
321 		err++;
322 
323 	tmp = cmd->stop_src;
324 	cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
325 	if (!cmd->stop_src || tmp != cmd->stop_src)
326 		err++;
327 
328 	if (err)
329 		return 1;
330 
331 	/* step 2 */
332 
333 	if (cmd->scan_begin_src != TRIG_TIMER &&
334 	    cmd->scan_begin_src != TRIG_EXT)
335 		err++;
336 	if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
337 		err++;
338 
339 	if (err)
340 		return 2;
341 
342 	/* step 3 */
343 
344 	if (cmd->start_arg != 0) {
345 		cmd->start_arg = 0;
346 		err++;
347 	}
348 	if (cmd->scan_begin_src == TRIG_EXT) {
349 		if (cmd->scan_begin_arg != 0) {
350 			cmd->scan_begin_arg = 0;
351 			err++;
352 		}
353 	} else {
354 #define MAX_SPEED 1000
355 #define TIMER_BASE 100
356 		if (cmd->scan_begin_arg < MAX_SPEED) {
357 			cmd->scan_begin_arg = MAX_SPEED;
358 			err++;
359 		}
360 	}
361 	if (cmd->convert_arg != 0) {
362 		cmd->convert_arg = 0;
363 		err++;
364 	}
365 	if (cmd->scan_end_arg != cmd->chanlist_len) {
366 		cmd->scan_end_arg = cmd->chanlist_len;
367 		err++;
368 	}
369 	if (cmd->stop_src == TRIG_NONE) {
370 		if (cmd->stop_arg != 0) {
371 			cmd->stop_arg = 0;
372 			err++;
373 		}
374 	} else {
375 		/* ignore */
376 	}
377 
378 	if (err)
379 		return 3;
380 
381 	/* step 4 */
382 
383 	if (cmd->scan_begin_src == TRIG_TIMER) {
384 		tmp = cmd->scan_begin_arg;
385 		i8253_cascade_ns_to_timer_2div(TIMER_BASE,
386 					       &devpriv->divisor1,
387 					       &devpriv->divisor2,
388 					       &cmd->scan_begin_arg,
389 					       cmd->flags & TRIG_ROUND_MASK);
390 		if (tmp != cmd->scan_begin_arg)
391 			err++;
392 	}
393 
394 	if (err)
395 		return 4;
396 
397 	return 0;
398 }
399 
pcl711_ai_cmd(struct comedi_device * dev,struct comedi_subdevice * s)400 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
401 {
402 	int timer1, timer2;
403 	struct comedi_cmd *cmd = &s->async->cmd;
404 
405 	pcl711_set_changain(dev, cmd->chanlist[0]);
406 
407 	if (cmd->scan_begin_src == TRIG_TIMER) {
408 		/*
409 		 *  Set timers
410 		 *      timer chip is an 8253, with timers 1 and 2
411 		 *      cascaded
412 		 *  0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
413 		 *        Mode 2 = Rate generator
414 		 *
415 		 *  0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
416 		 */
417 
418 		timer1 = timer2 = 0;
419 		i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
420 					  &cmd->scan_begin_arg,
421 					  TRIG_ROUND_NEAREST);
422 
423 		outb(0x74, dev->iobase + PCL711_CTRCTL);
424 		outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
425 		outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
426 		outb(0xb4, dev->iobase + PCL711_CTRCTL);
427 		outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
428 		outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
429 
430 		/* clear pending interrupts (just in case) */
431 		outb(0, dev->iobase + PCL711_CLRINTR);
432 
433 		/*
434 		 *  Set mode to IRQ transfer
435 		 */
436 		outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
437 	} else {
438 		/* external trigger */
439 		outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
440 	}
441 
442 	return 0;
443 }
444 
445 /*
446    analog output
447 */
pcl711_ao_insn(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)448 static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
449 			  struct comedi_insn *insn, unsigned int *data)
450 {
451 	int n;
452 	int chan = CR_CHAN(insn->chanspec);
453 
454 	for (n = 0; n < insn->n; n++) {
455 		outb((data[n] & 0xff),
456 		     dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
457 		outb((data[n] >> 8),
458 		     dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
459 
460 		devpriv->ao_readback[chan] = data[n];
461 	}
462 
463 	return n;
464 }
465 
pcl711_ao_insn_read(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)466 static int pcl711_ao_insn_read(struct comedi_device *dev,
467 			       struct comedi_subdevice *s,
468 			       struct comedi_insn *insn, unsigned int *data)
469 {
470 	int n;
471 	int chan = CR_CHAN(insn->chanspec);
472 
473 	for (n = 0; n < insn->n; n++)
474 		data[n] = devpriv->ao_readback[chan];
475 
476 	return n;
477 
478 }
479 
480 /* Digital port read - Untested on 8112 */
pcl711_di_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)481 static int pcl711_di_insn_bits(struct comedi_device *dev,
482 			       struct comedi_subdevice *s,
483 			       struct comedi_insn *insn, unsigned int *data)
484 {
485 	if (insn->n != 2)
486 		return -EINVAL;
487 
488 	data[1] = inb(dev->iobase + PCL711_DI_LO) |
489 	    (inb(dev->iobase + PCL711_DI_HI) << 8);
490 
491 	return 2;
492 }
493 
494 /* Digital port write - Untested on 8112 */
pcl711_do_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)495 static int pcl711_do_insn_bits(struct comedi_device *dev,
496 			       struct comedi_subdevice *s,
497 			       struct comedi_insn *insn, unsigned int *data)
498 {
499 	if (insn->n != 2)
500 		return -EINVAL;
501 
502 	if (data[0]) {
503 		s->state &= ~data[0];
504 		s->state |= data[0] & data[1];
505 	}
506 	if (data[0] & 0x00ff)
507 		outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
508 	if (data[0] & 0xff00)
509 		outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
510 
511 	data[1] = s->state;
512 
513 	return 2;
514 }
515 
516 /*  Free any resources that we have claimed  */
pcl711_detach(struct comedi_device * dev)517 static int pcl711_detach(struct comedi_device *dev)
518 {
519 	printk(KERN_INFO "comedi%d: pcl711: remove\n", dev->minor);
520 
521 	if (dev->irq)
522 		free_irq(dev->irq, dev);
523 
524 	if (dev->iobase)
525 		release_region(dev->iobase, PCL711_SIZE);
526 
527 	return 0;
528 }
529 
530 /*  Initialization */
pcl711_attach(struct comedi_device * dev,struct comedi_devconfig * it)531 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
532 {
533 	int ret;
534 	unsigned long iobase;
535 	unsigned int irq;
536 	struct comedi_subdevice *s;
537 
538 	/* claim our I/O space */
539 
540 	iobase = it->options[0];
541 	printk(KERN_INFO "comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
542 	if (!request_region(iobase, PCL711_SIZE, "pcl711")) {
543 		printk("I/O port conflict\n");
544 		return -EIO;
545 	}
546 	dev->iobase = iobase;
547 
548 	/* there should be a sanity check here */
549 
550 	/* set up some name stuff */
551 	dev->board_name = this_board->name;
552 
553 	/* grab our IRQ */
554 	irq = it->options[1];
555 	if (irq > this_board->maxirq) {
556 		printk(KERN_ERR "irq out of range\n");
557 		return -EINVAL;
558 	}
559 	if (irq) {
560 		if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
561 			printk(KERN_ERR "unable to allocate irq %u\n", irq);
562 			return -EINVAL;
563 		} else {
564 			printk(KERN_INFO "( irq = %u )\n", irq);
565 		}
566 	}
567 	dev->irq = irq;
568 
569 	ret = alloc_subdevices(dev, 4);
570 	if (ret < 0)
571 		return ret;
572 
573 	ret = alloc_private(dev, sizeof(struct pcl711_private));
574 	if (ret < 0)
575 		return ret;
576 
577 	s = dev->subdevices + 0;
578 	/* AI subdevice */
579 	s->type = COMEDI_SUBD_AI;
580 	s->subdev_flags = SDF_READABLE | SDF_GROUND;
581 	s->n_chan = this_board->n_aichan;
582 	s->maxdata = 0xfff;
583 	s->len_chanlist = 1;
584 	s->range_table = this_board->ai_range_type;
585 	s->insn_read = pcl711_ai_insn;
586 	if (irq) {
587 		dev->read_subdev = s;
588 		s->subdev_flags |= SDF_CMD_READ;
589 		s->do_cmdtest = pcl711_ai_cmdtest;
590 		s->do_cmd = pcl711_ai_cmd;
591 	}
592 
593 	s++;
594 	/* AO subdevice */
595 	s->type = COMEDI_SUBD_AO;
596 	s->subdev_flags = SDF_WRITABLE;
597 	s->n_chan = this_board->n_aochan;
598 	s->maxdata = 0xfff;
599 	s->len_chanlist = 1;
600 	s->range_table = &range_bipolar5;
601 	s->insn_write = pcl711_ao_insn;
602 	s->insn_read = pcl711_ao_insn_read;
603 
604 	s++;
605 	/* 16-bit digital input */
606 	s->type = COMEDI_SUBD_DI;
607 	s->subdev_flags = SDF_READABLE;
608 	s->n_chan = 16;
609 	s->maxdata = 1;
610 	s->len_chanlist = 16;
611 	s->range_table = &range_digital;
612 	s->insn_bits = pcl711_di_insn_bits;
613 
614 	s++;
615 	/* 16-bit digital out */
616 	s->type = COMEDI_SUBD_DO;
617 	s->subdev_flags = SDF_WRITABLE;
618 	s->n_chan = 16;
619 	s->maxdata = 1;
620 	s->len_chanlist = 16;
621 	s->range_table = &range_digital;
622 	s->state = 0;
623 	s->insn_bits = pcl711_do_insn_bits;
624 
625 	/*
626 	   this is the "base value" for the mode register, which is
627 	   used for the irq on the PCL711
628 	 */
629 	if (this_board->is_pcl711b)
630 		devpriv->mode = (dev->irq << 4);
631 
632 	/* clear DAC */
633 	outb(0, dev->iobase + PCL711_DA0_LO);
634 	outb(0, dev->iobase + PCL711_DA0_HI);
635 	outb(0, dev->iobase + PCL711_DA1_LO);
636 	outb(0, dev->iobase + PCL711_DA1_HI);
637 
638 	printk(KERN_INFO "\n");
639 
640 	return 0;
641 }
642 
643 MODULE_AUTHOR("Comedi http://www.comedi.org");
644 MODULE_DESCRIPTION("Comedi low-level driver");
645 MODULE_LICENSE("GPL");
646