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.
23 Description: Advantech PCL-711 and 711b, ADLink ACL-8112
24 Author: ds, Janne Jalkanen <jalkanen@cs.hut.fi>, Eric Bunn <ebu@cs.hut.fi>
25 Status: mostly complete
26 Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
27 [AdLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
29 Since these boards do not have DMA or FIFOs, only immediate mode is
35 Dave Andruczyk <dave@tech.buffalostate.edu> also wrote a
36 driver for the PCL-711. I used a few ideas from his driver
37 here. His driver also has more comments, if you are
38 interested in understanding how this driver works.
39 http://tech.buffalostate.edu/~dave/driver/
41 The ACL-8112 driver was hacked from the sources of the PCL-711
42 driver (the 744 chip used on the 8112 is almost the same as
43 the 711b chip, but it has more I/O channels) by
44 Janne Jalkanen (jalkanen@cs.hut.fi) and
45 Erik Bunn (ebu@cs.hut.fi). Remerged with the PCL-711 driver
49 This driver supports both TRIGNOW and TRIGCLK,
50 but does not yet support DMA transfers. It also supports
51 both high (HG) and low (DG) versions of the card, though
52 the HG version has been untested.
56 #include <linux/module.h>
57 #include <linux/interrupt.h>
58 #include "../comedidev.h"
60 #include <linux/delay.h>
62 #include "comedi_fc.h"
65 #define PCL711_SIZE 16
70 #define PCL711_CTRCTL 3
71 #define PCL711_AD_LO 4
72 #define PCL711_DA0_LO 4
73 #define PCL711_AD_HI 5
74 #define PCL711_DA0_HI 5
75 #define PCL711_DI_LO 6
76 #define PCL711_DA1_LO 6
77 #define PCL711_DI_HI 7
78 #define PCL711_DA1_HI 7
79 #define PCL711_CLRINTR 8
82 #define PCL711_MODE 11
83 #define PCL711_SOFTTRIG 12
84 #define PCL711_DO_LO 13
85 #define PCL711_DO_HI 14
87 static const struct comedi_lrange range_pcl711b_ai = { 5, {
96 static const struct comedi_lrange range_acl8112hg_ai = { 12, {
112 static const struct comedi_lrange range_acl8112dg_ai = { 9, {
129 #define PCL711_TIMEOUT 100
130 #define PCL711_DRDY 0x10
132 static const int i8253_osc_base = 500; /* 2 Mhz */
134 struct pcl711_board {
144 const struct comedi_lrange *ai_range_type;
147 struct pcl711_private {
154 unsigned int ao_readback[2];
155 unsigned int divisor1;
156 unsigned int divisor2;
159 static irqreturn_t pcl711_interrupt(int irq, void *d)
163 struct comedi_device *dev = d;
164 const struct pcl711_board *board = comedi_board(dev);
165 struct pcl711_private *devpriv = dev->private;
166 struct comedi_subdevice *s = &dev->subdevices[0];
168 if (!dev->attached) {
169 comedi_error(dev, "spurious interrupt");
173 hi = inb(dev->iobase + PCL711_AD_HI);
174 lo = inb(dev->iobase + PCL711_AD_LO);
175 outb(0, dev->iobase + PCL711_CLRINTR);
177 data = (hi << 8) | lo;
179 /* FIXME! Nothing else sets ntrig! */
180 if (!(--devpriv->ntrig)) {
182 outb(1, dev->iobase + PCL711_MODE);
184 outb(0, dev->iobase + PCL711_MODE);
186 s->async->events |= COMEDI_CB_EOA;
188 comedi_event(dev, s);
192 static void pcl711_set_changain(struct comedi_device *dev, int chan)
194 const struct pcl711_board *board = comedi_board(dev);
197 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
199 chan_register = CR_CHAN(chan);
201 if (board->is_8112) {
204 * Set the correct channel. The two channel banks are switched
205 * using the mask value.
206 * NB: To use differential channels, you should use
207 * mask = 0x30, but I haven't written the support for this
211 if (chan_register >= 8)
212 chan_register = 0x20 | (chan_register & 0x7);
214 chan_register |= 0x10;
216 outb(chan_register, dev->iobase + PCL711_MUX);
220 static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
221 struct comedi_insn *insn, unsigned int *data)
223 const struct pcl711_board *board = comedi_board(dev);
227 pcl711_set_changain(dev, insn->chanspec);
229 for (n = 0; n < insn->n; n++) {
231 * Write the correct mode (software polling) and start polling
232 * by writing to the trigger register
234 outb(1, dev->iobase + PCL711_MODE);
237 outb(0, dev->iobase + PCL711_SOFTTRIG);
241 hi = inb(dev->iobase + PCL711_AD_HI);
242 if (!(hi & PCL711_DRDY))
246 printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor);
250 lo = inb(dev->iobase + PCL711_AD_LO);
252 data[n] = ((hi & 0xf) << 8) | lo;
258 static int pcl711_ai_cmdtest(struct comedi_device *dev,
259 struct comedi_subdevice *s, struct comedi_cmd *cmd)
261 struct pcl711_private *devpriv = dev->private;
265 /* Step 1 : check if triggers are trivially valid */
267 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
268 err |= cfc_check_trigger_src(&cmd->scan_begin_src,
269 TRIG_TIMER | TRIG_EXT);
270 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
271 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
272 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
277 /* Step 2a : make sure trigger sources are unique */
279 err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
280 err |= cfc_check_trigger_is_unique(cmd->stop_src);
282 /* Step 2b : and mutually compatible */
287 /* Step 3: check if arguments are trivially valid */
289 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
291 if (cmd->scan_begin_src == TRIG_EXT) {
292 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
294 #define MAX_SPEED 1000
295 #define TIMER_BASE 100
296 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
300 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
301 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
303 if (cmd->stop_src == TRIG_NONE) {
304 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
314 if (cmd->scan_begin_src == TRIG_TIMER) {
315 tmp = cmd->scan_begin_arg;
316 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
319 &cmd->scan_begin_arg,
320 cmd->flags & TRIG_ROUND_MASK);
321 if (tmp != cmd->scan_begin_arg)
331 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
333 struct pcl711_private *devpriv = dev->private;
335 struct comedi_cmd *cmd = &s->async->cmd;
337 pcl711_set_changain(dev, cmd->chanlist[0]);
339 if (cmd->scan_begin_src == TRIG_TIMER) {
342 * timer chip is an 8253, with timers 1 and 2
344 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
345 * Mode 2 = Rate generator
347 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
351 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
352 &cmd->scan_begin_arg,
355 outb(0x74, dev->iobase + PCL711_CTRCTL);
356 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
357 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
358 outb(0xb4, dev->iobase + PCL711_CTRCTL);
359 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
360 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
362 /* clear pending interrupts (just in case) */
363 outb(0, dev->iobase + PCL711_CLRINTR);
366 * Set mode to IRQ transfer
368 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
370 /* external trigger */
371 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
380 static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
381 struct comedi_insn *insn, unsigned int *data)
383 struct pcl711_private *devpriv = dev->private;
385 int chan = CR_CHAN(insn->chanspec);
387 for (n = 0; n < insn->n; n++) {
388 outb((data[n] & 0xff),
389 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
391 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
393 devpriv->ao_readback[chan] = data[n];
399 static int pcl711_ao_insn_read(struct comedi_device *dev,
400 struct comedi_subdevice *s,
401 struct comedi_insn *insn, unsigned int *data)
403 struct pcl711_private *devpriv = dev->private;
405 int chan = CR_CHAN(insn->chanspec);
407 for (n = 0; n < insn->n; n++)
408 data[n] = devpriv->ao_readback[chan];
414 /* Digital port read - Untested on 8112 */
415 static int pcl711_di_insn_bits(struct comedi_device *dev,
416 struct comedi_subdevice *s,
417 struct comedi_insn *insn, unsigned int *data)
419 data[1] = inb(dev->iobase + PCL711_DI_LO) |
420 (inb(dev->iobase + PCL711_DI_HI) << 8);
425 /* Digital port write - Untested on 8112 */
426 static int pcl711_do_insn_bits(struct comedi_device *dev,
427 struct comedi_subdevice *s,
428 struct comedi_insn *insn, unsigned int *data)
431 s->state &= ~data[0];
432 s->state |= data[0] & data[1];
434 if (data[0] & 0x00ff)
435 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
436 if (data[0] & 0xff00)
437 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
444 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
446 const struct pcl711_board *board = comedi_board(dev);
447 struct pcl711_private *devpriv;
450 struct comedi_subdevice *s;
452 ret = comedi_request_region(dev, it->options[0], PCL711_SIZE);
457 irq = it->options[1];
458 if (irq > board->maxirq) {
459 printk(KERN_ERR "irq out of range\n");
463 if (request_irq(irq, pcl711_interrupt, 0, dev->board_name,
465 printk(KERN_ERR "unable to allocate irq %u\n", irq);
468 printk(KERN_INFO "( irq = %u )\n", irq);
473 ret = comedi_alloc_subdevices(dev, 4);
477 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
481 s = &dev->subdevices[0];
483 s->type = COMEDI_SUBD_AI;
484 s->subdev_flags = SDF_READABLE | SDF_GROUND;
485 s->n_chan = board->n_aichan;
488 s->range_table = board->ai_range_type;
489 s->insn_read = pcl711_ai_insn;
491 dev->read_subdev = s;
492 s->subdev_flags |= SDF_CMD_READ;
493 s->do_cmdtest = pcl711_ai_cmdtest;
494 s->do_cmd = pcl711_ai_cmd;
497 s = &dev->subdevices[1];
499 s->type = COMEDI_SUBD_AO;
500 s->subdev_flags = SDF_WRITABLE;
501 s->n_chan = board->n_aochan;
504 s->range_table = &range_bipolar5;
505 s->insn_write = pcl711_ao_insn;
506 s->insn_read = pcl711_ao_insn_read;
508 s = &dev->subdevices[2];
509 /* 16-bit digital input */
510 s->type = COMEDI_SUBD_DI;
511 s->subdev_flags = SDF_READABLE;
514 s->len_chanlist = 16;
515 s->range_table = &range_digital;
516 s->insn_bits = pcl711_di_insn_bits;
518 s = &dev->subdevices[3];
519 /* 16-bit digital out */
520 s->type = COMEDI_SUBD_DO;
521 s->subdev_flags = SDF_WRITABLE;
524 s->len_chanlist = 16;
525 s->range_table = &range_digital;
527 s->insn_bits = pcl711_do_insn_bits;
530 this is the "base value" for the mode register, which is
531 used for the irq on the PCL711
533 if (board->is_pcl711b)
534 devpriv->mode = (dev->irq << 4);
537 outb(0, dev->iobase + PCL711_DA0_LO);
538 outb(0, dev->iobase + PCL711_DA0_HI);
539 outb(0, dev->iobase + PCL711_DA1_LO);
540 outb(0, dev->iobase + PCL711_DA1_HI);
542 printk(KERN_INFO "\n");
547 static const struct pcl711_board boardtypes[] = {
548 { "pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5 },
549 { "pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai },
550 { "acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai },
551 { "acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai },
554 static struct comedi_driver pcl711_driver = {
555 .driver_name = "pcl711",
556 .module = THIS_MODULE,
557 .attach = pcl711_attach,
558 .detach = comedi_legacy_detach,
559 .board_name = &boardtypes[0].name,
560 .num_names = ARRAY_SIZE(boardtypes),
561 .offset = sizeof(struct pcl711_board),
563 module_comedi_driver(pcl711_driver);
565 MODULE_AUTHOR("Comedi http://www.comedi.org");
566 MODULE_DESCRIPTION("Comedi low-level driver");
567 MODULE_LICENSE("GPL");