init from v2.6.32.60
[mach-moxart.git] / drivers / staging / comedi / drivers / pcl711.c
blobdd9db069a932469f85aae339b842b8faef4fdb1f
1 /*
2 comedi/drivers/pcl711.c
3 hardware driver for PC-LabCard PCL-711 and AdSys ACL-8112
4 and compatibles
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.
27 Driver: pcl711
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
35 supported.
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
51 by ds.
53 [acl-8112]
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>
67 #include "8253.h"
69 #define PCL711_SIZE 16
71 #define PCL711_CTR0 0
72 #define PCL711_CTR1 1
73 #define PCL711_CTR2 2
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
84 #define PCL711_GAIN 9
85 #define PCL711_MUX 10
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, {
92 BIP_RANGE(5),
93 BIP_RANGE(2.5),
94 BIP_RANGE(1.25),
95 BIP_RANGE(0.625),
96 BIP_RANGE(0.3125)
100 static const struct comedi_lrange range_acl8112hg_ai = { 12, {
101 BIP_RANGE(5),
102 BIP_RANGE(0.5),
103 BIP_RANGE(0.05),
104 BIP_RANGE(0.005),
105 UNI_RANGE(10),
106 UNI_RANGE(1),
107 UNI_RANGE(0.1),
108 UNI_RANGE(0.01),
109 BIP_RANGE(10),
110 BIP_RANGE(1),
111 BIP_RANGE(0.1),
112 BIP_RANGE(0.01)
116 static const struct comedi_lrange range_acl8112dg_ai = { 9, {
117 BIP_RANGE(5),
118 BIP_RANGE(2.5),
119 BIP_RANGE(1.25),
120 BIP_RANGE(0.625),
121 UNI_RANGE(10),
122 UNI_RANGE(5),
123 UNI_RANGE(2.5),
124 UNI_RANGE(1.25),
125 BIP_RANGE(10)
130 * flags
133 #define PCL711_TIMEOUT 100
134 #define PCL711_DRDY 0x10
136 static const int i8253_osc_base = 500; /* 2 Mhz */
138 struct pcl711_board {
140 const char *name;
141 int is_pcl711b;
142 int is_8112;
143 int is_dg;
144 int n_ranges;
145 int n_aichan;
146 int n_aochan;
147 int maxirq;
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 {
178 int board;
179 int adchan;
180 int ntrig;
181 int aip[8];
182 int mode;
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)
192 int lo, hi;
193 int data;
194 struct comedi_device *dev = d;
195 struct comedi_subdevice *s = dev->subdevices + 0;
197 if (!dev->attached) {
198 comedi_error(dev, "spurious interrupt");
199 return IRQ_HANDLED;
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);
212 } else {
213 outb(0, dev->iobase + PCL711_MODE);
216 s->async->events |= COMEDI_CB_EOA;
218 comedi_event(dev, s);
219 return IRQ_HANDLED;
222 static void pcl711_set_changain(struct comedi_device *dev, int chan)
224 int chan_register;
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);
241 } else {
242 chan_register |= 0x10;
244 } else {
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)
252 int i, n;
253 int hi, lo;
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) {
265 } else {
266 outb(0, dev->iobase + PCL711_SOFTTRIG);
269 i = PCL711_TIMEOUT;
270 while (--i) {
271 hi = inb(dev->iobase + PCL711_AD_HI);
272 if (!(hi & PCL711_DRDY))
273 goto ok;
274 udelay(1);
276 printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
277 return -ETIME;
280 lo = inb(dev->iobase + PCL711_AD_LO);
282 data[n] = ((hi & 0xf) << 8) | lo;
285 return n;
288 static int pcl711_ai_cmdtest(struct comedi_device *dev,
289 struct comedi_subdevice *s, struct comedi_cmd *cmd)
291 int tmp;
292 int err = 0;
294 /* step 1 */
295 tmp = cmd->start_src;
296 cmd->start_src &= TRIG_NOW;
297 if (!cmd->start_src || tmp != cmd->start_src)
298 err++;
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)
303 err++;
305 tmp = cmd->convert_src;
306 cmd->convert_src &= TRIG_NOW;
307 if (!cmd->convert_src || tmp != cmd->convert_src)
308 err++;
310 tmp = cmd->scan_end_src;
311 cmd->scan_end_src &= TRIG_COUNT;
312 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
313 err++;
315 tmp = cmd->stop_src;
316 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
317 if (!cmd->stop_src || tmp != cmd->stop_src)
318 err++;
320 if (err)
321 return 1;
323 /* step 2 */
325 if (cmd->scan_begin_src != TRIG_TIMER &&
326 cmd->scan_begin_src != TRIG_EXT)
327 err++;
328 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
329 err++;
331 if (err)
332 return 2;
334 /* step 3 */
336 if (cmd->start_arg != 0) {
337 cmd->start_arg = 0;
338 err++;
340 if (cmd->scan_begin_src == TRIG_EXT) {
341 if (cmd->scan_begin_arg != 0) {
342 cmd->scan_begin_arg = 0;
343 err++;
345 } else {
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;
350 err++;
353 if (cmd->convert_arg != 0) {
354 cmd->convert_arg = 0;
355 err++;
357 if (cmd->scan_end_arg != cmd->chanlist_len) {
358 cmd->scan_end_arg = cmd->chanlist_len;
359 err++;
361 if (cmd->stop_src == TRIG_NONE) {
362 if (cmd->stop_arg != 0) {
363 cmd->stop_arg = 0;
364 err++;
366 } else {
367 /* ignore */
370 if (err)
371 return 3;
373 /* step 4 */
375 if (cmd->scan_begin_src == TRIG_TIMER) {
376 tmp = cmd->scan_begin_arg;
377 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
378 &devpriv->divisor1,
379 &devpriv->divisor2,
380 &cmd->scan_begin_arg,
381 cmd->flags & TRIG_ROUND_MASK);
382 if (tmp != cmd->scan_begin_arg)
383 err++;
386 if (err)
387 return 4;
389 return 0;
392 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
394 int timer1, timer2;
395 struct comedi_cmd *cmd = &s->async->cmd;
397 pcl711_set_changain(dev, cmd->chanlist[0]);
399 if (cmd->scan_begin_src == TRIG_TIMER) {
401 * Set timers
402 * timer chip is an 8253, with timers 1 and 2
403 * cascaded
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,
412 TRIG_ROUND_NEAREST);
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);
428 } else {
429 /* external trigger */
430 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
433 return 0;
437 analog output
439 static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
440 struct comedi_insn *insn, unsigned int *data)
442 int n;
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));
448 outb((data[n] >> 8),
449 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
451 devpriv->ao_readback[chan] = data[n];
454 return 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)
461 int n;
462 int chan = CR_CHAN(insn->chanspec);
464 for (n = 0; n < insn->n; n++) {
465 data[n] = devpriv->ao_readback[chan];
468 return n;
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)
477 if (insn->n != 2)
478 return -EINVAL;
480 data[1] = inb(dev->iobase + PCL711_DI_LO) |
481 (inb(dev->iobase + PCL711_DI_HI) << 8);
483 return 2;
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)
491 if (insn->n != 2)
492 return -EINVAL;
494 if (data[0]) {
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);
503 data[1] = s->state;
505 return 2;
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);
513 if (dev->irq)
514 free_irq(dev->irq, dev);
516 if (dev->iobase)
517 release_region(dev->iobase, PCL711_SIZE);
519 return 0;
522 /* Initialization */
523 static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
525 int ret;
526 unsigned long iobase;
527 unsigned int irq;
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");
536 return -EIO;
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;
545 /* grab our IRQ */
546 irq = it->options[1];
547 if (irq > this_board->maxirq) {
548 printk("irq out of range\n");
549 return -EINVAL;
551 if (irq) {
552 if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
553 printk("unable to allocate irq %u\n", irq);
554 return -EINVAL;
555 } else {
556 printk("( irq = %u )\n", irq);
559 dev->irq = irq;
561 ret = alloc_subdevices(dev, 4);
562 if (ret < 0)
563 return ret;
565 ret = alloc_private(dev, sizeof(struct pcl711_private));
566 if (ret < 0)
567 return ret;
569 s = dev->subdevices + 0;
570 /* AI subdevice */
571 s->type = COMEDI_SUBD_AI;
572 s->subdev_flags = SDF_READABLE | SDF_GROUND;
573 s->n_chan = this_board->n_aichan;
574 s->maxdata = 0xfff;
575 s->len_chanlist = 1;
576 s->range_table = this_board->ai_range_type;
577 s->insn_read = pcl711_ai_insn;
578 if (irq) {
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;
585 s++;
586 /* AO subdevice */
587 s->type = COMEDI_SUBD_AO;
588 s->subdev_flags = SDF_WRITABLE;
589 s->n_chan = this_board->n_aochan;
590 s->maxdata = 0xfff;
591 s->len_chanlist = 1;
592 s->range_table = &range_bipolar5;
593 s->insn_write = pcl711_ao_insn;
594 s->insn_read = pcl711_ao_insn_read;
596 s++;
597 /* 16-bit digital input */
598 s->type = COMEDI_SUBD_DI;
599 s->subdev_flags = SDF_READABLE;
600 s->n_chan = 16;
601 s->maxdata = 1;
602 s->len_chanlist = 16;
603 s->range_table = &range_digital;
604 s->insn_bits = pcl711_di_insn_bits;
606 s++;
607 /* 16-bit digital out */
608 s->type = COMEDI_SUBD_DO;
609 s->subdev_flags = SDF_WRITABLE;
610 s->n_chan = 16;
611 s->maxdata = 1;
612 s->len_chanlist = 16;
613 s->range_table = &range_digital;
614 s->state = 0;
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);
625 /* clear DAC */
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);
631 printk("\n");
633 return 0;