Merge branch 'for-linus' of git://github.com/schandinat/linux-2.6
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / drivers / staging / comedi / drivers / pcl711.c
CommitLineData
456af201
DS
1/*
2 comedi/drivers/pcl711.c
3 hardware driver for PC-LabCard PCL-711 and AdSys ACL-8112
4 and compatibles
5
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>
10
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.
15
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.
20
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.
24
25 */
26/*
27Driver: pcl711
28Description: Advantech PCL-711 and 711b, ADLink ACL-8112
29Author: ds, Janne Jalkanen <jalkanen@cs.hut.fi>, Eric Bunn <ebu@cs.hut.fi>
30Status: mostly complete
31Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
32 [AdLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
33
34Since these boards do not have DMA or FIFOs, only immediate mode is
35supported.
36
37*/
38
39/*
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/
45
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.
52
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.
58
59 */
60
25436dc9 61#include <linux/interrupt.h>
456af201
DS
62#include "../comedidev.h"
63
64#include <linux/ioport.h>
65#include <linux/delay.h>
66
67#include "8253.h"
68
69#define PCL711_SIZE 16
70
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
90
9ced1de6 91static const struct comedi_lrange range_pcl711b_ai = { 5, {
0a85b6f0
MT
92 BIP_RANGE(5),
93 BIP_RANGE(2.5),
94 BIP_RANGE(1.25),
95 BIP_RANGE(0.625),
96 BIP_RANGE(0.3125)
97 }
456af201 98};
0a85b6f0 99
9ced1de6 100static const struct comedi_lrange range_acl8112hg_ai = { 12, {
0a85b6f0
MT
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)
113 }
456af201 114};
0a85b6f0 115
9ced1de6 116static const struct comedi_lrange range_acl8112dg_ai = { 9, {
0a85b6f0
MT
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)
126 }
456af201
DS
127};
128
129/*
130 * flags
131 */
132
133#define PCL711_TIMEOUT 100
134#define PCL711_DRDY 0x10
135
136static const int i8253_osc_base = 500; /* 2 Mhz */
137
6445296e
BP
138struct pcl711_board {
139
456af201
DS
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;
9ced1de6 148 const struct comedi_lrange *ai_range_type;
6445296e
BP
149};
150
6445296e 151static const struct pcl711_board boardtypes[] = {
456af201
DS
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},
156};
157
6445296e
BP
158#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl711_board))
159#define this_board ((const struct pcl711_board *)dev->board_ptr)
456af201 160
0a85b6f0
MT
161static int pcl711_attach(struct comedi_device *dev,
162 struct comedi_devconfig *it);
da91b269 163static int pcl711_detach(struct comedi_device *dev);
139dfbdf 164static struct comedi_driver driver_pcl711 = {
68c3dbff
BP
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),
456af201
DS
172};
173
7114a280
AT
174static int __init driver_pcl711_init_module(void)
175{
176 return comedi_driver_register(&driver_pcl711);
177}
178
179static void __exit driver_pcl711_cleanup_module(void)
180{
181 comedi_driver_unregister(&driver_pcl711);
182}
183
184module_init(driver_pcl711_init_module);
185module_exit(driver_pcl711_cleanup_module);
456af201 186
e1c8638f
BP
187struct pcl711_private {
188
456af201
DS
189 int board;
190 int adchan;
191 int ntrig;
192 int aip[8];
193 int mode;
790c5541 194 unsigned int ao_readback[2];
456af201
DS
195 unsigned int divisor1;
196 unsigned int divisor2;
e1c8638f
BP
197};
198
e1c8638f 199#define devpriv ((struct pcl711_private *)dev->private)
456af201 200
70265d24 201static irqreturn_t pcl711_interrupt(int irq, void *d)
456af201
DS
202{
203 int lo, hi;
204 int data;
71b5f4f1 205 struct comedi_device *dev = d;
34c43922 206 struct comedi_subdevice *s = dev->subdevices + 0;
456af201
DS
207
208 if (!dev->attached) {
209 comedi_error(dev, "spurious interrupt");
210 return IRQ_HANDLED;
211 }
212
213 hi = inb(dev->iobase + PCL711_AD_HI);
214 lo = inb(dev->iobase + PCL711_AD_LO);
215 outb(0, dev->iobase + PCL711_CLRINTR);
216
217 data = (hi << 8) | lo;
218
219 /* FIXME! Nothing else sets ntrig! */
220 if (!(--devpriv->ntrig)) {
266bfbdd 221 if (this_board->is_8112)
456af201 222 outb(1, dev->iobase + PCL711_MODE);
266bfbdd 223 else
456af201 224 outb(0, dev->iobase + PCL711_MODE);
456af201
DS
225
226 s->async->events |= COMEDI_CB_EOA;
227 }
228 comedi_event(dev, s);
229 return IRQ_HANDLED;
230}
231
da91b269 232static void pcl711_set_changain(struct comedi_device *dev, int chan)
456af201
DS
233{
234 int chan_register;
235
236 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
237
238 chan_register = CR_CHAN(chan);
239
240 if (this_board->is_8112) {
241
242 /*
243 * Set the correct channel. The two channel banks are switched
244 * using the mask value.
266bfbdd
BA
245 * NB: To use differential channels, you should use
246 * mask = 0x30, but I haven't written the support for this
247 * yet. /JJ
456af201
DS
248 */
249
266bfbdd 250 if (chan_register >= 8)
456af201 251 chan_register = 0x20 | (chan_register & 0x7);
266bfbdd 252 else
456af201 253 chan_register |= 0x10;
456af201
DS
254 } else {
255 outb(chan_register, dev->iobase + PCL711_MUX);
256 }
257}
258
da91b269 259static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
0a85b6f0 260 struct comedi_insn *insn, unsigned int *data)
456af201
DS
261{
262 int i, n;
263 int hi, lo;
264
265 pcl711_set_changain(dev, insn->chanspec);
266
267 for (n = 0; n < insn->n; n++) {
268 /*
266bfbdd
BA
269 * Write the correct mode (software polling) and start polling
270 * by writing to the trigger register
456af201
DS
271 */
272 outb(1, dev->iobase + PCL711_MODE);
273
266bfbdd 274 if (!this_board->is_8112)
456af201 275 outb(0, dev->iobase + PCL711_SOFTTRIG);
456af201
DS
276
277 i = PCL711_TIMEOUT;
278 while (--i) {
279 hi = inb(dev->iobase + PCL711_AD_HI);
280 if (!(hi & PCL711_DRDY))
281 goto ok;
5f74ea14 282 udelay(1);
456af201 283 }
8f4e80af 284 printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor);
456af201
DS
285 return -ETIME;
286
0a85b6f0 287ok:
456af201
DS
288 lo = inb(dev->iobase + PCL711_AD_LO);
289
290 data[n] = ((hi & 0xf) << 8) | lo;
291 }
292
293 return n;
294}
295
0a85b6f0
MT
296static int pcl711_ai_cmdtest(struct comedi_device *dev,
297 struct comedi_subdevice *s, struct comedi_cmd *cmd)
456af201
DS
298{
299 int tmp;
300 int err = 0;
301
302 /* step 1 */
303 tmp = cmd->start_src;
304 cmd->start_src &= TRIG_NOW;
305 if (!cmd->start_src || tmp != cmd->start_src)
306 err++;
307
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)
311 err++;
312
313 tmp = cmd->convert_src;
314 cmd->convert_src &= TRIG_NOW;
315 if (!cmd->convert_src || tmp != cmd->convert_src)
316 err++;
317
318 tmp = cmd->scan_end_src;
319 cmd->scan_end_src &= TRIG_COUNT;
320 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
321 err++;
322
323 tmp = cmd->stop_src;
324 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
325 if (!cmd->stop_src || tmp != cmd->stop_src)
326 err++;
327
328 if (err)
329 return 1;
330
331 /* step 2 */
332
333 if (cmd->scan_begin_src != TRIG_TIMER &&
0a85b6f0 334 cmd->scan_begin_src != TRIG_EXT)
456af201
DS
335 err++;
336 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
337 err++;
338
339 if (err)
340 return 2;
341
342 /* step 3 */
343
344 if (cmd->start_arg != 0) {
345 cmd->start_arg = 0;
346 err++;
347 }
348 if (cmd->scan_begin_src == TRIG_EXT) {
349 if (cmd->scan_begin_arg != 0) {
350 cmd->scan_begin_arg = 0;
351 err++;
352 }
353 } else {
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;
358 err++;
359 }
360 }
361 if (cmd->convert_arg != 0) {
362 cmd->convert_arg = 0;
363 err++;
364 }
365 if (cmd->scan_end_arg != cmd->chanlist_len) {
366 cmd->scan_end_arg = cmd->chanlist_len;
367 err++;
368 }
369 if (cmd->stop_src == TRIG_NONE) {
370 if (cmd->stop_arg != 0) {
371 cmd->stop_arg = 0;
372 err++;
373 }
374 } else {
375 /* ignore */
376 }
377
378 if (err)
379 return 3;
380
381 /* step 4 */
382
383 if (cmd->scan_begin_src == TRIG_TIMER) {
384 tmp = cmd->scan_begin_arg;
385 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
0a85b6f0
MT
386 &devpriv->divisor1,
387 &devpriv->divisor2,
388 &cmd->scan_begin_arg,
389 cmd->flags & TRIG_ROUND_MASK);
456af201
DS
390 if (tmp != cmd->scan_begin_arg)
391 err++;
392 }
393
394 if (err)
395 return 4;
396
397 return 0;
398}
399
da91b269 400static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
456af201
DS
401{
402 int timer1, timer2;
ea6d0d4c 403 struct comedi_cmd *cmd = &s->async->cmd;
456af201
DS
404
405 pcl711_set_changain(dev, cmd->chanlist[0]);
406
407 if (cmd->scan_begin_src == TRIG_TIMER) {
408 /*
409 * Set timers
410 * timer chip is an 8253, with timers 1 and 2
411 * cascaded
412 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
413 * Mode 2 = Rate generator
414 *
415 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
416 */
417
48b1aff5 418 timer1 = timer2 = 0;
456af201 419 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
0a85b6f0
MT
420 &cmd->scan_begin_arg,
421 TRIG_ROUND_NEAREST);
456af201
DS
422
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);
429
430 /* clear pending interrupts (just in case) */
431 outb(0, dev->iobase + PCL711_CLRINTR);
432
433 /*
434 * Set mode to IRQ transfer
435 */
436 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
437 } else {
438 /* external trigger */
439 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
440 }
441
442 return 0;
443}
444
445/*
446 analog output
447*/
da91b269 448static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
0a85b6f0 449 struct comedi_insn *insn, unsigned int *data)
456af201
DS
450{
451 int n;
452 int chan = CR_CHAN(insn->chanspec);
453
454 for (n = 0; n < insn->n; n++) {
455 outb((data[n] & 0xff),
0a85b6f0 456 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
456af201 457 outb((data[n] >> 8),
0a85b6f0 458 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
456af201
DS
459
460 devpriv->ao_readback[chan] = data[n];
461 }
462
463 return n;
464}
465
0a85b6f0
MT
466static int pcl711_ao_insn_read(struct comedi_device *dev,
467 struct comedi_subdevice *s,
468 struct comedi_insn *insn, unsigned int *data)
456af201
DS
469{
470 int n;
471 int chan = CR_CHAN(insn->chanspec);
472
266bfbdd 473 for (n = 0; n < insn->n; n++)
456af201 474 data[n] = devpriv->ao_readback[chan];
456af201
DS
475
476 return n;
477
478}
479
480/* Digital port read - Untested on 8112 */
0a85b6f0
MT
481static int pcl711_di_insn_bits(struct comedi_device *dev,
482 struct comedi_subdevice *s,
483 struct comedi_insn *insn, unsigned int *data)
456af201
DS
484{
485 if (insn->n != 2)
486 return -EINVAL;
487
488 data[1] = inb(dev->iobase + PCL711_DI_LO) |
0a85b6f0 489 (inb(dev->iobase + PCL711_DI_HI) << 8);
456af201
DS
490
491 return 2;
492}
493
494/* Digital port write - Untested on 8112 */
0a85b6f0
MT
495static int pcl711_do_insn_bits(struct comedi_device *dev,
496 struct comedi_subdevice *s,
497 struct comedi_insn *insn, unsigned int *data)
456af201
DS
498{
499 if (insn->n != 2)
500 return -EINVAL;
501
502 if (data[0]) {
503 s->state &= ~data[0];
504 s->state |= data[0] & data[1];
505 }
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);
510
511 data[1] = s->state;
512
513 return 2;
514}
515
516/* Free any resources that we have claimed */
da91b269 517static int pcl711_detach(struct comedi_device *dev)
456af201 518{
8f4e80af 519 printk(KERN_INFO "comedi%d: pcl711: remove\n", dev->minor);
456af201
DS
520
521 if (dev->irq)
5f74ea14 522 free_irq(dev->irq, dev);
456af201
DS
523
524 if (dev->iobase)
525 release_region(dev->iobase, PCL711_SIZE);
526
527 return 0;
528}
529
530/* Initialization */
da91b269 531static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
456af201
DS
532{
533 int ret;
534 unsigned long iobase;
535 unsigned int irq;
34c43922 536 struct comedi_subdevice *s;
456af201
DS
537
538 /* claim our I/O space */
539
540 iobase = it->options[0];
8f4e80af 541 printk(KERN_INFO "comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
456af201
DS
542 if (!request_region(iobase, PCL711_SIZE, "pcl711")) {
543 printk("I/O port conflict\n");
544 return -EIO;
545 }
546 dev->iobase = iobase;
547
548 /* there should be a sanity check here */
549
550 /* set up some name stuff */
551 dev->board_name = this_board->name;
552
553 /* grab our IRQ */
554 irq = it->options[1];
555 if (irq > this_board->maxirq) {
8f4e80af 556 printk(KERN_ERR "irq out of range\n");
456af201
DS
557 return -EINVAL;
558 }
559 if (irq) {
5f74ea14 560 if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
8f4e80af 561 printk(KERN_ERR "unable to allocate irq %u\n", irq);
456af201
DS
562 return -EINVAL;
563 } else {
8f4e80af 564 printk(KERN_INFO "( irq = %u )\n", irq);
456af201
DS
565 }
566 }
567 dev->irq = irq;
568
c3744138
BP
569 ret = alloc_subdevices(dev, 4);
570 if (ret < 0)
456af201 571 return ret;
c3744138
BP
572
573 ret = alloc_private(dev, sizeof(struct pcl711_private));
574 if (ret < 0)
456af201
DS
575 return ret;
576
577 s = dev->subdevices + 0;
578 /* AI subdevice */
579 s->type = COMEDI_SUBD_AI;
580 s->subdev_flags = SDF_READABLE | SDF_GROUND;
581 s->n_chan = this_board->n_aichan;
582 s->maxdata = 0xfff;
583 s->len_chanlist = 1;
584 s->range_table = this_board->ai_range_type;
585 s->insn_read = pcl711_ai_insn;
586 if (irq) {
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;
591 }
592
593 s++;
594 /* AO subdevice */
595 s->type = COMEDI_SUBD_AO;
596 s->subdev_flags = SDF_WRITABLE;
597 s->n_chan = this_board->n_aochan;
598 s->maxdata = 0xfff;
599 s->len_chanlist = 1;
600 s->range_table = &range_bipolar5;
601 s->insn_write = pcl711_ao_insn;
602 s->insn_read = pcl711_ao_insn_read;
603
604 s++;
605 /* 16-bit digital input */
606 s->type = COMEDI_SUBD_DI;
607 s->subdev_flags = SDF_READABLE;
608 s->n_chan = 16;
609 s->maxdata = 1;
610 s->len_chanlist = 16;
611 s->range_table = &range_digital;
612 s->insn_bits = pcl711_di_insn_bits;
613
614 s++;
615 /* 16-bit digital out */
616 s->type = COMEDI_SUBD_DO;
617 s->subdev_flags = SDF_WRITABLE;
618 s->n_chan = 16;
619 s->maxdata = 1;
620 s->len_chanlist = 16;
621 s->range_table = &range_digital;
622 s->state = 0;
623 s->insn_bits = pcl711_do_insn_bits;
624
625 /*
626 this is the "base value" for the mode register, which is
627 used for the irq on the PCL711
628 */
266bfbdd 629 if (this_board->is_pcl711b)
456af201 630 devpriv->mode = (dev->irq << 4);
456af201
DS
631
632 /* clear DAC */
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);
637
8f4e80af 638 printk(KERN_INFO "\n");
456af201
DS
639
640 return 0;
641}
90f703d3
AT
642
643MODULE_AUTHOR("Comedi http://www.comedi.org");
644MODULE_DESCRIPTION("Comedi low-level driver");
645MODULE_LICENSE("GPL");