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_AI_MSB_DRDY (1 << 4)
75 #define PCL711_AO_LSB_REG(x) (0x04 + ((x) * 2))
76 #define PCL711_AO_MSB_REG(x) (0x05 + ((x) * 2))
77 #define PCL711_DI_LO 0x06
78 #define PCL711_DI_HI 0x07
79 #define PCL711_CLRINTR 0x08
80 #define PCL711_GAIN 0x09
81 #define PCL711_MUX_REG 0x0a
82 #define PCL711_MUX_CHAN(x) (((x) & 0xf) << 0)
83 #define PCL711_MUX_CS0 (1 << 4)
84 #define PCL711_MUX_CS1 (1 << 5)
85 #define PCL711_MUX_DIFF (PCL711_MUX_CS0 | PCL711_MUX_CS1)
86 #define PCL711_MODE_REG 0x0b
87 #define PCL711_MODE_DEFAULT (0 << 0)
88 #define PCL711_MODE_SOFTTRIG (1 << 0)
89 #define PCL711_MODE_EXT (2 << 0)
90 #define PCL711_MODE_EXT_IRQ (3 << 0)
91 #define PCL711_MODE_PACER (4 << 0)
92 #define PCL711_MODE_PACER_IRQ (6 << 0)
93 #define PCL711_MODE_IRQ(x) (((x) & 0x7) << 4)
94 #define PCL711_SOFTTRIG_REG 0x0c
95 #define PCL711_SOFTTRIG (0 << 0) /* any value will work */
96 #define PCL711_DO_LO 0x0d
97 #define PCL711_DO_HI 0x0e
99 static const struct comedi_lrange range_pcl711b_ai = {
109 static const struct comedi_lrange range_acl8112hg_ai = {
126 static const struct comedi_lrange range_acl8112dg_ai = {
140 static const int i8253_osc_base = 500; /* 2 Mhz */
142 struct pcl711_board {
144 unsigned int is_pcl711b:1;
145 unsigned int is_8112:1;
149 const struct comedi_lrange *ai_range_type;
152 static const struct pcl711_board boardtypes[] = {
157 .ai_range_type = &range_bipolar5,
164 .ai_range_type = &range_pcl711b_ai,
171 .ai_range_type = &range_acl8112hg_ai,
178 .ai_range_type = &range_acl8112dg_ai,
182 struct pcl711_private {
189 unsigned int ao_readback[2];
190 unsigned int divisor1;
191 unsigned int divisor2;
194 static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
196 struct pcl711_private *devpriv = dev->private;
198 if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
199 mode |= devpriv->mode;
201 outb(mode, dev->iobase + PCL711_MODE_REG);
204 static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
205 struct comedi_subdevice *s)
209 val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
210 val |= inb(dev->iobase + PCL711_AI_LSB_REG);
212 return val & s->maxdata;
215 static irqreturn_t pcl711_interrupt(int irq, void *d)
217 struct comedi_device *dev = d;
218 struct pcl711_private *devpriv = dev->private;
219 struct comedi_subdevice *s = dev->read_subdev;
222 if (!dev->attached) {
223 comedi_error(dev, "spurious interrupt");
227 data = pcl711_ai_get_sample(dev, s);
229 outb(0, dev->iobase + PCL711_CLRINTR);
231 /* FIXME! Nothing else sets ntrig! */
232 if (!(--devpriv->ntrig)) {
233 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
235 s->async->events |= COMEDI_CB_EOA;
237 comedi_event(dev, s);
241 static void pcl711_set_changain(struct comedi_device *dev,
242 unsigned int chanspec)
244 const struct pcl711_board *board = comedi_board(dev);
245 unsigned int chan = CR_CHAN(chanspec);
246 unsigned int range = CR_RANGE(chanspec);
247 unsigned int aref = CR_AREF(chanspec);
248 unsigned int mux = 0;
250 outb(range, dev->iobase + PCL711_GAIN);
252 if (board->is_8112) {
253 /* Select the correct MPC508A chip */
254 if (aref == AREF_DIFF) {
256 mux |= PCL711_MUX_DIFF;
259 mux |= PCL711_MUX_CS0;
261 mux |= PCL711_MUX_CS1;
264 outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
267 static int pcl711_ai_wait_for_eoc(struct comedi_device *dev,
268 unsigned int timeout)
273 msb = inb(dev->iobase + PCL711_AI_MSB_REG);
274 if ((msb & PCL711_AI_MSB_DRDY) == 0)
281 static int pcl711_ai_insn_read(struct comedi_device *dev,
282 struct comedi_subdevice *s,
283 struct comedi_insn *insn,
289 pcl711_set_changain(dev, insn->chanspec);
291 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
293 for (i = 0; i < insn->n; i++) {
294 outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
296 ret = pcl711_ai_wait_for_eoc(dev, 100);
300 data[i] = pcl711_ai_get_sample(dev, s);
306 static int pcl711_ai_cmdtest(struct comedi_device *dev,
307 struct comedi_subdevice *s, struct comedi_cmd *cmd)
309 struct pcl711_private *devpriv = dev->private;
313 /* Step 1 : check if triggers are trivially valid */
315 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
316 err |= cfc_check_trigger_src(&cmd->scan_begin_src,
317 TRIG_TIMER | TRIG_EXT);
318 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
319 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
320 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
325 /* Step 2a : make sure trigger sources are unique */
327 err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
328 err |= cfc_check_trigger_is_unique(cmd->stop_src);
330 /* Step 2b : and mutually compatible */
335 /* Step 3: check if arguments are trivially valid */
337 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
339 if (cmd->scan_begin_src == TRIG_EXT) {
340 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
342 #define MAX_SPEED 1000
343 #define TIMER_BASE 100
344 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
348 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
349 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
351 if (cmd->stop_src == TRIG_NONE) {
352 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
362 if (cmd->scan_begin_src == TRIG_TIMER) {
363 tmp = cmd->scan_begin_arg;
364 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
367 &cmd->scan_begin_arg,
368 cmd->flags & TRIG_ROUND_MASK);
369 if (tmp != cmd->scan_begin_arg)
379 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
382 struct comedi_cmd *cmd = &s->async->cmd;
384 pcl711_set_changain(dev, cmd->chanlist[0]);
386 if (cmd->scan_begin_src == TRIG_TIMER) {
389 * timer chip is an 8253, with timers 1 and 2
391 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
392 * Mode 2 = Rate generator
394 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
398 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
399 &cmd->scan_begin_arg,
402 outb(0x74, dev->iobase + PCL711_CTRCTL);
403 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
404 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
405 outb(0xb4, dev->iobase + PCL711_CTRCTL);
406 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
407 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
409 /* clear pending interrupts (just in case) */
410 outb(0, dev->iobase + PCL711_CLRINTR);
412 pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
414 pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
420 static void pcl711_ao_write(struct comedi_device *dev,
421 unsigned int chan, unsigned int val)
423 outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
424 outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
427 static int pcl711_ao_insn_write(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);
434 unsigned int val = devpriv->ao_readback[chan];
437 for (i = 0; i < insn->n; i++) {
439 pcl711_ao_write(dev, chan, val);
441 devpriv->ao_readback[chan] = val;
446 static int pcl711_ao_insn_read(struct comedi_device *dev,
447 struct comedi_subdevice *s,
448 struct comedi_insn *insn,
451 struct pcl711_private *devpriv = dev->private;
452 unsigned int chan = CR_CHAN(insn->chanspec);
455 for (i = 0; i < insn->n; i++)
456 data[i] = devpriv->ao_readback[chan];
461 /* Digital port read - Untested on 8112 */
462 static int pcl711_di_insn_bits(struct comedi_device *dev,
463 struct comedi_subdevice *s,
464 struct comedi_insn *insn, unsigned int *data)
466 data[1] = inb(dev->iobase + PCL711_DI_LO) |
467 (inb(dev->iobase + PCL711_DI_HI) << 8);
472 static int pcl711_do_insn_bits(struct comedi_device *dev,
473 struct comedi_subdevice *s,
474 struct comedi_insn *insn,
479 mask = comedi_dio_update_state(s, data);
482 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
484 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
492 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
494 const struct pcl711_board *board = comedi_board(dev);
495 struct pcl711_private *devpriv;
496 struct comedi_subdevice *s;
499 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
503 ret = comedi_request_region(dev, it->options[0], 0x10);
507 if (it->options[1] && it->options[1] <= board->maxirq) {
508 ret = request_irq(it->options[1], pcl711_interrupt, 0,
509 dev->board_name, dev);
511 dev->irq = it->options[1];
514 * The PCL711b needs the irq number in the
517 if (board->is_pcl711b)
518 devpriv->mode = PCL711_MODE_IRQ(dev->irq);
522 ret = comedi_alloc_subdevices(dev, 4);
526 /* Analog Input subdevice */
527 s = &dev->subdevices[0];
528 s->type = COMEDI_SUBD_AI;
529 s->subdev_flags = SDF_READABLE | SDF_GROUND;
531 s->subdev_flags |= SDF_DIFF;
532 s->n_chan = board->n_aichan;
534 s->range_table = board->ai_range_type;
535 s->insn_read = pcl711_ai_insn_read;
537 dev->read_subdev = s;
538 s->subdev_flags |= SDF_CMD_READ;
540 s->do_cmdtest = pcl711_ai_cmdtest;
541 s->do_cmd = pcl711_ai_cmd;
544 /* Analog Output subdevice */
545 s = &dev->subdevices[1];
546 s->type = COMEDI_SUBD_AO;
547 s->subdev_flags = SDF_WRITABLE;
548 s->n_chan = board->n_aochan;
550 s->range_table = &range_bipolar5;
551 s->insn_write = pcl711_ao_insn_write;
552 s->insn_read = pcl711_ao_insn_read;
554 /* Digital Input subdevice */
555 s = &dev->subdevices[2];
556 s->type = COMEDI_SUBD_DI;
557 s->subdev_flags = SDF_READABLE;
560 s->range_table = &range_digital;
561 s->insn_bits = pcl711_di_insn_bits;
563 /* Digital Output subdevice */
564 s = &dev->subdevices[3];
565 s->type = COMEDI_SUBD_DO;
566 s->subdev_flags = SDF_WRITABLE;
569 s->range_table = &range_digital;
570 s->insn_bits = pcl711_do_insn_bits;
573 pcl711_ao_write(dev, 0, 0x0);
574 pcl711_ao_write(dev, 1, 0x0);
579 static struct comedi_driver pcl711_driver = {
580 .driver_name = "pcl711",
581 .module = THIS_MODULE,
582 .attach = pcl711_attach,
583 .detach = comedi_legacy_detach,
584 .board_name = &boardtypes[0].name,
585 .num_names = ARRAY_SIZE(boardtypes),
586 .offset = sizeof(struct pcl711_board),
588 module_comedi_driver(pcl711_driver);
590 MODULE_AUTHOR("Comedi http://www.comedi.org");
591 MODULE_DESCRIPTION("Comedi low-level driver");
592 MODULE_LICENSE("GPL");