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 "../comedidev.h"
63 #include <linux/ioport.h>
64 #include <linux/delay.h>
68 #define PCL711_SIZE 16
73 #define PCL711_CTRCTL 3
74 #define PCL711_AD_LO 4
75 #define PCL711_DA0_LO 4
76 #define PCL711_AD_HI 5
77 #define PCL711_DA0_HI 5
78 #define PCL711_DI_LO 6
79 #define PCL711_DA1_LO 6
80 #define PCL711_DI_HI 7
81 #define PCL711_DA1_HI 7
82 #define PCL711_CLRINTR 8
85 #define PCL711_MODE 11
86 #define PCL711_SOFTTRIG 12
87 #define PCL711_DO_LO 13
88 #define PCL711_DO_HI 14
90 static const comedi_lrange range_pcl711b_ai = { 5, {
98 static const comedi_lrange range_acl8112hg_ai = { 12, {
113 static const comedi_lrange range_acl8112dg_ai = { 9, {
130 #define PCL711_TIMEOUT 100
131 #define PCL711_DRDY 0x10
133 static const int i8253_osc_base = 500; /* 2 Mhz */
144 const comedi_lrange *ai_range_type;
147 static const boardtype boardtypes[] = {
148 {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5},
149 {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai},
150 {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai},
151 {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai},
154 #define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype))
155 #define this_board ((const boardtype *)dev->board_ptr)
157 static int pcl711_attach(comedi_device * dev, comedi_devconfig * it);
158 static int pcl711_detach(comedi_device * dev);
159 static comedi_driver driver_pcl711 = {
160 driver_name:"pcl711",
162 attach:pcl711_attach,
163 detach:pcl711_detach,
164 board_name:&boardtypes[0].name,
165 num_names:n_boardtypes,
166 offset:sizeof(boardtype),
169 COMEDI_INITCLEANUP(driver_pcl711);
177 lsampl_t ao_readback[2];
178 unsigned int divisor1;
179 unsigned int divisor2;
182 #define devpriv ((pcl711_private *)dev->private)
184 static irqreturn_t pcl711_interrupt(int irq, void *d PT_REGS_ARG)
188 comedi_device *dev = d;
189 comedi_subdevice *s = dev->subdevices + 0;
191 if (!dev->attached) {
192 comedi_error(dev, "spurious interrupt");
196 hi = inb(dev->iobase + PCL711_AD_HI);
197 lo = inb(dev->iobase + PCL711_AD_LO);
198 outb(0, dev->iobase + PCL711_CLRINTR);
200 data = (hi << 8) | lo;
202 /* FIXME! Nothing else sets ntrig! */
203 if (!(--devpriv->ntrig)) {
204 if (this_board->is_8112) {
205 outb(1, dev->iobase + PCL711_MODE);
207 outb(0, dev->iobase + PCL711_MODE);
210 s->async->events |= COMEDI_CB_EOA;
212 comedi_event(dev, s);
216 static void pcl711_set_changain(comedi_device * dev, int chan)
220 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
222 chan_register = CR_CHAN(chan);
224 if (this_board->is_8112) {
227 * Set the correct channel. The two channel banks are switched
228 * using the mask value.
229 * NB: To use differential channels, you should use mask = 0x30,
230 * but I haven't written the support for this yet. /JJ
233 if (chan_register >= 8) {
234 chan_register = 0x20 | (chan_register & 0x7);
236 chan_register |= 0x10;
239 outb(chan_register, dev->iobase + PCL711_MUX);
243 static int pcl711_ai_insn(comedi_device * dev, comedi_subdevice * s,
244 comedi_insn * insn, lsampl_t * data)
249 pcl711_set_changain(dev, insn->chanspec);
251 for (n = 0; n < insn->n; n++) {
253 * Write the correct mode (software polling) and start polling by writing
254 * to the trigger register
256 outb(1, dev->iobase + PCL711_MODE);
258 if (this_board->is_8112) {
260 outb(0, dev->iobase + PCL711_SOFTTRIG);
265 hi = inb(dev->iobase + PCL711_AD_HI);
266 if (!(hi & PCL711_DRDY))
270 rt_printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
274 lo = inb(dev->iobase + PCL711_AD_LO);
276 data[n] = ((hi & 0xf) << 8) | lo;
282 static int pcl711_ai_cmdtest(comedi_device * dev, comedi_subdevice * s,
289 tmp = cmd->start_src;
290 cmd->start_src &= TRIG_NOW;
291 if (!cmd->start_src || tmp != cmd->start_src)
294 tmp = cmd->scan_begin_src;
295 cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT;
296 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
299 tmp = cmd->convert_src;
300 cmd->convert_src &= TRIG_NOW;
301 if (!cmd->convert_src || tmp != cmd->convert_src)
304 tmp = cmd->scan_end_src;
305 cmd->scan_end_src &= TRIG_COUNT;
306 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
310 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
311 if (!cmd->stop_src || tmp != cmd->stop_src)
319 if (cmd->scan_begin_src != TRIG_TIMER &&
320 cmd->scan_begin_src != TRIG_EXT)
322 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
330 if (cmd->start_arg != 0) {
334 if (cmd->scan_begin_src == TRIG_EXT) {
335 if (cmd->scan_begin_arg != 0) {
336 cmd->scan_begin_arg = 0;
340 #define MAX_SPEED 1000
341 #define TIMER_BASE 100
342 if (cmd->scan_begin_arg < MAX_SPEED) {
343 cmd->scan_begin_arg = MAX_SPEED;
347 if (cmd->convert_arg != 0) {
348 cmd->convert_arg = 0;
351 if (cmd->scan_end_arg != cmd->chanlist_len) {
352 cmd->scan_end_arg = cmd->chanlist_len;
355 if (cmd->stop_src == TRIG_NONE) {
356 if (cmd->stop_arg != 0) {
369 if (cmd->scan_begin_src == TRIG_TIMER) {
370 tmp = cmd->scan_begin_arg;
371 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
372 &devpriv->divisor1, &devpriv->divisor2,
373 &cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK);
374 if (tmp != cmd->scan_begin_arg)
384 static int pcl711_ai_cmd(comedi_device * dev, comedi_subdevice * s)
387 comedi_cmd *cmd = &s->async->cmd;
389 pcl711_set_changain(dev, cmd->chanlist[0]);
391 if (cmd->scan_begin_src == TRIG_TIMER) {
394 * timer chip is an 8253, with timers 1 and 2
396 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
397 * Mode 2 = Rate generator
399 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
402 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
403 &cmd->scan_begin_arg, TRIG_ROUND_NEAREST);
405 outb(0x74, dev->iobase + PCL711_CTRCTL);
406 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
407 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
408 outb(0xb4, dev->iobase + PCL711_CTRCTL);
409 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
410 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
412 /* clear pending interrupts (just in case) */
413 outb(0, dev->iobase + PCL711_CLRINTR);
416 * Set mode to IRQ transfer
418 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
420 /* external trigger */
421 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
430 static int pcl711_ao_insn(comedi_device * dev, comedi_subdevice * s,
431 comedi_insn * insn, lsampl_t * data)
434 int chan = CR_CHAN(insn->chanspec);
436 for (n = 0; n < insn->n; n++) {
437 outb((data[n] & 0xff),
438 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
440 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
442 devpriv->ao_readback[chan] = data[n];
448 static int pcl711_ao_insn_read(comedi_device * dev, comedi_subdevice * s,
449 comedi_insn * insn, lsampl_t * data)
452 int chan = CR_CHAN(insn->chanspec);
454 for (n = 0; n < insn->n; n++) {
455 data[n] = devpriv->ao_readback[chan];
462 /* Digital port read - Untested on 8112 */
463 static int pcl711_di_insn_bits(comedi_device * dev, comedi_subdevice * s,
464 comedi_insn * insn, lsampl_t * data)
469 data[1] = inb(dev->iobase + PCL711_DI_LO) |
470 (inb(dev->iobase + PCL711_DI_HI) << 8);
475 /* Digital port write - Untested on 8112 */
476 static int pcl711_do_insn_bits(comedi_device * dev, comedi_subdevice * s,
477 comedi_insn * insn, lsampl_t * data)
483 s->state &= ~data[0];
484 s->state |= data[0] & data[1];
486 if (data[0] & 0x00ff)
487 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
488 if (data[0] & 0xff00)
489 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
496 /* Free any resources that we have claimed */
497 static int pcl711_detach(comedi_device * dev)
499 printk("comedi%d: pcl711: remove\n", dev->minor);
502 comedi_free_irq(dev->irq, dev);
505 release_region(dev->iobase, PCL711_SIZE);
511 static int pcl711_attach(comedi_device * dev, comedi_devconfig * it)
514 unsigned long iobase;
518 /* claim our I/O space */
520 iobase = it->options[0];
521 printk("comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
522 if (!request_region(iobase, PCL711_SIZE, "pcl711")) {
523 printk("I/O port conflict\n");
526 dev->iobase = iobase;
528 /* there should be a sanity check here */
530 /* set up some name stuff */
531 dev->board_name = this_board->name;
534 irq = it->options[1];
535 if (irq > this_board->maxirq) {
536 printk("irq out of range\n");
540 if (comedi_request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
541 printk("unable to allocate irq %u\n", irq);
544 printk("( irq = %u )\n", irq);
549 if ((ret = alloc_subdevices(dev, 4)) < 0)
551 if ((ret = alloc_private(dev, sizeof(pcl711_private))) < 0)
554 s = dev->subdevices + 0;
556 s->type = COMEDI_SUBD_AI;
557 s->subdev_flags = SDF_READABLE | SDF_GROUND;
558 s->n_chan = this_board->n_aichan;
561 s->range_table = this_board->ai_range_type;
562 s->insn_read = pcl711_ai_insn;
564 dev->read_subdev = s;
565 s->subdev_flags |= SDF_CMD_READ;
566 s->do_cmdtest = pcl711_ai_cmdtest;
567 s->do_cmd = pcl711_ai_cmd;
572 s->type = COMEDI_SUBD_AO;
573 s->subdev_flags = SDF_WRITABLE;
574 s->n_chan = this_board->n_aochan;
577 s->range_table = &range_bipolar5;
578 s->insn_write = pcl711_ao_insn;
579 s->insn_read = pcl711_ao_insn_read;
582 /* 16-bit digital input */
583 s->type = COMEDI_SUBD_DI;
584 s->subdev_flags = SDF_READABLE;
587 s->len_chanlist = 16;
588 s->range_table = &range_digital;
589 s->insn_bits = pcl711_di_insn_bits;
592 /* 16-bit digital out */
593 s->type = COMEDI_SUBD_DO;
594 s->subdev_flags = SDF_WRITABLE;
597 s->len_chanlist = 16;
598 s->range_table = &range_digital;
600 s->insn_bits = pcl711_do_insn_bits;
603 this is the "base value" for the mode register, which is
604 used for the irq on the PCL711
606 if (this_board->is_pcl711b) {
607 devpriv->mode = (dev->irq << 4);
611 outb(0, dev->iobase + PCL711_DA0_LO);
612 outb(0, dev->iobase + PCL711_DA0_HI);
613 outb(0, dev->iobase + PCL711_DA1_LO);
614 outb(0, dev->iobase + PCL711_DA1_HI);