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.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28 Description: Advantech PCL-711 and 711b, ADLink ACL-8112
29 Author: ds, Janne Jalkanen <jalkanen@cs.hut.fi>, Eric Bunn <ebu@cs.hut.fi>
30 Status: mostly complete
31 Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
32 [AdLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
34 Since these boards do not have DMA or FIFOs, only immediate mode is
40 Dave Andruczyk <dave@tech.buffalostate.edu> also wrote a
41 driver for the PCL-711. I used a few ideas from his driver
42 here. His driver also has more comments, if you are
43 interested in understanding how this driver works.
44 http://tech.buffalostate.edu/~dave/driver/
46 The ACL-8112 driver was hacked from the sources of the PCL-711
47 driver (the 744 chip used on the 8112 is almost the same as
48 the 711b chip, but it has more I/O channels) by
49 Janne Jalkanen (jalkanen@cs.hut.fi) and
50 Erik Bunn (ebu@cs.hut.fi). Remerged with the PCL-711 driver
54 This driver supports both TRIGNOW and TRIGCLK,
55 but does not yet support DMA transfers. It also supports
56 both high (HG) and low (DG) versions of the card, though
57 the HG version has been untested.
61 #include <linux/interrupt.h>
62 #include "../comedidev.h"
64 #include <linux/ioport.h>
65 #include <linux/delay.h>
69 #define PCL711_SIZE 16
74 #define PCL711_CTRCTL 3
75 #define PCL711_AD_LO 4
76 #define PCL711_DA0_LO 4
77 #define PCL711_AD_HI 5
78 #define PCL711_DA0_HI 5
79 #define PCL711_DI_LO 6
80 #define PCL711_DA1_LO 6
81 #define PCL711_DI_HI 7
82 #define PCL711_DA1_HI 7
83 #define PCL711_CLRINTR 8
86 #define PCL711_MODE 11
87 #define PCL711_SOFTTRIG 12
88 #define PCL711_DO_LO 13
89 #define PCL711_DO_HI 14
91 static const struct comedi_lrange range_pcl711b_ai
= { 5, {
100 static const struct comedi_lrange range_acl8112hg_ai
= { 12, {
116 static const struct comedi_lrange range_acl8112dg_ai
= { 9, {
133 #define PCL711_TIMEOUT 100
134 #define PCL711_DRDY 0x10
136 static const int i8253_osc_base
= 500; /* 2 Mhz */
138 struct pcl711_board
{
148 const struct comedi_lrange
*ai_range_type
;
151 static const struct pcl711_board boardtypes
[] = {
152 {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5
},
153 {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai
},
154 {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai
},
155 {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai
},
158 #define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl711_board))
159 #define this_board ((const struct pcl711_board *)dev->board_ptr)
161 static int pcl711_attach(struct comedi_device
*dev
,
162 struct comedi_devconfig
*it
);
163 static int pcl711_detach(struct comedi_device
*dev
);
164 static struct comedi_driver driver_pcl711
= {
165 .driver_name
= "pcl711",
166 .module
= THIS_MODULE
,
167 .attach
= pcl711_attach
,
168 .detach
= pcl711_detach
,
169 .board_name
= &boardtypes
[0].name
,
170 .num_names
= n_boardtypes
,
171 .offset
= sizeof(struct pcl711_board
),
174 COMEDI_INITCLEANUP(driver_pcl711
);
176 struct pcl711_private
{
183 unsigned int ao_readback
[2];
184 unsigned int divisor1
;
185 unsigned int divisor2
;
188 #define devpriv ((struct pcl711_private *)dev->private)
190 static irqreturn_t
pcl711_interrupt(int irq
, void *d
)
194 struct comedi_device
*dev
= d
;
195 struct comedi_subdevice
*s
= dev
->subdevices
+ 0;
197 if (!dev
->attached
) {
198 comedi_error(dev
, "spurious interrupt");
202 hi
= inb(dev
->iobase
+ PCL711_AD_HI
);
203 lo
= inb(dev
->iobase
+ PCL711_AD_LO
);
204 outb(0, dev
->iobase
+ PCL711_CLRINTR
);
206 data
= (hi
<< 8) | lo
;
208 /* FIXME! Nothing else sets ntrig! */
209 if (!(--devpriv
->ntrig
)) {
210 if (this_board
->is_8112
) {
211 outb(1, dev
->iobase
+ PCL711_MODE
);
213 outb(0, dev
->iobase
+ PCL711_MODE
);
216 s
->async
->events
|= COMEDI_CB_EOA
;
218 comedi_event(dev
, s
);
222 static void pcl711_set_changain(struct comedi_device
*dev
, int chan
)
226 outb(CR_RANGE(chan
), dev
->iobase
+ PCL711_GAIN
);
228 chan_register
= CR_CHAN(chan
);
230 if (this_board
->is_8112
) {
233 * Set the correct channel. The two channel banks are switched
234 * using the mask value.
235 * NB: To use differential channels, you should use mask = 0x30,
236 * but I haven't written the support for this yet. /JJ
239 if (chan_register
>= 8) {
240 chan_register
= 0x20 | (chan_register
& 0x7);
242 chan_register
|= 0x10;
245 outb(chan_register
, dev
->iobase
+ PCL711_MUX
);
249 static int pcl711_ai_insn(struct comedi_device
*dev
, struct comedi_subdevice
*s
,
250 struct comedi_insn
*insn
, unsigned int *data
)
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 by writing
260 * to the trigger register
262 outb(1, dev
->iobase
+ PCL711_MODE
);
264 if (this_board
->is_8112
) {
266 outb(0, dev
->iobase
+ PCL711_SOFTTRIG
);
271 hi
= inb(dev
->iobase
+ PCL711_AD_HI
);
272 if (!(hi
& PCL711_DRDY
))
276 printk("comedi%d: pcl711: A/D timeout\n", dev
->minor
);
280 lo
= inb(dev
->iobase
+ PCL711_AD_LO
);
282 data
[n
] = ((hi
& 0xf) << 8) | lo
;
288 static int pcl711_ai_cmdtest(struct comedi_device
*dev
,
289 struct comedi_subdevice
*s
, struct comedi_cmd
*cmd
)
295 tmp
= cmd
->start_src
;
296 cmd
->start_src
&= TRIG_NOW
;
297 if (!cmd
->start_src
|| tmp
!= cmd
->start_src
)
300 tmp
= cmd
->scan_begin_src
;
301 cmd
->scan_begin_src
&= TRIG_TIMER
| TRIG_EXT
;
302 if (!cmd
->scan_begin_src
|| tmp
!= cmd
->scan_begin_src
)
305 tmp
= cmd
->convert_src
;
306 cmd
->convert_src
&= TRIG_NOW
;
307 if (!cmd
->convert_src
|| tmp
!= cmd
->convert_src
)
310 tmp
= cmd
->scan_end_src
;
311 cmd
->scan_end_src
&= TRIG_COUNT
;
312 if (!cmd
->scan_end_src
|| tmp
!= cmd
->scan_end_src
)
316 cmd
->stop_src
&= TRIG_COUNT
| TRIG_NONE
;
317 if (!cmd
->stop_src
|| tmp
!= cmd
->stop_src
)
325 if (cmd
->scan_begin_src
!= TRIG_TIMER
&&
326 cmd
->scan_begin_src
!= TRIG_EXT
)
328 if (cmd
->stop_src
!= TRIG_COUNT
&& cmd
->stop_src
!= TRIG_NONE
)
336 if (cmd
->start_arg
!= 0) {
340 if (cmd
->scan_begin_src
== TRIG_EXT
) {
341 if (cmd
->scan_begin_arg
!= 0) {
342 cmd
->scan_begin_arg
= 0;
346 #define MAX_SPEED 1000
347 #define TIMER_BASE 100
348 if (cmd
->scan_begin_arg
< MAX_SPEED
) {
349 cmd
->scan_begin_arg
= MAX_SPEED
;
353 if (cmd
->convert_arg
!= 0) {
354 cmd
->convert_arg
= 0;
357 if (cmd
->scan_end_arg
!= cmd
->chanlist_len
) {
358 cmd
->scan_end_arg
= cmd
->chanlist_len
;
361 if (cmd
->stop_src
== TRIG_NONE
) {
362 if (cmd
->stop_arg
!= 0) {
375 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
376 tmp
= cmd
->scan_begin_arg
;
377 i8253_cascade_ns_to_timer_2div(TIMER_BASE
,
380 &cmd
->scan_begin_arg
,
381 cmd
->flags
& TRIG_ROUND_MASK
);
382 if (tmp
!= cmd
->scan_begin_arg
)
392 static int pcl711_ai_cmd(struct comedi_device
*dev
, struct comedi_subdevice
*s
)
395 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
397 pcl711_set_changain(dev
, cmd
->chanlist
[0]);
399 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
402 * timer chip is an 8253, with timers 1 and 2
404 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
405 * Mode 2 = Rate generator
407 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
410 i8253_cascade_ns_to_timer(i8253_osc_base
, &timer1
, &timer2
,
411 &cmd
->scan_begin_arg
,
414 outb(0x74, dev
->iobase
+ PCL711_CTRCTL
);
415 outb(timer1
& 0xff, dev
->iobase
+ PCL711_CTR1
);
416 outb((timer1
>> 8) & 0xff, dev
->iobase
+ PCL711_CTR1
);
417 outb(0xb4, dev
->iobase
+ PCL711_CTRCTL
);
418 outb(timer2
& 0xff, dev
->iobase
+ PCL711_CTR2
);
419 outb((timer2
>> 8) & 0xff, dev
->iobase
+ PCL711_CTR2
);
421 /* clear pending interrupts (just in case) */
422 outb(0, dev
->iobase
+ PCL711_CLRINTR
);
425 * Set mode to IRQ transfer
427 outb(devpriv
->mode
| 6, dev
->iobase
+ PCL711_MODE
);
429 /* external trigger */
430 outb(devpriv
->mode
| 3, dev
->iobase
+ PCL711_MODE
);
439 static int pcl711_ao_insn(struct comedi_device
*dev
, struct comedi_subdevice
*s
,
440 struct comedi_insn
*insn
, unsigned int *data
)
443 int chan
= CR_CHAN(insn
->chanspec
);
445 for (n
= 0; n
< insn
->n
; n
++) {
446 outb((data
[n
] & 0xff),
447 dev
->iobase
+ (chan
? PCL711_DA1_LO
: PCL711_DA0_LO
));
449 dev
->iobase
+ (chan
? PCL711_DA1_HI
: PCL711_DA0_HI
));
451 devpriv
->ao_readback
[chan
] = data
[n
];
457 static int pcl711_ao_insn_read(struct comedi_device
*dev
,
458 struct comedi_subdevice
*s
,
459 struct comedi_insn
*insn
, unsigned int *data
)
462 int chan
= CR_CHAN(insn
->chanspec
);
464 for (n
= 0; n
< insn
->n
; n
++) {
465 data
[n
] = devpriv
->ao_readback
[chan
];
472 /* Digital port read - Untested on 8112 */
473 static int pcl711_di_insn_bits(struct comedi_device
*dev
,
474 struct comedi_subdevice
*s
,
475 struct comedi_insn
*insn
, unsigned int *data
)
480 data
[1] = inb(dev
->iobase
+ PCL711_DI_LO
) |
481 (inb(dev
->iobase
+ PCL711_DI_HI
) << 8);
486 /* Digital port write - Untested on 8112 */
487 static int pcl711_do_insn_bits(struct comedi_device
*dev
,
488 struct comedi_subdevice
*s
,
489 struct comedi_insn
*insn
, unsigned int *data
)
495 s
->state
&= ~data
[0];
496 s
->state
|= data
[0] & data
[1];
498 if (data
[0] & 0x00ff)
499 outb(s
->state
& 0xff, dev
->iobase
+ PCL711_DO_LO
);
500 if (data
[0] & 0xff00)
501 outb((s
->state
>> 8), dev
->iobase
+ PCL711_DO_HI
);
508 /* Free any resources that we have claimed */
509 static int pcl711_detach(struct comedi_device
*dev
)
511 printk("comedi%d: pcl711: remove\n", dev
->minor
);
514 free_irq(dev
->irq
, dev
);
517 release_region(dev
->iobase
, PCL711_SIZE
);
523 static int pcl711_attach(struct comedi_device
*dev
, struct comedi_devconfig
*it
)
526 unsigned long iobase
;
528 struct comedi_subdevice
*s
;
530 /* claim our I/O space */
532 iobase
= it
->options
[0];
533 printk("comedi%d: pcl711: 0x%04lx ", dev
->minor
, iobase
);
534 if (!request_region(iobase
, PCL711_SIZE
, "pcl711")) {
535 printk("I/O port conflict\n");
538 dev
->iobase
= iobase
;
540 /* there should be a sanity check here */
542 /* set up some name stuff */
543 dev
->board_name
= this_board
->name
;
546 irq
= it
->options
[1];
547 if (irq
> this_board
->maxirq
) {
548 printk("irq out of range\n");
552 if (request_irq(irq
, pcl711_interrupt
, 0, "pcl711", dev
)) {
553 printk("unable to allocate irq %u\n", irq
);
556 printk("( irq = %u )\n", irq
);
561 ret
= alloc_subdevices(dev
, 4);
565 ret
= alloc_private(dev
, sizeof(struct pcl711_private
));
569 s
= dev
->subdevices
+ 0;
571 s
->type
= COMEDI_SUBD_AI
;
572 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
;
573 s
->n_chan
= this_board
->n_aichan
;
576 s
->range_table
= this_board
->ai_range_type
;
577 s
->insn_read
= pcl711_ai_insn
;
579 dev
->read_subdev
= s
;
580 s
->subdev_flags
|= SDF_CMD_READ
;
581 s
->do_cmdtest
= pcl711_ai_cmdtest
;
582 s
->do_cmd
= pcl711_ai_cmd
;
587 s
->type
= COMEDI_SUBD_AO
;
588 s
->subdev_flags
= SDF_WRITABLE
;
589 s
->n_chan
= this_board
->n_aochan
;
592 s
->range_table
= &range_bipolar5
;
593 s
->insn_write
= pcl711_ao_insn
;
594 s
->insn_read
= pcl711_ao_insn_read
;
597 /* 16-bit digital input */
598 s
->type
= COMEDI_SUBD_DI
;
599 s
->subdev_flags
= SDF_READABLE
;
602 s
->len_chanlist
= 16;
603 s
->range_table
= &range_digital
;
604 s
->insn_bits
= pcl711_di_insn_bits
;
607 /* 16-bit digital out */
608 s
->type
= COMEDI_SUBD_DO
;
609 s
->subdev_flags
= SDF_WRITABLE
;
612 s
->len_chanlist
= 16;
613 s
->range_table
= &range_digital
;
615 s
->insn_bits
= pcl711_do_insn_bits
;
618 this is the "base value" for the mode register, which is
619 used for the irq on the PCL711
621 if (this_board
->is_pcl711b
) {
622 devpriv
->mode
= (dev
->irq
<< 4);
626 outb(0, dev
->iobase
+ PCL711_DA0_LO
);
627 outb(0, dev
->iobase
+ PCL711_DA0_HI
);
628 outb(0, dev
->iobase
+ PCL711_DA1_LO
);
629 outb(0, dev
->iobase
+ PCL711_DA1_HI
);