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 struct comedi_lrange range_pcl711b_ai = { 5, {
98 static const struct comedi_lrange range_acl8112hg_ai = { 12, {
113 static const struct 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 */
135 struct pcl711_board {
145 const struct comedi_lrange *ai_range_type;
149 static const struct pcl711_board boardtypes[] = {
150 {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5},
151 {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai},
152 {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai},
153 {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai},
156 #define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl711_board))
157 #define this_board ((const struct pcl711_board *)dev->board_ptr)
159 static int pcl711_attach(struct comedi_device * dev, struct comedi_devconfig * it);
160 static int pcl711_detach(struct comedi_device * dev);
161 static struct comedi_driver driver_pcl711 = {
162 driver_name:"pcl711",
164 attach:pcl711_attach,
165 detach:pcl711_detach,
166 board_name:&boardtypes[0].name,
167 num_names:n_boardtypes,
168 offset:sizeof(struct pcl711_board),
171 COMEDI_INITCLEANUP(driver_pcl711);
173 struct pcl711_private {
180 unsigned int ao_readback[2];
181 unsigned int divisor1;
182 unsigned int divisor2;
186 #define devpriv ((struct pcl711_private *)dev->private)
188 static irqreturn_t pcl711_interrupt(int irq, void *d PT_REGS_ARG)
192 struct comedi_device *dev = d;
193 struct comedi_subdevice *s = dev->subdevices + 0;
195 if (!dev->attached) {
196 comedi_error(dev, "spurious interrupt");
200 hi = inb(dev->iobase + PCL711_AD_HI);
201 lo = inb(dev->iobase + PCL711_AD_LO);
202 outb(0, dev->iobase + PCL711_CLRINTR);
204 data = (hi << 8) | lo;
206 /* FIXME! Nothing else sets ntrig! */
207 if (!(--devpriv->ntrig)) {
208 if (this_board->is_8112) {
209 outb(1, dev->iobase + PCL711_MODE);
211 outb(0, dev->iobase + PCL711_MODE);
214 s->async->events |= COMEDI_CB_EOA;
216 comedi_event(dev, s);
220 static void pcl711_set_changain(struct comedi_device * dev, int chan)
224 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
226 chan_register = CR_CHAN(chan);
228 if (this_board->is_8112) {
231 * Set the correct channel. The two channel banks are switched
232 * using the mask value.
233 * NB: To use differential channels, you should use mask = 0x30,
234 * but I haven't written the support for this yet. /JJ
237 if (chan_register >= 8) {
238 chan_register = 0x20 | (chan_register & 0x7);
240 chan_register |= 0x10;
243 outb(chan_register, dev->iobase + PCL711_MUX);
247 static int pcl711_ai_insn(struct comedi_device * dev, struct comedi_subdevice * s,
248 struct comedi_insn * insn, unsigned int * data)
253 pcl711_set_changain(dev, insn->chanspec);
255 for (n = 0; n < insn->n; n++) {
257 * Write the correct mode (software polling) and start polling by writing
258 * to the trigger register
260 outb(1, dev->iobase + PCL711_MODE);
262 if (this_board->is_8112) {
264 outb(0, dev->iobase + PCL711_SOFTTRIG);
269 hi = inb(dev->iobase + PCL711_AD_HI);
270 if (!(hi & PCL711_DRDY))
274 rt_printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
278 lo = inb(dev->iobase + PCL711_AD_LO);
280 data[n] = ((hi & 0xf) << 8) | lo;
286 static int pcl711_ai_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s,
287 struct comedi_cmd * cmd)
293 tmp = cmd->start_src;
294 cmd->start_src &= TRIG_NOW;
295 if (!cmd->start_src || tmp != cmd->start_src)
298 tmp = cmd->scan_begin_src;
299 cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT;
300 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
303 tmp = cmd->convert_src;
304 cmd->convert_src &= TRIG_NOW;
305 if (!cmd->convert_src || tmp != cmd->convert_src)
308 tmp = cmd->scan_end_src;
309 cmd->scan_end_src &= TRIG_COUNT;
310 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
314 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
315 if (!cmd->stop_src || tmp != cmd->stop_src)
323 if (cmd->scan_begin_src != TRIG_TIMER &&
324 cmd->scan_begin_src != TRIG_EXT)
326 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
334 if (cmd->start_arg != 0) {
338 if (cmd->scan_begin_src == TRIG_EXT) {
339 if (cmd->scan_begin_arg != 0) {
340 cmd->scan_begin_arg = 0;
344 #define MAX_SPEED 1000
345 #define TIMER_BASE 100
346 if (cmd->scan_begin_arg < MAX_SPEED) {
347 cmd->scan_begin_arg = MAX_SPEED;
351 if (cmd->convert_arg != 0) {
352 cmd->convert_arg = 0;
355 if (cmd->scan_end_arg != cmd->chanlist_len) {
356 cmd->scan_end_arg = cmd->chanlist_len;
359 if (cmd->stop_src == TRIG_NONE) {
360 if (cmd->stop_arg != 0) {
373 if (cmd->scan_begin_src == TRIG_TIMER) {
374 tmp = cmd->scan_begin_arg;
375 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
376 &devpriv->divisor1, &devpriv->divisor2,
377 &cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK);
378 if (tmp != cmd->scan_begin_arg)
388 static int pcl711_ai_cmd(struct comedi_device * dev, struct comedi_subdevice * s)
391 struct comedi_cmd *cmd = &s->async->cmd;
393 pcl711_set_changain(dev, cmd->chanlist[0]);
395 if (cmd->scan_begin_src == TRIG_TIMER) {
398 * timer chip is an 8253, with timers 1 and 2
400 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
401 * Mode 2 = Rate generator
403 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
406 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
407 &cmd->scan_begin_arg, TRIG_ROUND_NEAREST);
409 outb(0x74, dev->iobase + PCL711_CTRCTL);
410 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
411 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
412 outb(0xb4, dev->iobase + PCL711_CTRCTL);
413 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
414 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
416 /* clear pending interrupts (just in case) */
417 outb(0, dev->iobase + PCL711_CLRINTR);
420 * Set mode to IRQ transfer
422 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
424 /* external trigger */
425 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
434 static int pcl711_ao_insn(struct comedi_device * dev, struct comedi_subdevice * s,
435 struct comedi_insn * insn, unsigned int * data)
438 int chan = CR_CHAN(insn->chanspec);
440 for (n = 0; n < insn->n; n++) {
441 outb((data[n] & 0xff),
442 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
444 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
446 devpriv->ao_readback[chan] = data[n];
452 static int pcl711_ao_insn_read(struct comedi_device * dev, struct comedi_subdevice * s,
453 struct comedi_insn * insn, unsigned int * data)
456 int chan = CR_CHAN(insn->chanspec);
458 for (n = 0; n < insn->n; n++) {
459 data[n] = devpriv->ao_readback[chan];
466 /* Digital port read - Untested on 8112 */
467 static int pcl711_di_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s,
468 struct comedi_insn * insn, unsigned int * data)
473 data[1] = inb(dev->iobase + PCL711_DI_LO) |
474 (inb(dev->iobase + PCL711_DI_HI) << 8);
479 /* Digital port write - Untested on 8112 */
480 static int pcl711_do_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s,
481 struct comedi_insn * insn, unsigned int * data)
487 s->state &= ~data[0];
488 s->state |= data[0] & data[1];
490 if (data[0] & 0x00ff)
491 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
492 if (data[0] & 0xff00)
493 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
500 /* Free any resources that we have claimed */
501 static int pcl711_detach(struct comedi_device * dev)
503 printk("comedi%d: pcl711: remove\n", dev->minor);
506 comedi_free_irq(dev->irq, dev);
509 release_region(dev->iobase, PCL711_SIZE);
515 static int pcl711_attach(struct comedi_device * dev, struct comedi_devconfig * it)
518 unsigned long iobase;
520 struct comedi_subdevice *s;
522 /* claim our I/O space */
524 iobase = it->options[0];
525 printk("comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
526 if (!request_region(iobase, PCL711_SIZE, "pcl711")) {
527 printk("I/O port conflict\n");
530 dev->iobase = iobase;
532 /* there should be a sanity check here */
534 /* set up some name stuff */
535 dev->board_name = this_board->name;
538 irq = it->options[1];
539 if (irq > this_board->maxirq) {
540 printk("irq out of range\n");
544 if (comedi_request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
545 printk("unable to allocate irq %u\n", irq);
548 printk("( irq = %u )\n", irq);
553 if ((ret = alloc_subdevices(dev, 4)) < 0)
555 if ((ret = alloc_private(dev, sizeof(struct pcl711_private))) < 0)
558 s = dev->subdevices + 0;
560 s->type = COMEDI_SUBD_AI;
561 s->subdev_flags = SDF_READABLE | SDF_GROUND;
562 s->n_chan = this_board->n_aichan;
565 s->range_table = this_board->ai_range_type;
566 s->insn_read = pcl711_ai_insn;
568 dev->read_subdev = s;
569 s->subdev_flags |= SDF_CMD_READ;
570 s->do_cmdtest = pcl711_ai_cmdtest;
571 s->do_cmd = pcl711_ai_cmd;
576 s->type = COMEDI_SUBD_AO;
577 s->subdev_flags = SDF_WRITABLE;
578 s->n_chan = this_board->n_aochan;
581 s->range_table = &range_bipolar5;
582 s->insn_write = pcl711_ao_insn;
583 s->insn_read = pcl711_ao_insn_read;
586 /* 16-bit digital input */
587 s->type = COMEDI_SUBD_DI;
588 s->subdev_flags = SDF_READABLE;
591 s->len_chanlist = 16;
592 s->range_table = &range_digital;
593 s->insn_bits = pcl711_di_insn_bits;
596 /* 16-bit digital out */
597 s->type = COMEDI_SUBD_DO;
598 s->subdev_flags = SDF_WRITABLE;
601 s->len_chanlist = 16;
602 s->range_table = &range_digital;
604 s->insn_bits = pcl711_do_insn_bits;
607 this is the "base value" for the mode register, which is
608 used for the irq on the PCL711
610 if (this_board->is_pcl711b) {
611 devpriv->mode = (dev->irq << 4);
615 outb(0, dev->iobase + PCL711_DA0_LO);
616 outb(0, dev->iobase + PCL711_DA0_HI);
617 outb(0, dev->iobase + PCL711_DA1_LO);
618 outb(0, dev->iobase + PCL711_DA1_HI);