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_TIMER_BASE 0x00
69 #define PCL711_AI_LSB_REG 0x04
70 #define PCL711_AI_MSB_REG 0x05
71 #define PCL711_AI_MSB_DRDY (1 << 4)
72 #define PCL711_AO_LSB_REG(x) (0x04 + ((x) * 2))
73 #define PCL711_AO_MSB_REG(x) (0x05 + ((x) * 2))
74 #define PCL711_DI_LO 0x06
75 #define PCL711_DI_HI 0x07
76 #define PCL711_CLRINTR 0x08
77 #define PCL711_GAIN 0x09
78 #define PCL711_MUX_REG 0x0a
79 #define PCL711_MUX_CHAN(x) (((x) & 0xf) << 0)
80 #define PCL711_MUX_CS0 (1 << 4)
81 #define PCL711_MUX_CS1 (1 << 5)
82 #define PCL711_MUX_DIFF (PCL711_MUX_CS0 | PCL711_MUX_CS1)
83 #define PCL711_MODE_REG 0x0b
84 #define PCL711_MODE_DEFAULT (0 << 0)
85 #define PCL711_MODE_SOFTTRIG (1 << 0)
86 #define PCL711_MODE_EXT (2 << 0)
87 #define PCL711_MODE_EXT_IRQ (3 << 0)
88 #define PCL711_MODE_PACER (4 << 0)
89 #define PCL711_MODE_PACER_IRQ (6 << 0)
90 #define PCL711_MODE_IRQ(x) (((x) & 0x7) << 4)
91 #define PCL711_SOFTTRIG_REG 0x0c
92 #define PCL711_SOFTTRIG (0 << 0) /* any value will work */
93 #define PCL711_DO_LO 0x0d
94 #define PCL711_DO_HI 0x0e
96 static const struct comedi_lrange range_pcl711b_ai = {
106 static const struct comedi_lrange range_acl8112hg_ai = {
123 static const struct comedi_lrange range_acl8112dg_ai = {
137 static const int i8253_osc_base = 500; /* 2 Mhz */
139 struct pcl711_board {
144 const struct comedi_lrange *ai_range_type;
147 static const struct pcl711_board boardtypes[] = {
152 .ai_range_type = &range_bipolar5,
158 .ai_range_type = &range_pcl711b_ai,
164 .ai_range_type = &range_acl8112hg_ai,
170 .ai_range_type = &range_acl8112dg_ai,
174 struct pcl711_private {
176 unsigned int ao_readback[2];
177 unsigned int divisor1;
178 unsigned int divisor2;
181 static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
184 * The pcl711b board uses bits in the mode register to select the
185 * interrupt. The other boards supported by this driver all use
186 * jumpers on the board.
188 * Enables the interrupt when needed on the pcl711b board. These
189 * bits do nothing on the other boards.
191 if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
192 mode |= PCL711_MODE_IRQ(dev->irq);
194 outb(mode, dev->iobase + PCL711_MODE_REG);
197 static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
198 struct comedi_subdevice *s)
202 val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
203 val |= inb(dev->iobase + PCL711_AI_LSB_REG);
205 return val & s->maxdata;
208 static irqreturn_t pcl711_interrupt(int irq, void *d)
210 struct comedi_device *dev = d;
211 struct pcl711_private *devpriv = dev->private;
212 struct comedi_subdevice *s = dev->read_subdev;
215 if (!dev->attached) {
216 comedi_error(dev, "spurious interrupt");
220 data = pcl711_ai_get_sample(dev, s);
222 outb(0, dev->iobase + PCL711_CLRINTR);
224 /* FIXME! Nothing else sets ntrig! */
225 if (!(--devpriv->ntrig)) {
226 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
228 s->async->events |= COMEDI_CB_EOA;
230 comedi_event(dev, s);
234 static void pcl711_set_changain(struct comedi_device *dev,
235 struct comedi_subdevice *s,
236 unsigned int chanspec)
238 unsigned int chan = CR_CHAN(chanspec);
239 unsigned int range = CR_RANGE(chanspec);
240 unsigned int aref = CR_AREF(chanspec);
241 unsigned int mux = 0;
243 outb(range, dev->iobase + PCL711_GAIN);
246 /* Select the correct MPC508A chip */
247 if (aref == AREF_DIFF) {
249 mux |= PCL711_MUX_DIFF;
252 mux |= PCL711_MUX_CS0;
254 mux |= PCL711_MUX_CS1;
257 outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
260 static int pcl711_ai_wait_for_eoc(struct comedi_device *dev,
261 unsigned int timeout)
266 msb = inb(dev->iobase + PCL711_AI_MSB_REG);
267 if ((msb & PCL711_AI_MSB_DRDY) == 0)
274 static int pcl711_ai_insn_read(struct comedi_device *dev,
275 struct comedi_subdevice *s,
276 struct comedi_insn *insn,
282 pcl711_set_changain(dev, s, insn->chanspec);
284 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
286 for (i = 0; i < insn->n; i++) {
287 outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
289 ret = pcl711_ai_wait_for_eoc(dev, 100);
293 data[i] = pcl711_ai_get_sample(dev, s);
299 static int pcl711_ai_cmdtest(struct comedi_device *dev,
300 struct comedi_subdevice *s, struct comedi_cmd *cmd)
302 struct pcl711_private *devpriv = dev->private;
306 /* Step 1 : check if triggers are trivially valid */
308 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
309 err |= cfc_check_trigger_src(&cmd->scan_begin_src,
310 TRIG_TIMER | TRIG_EXT);
311 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
312 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
313 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
318 /* Step 2a : make sure trigger sources are unique */
320 err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
321 err |= cfc_check_trigger_is_unique(cmd->stop_src);
323 /* Step 2b : and mutually compatible */
328 /* Step 3: check if arguments are trivially valid */
330 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
332 if (cmd->scan_begin_src == TRIG_EXT) {
333 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
335 #define MAX_SPEED 1000
336 #define TIMER_BASE 100
337 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
341 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
342 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
344 if (cmd->stop_src == TRIG_NONE) {
345 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
355 if (cmd->scan_begin_src == TRIG_TIMER) {
356 tmp = cmd->scan_begin_arg;
357 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
360 &cmd->scan_begin_arg,
361 cmd->flags & TRIG_ROUND_MASK);
362 if (tmp != cmd->scan_begin_arg)
372 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
375 struct comedi_cmd *cmd = &s->async->cmd;
377 pcl711_set_changain(dev, s, cmd->chanlist[0]);
379 if (cmd->scan_begin_src == TRIG_TIMER) {
381 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
382 &cmd->scan_begin_arg,
385 i8254_load(dev->iobase + PCL711_TIMER_BASE, 0,
386 1, timer1, I8254_MODE2 | I8254_BINARY);
387 i8254_load(dev->iobase + PCL711_TIMER_BASE, 0,
388 2, timer2, I8254_MODE2 | I8254_BINARY);
390 /* clear pending interrupts (just in case) */
391 outb(0, dev->iobase + PCL711_CLRINTR);
393 pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
395 pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
401 static void pcl711_ao_write(struct comedi_device *dev,
402 unsigned int chan, unsigned int val)
404 outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
405 outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
408 static int pcl711_ao_insn_write(struct comedi_device *dev,
409 struct comedi_subdevice *s,
410 struct comedi_insn *insn,
413 struct pcl711_private *devpriv = dev->private;
414 unsigned int chan = CR_CHAN(insn->chanspec);
415 unsigned int val = devpriv->ao_readback[chan];
418 for (i = 0; i < insn->n; i++) {
420 pcl711_ao_write(dev, chan, val);
422 devpriv->ao_readback[chan] = val;
427 static int pcl711_ao_insn_read(struct comedi_device *dev,
428 struct comedi_subdevice *s,
429 struct comedi_insn *insn,
432 struct pcl711_private *devpriv = dev->private;
433 unsigned int chan = CR_CHAN(insn->chanspec);
436 for (i = 0; i < insn->n; i++)
437 data[i] = 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 ret = comedi_alloc_subdevices(dev, 4);
499 /* Analog Input subdevice */
500 s = &dev->subdevices[0];
501 s->type = COMEDI_SUBD_AI;
502 s->subdev_flags = SDF_READABLE | SDF_GROUND;
503 if (board->n_aichan > 8)
504 s->subdev_flags |= SDF_DIFF;
505 s->n_chan = board->n_aichan;
507 s->range_table = board->ai_range_type;
508 s->insn_read = pcl711_ai_insn_read;
510 dev->read_subdev = s;
511 s->subdev_flags |= SDF_CMD_READ;
513 s->do_cmdtest = pcl711_ai_cmdtest;
514 s->do_cmd = pcl711_ai_cmd;
517 /* Analog Output subdevice */
518 s = &dev->subdevices[1];
519 s->type = COMEDI_SUBD_AO;
520 s->subdev_flags = SDF_WRITABLE;
521 s->n_chan = board->n_aochan;
523 s->range_table = &range_bipolar5;
524 s->insn_write = pcl711_ao_insn_write;
525 s->insn_read = pcl711_ao_insn_read;
527 /* Digital Input subdevice */
528 s = &dev->subdevices[2];
529 s->type = COMEDI_SUBD_DI;
530 s->subdev_flags = SDF_READABLE;
533 s->range_table = &range_digital;
534 s->insn_bits = pcl711_di_insn_bits;
536 /* Digital Output subdevice */
537 s = &dev->subdevices[3];
538 s->type = COMEDI_SUBD_DO;
539 s->subdev_flags = SDF_WRITABLE;
542 s->range_table = &range_digital;
543 s->insn_bits = pcl711_do_insn_bits;
546 pcl711_ao_write(dev, 0, 0x0);
547 pcl711_ao_write(dev, 1, 0x0);
552 static struct comedi_driver pcl711_driver = {
553 .driver_name = "pcl711",
554 .module = THIS_MODULE,
555 .attach = pcl711_attach,
556 .detach = comedi_legacy_detach,
557 .board_name = &boardtypes[0].name,
558 .num_names = ARRAY_SIZE(boardtypes),
559 .offset = sizeof(struct pcl711_board),
561 module_comedi_driver(pcl711_driver);
563 MODULE_AUTHOR("Comedi http://www.comedi.org");
564 MODULE_DESCRIPTION("Comedi low-level driver");
565 MODULE_LICENSE("GPL");