Staging: comedi: serial2002: decrease stack usage
[deliverable/linux.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
174COMEDI_INITCLEANUP(driver_pcl711);
175
e1c8638f
BP
176struct pcl711_private {
177
456af201
DS
178 int board;
179 int adchan;
180 int ntrig;
181 int aip[8];
182 int mode;
790c5541 183 unsigned int ao_readback[2];
456af201
DS
184 unsigned int divisor1;
185 unsigned int divisor2;
e1c8638f
BP
186};
187
e1c8638f 188#define devpriv ((struct pcl711_private *)dev->private)
456af201 189
70265d24 190static irqreturn_t pcl711_interrupt(int irq, void *d)
456af201
DS
191{
192 int lo, hi;
193 int data;
71b5f4f1 194 struct comedi_device *dev = d;
34c43922 195 struct comedi_subdevice *s = dev->subdevices + 0;
456af201
DS
196
197 if (!dev->attached) {
198 comedi_error(dev, "spurious interrupt");
199 return IRQ_HANDLED;
200 }
201
202 hi = inb(dev->iobase + PCL711_AD_HI);
203 lo = inb(dev->iobase + PCL711_AD_LO);
204 outb(0, dev->iobase + PCL711_CLRINTR);
205
206 data = (hi << 8) | lo;
207
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);
214 }
215
216 s->async->events |= COMEDI_CB_EOA;
217 }
218 comedi_event(dev, s);
219 return IRQ_HANDLED;
220}
221
da91b269 222static void pcl711_set_changain(struct comedi_device *dev, int chan)
456af201
DS
223{
224 int chan_register;
225
226 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
227
228 chan_register = CR_CHAN(chan);
229
230 if (this_board->is_8112) {
231
232 /*
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
237 */
238
239 if (chan_register >= 8) {
240 chan_register = 0x20 | (chan_register & 0x7);
241 } else {
242 chan_register |= 0x10;
243 }
244 } else {
245 outb(chan_register, dev->iobase + PCL711_MUX);
246 }
247}
248
da91b269 249static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
0a85b6f0 250 struct comedi_insn *insn, unsigned int *data)
456af201
DS
251{
252 int i, n;
253 int hi, lo;
254
255 pcl711_set_changain(dev, insn->chanspec);
256
257 for (n = 0; n < insn->n; n++) {
258 /*
259 * Write the correct mode (software polling) and start polling by writing
260 * to the trigger register
261 */
262 outb(1, dev->iobase + PCL711_MODE);
263
264 if (this_board->is_8112) {
265 } else {
266 outb(0, dev->iobase + PCL711_SOFTTRIG);
267 }
268
269 i = PCL711_TIMEOUT;
270 while (--i) {
271 hi = inb(dev->iobase + PCL711_AD_HI);
272 if (!(hi & PCL711_DRDY))
273 goto ok;
5f74ea14 274 udelay(1);
456af201 275 }
5f74ea14 276 printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
456af201
DS
277 return -ETIME;
278
0a85b6f0 279ok:
456af201
DS
280 lo = inb(dev->iobase + PCL711_AD_LO);
281
282 data[n] = ((hi & 0xf) << 8) | lo;
283 }
284
285 return n;
286}
287
0a85b6f0
MT
288static int pcl711_ai_cmdtest(struct comedi_device *dev,
289 struct comedi_subdevice *s, struct comedi_cmd *cmd)
456af201
DS
290{
291 int tmp;
292 int err = 0;
293
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++;
299
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++;
304
305 tmp = cmd->convert_src;
306 cmd->convert_src &= TRIG_NOW;
307 if (!cmd->convert_src || tmp != cmd->convert_src)
308 err++;
309
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++;
314
315 tmp = cmd->stop_src;
316 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
317 if (!cmd->stop_src || tmp != cmd->stop_src)
318 err++;
319
320 if (err)
321 return 1;
322
323 /* step 2 */
324
325 if (cmd->scan_begin_src != TRIG_TIMER &&
0a85b6f0 326 cmd->scan_begin_src != TRIG_EXT)
456af201
DS
327 err++;
328 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
329 err++;
330
331 if (err)
332 return 2;
333
334 /* step 3 */
335
336 if (cmd->start_arg != 0) {
337 cmd->start_arg = 0;
338 err++;
339 }
340 if (cmd->scan_begin_src == TRIG_EXT) {
341 if (cmd->scan_begin_arg != 0) {
342 cmd->scan_begin_arg = 0;
343 err++;
344 }
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++;
351 }
352 }
353 if (cmd->convert_arg != 0) {
354 cmd->convert_arg = 0;
355 err++;
356 }
357 if (cmd->scan_end_arg != cmd->chanlist_len) {
358 cmd->scan_end_arg = cmd->chanlist_len;
359 err++;
360 }
361 if (cmd->stop_src == TRIG_NONE) {
362 if (cmd->stop_arg != 0) {
363 cmd->stop_arg = 0;
364 err++;
365 }
366 } else {
367 /* ignore */
368 }
369
370 if (err)
371 return 3;
372
373 /* step 4 */
374
375 if (cmd->scan_begin_src == TRIG_TIMER) {
376 tmp = cmd->scan_begin_arg;
377 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
0a85b6f0
MT
378 &devpriv->divisor1,
379 &devpriv->divisor2,
380 &cmd->scan_begin_arg,
381 cmd->flags & TRIG_ROUND_MASK);
456af201
DS
382 if (tmp != cmd->scan_begin_arg)
383 err++;
384 }
385
386 if (err)
387 return 4;
388
389 return 0;
390}
391
da91b269 392static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
456af201
DS
393{
394 int timer1, timer2;
ea6d0d4c 395 struct comedi_cmd *cmd = &s->async->cmd;
456af201
DS
396
397 pcl711_set_changain(dev, cmd->chanlist[0]);
398
399 if (cmd->scan_begin_src == TRIG_TIMER) {
400 /*
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
406 *
407 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
408 */
409
410 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
0a85b6f0
MT
411 &cmd->scan_begin_arg,
412 TRIG_ROUND_NEAREST);
456af201
DS
413
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);
420
421 /* clear pending interrupts (just in case) */
422 outb(0, dev->iobase + PCL711_CLRINTR);
423
424 /*
425 * Set mode to IRQ transfer
426 */
427 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
428 } else {
429 /* external trigger */
430 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
431 }
432
433 return 0;
434}
435
436/*
437 analog output
438*/
da91b269 439static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
0a85b6f0 440 struct comedi_insn *insn, unsigned int *data)
456af201
DS
441{
442 int n;
443 int chan = CR_CHAN(insn->chanspec);
444
445 for (n = 0; n < insn->n; n++) {
446 outb((data[n] & 0xff),
0a85b6f0 447 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
456af201 448 outb((data[n] >> 8),
0a85b6f0 449 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
456af201
DS
450
451 devpriv->ao_readback[chan] = data[n];
452 }
453
454 return n;
455}
456
0a85b6f0
MT
457static int pcl711_ao_insn_read(struct comedi_device *dev,
458 struct comedi_subdevice *s,
459 struct comedi_insn *insn, unsigned int *data)
456af201
DS
460{
461 int n;
462 int chan = CR_CHAN(insn->chanspec);
463
464 for (n = 0; n < insn->n; n++) {
465 data[n] = devpriv->ao_readback[chan];
466 }
467
468 return n;
469
470}
471
472/* Digital port read - Untested on 8112 */
0a85b6f0
MT
473static int pcl711_di_insn_bits(struct comedi_device *dev,
474 struct comedi_subdevice *s,
475 struct comedi_insn *insn, unsigned int *data)
456af201
DS
476{
477 if (insn->n != 2)
478 return -EINVAL;
479
480 data[1] = inb(dev->iobase + PCL711_DI_LO) |
0a85b6f0 481 (inb(dev->iobase + PCL711_DI_HI) << 8);
456af201
DS
482
483 return 2;
484}
485
486/* Digital port write - Untested on 8112 */
0a85b6f0
MT
487static int pcl711_do_insn_bits(struct comedi_device *dev,
488 struct comedi_subdevice *s,
489 struct comedi_insn *insn, unsigned int *data)
456af201
DS
490{
491 if (insn->n != 2)
492 return -EINVAL;
493
494 if (data[0]) {
495 s->state &= ~data[0];
496 s->state |= data[0] & data[1];
497 }
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);
502
503 data[1] = s->state;
504
505 return 2;
506}
507
508/* Free any resources that we have claimed */
da91b269 509static int pcl711_detach(struct comedi_device *dev)
456af201
DS
510{
511 printk("comedi%d: pcl711: remove\n", dev->minor);
512
513 if (dev->irq)
5f74ea14 514 free_irq(dev->irq, dev);
456af201
DS
515
516 if (dev->iobase)
517 release_region(dev->iobase, PCL711_SIZE);
518
519 return 0;
520}
521
522/* Initialization */
da91b269 523static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
456af201
DS
524{
525 int ret;
526 unsigned long iobase;
527 unsigned int irq;
34c43922 528 struct comedi_subdevice *s;
456af201
DS
529
530 /* claim our I/O space */
531
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;
537 }
538 dev->iobase = iobase;
539
540 /* there should be a sanity check here */
541
542 /* set up some name stuff */
543 dev->board_name = this_board->name;
544
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;
550 }
551 if (irq) {
5f74ea14 552 if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
456af201
DS
553 printk("unable to allocate irq %u\n", irq);
554 return -EINVAL;
555 } else {
556 printk("( irq = %u )\n", irq);
557 }
558 }
559 dev->irq = irq;
560
c3744138
BP
561 ret = alloc_subdevices(dev, 4);
562 if (ret < 0)
456af201 563 return ret;
c3744138
BP
564
565 ret = alloc_private(dev, sizeof(struct pcl711_private));
566 if (ret < 0)
456af201
DS
567 return ret;
568
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;
583 }
584
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;
595
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;
605
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;
616
617 /*
618 this is the "base value" for the mode register, which is
619 used for the irq on the PCL711
620 */
621 if (this_board->is_pcl711b) {
622 devpriv->mode = (dev->irq << 4);
623 }
624
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);
630
631 printk("\n");
632
633 return 0;
634}
This page took 0.223625 seconds and 5 git commands to generate.