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"
68 #define PCL711_CTRCTL 3
69 #define PCL711_AD_LO 4
70 #define PCL711_DA0_LO 4
71 #define PCL711_AD_HI 5
72 #define PCL711_DA0_HI 5
73 #define PCL711_DI_LO 6
74 #define PCL711_DA1_LO 6
75 #define PCL711_DI_HI 7
76 #define PCL711_DA1_HI 7
77 #define PCL711_CLRINTR 8
80 #define PCL711_MODE 11
81 #define PCL711_SOFTTRIG 12
82 #define PCL711_DO_LO 13
83 #define PCL711_DO_HI 14
85 static const struct comedi_lrange range_pcl711b_ai = {
95 static const struct comedi_lrange range_acl8112hg_ai = {
112 static const struct comedi_lrange range_acl8112dg_ai = {
130 #define PCL711_TIMEOUT 100
131 #define PCL711_DRDY 0x10
133 static const int i8253_osc_base = 500; /* 2 Mhz */
135 struct pcl711_board {
137 unsigned int is_pcl711b:1;
138 unsigned int is_8112:1;
142 const struct comedi_lrange *ai_range_type;
145 static const struct pcl711_board boardtypes[] = {
150 .ai_range_type = &range_bipolar5,
157 .ai_range_type = &range_pcl711b_ai,
164 .ai_range_type = &range_acl8112hg_ai,
171 .ai_range_type = &range_acl8112dg_ai,
175 struct pcl711_private {
182 unsigned int ao_readback[2];
183 unsigned int divisor1;
184 unsigned int divisor2;
187 static irqreturn_t pcl711_interrupt(int irq, void *d)
191 struct comedi_device *dev = d;
192 const struct pcl711_board *board = comedi_board(dev);
193 struct pcl711_private *devpriv = dev->private;
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)) {
210 outb(1, dev->iobase + PCL711_MODE);
212 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)
222 const struct pcl711_board *board = comedi_board(dev);
225 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
227 chan_register = CR_CHAN(chan);
229 if (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
235 * mask = 0x30, but I haven't written the support for this
239 if (chan_register >= 8)
240 chan_register = 0x20 | (chan_register & 0x7);
242 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)
251 const struct pcl711_board *board = comedi_board(dev);
255 pcl711_set_changain(dev, insn->chanspec);
257 for (n = 0; n < insn->n; n++) {
259 * Write the correct mode (software polling) and start polling
260 * by writing to the trigger register
262 outb(1, dev->iobase + PCL711_MODE);
265 outb(0, dev->iobase + PCL711_SOFTTRIG);
269 hi = inb(dev->iobase + PCL711_AD_HI);
270 if (!(hi & PCL711_DRDY))
274 printk(KERN_ERR "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,
287 struct comedi_subdevice *s, struct comedi_cmd *cmd)
289 struct pcl711_private *devpriv = dev->private;
293 /* Step 1 : check if triggers are trivially valid */
295 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
296 err |= cfc_check_trigger_src(&cmd->scan_begin_src,
297 TRIG_TIMER | TRIG_EXT);
298 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
299 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
300 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
305 /* Step 2a : make sure trigger sources are unique */
307 err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
308 err |= cfc_check_trigger_is_unique(cmd->stop_src);
310 /* Step 2b : and mutually compatible */
315 /* Step 3: check if arguments are trivially valid */
317 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
319 if (cmd->scan_begin_src == TRIG_EXT) {
320 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
322 #define MAX_SPEED 1000
323 #define TIMER_BASE 100
324 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
328 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
329 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
331 if (cmd->stop_src == TRIG_NONE) {
332 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
342 if (cmd->scan_begin_src == TRIG_TIMER) {
343 tmp = cmd->scan_begin_arg;
344 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
347 &cmd->scan_begin_arg,
348 cmd->flags & TRIG_ROUND_MASK);
349 if (tmp != cmd->scan_begin_arg)
359 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
361 struct pcl711_private *devpriv = dev->private;
363 struct comedi_cmd *cmd = &s->async->cmd;
365 pcl711_set_changain(dev, cmd->chanlist[0]);
367 if (cmd->scan_begin_src == TRIG_TIMER) {
370 * timer chip is an 8253, with timers 1 and 2
372 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
373 * Mode 2 = Rate generator
375 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
379 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
380 &cmd->scan_begin_arg,
383 outb(0x74, dev->iobase + PCL711_CTRCTL);
384 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
385 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
386 outb(0xb4, dev->iobase + PCL711_CTRCTL);
387 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
388 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
390 /* clear pending interrupts (just in case) */
391 outb(0, dev->iobase + PCL711_CLRINTR);
394 * Set mode to IRQ transfer
396 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
398 /* external trigger */
399 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
408 static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
409 struct comedi_insn *insn, unsigned int *data)
411 struct pcl711_private *devpriv = dev->private;
413 int chan = CR_CHAN(insn->chanspec);
415 for (n = 0; n < insn->n; n++) {
416 outb((data[n] & 0xff),
417 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
419 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
421 devpriv->ao_readback[chan] = data[n];
427 static int pcl711_ao_insn_read(struct comedi_device *dev,
428 struct comedi_subdevice *s,
429 struct comedi_insn *insn, unsigned int *data)
431 struct pcl711_private *devpriv = dev->private;
433 int chan = CR_CHAN(insn->chanspec);
435 for (n = 0; n < insn->n; n++)
436 data[n] = devpriv->ao_readback[chan];
442 /* Digital port read - Untested on 8112 */
443 static int pcl711_di_insn_bits(struct comedi_device *dev,
444 struct comedi_subdevice *s,
445 struct comedi_insn *insn, unsigned int *data)
447 data[1] = inb(dev->iobase + PCL711_DI_LO) |
448 (inb(dev->iobase + PCL711_DI_HI) << 8);
453 static int pcl711_do_insn_bits(struct comedi_device *dev,
454 struct comedi_subdevice *s,
455 struct comedi_insn *insn,
460 mask = comedi_dio_update_state(s, data);
463 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
465 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
473 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
475 const struct pcl711_board *board = comedi_board(dev);
476 struct pcl711_private *devpriv;
477 struct comedi_subdevice *s;
480 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
484 ret = comedi_request_region(dev, it->options[0], 0x10);
488 if (it->options[1] && it->options[1] <= board->maxirq) {
489 ret = request_irq(it->options[1], pcl711_interrupt, 0,
490 dev->board_name, dev);
492 dev->irq = it->options[1];
495 * The PCL711b needs the irq number in the
498 if (board->is_pcl711b)
499 devpriv->mode = (dev->irq << 4);
503 ret = comedi_alloc_subdevices(dev, 4);
507 /* Analog Input subdevice */
508 s = &dev->subdevices[0];
509 s->type = COMEDI_SUBD_AI;
510 s->subdev_flags = SDF_READABLE | SDF_GROUND;
511 s->n_chan = board->n_aichan;
513 s->range_table = board->ai_range_type;
514 s->insn_read = pcl711_ai_insn;
516 dev->read_subdev = s;
517 s->subdev_flags |= SDF_CMD_READ;
519 s->do_cmdtest = pcl711_ai_cmdtest;
520 s->do_cmd = pcl711_ai_cmd;
523 /* Analog Output subdevice */
524 s = &dev->subdevices[1];
525 s->type = COMEDI_SUBD_AO;
526 s->subdev_flags = SDF_WRITABLE;
527 s->n_chan = board->n_aochan;
529 s->range_table = &range_bipolar5;
530 s->insn_write = pcl711_ao_insn;
531 s->insn_read = pcl711_ao_insn_read;
533 /* Digital Input subdevice */
534 s = &dev->subdevices[2];
535 s->type = COMEDI_SUBD_DI;
536 s->subdev_flags = SDF_READABLE;
539 s->range_table = &range_digital;
540 s->insn_bits = pcl711_di_insn_bits;
542 /* Digital Output subdevice */
543 s = &dev->subdevices[3];
544 s->type = COMEDI_SUBD_DO;
545 s->subdev_flags = SDF_WRITABLE;
548 s->range_table = &range_digital;
549 s->insn_bits = pcl711_do_insn_bits;
552 outb(0, dev->iobase + PCL711_DA0_LO);
553 outb(0, dev->iobase + PCL711_DA0_HI);
554 outb(0, dev->iobase + PCL711_DA1_LO);
555 outb(0, dev->iobase + PCL711_DA1_HI);
557 printk(KERN_INFO "\n");
562 static struct comedi_driver pcl711_driver = {
563 .driver_name = "pcl711",
564 .module = THIS_MODULE,
565 .attach = pcl711_attach,
566 .detach = comedi_legacy_detach,
567 .board_name = &boardtypes[0].name,
568 .num_names = ARRAY_SIZE(boardtypes),
569 .offset = sizeof(struct pcl711_board),
571 module_comedi_driver(pcl711_driver);
573 MODULE_AUTHOR("Comedi http://www.comedi.org");
574 MODULE_DESCRIPTION("Comedi low-level driver");
575 MODULE_LICENSE("GPL");