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 struct pcl711_private {
158 unsigned int ao_readback[2];
159 unsigned int divisor1;
160 unsigned int divisor2;
163 #define devpriv ((struct pcl711_private *)dev->private)
165 static irqreturn_t pcl711_interrupt(int irq, void *d)
169 struct comedi_device *dev = d;
170 const struct pcl711_board *board = comedi_board(dev);
171 struct comedi_subdevice *s = dev->subdevices + 0;
173 if (!dev->attached) {
174 comedi_error(dev, "spurious interrupt");
178 hi = inb(dev->iobase + PCL711_AD_HI);
179 lo = inb(dev->iobase + PCL711_AD_LO);
180 outb(0, dev->iobase + PCL711_CLRINTR);
182 data = (hi << 8) | lo;
184 /* FIXME! Nothing else sets ntrig! */
185 if (!(--devpriv->ntrig)) {
187 outb(1, dev->iobase + PCL711_MODE);
189 outb(0, dev->iobase + PCL711_MODE);
191 s->async->events |= COMEDI_CB_EOA;
193 comedi_event(dev, s);
197 static void pcl711_set_changain(struct comedi_device *dev, int chan)
199 const struct pcl711_board *board = comedi_board(dev);
202 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
204 chan_register = CR_CHAN(chan);
206 if (board->is_8112) {
209 * Set the correct channel. The two channel banks are switched
210 * using the mask value.
211 * NB: To use differential channels, you should use
212 * mask = 0x30, but I haven't written the support for this
216 if (chan_register >= 8)
217 chan_register = 0x20 | (chan_register & 0x7);
219 chan_register |= 0x10;
221 outb(chan_register, dev->iobase + PCL711_MUX);
225 static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
226 struct comedi_insn *insn, unsigned int *data)
228 const struct pcl711_board *board = comedi_board(dev);
232 pcl711_set_changain(dev, insn->chanspec);
234 for (n = 0; n < insn->n; n++) {
236 * Write the correct mode (software polling) and start polling
237 * by writing to the trigger register
239 outb(1, dev->iobase + PCL711_MODE);
242 outb(0, dev->iobase + PCL711_SOFTTRIG);
246 hi = inb(dev->iobase + PCL711_AD_HI);
247 if (!(hi & PCL711_DRDY))
251 printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor);
255 lo = inb(dev->iobase + PCL711_AD_LO);
257 data[n] = ((hi & 0xf) << 8) | lo;
263 static int pcl711_ai_cmdtest(struct comedi_device *dev,
264 struct comedi_subdevice *s, struct comedi_cmd *cmd)
270 tmp = cmd->start_src;
271 cmd->start_src &= TRIG_NOW;
272 if (!cmd->start_src || tmp != cmd->start_src)
275 tmp = cmd->scan_begin_src;
276 cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT;
277 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
280 tmp = cmd->convert_src;
281 cmd->convert_src &= TRIG_NOW;
282 if (!cmd->convert_src || tmp != cmd->convert_src)
285 tmp = cmd->scan_end_src;
286 cmd->scan_end_src &= TRIG_COUNT;
287 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
291 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
292 if (!cmd->stop_src || tmp != cmd->stop_src)
300 if (cmd->scan_begin_src != TRIG_TIMER &&
301 cmd->scan_begin_src != TRIG_EXT)
303 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
311 if (cmd->start_arg != 0) {
315 if (cmd->scan_begin_src == TRIG_EXT) {
316 if (cmd->scan_begin_arg != 0) {
317 cmd->scan_begin_arg = 0;
321 #define MAX_SPEED 1000
322 #define TIMER_BASE 100
323 if (cmd->scan_begin_arg < MAX_SPEED) {
324 cmd->scan_begin_arg = MAX_SPEED;
328 if (cmd->convert_arg != 0) {
329 cmd->convert_arg = 0;
332 if (cmd->scan_end_arg != cmd->chanlist_len) {
333 cmd->scan_end_arg = cmd->chanlist_len;
336 if (cmd->stop_src == TRIG_NONE) {
337 if (cmd->stop_arg != 0) {
350 if (cmd->scan_begin_src == TRIG_TIMER) {
351 tmp = cmd->scan_begin_arg;
352 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
355 &cmd->scan_begin_arg,
356 cmd->flags & TRIG_ROUND_MASK);
357 if (tmp != cmd->scan_begin_arg)
367 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
370 struct comedi_cmd *cmd = &s->async->cmd;
372 pcl711_set_changain(dev, cmd->chanlist[0]);
374 if (cmd->scan_begin_src == TRIG_TIMER) {
377 * timer chip is an 8253, with timers 1 and 2
379 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
380 * Mode 2 = Rate generator
382 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
386 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
387 &cmd->scan_begin_arg,
390 outb(0x74, dev->iobase + PCL711_CTRCTL);
391 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
392 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
393 outb(0xb4, dev->iobase + PCL711_CTRCTL);
394 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
395 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
397 /* clear pending interrupts (just in case) */
398 outb(0, dev->iobase + PCL711_CLRINTR);
401 * Set mode to IRQ transfer
403 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
405 /* external trigger */
406 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
415 static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
416 struct comedi_insn *insn, unsigned int *data)
419 int chan = CR_CHAN(insn->chanspec);
421 for (n = 0; n < insn->n; n++) {
422 outb((data[n] & 0xff),
423 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
425 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
427 devpriv->ao_readback[chan] = data[n];
433 static int pcl711_ao_insn_read(struct comedi_device *dev,
434 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 data[n] = devpriv->ao_readback[chan];
447 /* Digital port read - Untested on 8112 */
448 static int pcl711_di_insn_bits(struct comedi_device *dev,
449 struct comedi_subdevice *s,
450 struct comedi_insn *insn, unsigned int *data)
452 data[1] = inb(dev->iobase + PCL711_DI_LO) |
453 (inb(dev->iobase + PCL711_DI_HI) << 8);
458 /* Digital port write - Untested on 8112 */
459 static int pcl711_do_insn_bits(struct comedi_device *dev,
460 struct comedi_subdevice *s,
461 struct comedi_insn *insn, unsigned int *data)
464 s->state &= ~data[0];
465 s->state |= data[0] & data[1];
467 if (data[0] & 0x00ff)
468 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
469 if (data[0] & 0xff00)
470 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
477 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
479 const struct pcl711_board *board = comedi_board(dev);
481 unsigned long iobase;
483 struct comedi_subdevice *s;
485 /* claim our I/O space */
487 iobase = it->options[0];
488 printk(KERN_INFO "comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
489 if (!request_region(iobase, PCL711_SIZE, "pcl711")) {
490 printk("I/O port conflict\n");
493 dev->iobase = iobase;
495 /* there should be a sanity check here */
497 dev->board_name = board->name;
500 irq = it->options[1];
501 if (irq > board->maxirq) {
502 printk(KERN_ERR "irq out of range\n");
506 if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
507 printk(KERN_ERR "unable to allocate irq %u\n", irq);
510 printk(KERN_INFO "( irq = %u )\n", irq);
515 ret = comedi_alloc_subdevices(dev, 4);
519 ret = alloc_private(dev, sizeof(struct pcl711_private));
523 s = dev->subdevices + 0;
525 s->type = COMEDI_SUBD_AI;
526 s->subdev_flags = SDF_READABLE | SDF_GROUND;
527 s->n_chan = board->n_aichan;
530 s->range_table = board->ai_range_type;
531 s->insn_read = pcl711_ai_insn;
533 dev->read_subdev = s;
534 s->subdev_flags |= SDF_CMD_READ;
535 s->do_cmdtest = pcl711_ai_cmdtest;
536 s->do_cmd = pcl711_ai_cmd;
541 s->type = COMEDI_SUBD_AO;
542 s->subdev_flags = SDF_WRITABLE;
543 s->n_chan = board->n_aochan;
546 s->range_table = &range_bipolar5;
547 s->insn_write = pcl711_ao_insn;
548 s->insn_read = pcl711_ao_insn_read;
551 /* 16-bit digital input */
552 s->type = COMEDI_SUBD_DI;
553 s->subdev_flags = SDF_READABLE;
556 s->len_chanlist = 16;
557 s->range_table = &range_digital;
558 s->insn_bits = pcl711_di_insn_bits;
561 /* 16-bit digital out */
562 s->type = COMEDI_SUBD_DO;
563 s->subdev_flags = SDF_WRITABLE;
566 s->len_chanlist = 16;
567 s->range_table = &range_digital;
569 s->insn_bits = pcl711_do_insn_bits;
572 this is the "base value" for the mode register, which is
573 used for the irq on the PCL711
575 if (board->is_pcl711b)
576 devpriv->mode = (dev->irq << 4);
579 outb(0, dev->iobase + PCL711_DA0_LO);
580 outb(0, dev->iobase + PCL711_DA0_HI);
581 outb(0, dev->iobase + PCL711_DA1_LO);
582 outb(0, dev->iobase + PCL711_DA1_HI);
584 printk(KERN_INFO "\n");
589 static void pcl711_detach(struct comedi_device *dev)
592 free_irq(dev->irq, dev);
594 release_region(dev->iobase, PCL711_SIZE);
597 static const struct pcl711_board boardtypes[] = {
598 { "pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5 },
599 { "pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai },
600 { "acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai },
601 { "acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai },
604 static struct comedi_driver pcl711_driver = {
605 .driver_name = "pcl711",
606 .module = THIS_MODULE,
607 .attach = pcl711_attach,
608 .detach = pcl711_detach,
609 .board_name = &boardtypes[0].name,
610 .num_names = ARRAY_SIZE(boardtypes),
611 .offset = sizeof(struct pcl711_board),
613 module_comedi_driver(pcl711_driver);
615 MODULE_AUTHOR("Comedi http://www.comedi.org");
616 MODULE_DESCRIPTION("Comedi low-level driver");
617 MODULE_LICENSE("GPL");