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, {
99 static const struct comedi_lrange range_acl8112hg_ai = { 12, {
114 static const struct comedi_lrange range_acl8112dg_ai = { 9, {
131 #define PCL711_TIMEOUT 100
132 #define PCL711_DRDY 0x10
134 static const int i8253_osc_base = 500; /* 2 Mhz */
136 struct pcl711_board {
146 const struct comedi_lrange *ai_range_type;
150 static const struct pcl711_board boardtypes[] = {
151 {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5},
152 {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai},
153 {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai},
154 {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai},
157 #define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl711_board))
158 #define this_board ((const struct pcl711_board *)dev->board_ptr)
160 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it);
161 static int pcl711_detach(struct comedi_device *dev);
162 static struct comedi_driver driver_pcl711 = {
163 .driver_name = "pcl711",
164 .module = THIS_MODULE,
165 .attach = pcl711_attach,
166 .detach = pcl711_detach,
167 .board_name = &boardtypes[0].name,
168 .num_names = n_boardtypes,
169 .offset = sizeof(struct pcl711_board),
172 COMEDI_INITCLEANUP(driver_pcl711);
174 struct pcl711_private {
181 unsigned int ao_readback[2];
182 unsigned int divisor1;
183 unsigned int divisor2;
187 #define devpriv ((struct pcl711_private *)dev->private)
189 static irqreturn_t pcl711_interrupt(int irq, void *d)
193 struct comedi_device *dev = d;
194 struct comedi_subdevice *s = dev->subdevices + 0;
196 if (!dev->attached) {
197 comedi_error(dev, "spurious interrupt");
201 hi = inb(dev->iobase + PCL711_AD_HI);
202 lo = inb(dev->iobase + PCL711_AD_LO);
203 outb(0, dev->iobase + PCL711_CLRINTR);
205 data = (hi << 8) | lo;
207 /* FIXME! Nothing else sets ntrig! */
208 if (!(--devpriv->ntrig)) {
209 if (this_board->is_8112) {
210 outb(1, dev->iobase + PCL711_MODE);
212 outb(0, dev->iobase + PCL711_MODE);
215 s->async->events |= COMEDI_CB_EOA;
217 comedi_event(dev, s);
221 static void pcl711_set_changain(struct comedi_device *dev, int chan)
225 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
227 chan_register = CR_CHAN(chan);
229 if (this_board->is_8112) {
232 * Set the correct channel. The two channel banks are switched
233 * using the mask value.
234 * NB: To use differential channels, you should use mask = 0x30,
235 * but I haven't written the support for this yet. /JJ
238 if (chan_register >= 8) {
239 chan_register = 0x20 | (chan_register & 0x7);
241 chan_register |= 0x10;
244 outb(chan_register, dev->iobase + PCL711_MUX);
248 static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
249 struct comedi_insn *insn, unsigned int *data)
254 pcl711_set_changain(dev, insn->chanspec);
256 for (n = 0; n < insn->n; n++) {
258 * Write the correct mode (software polling) and start polling by writing
259 * to the trigger register
261 outb(1, dev->iobase + PCL711_MODE);
263 if (this_board->is_8112) {
265 outb(0, dev->iobase + PCL711_SOFTTRIG);
270 hi = inb(dev->iobase + PCL711_AD_HI);
271 if (!(hi & PCL711_DRDY))
275 printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
279 lo = inb(dev->iobase + PCL711_AD_LO);
281 data[n] = ((hi & 0xf) << 8) | lo;
287 static int pcl711_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
288 struct comedi_cmd *cmd)
294 tmp = cmd->start_src;
295 cmd->start_src &= TRIG_NOW;
296 if (!cmd->start_src || tmp != cmd->start_src)
299 tmp = cmd->scan_begin_src;
300 cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT;
301 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
304 tmp = cmd->convert_src;
305 cmd->convert_src &= TRIG_NOW;
306 if (!cmd->convert_src || tmp != cmd->convert_src)
309 tmp = cmd->scan_end_src;
310 cmd->scan_end_src &= TRIG_COUNT;
311 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
315 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
316 if (!cmd->stop_src || tmp != cmd->stop_src)
324 if (cmd->scan_begin_src != TRIG_TIMER &&
325 cmd->scan_begin_src != TRIG_EXT)
327 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
335 if (cmd->start_arg != 0) {
339 if (cmd->scan_begin_src == TRIG_EXT) {
340 if (cmd->scan_begin_arg != 0) {
341 cmd->scan_begin_arg = 0;
345 #define MAX_SPEED 1000
346 #define TIMER_BASE 100
347 if (cmd->scan_begin_arg < MAX_SPEED) {
348 cmd->scan_begin_arg = MAX_SPEED;
352 if (cmd->convert_arg != 0) {
353 cmd->convert_arg = 0;
356 if (cmd->scan_end_arg != cmd->chanlist_len) {
357 cmd->scan_end_arg = cmd->chanlist_len;
360 if (cmd->stop_src == TRIG_NONE) {
361 if (cmd->stop_arg != 0) {
374 if (cmd->scan_begin_src == TRIG_TIMER) {
375 tmp = cmd->scan_begin_arg;
376 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
377 &devpriv->divisor1, &devpriv->divisor2,
378 &cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK);
379 if (tmp != cmd->scan_begin_arg)
389 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
392 struct comedi_cmd *cmd = &s->async->cmd;
394 pcl711_set_changain(dev, cmd->chanlist[0]);
396 if (cmd->scan_begin_src == TRIG_TIMER) {
399 * timer chip is an 8253, with timers 1 and 2
401 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
402 * Mode 2 = Rate generator
404 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
407 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
408 &cmd->scan_begin_arg, TRIG_ROUND_NEAREST);
410 outb(0x74, dev->iobase + PCL711_CTRCTL);
411 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
412 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
413 outb(0xb4, dev->iobase + PCL711_CTRCTL);
414 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
415 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
417 /* clear pending interrupts (just in case) */
418 outb(0, dev->iobase + PCL711_CLRINTR);
421 * Set mode to IRQ transfer
423 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
425 /* external trigger */
426 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
435 static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
436 struct comedi_insn *insn, unsigned int *data)
439 int chan = CR_CHAN(insn->chanspec);
441 for (n = 0; n < insn->n; n++) {
442 outb((data[n] & 0xff),
443 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
445 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
447 devpriv->ao_readback[chan] = data[n];
453 static int pcl711_ao_insn_read(struct comedi_device *dev, struct comedi_subdevice *s,
454 struct comedi_insn *insn, unsigned int *data)
457 int chan = CR_CHAN(insn->chanspec);
459 for (n = 0; n < insn->n; n++) {
460 data[n] = devpriv->ao_readback[chan];
467 /* Digital port read - Untested on 8112 */
468 static int pcl711_di_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s,
469 struct comedi_insn *insn, unsigned int *data)
474 data[1] = inb(dev->iobase + PCL711_DI_LO) |
475 (inb(dev->iobase + PCL711_DI_HI) << 8);
480 /* Digital port write - Untested on 8112 */
481 static int pcl711_do_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s,
482 struct comedi_insn *insn, unsigned int *data)
488 s->state &= ~data[0];
489 s->state |= data[0] & data[1];
491 if (data[0] & 0x00ff)
492 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
493 if (data[0] & 0xff00)
494 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
501 /* Free any resources that we have claimed */
502 static int pcl711_detach(struct comedi_device *dev)
504 printk("comedi%d: pcl711: remove\n", dev->minor);
507 free_irq(dev->irq, dev);
510 release_region(dev->iobase, PCL711_SIZE);
516 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
519 unsigned long iobase;
521 struct comedi_subdevice *s;
523 /* claim our I/O space */
525 iobase = it->options[0];
526 printk("comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
527 if (!request_region(iobase, PCL711_SIZE, "pcl711")) {
528 printk("I/O port conflict\n");
531 dev->iobase = iobase;
533 /* there should be a sanity check here */
535 /* set up some name stuff */
536 dev->board_name = this_board->name;
539 irq = it->options[1];
540 if (irq > this_board->maxirq) {
541 printk("irq out of range\n");
545 if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
546 printk("unable to allocate irq %u\n", irq);
549 printk("( irq = %u )\n", irq);
554 ret = alloc_subdevices(dev, 4);
558 ret = alloc_private(dev, sizeof(struct pcl711_private));
562 s = dev->subdevices + 0;
564 s->type = COMEDI_SUBD_AI;
565 s->subdev_flags = SDF_READABLE | SDF_GROUND;
566 s->n_chan = this_board->n_aichan;
569 s->range_table = this_board->ai_range_type;
570 s->insn_read = pcl711_ai_insn;
572 dev->read_subdev = s;
573 s->subdev_flags |= SDF_CMD_READ;
574 s->do_cmdtest = pcl711_ai_cmdtest;
575 s->do_cmd = pcl711_ai_cmd;
580 s->type = COMEDI_SUBD_AO;
581 s->subdev_flags = SDF_WRITABLE;
582 s->n_chan = this_board->n_aochan;
585 s->range_table = &range_bipolar5;
586 s->insn_write = pcl711_ao_insn;
587 s->insn_read = pcl711_ao_insn_read;
590 /* 16-bit digital input */
591 s->type = COMEDI_SUBD_DI;
592 s->subdev_flags = SDF_READABLE;
595 s->len_chanlist = 16;
596 s->range_table = &range_digital;
597 s->insn_bits = pcl711_di_insn_bits;
600 /* 16-bit digital out */
601 s->type = COMEDI_SUBD_DO;
602 s->subdev_flags = SDF_WRITABLE;
605 s->len_chanlist = 16;
606 s->range_table = &range_digital;
608 s->insn_bits = pcl711_do_insn_bits;
611 this is the "base value" for the mode register, which is
612 used for the irq on the PCL711
614 if (this_board->is_pcl711b) {
615 devpriv->mode = (dev->irq << 4);
619 outb(0, dev->iobase + PCL711_DA0_LO);
620 outb(0, dev->iobase + PCL711_DA0_HI);
621 outb(0, dev->iobase + PCL711_DA1_LO);
622 outb(0, dev->iobase + PCL711_DA1_HI);