2 comedi/drivers/pcl711.c
3 hardware driver for PC-LabCard PCL-711 and AdSys ACL-8112
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>
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.
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.
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.
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)
34 Since these boards do not have DMA or FIFOs, only immediate mode is
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/
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
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.
61 #include <linux/interrupt.h>
62 #include "../comedidev.h"
64 #include <linux/ioport.h>
65 #include <linux/delay.h>
69 #define PCL711_SIZE 16
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
86 #define PCL711_MODE 11
87 #define PCL711_SOFTTRIG 12
88 #define PCL711_DO_LO 13
89 #define PCL711_DO_HI 14
91 static const struct comedi_lrange range_pcl711b_ai = { 5, {
100 static const struct comedi_lrange range_acl8112hg_ai = { 12, {
116 static const struct comedi_lrange range_acl8112dg_ai = { 9, {
133 #define PCL711_TIMEOUT 100
134 #define PCL711_DRDY 0x10
136 static const int i8253_osc_base = 500; /* 2 Mhz */
138 struct pcl711_board {
148 const struct comedi_lrange *ai_range_type;
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},
158 #define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl711_board))
159 #define this_board ((const struct pcl711_board *)dev->board_ptr)
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),
174 COMEDI_INITCLEANUP(driver_pcl711);
176 struct pcl711_private {
183 unsigned int ao_readback[2];
184 unsigned int divisor1;
185 unsigned int divisor2;
188 #define devpriv ((struct pcl711_private *)dev->private)
190 static irqreturn_t pcl711_interrupt(int irq, void *d)
194 struct comedi_device *dev = d;
195 struct comedi_subdevice *s = dev->subdevices + 0;
197 if (!dev->attached) {
198 comedi_error(dev, "spurious interrupt");
202 hi = inb(dev->iobase + PCL711_AD_HI);
203 lo = inb(dev->iobase + PCL711_AD_LO);
204 outb(0, dev->iobase + PCL711_CLRINTR);
206 data = (hi << 8) | lo;
208 /* FIXME! Nothing else sets ntrig! */
209 if (!(--devpriv->ntrig)) {
210 if (this_board->is_8112) {
211 outb(1, dev->iobase + PCL711_MODE);
213 outb(0, dev->iobase + PCL711_MODE);
216 s->async->events |= COMEDI_CB_EOA;
218 comedi_event(dev, s);
222 static void pcl711_set_changain(struct comedi_device *dev, int chan)
226 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
228 chan_register = CR_CHAN(chan);
230 if (this_board->is_8112) {
233 * Set the correct channel. The two channel banks are switched
234 * using the mask value.
235 * NB: To use differential channels, you should use mask = 0x30,
236 * but I haven't written the support for this yet. /JJ
239 if (chan_register >= 8) {
240 chan_register = 0x20 | (chan_register & 0x7);
242 chan_register |= 0x10;
245 outb(chan_register, dev->iobase + PCL711_MUX);
249 static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
250 struct comedi_insn *insn, unsigned int *data)
255 pcl711_set_changain(dev, insn->chanspec);
257 for (n = 0; n < insn->n; n++) {
259 * Write the correct mode (software polling) and start polling by writing
260 * to the trigger register
262 outb(1, dev->iobase + PCL711_MODE);
264 if (this_board->is_8112) {
266 outb(0, dev->iobase + PCL711_SOFTTRIG);
271 hi = inb(dev->iobase + PCL711_AD_HI);
272 if (!(hi & PCL711_DRDY))
276 printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
280 lo = inb(dev->iobase + PCL711_AD_LO);
282 data[n] = ((hi & 0xf) << 8) | lo;
288 static int pcl711_ai_cmdtest(struct comedi_device *dev,
289 struct comedi_subdevice *s, struct comedi_cmd *cmd)
295 tmp = cmd->start_src;
296 cmd->start_src &= TRIG_NOW;
297 if (!cmd->start_src || tmp != cmd->start_src)
300 tmp = cmd->scan_begin_src;
301 cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT;
302 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
305 tmp = cmd->convert_src;
306 cmd->convert_src &= TRIG_NOW;
307 if (!cmd->convert_src || tmp != cmd->convert_src)
310 tmp = cmd->scan_end_src;
311 cmd->scan_end_src &= TRIG_COUNT;
312 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
316 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
317 if (!cmd->stop_src || tmp != cmd->stop_src)
325 if (cmd->scan_begin_src != TRIG_TIMER &&
326 cmd->scan_begin_src != TRIG_EXT)
328 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
336 if (cmd->start_arg != 0) {
340 if (cmd->scan_begin_src == TRIG_EXT) {
341 if (cmd->scan_begin_arg != 0) {
342 cmd->scan_begin_arg = 0;
346 #define MAX_SPEED 1000
347 #define TIMER_BASE 100
348 if (cmd->scan_begin_arg < MAX_SPEED) {
349 cmd->scan_begin_arg = MAX_SPEED;
353 if (cmd->convert_arg != 0) {
354 cmd->convert_arg = 0;
357 if (cmd->scan_end_arg != cmd->chanlist_len) {
358 cmd->scan_end_arg = cmd->chanlist_len;
361 if (cmd->stop_src == TRIG_NONE) {
362 if (cmd->stop_arg != 0) {
375 if (cmd->scan_begin_src == TRIG_TIMER) {
376 tmp = cmd->scan_begin_arg;
377 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
380 &cmd->scan_begin_arg,
381 cmd->flags & TRIG_ROUND_MASK);
382 if (tmp != cmd->scan_begin_arg)
392 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
395 struct comedi_cmd *cmd = &s->async->cmd;
397 pcl711_set_changain(dev, cmd->chanlist[0]);
399 if (cmd->scan_begin_src == TRIG_TIMER) {
402 * timer chip is an 8253, with timers 1 and 2
404 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
405 * Mode 2 = Rate generator
407 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
411 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
412 &cmd->scan_begin_arg,
415 outb(0x74, dev->iobase + PCL711_CTRCTL);
416 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
417 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
418 outb(0xb4, dev->iobase + PCL711_CTRCTL);
419 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
420 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
422 /* clear pending interrupts (just in case) */
423 outb(0, dev->iobase + PCL711_CLRINTR);
426 * Set mode to IRQ transfer
428 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
430 /* external trigger */
431 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
440 static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
441 struct comedi_insn *insn, unsigned int *data)
444 int chan = CR_CHAN(insn->chanspec);
446 for (n = 0; n < insn->n; n++) {
447 outb((data[n] & 0xff),
448 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
450 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
452 devpriv->ao_readback[chan] = data[n];
458 static int pcl711_ao_insn_read(struct comedi_device *dev,
459 struct comedi_subdevice *s,
460 struct comedi_insn *insn, unsigned int *data)
463 int chan = CR_CHAN(insn->chanspec);
465 for (n = 0; n < insn->n; n++) {
466 data[n] = devpriv->ao_readback[chan];
473 /* Digital port read - Untested on 8112 */
474 static int pcl711_di_insn_bits(struct comedi_device *dev,
475 struct comedi_subdevice *s,
476 struct comedi_insn *insn, unsigned int *data)
481 data[1] = inb(dev->iobase + PCL711_DI_LO) |
482 (inb(dev->iobase + PCL711_DI_HI) << 8);
487 /* Digital port write - Untested on 8112 */
488 static int pcl711_do_insn_bits(struct comedi_device *dev,
489 struct comedi_subdevice *s,
490 struct comedi_insn *insn, unsigned int *data)
496 s->state &= ~data[0];
497 s->state |= data[0] & data[1];
499 if (data[0] & 0x00ff)
500 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
501 if (data[0] & 0xff00)
502 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
509 /* Free any resources that we have claimed */
510 static int pcl711_detach(struct comedi_device *dev)
512 printk("comedi%d: pcl711: remove\n", dev->minor);
515 free_irq(dev->irq, dev);
518 release_region(dev->iobase, PCL711_SIZE);
524 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
527 unsigned long iobase;
529 struct comedi_subdevice *s;
531 /* claim our I/O space */
533 iobase = it->options[0];
534 printk("comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
535 if (!request_region(iobase, PCL711_SIZE, "pcl711")) {
536 printk("I/O port conflict\n");
539 dev->iobase = iobase;
541 /* there should be a sanity check here */
543 /* set up some name stuff */
544 dev->board_name = this_board->name;
547 irq = it->options[1];
548 if (irq > this_board->maxirq) {
549 printk("irq out of range\n");
553 if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
554 printk("unable to allocate irq %u\n", irq);
557 printk("( irq = %u )\n", irq);
562 ret = alloc_subdevices(dev, 4);
566 ret = alloc_private(dev, sizeof(struct pcl711_private));
570 s = dev->subdevices + 0;
572 s->type = COMEDI_SUBD_AI;
573 s->subdev_flags = SDF_READABLE | SDF_GROUND;
574 s->n_chan = this_board->n_aichan;
577 s->range_table = this_board->ai_range_type;
578 s->insn_read = pcl711_ai_insn;
580 dev->read_subdev = s;
581 s->subdev_flags |= SDF_CMD_READ;
582 s->do_cmdtest = pcl711_ai_cmdtest;
583 s->do_cmd = pcl711_ai_cmd;
588 s->type = COMEDI_SUBD_AO;
589 s->subdev_flags = SDF_WRITABLE;
590 s->n_chan = this_board->n_aochan;
593 s->range_table = &range_bipolar5;
594 s->insn_write = pcl711_ao_insn;
595 s->insn_read = pcl711_ao_insn_read;
598 /* 16-bit digital input */
599 s->type = COMEDI_SUBD_DI;
600 s->subdev_flags = SDF_READABLE;
603 s->len_chanlist = 16;
604 s->range_table = &range_digital;
605 s->insn_bits = pcl711_di_insn_bits;
608 /* 16-bit digital out */
609 s->type = COMEDI_SUBD_DO;
610 s->subdev_flags = SDF_WRITABLE;
613 s->len_chanlist = 16;
614 s->range_table = &range_digital;
616 s->insn_bits = pcl711_do_insn_bits;
619 this is the "base value" for the mode register, which is
620 used for the irq on the PCL711
622 if (this_board->is_pcl711b) {
623 devpriv->mode = (dev->irq << 4);
627 outb(0, dev->iobase + PCL711_DA0_LO);
628 outb(0, dev->iobase + PCL711_DA0_HI);
629 outb(0, dev->iobase + PCL711_DA1_LO);
630 outb(0, dev->iobase + PCL711_DA1_HI);