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_AI_LSB_REG 0x04
73 #define PCL711_AI_MSB_REG 0x05
74 #define PCL711_AO_LSB_REG(x) (0x04 + ((x) * 2))
75 #define PCL711_AO_MSB_REG(x) (0x05 + ((x) * 2))
76 #define PCL711_DI_LO 0x06
77 #define PCL711_DI_HI 0x07
78 #define PCL711_CLRINTR 0x08
79 #define PCL711_GAIN 0x09
80 #define PCL711_MUX 0x0a
81 #define PCL711_MODE 0x0b
82 #define PCL711_SOFTTRIG 0x0c
83 #define PCL711_DO_LO 0x0d
84 #define PCL711_DO_HI 0x0e
86 static const struct comedi_lrange range_pcl711b_ai = {
96 static const struct comedi_lrange range_acl8112hg_ai = {
113 static const struct comedi_lrange range_acl8112dg_ai = {
131 #define PCL711_TIMEOUT 100
132 #define PCL711_DRDY 0x10
134 static const int i8253_osc_base = 500; /* 2 Mhz */
136 struct pcl711_board {
138 unsigned int is_pcl711b:1;
139 unsigned int is_8112:1;
143 const struct comedi_lrange *ai_range_type;
146 static const struct pcl711_board boardtypes[] = {
151 .ai_range_type = &range_bipolar5,
158 .ai_range_type = &range_pcl711b_ai,
165 .ai_range_type = &range_acl8112hg_ai,
172 .ai_range_type = &range_acl8112dg_ai,
176 struct pcl711_private {
183 unsigned int ao_readback[2];
184 unsigned int divisor1;
185 unsigned int divisor2;
188 static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
189 struct comedi_subdevice *s)
193 val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
194 val |= inb(dev->iobase + PCL711_AI_LSB_REG);
196 return val & s->maxdata;
199 static irqreturn_t pcl711_interrupt(int irq, void *d)
201 struct comedi_device *dev = d;
202 const struct pcl711_board *board = comedi_board(dev);
203 struct pcl711_private *devpriv = dev->private;
204 struct comedi_subdevice *s = dev->read_subdev;
207 if (!dev->attached) {
208 comedi_error(dev, "spurious interrupt");
212 data = pcl711_ai_get_sample(dev, s);
214 outb(0, dev->iobase + PCL711_CLRINTR);
216 /* FIXME! Nothing else sets ntrig! */
217 if (!(--devpriv->ntrig)) {
219 outb(1, dev->iobase + PCL711_MODE);
221 outb(0, dev->iobase + PCL711_MODE);
223 s->async->events |= COMEDI_CB_EOA;
225 comedi_event(dev, s);
229 static void pcl711_set_changain(struct comedi_device *dev, int chan)
231 const struct pcl711_board *board = comedi_board(dev);
234 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
236 chan_register = CR_CHAN(chan);
238 if (board->is_8112) {
241 * Set the correct channel. The two channel banks are switched
242 * using the mask value.
243 * NB: To use differential channels, you should use
244 * mask = 0x30, but I haven't written the support for this
248 if (chan_register >= 8)
249 chan_register = 0x20 | (chan_register & 0x7);
251 chan_register |= 0x10;
253 outb(chan_register, dev->iobase + PCL711_MUX);
257 static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
258 struct comedi_insn *insn, unsigned int *data)
260 const struct pcl711_board *board = comedi_board(dev);
264 pcl711_set_changain(dev, insn->chanspec);
266 for (n = 0; n < insn->n; n++) {
268 * Write the correct mode (software polling) and start polling
269 * by writing to the trigger register
271 outb(1, dev->iobase + PCL711_MODE);
274 outb(0, dev->iobase + PCL711_SOFTTRIG);
278 hi = inb(dev->iobase + PCL711_AI_MSB_REG);
279 if (!(hi & PCL711_DRDY))
283 printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor);
287 data[n] = pcl711_ai_get_sample(dev, s);
293 static int pcl711_ai_cmdtest(struct comedi_device *dev,
294 struct comedi_subdevice *s, struct comedi_cmd *cmd)
296 struct pcl711_private *devpriv = dev->private;
300 /* Step 1 : check if triggers are trivially valid */
302 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
303 err |= cfc_check_trigger_src(&cmd->scan_begin_src,
304 TRIG_TIMER | TRIG_EXT);
305 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
306 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
307 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
312 /* Step 2a : make sure trigger sources are unique */
314 err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
315 err |= cfc_check_trigger_is_unique(cmd->stop_src);
317 /* Step 2b : and mutually compatible */
322 /* Step 3: check if arguments are trivially valid */
324 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
326 if (cmd->scan_begin_src == TRIG_EXT) {
327 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
329 #define MAX_SPEED 1000
330 #define TIMER_BASE 100
331 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
335 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
336 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
338 if (cmd->stop_src == TRIG_NONE) {
339 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
349 if (cmd->scan_begin_src == TRIG_TIMER) {
350 tmp = cmd->scan_begin_arg;
351 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
354 &cmd->scan_begin_arg,
355 cmd->flags & TRIG_ROUND_MASK);
356 if (tmp != cmd->scan_begin_arg)
366 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
368 struct pcl711_private *devpriv = dev->private;
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);
412 static void pcl711_ao_write(struct comedi_device *dev,
413 unsigned int chan, unsigned int val)
415 outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
416 outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
419 static int pcl711_ao_insn_write(struct comedi_device *dev,
420 struct comedi_subdevice *s,
421 struct comedi_insn *insn,
424 struct pcl711_private *devpriv = dev->private;
425 unsigned int chan = CR_CHAN(insn->chanspec);
426 unsigned int val = devpriv->ao_readback[chan];
429 for (i = 0; i < insn->n; i++) {
431 pcl711_ao_write(dev, chan, val);
433 devpriv->ao_readback[chan] = val;
438 static int pcl711_ao_insn_read(struct comedi_device *dev,
439 struct comedi_subdevice *s,
440 struct comedi_insn *insn,
443 struct pcl711_private *devpriv = dev->private;
444 unsigned int chan = CR_CHAN(insn->chanspec);
447 for (i = 0; i < insn->n; i++)
448 data[i] = devpriv->ao_readback[chan];
453 /* Digital port read - Untested on 8112 */
454 static int pcl711_di_insn_bits(struct comedi_device *dev,
455 struct comedi_subdevice *s,
456 struct comedi_insn *insn, unsigned int *data)
458 data[1] = inb(dev->iobase + PCL711_DI_LO) |
459 (inb(dev->iobase + PCL711_DI_HI) << 8);
464 static int pcl711_do_insn_bits(struct comedi_device *dev,
465 struct comedi_subdevice *s,
466 struct comedi_insn *insn,
471 mask = comedi_dio_update_state(s, data);
474 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
476 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
484 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
486 const struct pcl711_board *board = comedi_board(dev);
487 struct pcl711_private *devpriv;
488 struct comedi_subdevice *s;
491 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
495 ret = comedi_request_region(dev, it->options[0], 0x10);
499 if (it->options[1] && it->options[1] <= board->maxirq) {
500 ret = request_irq(it->options[1], pcl711_interrupt, 0,
501 dev->board_name, dev);
503 dev->irq = it->options[1];
506 * The PCL711b needs the irq number in the
509 if (board->is_pcl711b)
510 devpriv->mode = (dev->irq << 4);
514 ret = comedi_alloc_subdevices(dev, 4);
518 /* Analog Input subdevice */
519 s = &dev->subdevices[0];
520 s->type = COMEDI_SUBD_AI;
521 s->subdev_flags = SDF_READABLE | SDF_GROUND;
522 s->n_chan = board->n_aichan;
524 s->range_table = board->ai_range_type;
525 s->insn_read = pcl711_ai_insn;
527 dev->read_subdev = s;
528 s->subdev_flags |= SDF_CMD_READ;
530 s->do_cmdtest = pcl711_ai_cmdtest;
531 s->do_cmd = pcl711_ai_cmd;
534 /* Analog Output subdevice */
535 s = &dev->subdevices[1];
536 s->type = COMEDI_SUBD_AO;
537 s->subdev_flags = SDF_WRITABLE;
538 s->n_chan = board->n_aochan;
540 s->range_table = &range_bipolar5;
541 s->insn_write = pcl711_ao_insn_write;
542 s->insn_read = pcl711_ao_insn_read;
544 /* Digital Input subdevice */
545 s = &dev->subdevices[2];
546 s->type = COMEDI_SUBD_DI;
547 s->subdev_flags = SDF_READABLE;
550 s->range_table = &range_digital;
551 s->insn_bits = pcl711_di_insn_bits;
553 /* Digital Output subdevice */
554 s = &dev->subdevices[3];
555 s->type = COMEDI_SUBD_DO;
556 s->subdev_flags = SDF_WRITABLE;
559 s->range_table = &range_digital;
560 s->insn_bits = pcl711_do_insn_bits;
563 pcl711_ao_write(dev, 0, 0x0);
564 pcl711_ao_write(dev, 1, 0x0);
569 static struct comedi_driver pcl711_driver = {
570 .driver_name = "pcl711",
571 .module = THIS_MODULE,
572 .attach = pcl711_attach,
573 .detach = comedi_legacy_detach,
574 .board_name = &boardtypes[0].name,
575 .num_names = ARRAY_SIZE(boardtypes),
576 .offset = sizeof(struct pcl711_board),
578 module_comedi_driver(pcl711_driver);
580 MODULE_AUTHOR("Comedi http://www.comedi.org");
581 MODULE_DESCRIPTION("Comedi low-level driver");
582 MODULE_LICENSE("GPL");