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 static int __init
driver_pcl711_init_module(void)
176 return comedi_driver_register(&driver_pcl711
);
179 static void __exit
driver_pcl711_cleanup_module(void)
181 comedi_driver_unregister(&driver_pcl711
);
184 module_init(driver_pcl711_init_module
);
185 module_exit(driver_pcl711_cleanup_module
);
187 struct pcl711_private
{
194 unsigned int ao_readback
[2];
195 unsigned int divisor1
;
196 unsigned int divisor2
;
199 #define devpriv ((struct pcl711_private *)dev->private)
201 static irqreturn_t
pcl711_interrupt(int irq
, void *d
)
205 struct comedi_device
*dev
= d
;
206 struct comedi_subdevice
*s
= dev
->subdevices
+ 0;
208 if (!dev
->attached
) {
209 comedi_error(dev
, "spurious interrupt");
213 hi
= inb(dev
->iobase
+ PCL711_AD_HI
);
214 lo
= inb(dev
->iobase
+ PCL711_AD_LO
);
215 outb(0, dev
->iobase
+ PCL711_CLRINTR
);
217 data
= (hi
<< 8) | lo
;
219 /* FIXME! Nothing else sets ntrig! */
220 if (!(--devpriv
->ntrig
)) {
221 if (this_board
->is_8112
)
222 outb(1, dev
->iobase
+ PCL711_MODE
);
224 outb(0, dev
->iobase
+ PCL711_MODE
);
226 s
->async
->events
|= COMEDI_CB_EOA
;
228 comedi_event(dev
, s
);
232 static void pcl711_set_changain(struct comedi_device
*dev
, int chan
)
236 outb(CR_RANGE(chan
), dev
->iobase
+ PCL711_GAIN
);
238 chan_register
= CR_CHAN(chan
);
240 if (this_board
->is_8112
) {
243 * Set the correct channel. The two channel banks are switched
244 * using the mask value.
245 * NB: To use differential channels, you should use
246 * mask = 0x30, but I haven't written the support for this
250 if (chan_register
>= 8)
251 chan_register
= 0x20 | (chan_register
& 0x7);
253 chan_register
|= 0x10;
255 outb(chan_register
, dev
->iobase
+ PCL711_MUX
);
259 static int pcl711_ai_insn(struct comedi_device
*dev
, struct comedi_subdevice
*s
,
260 struct comedi_insn
*insn
, unsigned int *data
)
265 pcl711_set_changain(dev
, insn
->chanspec
);
267 for (n
= 0; n
< insn
->n
; n
++) {
269 * Write the correct mode (software polling) and start polling
270 * by writing to the trigger register
272 outb(1, dev
->iobase
+ PCL711_MODE
);
274 if (!this_board
->is_8112
)
275 outb(0, dev
->iobase
+ PCL711_SOFTTRIG
);
279 hi
= inb(dev
->iobase
+ PCL711_AD_HI
);
280 if (!(hi
& PCL711_DRDY
))
284 printk(KERN_ERR
"comedi%d: pcl711: A/D timeout\n", dev
->minor
);
288 lo
= inb(dev
->iobase
+ PCL711_AD_LO
);
290 data
[n
] = ((hi
& 0xf) << 8) | lo
;
296 static int pcl711_ai_cmdtest(struct comedi_device
*dev
,
297 struct comedi_subdevice
*s
, struct comedi_cmd
*cmd
)
303 tmp
= cmd
->start_src
;
304 cmd
->start_src
&= TRIG_NOW
;
305 if (!cmd
->start_src
|| tmp
!= cmd
->start_src
)
308 tmp
= cmd
->scan_begin_src
;
309 cmd
->scan_begin_src
&= TRIG_TIMER
| TRIG_EXT
;
310 if (!cmd
->scan_begin_src
|| tmp
!= cmd
->scan_begin_src
)
313 tmp
= cmd
->convert_src
;
314 cmd
->convert_src
&= TRIG_NOW
;
315 if (!cmd
->convert_src
|| tmp
!= cmd
->convert_src
)
318 tmp
= cmd
->scan_end_src
;
319 cmd
->scan_end_src
&= TRIG_COUNT
;
320 if (!cmd
->scan_end_src
|| tmp
!= cmd
->scan_end_src
)
324 cmd
->stop_src
&= TRIG_COUNT
| TRIG_NONE
;
325 if (!cmd
->stop_src
|| tmp
!= cmd
->stop_src
)
333 if (cmd
->scan_begin_src
!= TRIG_TIMER
&&
334 cmd
->scan_begin_src
!= TRIG_EXT
)
336 if (cmd
->stop_src
!= TRIG_COUNT
&& cmd
->stop_src
!= TRIG_NONE
)
344 if (cmd
->start_arg
!= 0) {
348 if (cmd
->scan_begin_src
== TRIG_EXT
) {
349 if (cmd
->scan_begin_arg
!= 0) {
350 cmd
->scan_begin_arg
= 0;
354 #define MAX_SPEED 1000
355 #define TIMER_BASE 100
356 if (cmd
->scan_begin_arg
< MAX_SPEED
) {
357 cmd
->scan_begin_arg
= MAX_SPEED
;
361 if (cmd
->convert_arg
!= 0) {
362 cmd
->convert_arg
= 0;
365 if (cmd
->scan_end_arg
!= cmd
->chanlist_len
) {
366 cmd
->scan_end_arg
= cmd
->chanlist_len
;
369 if (cmd
->stop_src
== TRIG_NONE
) {
370 if (cmd
->stop_arg
!= 0) {
383 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
384 tmp
= cmd
->scan_begin_arg
;
385 i8253_cascade_ns_to_timer_2div(TIMER_BASE
,
388 &cmd
->scan_begin_arg
,
389 cmd
->flags
& TRIG_ROUND_MASK
);
390 if (tmp
!= cmd
->scan_begin_arg
)
400 static int pcl711_ai_cmd(struct comedi_device
*dev
, struct comedi_subdevice
*s
)
403 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
405 pcl711_set_changain(dev
, cmd
->chanlist
[0]);
407 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
410 * timer chip is an 8253, with timers 1 and 2
412 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
413 * Mode 2 = Rate generator
415 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
419 i8253_cascade_ns_to_timer(i8253_osc_base
, &timer1
, &timer2
,
420 &cmd
->scan_begin_arg
,
423 outb(0x74, dev
->iobase
+ PCL711_CTRCTL
);
424 outb(timer1
& 0xff, dev
->iobase
+ PCL711_CTR1
);
425 outb((timer1
>> 8) & 0xff, dev
->iobase
+ PCL711_CTR1
);
426 outb(0xb4, dev
->iobase
+ PCL711_CTRCTL
);
427 outb(timer2
& 0xff, dev
->iobase
+ PCL711_CTR2
);
428 outb((timer2
>> 8) & 0xff, dev
->iobase
+ PCL711_CTR2
);
430 /* clear pending interrupts (just in case) */
431 outb(0, dev
->iobase
+ PCL711_CLRINTR
);
434 * Set mode to IRQ transfer
436 outb(devpriv
->mode
| 6, dev
->iobase
+ PCL711_MODE
);
438 /* external trigger */
439 outb(devpriv
->mode
| 3, dev
->iobase
+ PCL711_MODE
);
448 static int pcl711_ao_insn(struct comedi_device
*dev
, struct comedi_subdevice
*s
,
449 struct comedi_insn
*insn
, unsigned int *data
)
452 int chan
= CR_CHAN(insn
->chanspec
);
454 for (n
= 0; n
< insn
->n
; n
++) {
455 outb((data
[n
] & 0xff),
456 dev
->iobase
+ (chan
? PCL711_DA1_LO
: PCL711_DA0_LO
));
458 dev
->iobase
+ (chan
? PCL711_DA1_HI
: PCL711_DA0_HI
));
460 devpriv
->ao_readback
[chan
] = data
[n
];
466 static int pcl711_ao_insn_read(struct comedi_device
*dev
,
467 struct comedi_subdevice
*s
,
468 struct comedi_insn
*insn
, unsigned int *data
)
471 int chan
= CR_CHAN(insn
->chanspec
);
473 for (n
= 0; n
< insn
->n
; n
++)
474 data
[n
] = devpriv
->ao_readback
[chan
];
480 /* Digital port read - Untested on 8112 */
481 static int pcl711_di_insn_bits(struct comedi_device
*dev
,
482 struct comedi_subdevice
*s
,
483 struct comedi_insn
*insn
, unsigned int *data
)
488 data
[1] = inb(dev
->iobase
+ PCL711_DI_LO
) |
489 (inb(dev
->iobase
+ PCL711_DI_HI
) << 8);
494 /* Digital port write - Untested on 8112 */
495 static int pcl711_do_insn_bits(struct comedi_device
*dev
,
496 struct comedi_subdevice
*s
,
497 struct comedi_insn
*insn
, unsigned int *data
)
503 s
->state
&= ~data
[0];
504 s
->state
|= data
[0] & data
[1];
506 if (data
[0] & 0x00ff)
507 outb(s
->state
& 0xff, dev
->iobase
+ PCL711_DO_LO
);
508 if (data
[0] & 0xff00)
509 outb((s
->state
>> 8), dev
->iobase
+ PCL711_DO_HI
);
516 /* Free any resources that we have claimed */
517 static int pcl711_detach(struct comedi_device
*dev
)
519 printk(KERN_INFO
"comedi%d: pcl711: remove\n", dev
->minor
);
522 free_irq(dev
->irq
, dev
);
525 release_region(dev
->iobase
, PCL711_SIZE
);
531 static int pcl711_attach(struct comedi_device
*dev
, struct comedi_devconfig
*it
)
534 unsigned long iobase
;
536 struct comedi_subdevice
*s
;
538 /* claim our I/O space */
540 iobase
= it
->options
[0];
541 printk(KERN_INFO
"comedi%d: pcl711: 0x%04lx ", dev
->minor
, iobase
);
542 if (!request_region(iobase
, PCL711_SIZE
, "pcl711")) {
543 printk("I/O port conflict\n");
546 dev
->iobase
= iobase
;
548 /* there should be a sanity check here */
550 /* set up some name stuff */
551 dev
->board_name
= this_board
->name
;
554 irq
= it
->options
[1];
555 if (irq
> this_board
->maxirq
) {
556 printk(KERN_ERR
"irq out of range\n");
560 if (request_irq(irq
, pcl711_interrupt
, 0, "pcl711", dev
)) {
561 printk(KERN_ERR
"unable to allocate irq %u\n", irq
);
564 printk(KERN_INFO
"( irq = %u )\n", irq
);
569 ret
= alloc_subdevices(dev
, 4);
573 ret
= alloc_private(dev
, sizeof(struct pcl711_private
));
577 s
= dev
->subdevices
+ 0;
579 s
->type
= COMEDI_SUBD_AI
;
580 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
;
581 s
->n_chan
= this_board
->n_aichan
;
584 s
->range_table
= this_board
->ai_range_type
;
585 s
->insn_read
= pcl711_ai_insn
;
587 dev
->read_subdev
= s
;
588 s
->subdev_flags
|= SDF_CMD_READ
;
589 s
->do_cmdtest
= pcl711_ai_cmdtest
;
590 s
->do_cmd
= pcl711_ai_cmd
;
595 s
->type
= COMEDI_SUBD_AO
;
596 s
->subdev_flags
= SDF_WRITABLE
;
597 s
->n_chan
= this_board
->n_aochan
;
600 s
->range_table
= &range_bipolar5
;
601 s
->insn_write
= pcl711_ao_insn
;
602 s
->insn_read
= pcl711_ao_insn_read
;
605 /* 16-bit digital input */
606 s
->type
= COMEDI_SUBD_DI
;
607 s
->subdev_flags
= SDF_READABLE
;
610 s
->len_chanlist
= 16;
611 s
->range_table
= &range_digital
;
612 s
->insn_bits
= pcl711_di_insn_bits
;
615 /* 16-bit digital out */
616 s
->type
= COMEDI_SUBD_DO
;
617 s
->subdev_flags
= SDF_WRITABLE
;
620 s
->len_chanlist
= 16;
621 s
->range_table
= &range_digital
;
623 s
->insn_bits
= pcl711_do_insn_bits
;
626 this is the "base value" for the mode register, which is
627 used for the irq on the PCL711
629 if (this_board
->is_pcl711b
)
630 devpriv
->mode
= (dev
->irq
<< 4);
633 outb(0, dev
->iobase
+ PCL711_DA0_LO
);
634 outb(0, dev
->iobase
+ PCL711_DA0_HI
);
635 outb(0, dev
->iobase
+ PCL711_DA1_LO
);
636 outb(0, dev
->iobase
+ PCL711_DA1_HI
);
638 printk(KERN_INFO
"\n");
643 MODULE_AUTHOR("Comedi http://www.comedi.org");
644 MODULE_DESCRIPTION("Comedi low-level driver");
645 MODULE_LICENSE("GPL");