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@stm.lbl.gov>
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 Dave Andruczyk <dave@tech.buffalostate.edu> also wrote a
29 driver for the PCL-711. I used a few ideas from his driver
30 here. His driver also has more comments, if you are
31 interested in understanding how this driver works.
32 http://tech.buffalostate.edu/~dave/driver/
34 The ACL-8112 driver was hacked from the sources of the PCL-711
35 driver (the 744 chip used on the 8112 is almost the same as
36 the 711b chip, but it has more I/O channels) by
37 Janne Jalkanen (jalkanen@cs.hut.fi) and
38 Erik Bunn (ebu@cs.hut.fi). Remerged with the PCL-711 driver
42 This driver supports both TRIGNOW and TRIGCLK,
43 but does not yet support DMA transfers. It also supports
44 both high (HG) and low (DG) versions of the card, though
45 the HG version has been untested.
49 #include <linux/kernel.h>
50 #include <linux/module.h>
51 #include <linux/sched.h>
53 #include <linux/malloc.h>
54 #include <linux/errno.h>
55 #include <linux/ioport.h>
56 #include <linux/delay.h>
57 #include <linux/interrupt.h>
58 #include <linux/timex.h>
59 #include <linux/timer.h>
61 #include <comedi_module.h>
66 #define PCL711_SIZE 16
71 #define PCL711_CTRCTL 3
72 #define PCL711_AD_LO 4
73 #define PCL711_DA0_LO 4
74 #define PCL711_AD_HI 5
75 #define PCL711_DA0_HI 5
76 #define PCL711_DI_LO 6
77 #define PCL711_DA1_LO 6
78 #define PCL711_DI_HI 7
79 #define PCL711_DA1_HI 7
80 #define PCL711_CLRINTR 8
83 #define PCL711_MODE 11
84 #define PCL711_SOFTTRIG 12
85 #define PCL711_DO_LO 13
86 #define PCL711_DO_HI 14
88 static comedi_lrange range_pcl711b_ai = { 5, {
95 static comedi_lrange range_acl8112hg_ai = { 12, {
109 static comedi_lrange range_acl8112dg_ai = { 9, {
121 static int pcl711_attach(comedi_device *dev,comedi_devconfig *it);
122 static int pcl711_detach(comedi_device *dev);
123 static int pcl711_recognize(char *name);
124 comedi_driver driver_pcl711={
125 driver_name: "pcl711",
127 attach: pcl711_attach,
128 detach: pcl711_detach,
129 recognize: pcl711_recognize,
138 #define PCL711_TIMEOUT 100
139 #define PCL711_DRDY 0x10
141 int i8253_osc_base = 500; /* 2 Mhz */
152 comedi_lrange * ai_range_type;
155 static boardtype boardtypes[] =
157 {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5},
158 {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai},
159 {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai},
160 {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai},
162 #define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype))
172 #define devpriv ((pcl711_private *)dev->private)
173 #define this_board (boardtypes+dev->board)
175 static void pcl711_interrupt(int irq, void *d, struct pt_regs *regs)
179 comedi_device *dev = d;
180 comedi_subdevice *s = dev->subdevices + 0;
182 hi = inb(dev->iobase + PCL711_AD_HI);
183 lo = inb(dev->iobase + PCL711_AD_LO);
184 outb(0, dev->iobase + PCL711_CLRINTR);
186 data = (hi << 8) | lo;
188 if (!(--devpriv->ntrig)) {
189 if (this_board->is_8112) {
190 outb(1, dev->iobase + PCL711_MODE);
192 outb(0, dev->iobase + PCL711_MODE);
200 static void pcl711_set_changain(comedi_device * dev, int chan)
204 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
206 chan_register=CR_CHAN(chan);
208 if (this_board->is_8112) {
211 * Set the correct channel. The two channel banks are switched
212 * using the mask value.
213 * NB: To use differential channels, you should use mask = 0x30,
214 * but I haven't written the support for this yet. /JJ
217 if (chan_register >= 8){
218 chan_register = 0x20 | (chan_register & 0x7);
220 chan_register |= 0x10;
223 outb(chan_register, dev->iobase + PCL711_MUX);
227 static int pcl711_ai_mode0(comedi_device * dev, comedi_subdevice * s, comedi_trig * it)
230 int nmax=40; /* 1000 us / 25 us */
233 if(it->n<=nmax)nmax=it->n;
235 pcl711_set_changain(dev,it->chanlist[0]);
238 a sensible precaution to wait for the mux to
239 settle here. is 10us enough?
245 * Write the correct mode (software polling) and start polling by writing
246 * to the trigger register
248 outb(1, dev->iobase + PCL711_MODE);
250 if (this_board->is_8112) {
252 outb(0, dev->iobase + PCL711_SOFTTRIG);
257 hi = inb(dev->iobase + PCL711_AD_HI);
258 if (!(hi & PCL711_DRDY))
262 rt_printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
266 lo = inb(dev->iobase + PCL711_AD_LO);
268 it->data[n] = ((hi & 0xf) << 8) | lo;
274 static int pcl711_ai_mode4(comedi_device * dev, comedi_subdevice * s, comedi_trig * it)
276 if (!this_board->is_pcl711b || dev->irq == 0)
279 pcl711_set_changain(dev,it->chanlist[0]);
282 * Set mode to "no internal trigger"
284 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
290 static int pcl711_ai_mode1(comedi_device * dev, comedi_subdevice * s, comedi_trig * it)
297 pcl711_set_changain(dev,it->chanlist[0]);
301 * timer chip is an 8253, with timers 1 and 2
303 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
304 * Mode 2 = Rate generator
306 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
309 i8253_cascade_ns_to_timer(i8253_osc_base,&timer1,&timer2,&it->trigvar,TRIG_ROUND_NEAREST);
311 outb(0x74, dev->iobase + PCL711_CTRCTL);
312 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
313 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
314 outb(0xb4, dev->iobase + PCL711_CTRCTL);
315 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
316 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
318 /* clear pending interrupts (just in case) */
319 outb(0, dev->iobase + PCL711_CLRINTR);
322 * Set mode to IRQ transfer
324 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
332 static int pcl711_ao(comedi_device * dev, comedi_subdevice * s, comedi_trig * it)
334 int chan = CR_CHAN(it->chanlist[0]);
335 sampl_t data = it->data[0];
337 outb((data & 0xff), dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
338 outb((data >> 8), dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
343 /* Digital port read - Untested on 8112 */
344 static int pcl711_di(comedi_device * dev, comedi_subdevice * s, comedi_trig * it)
350 data = inb(dev->iobase + PCL711_DI_LO) |
351 (inb(dev->iobase + PCL711_DI_HI) << 8);
353 for(i=0;i<it->n_chan;i++){
354 chan=CR_CHAN(it->chanlist[i]);
355 it->data[i]=(data>>chan)&1;
361 /* Digital port write - Untested on 8112 */
362 static int pcl711_do(comedi_device * dev, comedi_subdevice * s, comedi_trig * it)
369 for(i=0;i<it->n_chan;i++){
370 chan=CR_CHAN(it->chanlist[i]);
376 outb(data & 0xff, dev->iobase + PCL711_DO_LO);
377 outb((data >> 8), dev->iobase + PCL711_DO_HI);
383 /* Free any resources that we have claimed */
384 static void free_resources(comedi_device * dev)
387 free_irq(dev->irq, dev);
390 release_region(dev->iobase, dev->iosize);
393 static int pcl711_recognize(char *name)
397 for (i = 0; i < n_boardtypes; i++) {
398 if (!strcmp(boardtypes[i].name, name)) {
406 static int pcl711_attach(comedi_device * dev, comedi_devconfig * it)
413 /* claim our I/O space */
415 iobase = it->options[0];
416 printk("comedi%d: pcl711: 0x%04x ", dev->minor, iobase);
417 if (check_region(iobase, PCL711_SIZE) < 0) {
418 printk("I/O port conflict\n");
421 request_region(dev->iobase, PCL711_SIZE, "pcl711");
422 dev->iobase = iobase;
423 dev->iosize = PCL711_SIZE;
425 /* there should be a sanity check here */
427 /* set up some name stuff */
428 dev->board_name = boardtypes[dev->board].name;
431 irq = it->options[1];
432 if (irq < 0 || irq > boardtypes[dev->board].maxirq) {
433 printk("irq out of range\n");
438 if (request_irq(irq, pcl711_interrupt, SA_INTERRUPT, "pcl711", dev)) {
439 printk("unable to allocate irq %d\n", irq);
443 printk("( irq = %d )\n", irq);
448 dev->n_subdevices = 4;
449 if((ret=alloc_subdevices(dev))<0)
451 if((ret=alloc_private(dev,sizeof(pcl711_private)))<0)
454 s = dev->subdevices + 0;
456 s->type = COMEDI_SUBD_AI;
457 s->subdev_flags = SDF_READABLE;
458 s->n_chan = this_board->n_aichan;
461 s->range_table = this_board->ai_range_type;
462 s->trig[0] = pcl711_ai_mode0;
463 s->trig[1] = pcl711_ai_mode1;
464 s->trig[4] = pcl711_ai_mode4;
468 s->type = COMEDI_SUBD_AO;
469 s->subdev_flags = SDF_WRITEABLE;
470 s->n_chan = this_board->n_aochan;
473 s->range_table = &range_bipolar5;
474 s->trig[0] = pcl711_ao;
477 /* 16-bit digital input */
478 s->type = COMEDI_SUBD_DI;
479 s->subdev_flags = SDF_READABLE;
482 s->len_chanlist = 16;
483 s->range_table = &range_digital;
484 s->trig[0] = pcl711_di;
487 /* 16-bit digital out */
488 s->type = COMEDI_SUBD_DO;
489 s->subdev_flags = SDF_WRITEABLE;
492 s->len_chanlist = 16;
493 s->range_table = &range_digital;
495 s->trig[0] = pcl711_do;
498 this is the "base value" for the mode register, which is
499 used for the irq on the PCL711
501 if(this_board->is_pcl711b){
502 devpriv->mode=(dev->irq<<4);
506 outb(0, dev->iobase + PCL711_DA0_LO);
507 outb(0, dev->iobase + PCL711_DA0_HI);
508 outb(0, dev->iobase + PCL711_DA1_LO);
509 outb(0, dev->iobase + PCL711_DA1_HI);
521 static int pcl711_detach(comedi_device * dev)
523 printk("comedi%d: pcl711: remove\n", dev->minor);
531 int init_module(void)
533 comedi_driver_register(&driver_pcl711);
538 void cleanup_module(void)
540 comedi_driver_unregister(&driver_pcl711);