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"
66 * I/O port register map
68 #define PCL711_CTR0 0x00
69 #define PCL711_CTR1 0x01
70 #define PCL711_CTR2 0x02
71 #define PCL711_CTRCTL 0x03
72 #define PCL711_AD_LO 0x04
73 #define PCL711_DA0_LO 0x04
74 #define PCL711_AD_HI 0x05
75 #define PCL711_DA0_HI 0x05
76 #define PCL711_DI_LO 0x06
77 #define PCL711_DA1_LO 0x06
78 #define PCL711_DI_HI 0x07
79 #define PCL711_DA1_HI 0x07
80 #define PCL711_CLRINTR 0x08
81 #define PCL711_GAIN 0x09
82 #define PCL711_MUX 0x0a
83 #define PCL711_MODE 0x0b
84 #define PCL711_SOFTTRIG 0x0c
85 #define PCL711_DO_LO 0x0d
86 #define PCL711_DO_HI 0x0e
88 static const struct comedi_lrange range_pcl711b_ai = {
98 static const struct comedi_lrange range_acl8112hg_ai = {
115 static const struct comedi_lrange range_acl8112dg_ai = {
133 #define PCL711_TIMEOUT 100
134 #define PCL711_DRDY 0x10
136 static const int i8253_osc_base = 500; /* 2 Mhz */
138 struct pcl711_board {
140 unsigned int is_pcl711b:1;
141 unsigned int is_8112:1;
145 const struct comedi_lrange *ai_range_type;
148 static const struct pcl711_board boardtypes[] = {
153 .ai_range_type = &range_bipolar5,
160 .ai_range_type = &range_pcl711b_ai,
167 .ai_range_type = &range_acl8112hg_ai,
174 .ai_range_type = &range_acl8112dg_ai,
178 struct pcl711_private {
185 unsigned int ao_readback[2];
186 unsigned int divisor1;
187 unsigned int divisor2;
190 static irqreturn_t pcl711_interrupt(int irq, void *d)
194 struct comedi_device *dev = d;
195 const struct pcl711_board *board = comedi_board(dev);
196 struct pcl711_private *devpriv = dev->private;
197 struct comedi_subdevice *s = &dev->subdevices[0];
199 if (!dev->attached) {
200 comedi_error(dev, "spurious interrupt");
204 hi = inb(dev->iobase + PCL711_AD_HI);
205 lo = inb(dev->iobase + PCL711_AD_LO);
206 outb(0, dev->iobase + PCL711_CLRINTR);
208 data = (hi << 8) | lo;
210 /* FIXME! Nothing else sets ntrig! */
211 if (!(--devpriv->ntrig)) {
213 outb(1, dev->iobase + PCL711_MODE);
215 outb(0, dev->iobase + PCL711_MODE);
217 s->async->events |= COMEDI_CB_EOA;
219 comedi_event(dev, s);
223 static void pcl711_set_changain(struct comedi_device *dev, int chan)
225 const struct pcl711_board *board = comedi_board(dev);
228 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
230 chan_register = CR_CHAN(chan);
232 if (board->is_8112) {
235 * Set the correct channel. The two channel banks are switched
236 * using the mask value.
237 * NB: To use differential channels, you should use
238 * mask = 0x30, but I haven't written the support for this
242 if (chan_register >= 8)
243 chan_register = 0x20 | (chan_register & 0x7);
245 chan_register |= 0x10;
247 outb(chan_register, dev->iobase + PCL711_MUX);
251 static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
252 struct comedi_insn *insn, unsigned int *data)
254 const struct pcl711_board *board = comedi_board(dev);
258 pcl711_set_changain(dev, insn->chanspec);
260 for (n = 0; n < insn->n; n++) {
262 * Write the correct mode (software polling) and start polling
263 * by writing to the trigger register
265 outb(1, dev->iobase + PCL711_MODE);
268 outb(0, dev->iobase + PCL711_SOFTTRIG);
272 hi = inb(dev->iobase + PCL711_AD_HI);
273 if (!(hi & PCL711_DRDY))
277 printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor);
281 lo = inb(dev->iobase + PCL711_AD_LO);
283 data[n] = ((hi & 0xf) << 8) | lo;
289 static int pcl711_ai_cmdtest(struct comedi_device *dev,
290 struct comedi_subdevice *s, struct comedi_cmd *cmd)
292 struct pcl711_private *devpriv = dev->private;
296 /* Step 1 : check if triggers are trivially valid */
298 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
299 err |= cfc_check_trigger_src(&cmd->scan_begin_src,
300 TRIG_TIMER | TRIG_EXT);
301 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
302 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
303 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
308 /* Step 2a : make sure trigger sources are unique */
310 err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
311 err |= cfc_check_trigger_is_unique(cmd->stop_src);
313 /* Step 2b : and mutually compatible */
318 /* Step 3: check if arguments are trivially valid */
320 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
322 if (cmd->scan_begin_src == TRIG_EXT) {
323 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
325 #define MAX_SPEED 1000
326 #define TIMER_BASE 100
327 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
331 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
332 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
334 if (cmd->stop_src == TRIG_NONE) {
335 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
345 if (cmd->scan_begin_src == TRIG_TIMER) {
346 tmp = cmd->scan_begin_arg;
347 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
350 &cmd->scan_begin_arg,
351 cmd->flags & TRIG_ROUND_MASK);
352 if (tmp != cmd->scan_begin_arg)
362 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
364 struct pcl711_private *devpriv = dev->private;
366 struct comedi_cmd *cmd = &s->async->cmd;
368 pcl711_set_changain(dev, cmd->chanlist[0]);
370 if (cmd->scan_begin_src == TRIG_TIMER) {
373 * timer chip is an 8253, with timers 1 and 2
375 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
376 * Mode 2 = Rate generator
378 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
382 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
383 &cmd->scan_begin_arg,
386 outb(0x74, dev->iobase + PCL711_CTRCTL);
387 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
388 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
389 outb(0xb4, dev->iobase + PCL711_CTRCTL);
390 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
391 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
393 /* clear pending interrupts (just in case) */
394 outb(0, dev->iobase + PCL711_CLRINTR);
397 * Set mode to IRQ transfer
399 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
401 /* external trigger */
402 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
411 static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
412 struct comedi_insn *insn, unsigned int *data)
414 struct pcl711_private *devpriv = dev->private;
416 int chan = CR_CHAN(insn->chanspec);
418 for (n = 0; n < insn->n; n++) {
419 outb((data[n] & 0xff),
420 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
422 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
424 devpriv->ao_readback[chan] = data[n];
430 static int pcl711_ao_insn_read(struct comedi_device *dev,
431 struct comedi_subdevice *s,
432 struct comedi_insn *insn, unsigned int *data)
434 struct pcl711_private *devpriv = dev->private;
436 int chan = CR_CHAN(insn->chanspec);
438 for (n = 0; n < insn->n; n++)
439 data[n] = devpriv->ao_readback[chan];
445 /* Digital port read - Untested on 8112 */
446 static int pcl711_di_insn_bits(struct comedi_device *dev,
447 struct comedi_subdevice *s,
448 struct comedi_insn *insn, unsigned int *data)
450 data[1] = inb(dev->iobase + PCL711_DI_LO) |
451 (inb(dev->iobase + PCL711_DI_HI) << 8);
456 static int pcl711_do_insn_bits(struct comedi_device *dev,
457 struct comedi_subdevice *s,
458 struct comedi_insn *insn,
463 mask = comedi_dio_update_state(s, data);
466 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
468 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
476 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
478 const struct pcl711_board *board = comedi_board(dev);
479 struct pcl711_private *devpriv;
480 struct comedi_subdevice *s;
483 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
487 ret = comedi_request_region(dev, it->options[0], 0x10);
491 if (it->options[1] && it->options[1] <= board->maxirq) {
492 ret = request_irq(it->options[1], pcl711_interrupt, 0,
493 dev->board_name, dev);
495 dev->irq = it->options[1];
498 * The PCL711b needs the irq number in the
501 if (board->is_pcl711b)
502 devpriv->mode = (dev->irq << 4);
506 ret = comedi_alloc_subdevices(dev, 4);
510 /* Analog Input subdevice */
511 s = &dev->subdevices[0];
512 s->type = COMEDI_SUBD_AI;
513 s->subdev_flags = SDF_READABLE | SDF_GROUND;
514 s->n_chan = board->n_aichan;
516 s->range_table = board->ai_range_type;
517 s->insn_read = pcl711_ai_insn;
519 dev->read_subdev = s;
520 s->subdev_flags |= SDF_CMD_READ;
522 s->do_cmdtest = pcl711_ai_cmdtest;
523 s->do_cmd = pcl711_ai_cmd;
526 /* Analog Output subdevice */
527 s = &dev->subdevices[1];
528 s->type = COMEDI_SUBD_AO;
529 s->subdev_flags = SDF_WRITABLE;
530 s->n_chan = board->n_aochan;
532 s->range_table = &range_bipolar5;
533 s->insn_write = pcl711_ao_insn;
534 s->insn_read = pcl711_ao_insn_read;
536 /* Digital Input subdevice */
537 s = &dev->subdevices[2];
538 s->type = COMEDI_SUBD_DI;
539 s->subdev_flags = SDF_READABLE;
542 s->range_table = &range_digital;
543 s->insn_bits = pcl711_di_insn_bits;
545 /* Digital Output subdevice */
546 s = &dev->subdevices[3];
547 s->type = COMEDI_SUBD_DO;
548 s->subdev_flags = SDF_WRITABLE;
551 s->range_table = &range_digital;
552 s->insn_bits = pcl711_do_insn_bits;
555 outb(0, dev->iobase + PCL711_DA0_LO);
556 outb(0, dev->iobase + PCL711_DA0_HI);
557 outb(0, dev->iobase + PCL711_DA1_LO);
558 outb(0, dev->iobase + PCL711_DA1_HI);
560 printk(KERN_INFO "\n");
565 static struct comedi_driver pcl711_driver = {
566 .driver_name = "pcl711",
567 .module = THIS_MODULE,
568 .attach = pcl711_attach,
569 .detach = comedi_legacy_detach,
570 .board_name = &boardtypes[0].name,
571 .num_names = ARRAY_SIZE(boardtypes),
572 .offset = sizeof(struct pcl711_board),
574 module_comedi_driver(pcl711_driver);
576 MODULE_AUTHOR("Comedi http://www.comedi.org");
577 MODULE_DESCRIPTION("Comedi low-level driver");
578 MODULE_LICENSE("GPL");