Staging: comedi: remove comedi-specific wrappers
[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
61#include "../comedidev.h"
62
63#include <linux/ioport.h>
64#include <linux/delay.h>
65
66#include "8253.h"
67
68#define PCL711_SIZE 16
69
70#define PCL711_CTR0 0
71#define PCL711_CTR1 1
72#define PCL711_CTR2 2
73#define PCL711_CTRCTL 3
74#define PCL711_AD_LO 4
75#define PCL711_DA0_LO 4
76#define PCL711_AD_HI 5
77#define PCL711_DA0_HI 5
78#define PCL711_DI_LO 6
79#define PCL711_DA1_LO 6
80#define PCL711_DI_HI 7
81#define PCL711_DA1_HI 7
82#define PCL711_CLRINTR 8
83#define PCL711_GAIN 9
84#define PCL711_MUX 10
85#define PCL711_MODE 11
86#define PCL711_SOFTTRIG 12
87#define PCL711_DO_LO 13
88#define PCL711_DO_HI 14
89
9ced1de6 90static const struct comedi_lrange range_pcl711b_ai = { 5, {
456af201
DS
91 BIP_RANGE(5),
92 BIP_RANGE(2.5),
93 BIP_RANGE(1.25),
94 BIP_RANGE(0.625),
95 BIP_RANGE(0.3125)
96 }
97};
9ced1de6 98static const struct comedi_lrange range_acl8112hg_ai = { 12, {
456af201
DS
99 BIP_RANGE(5),
100 BIP_RANGE(0.5),
101 BIP_RANGE(0.05),
102 BIP_RANGE(0.005),
103 UNI_RANGE(10),
104 UNI_RANGE(1),
105 UNI_RANGE(0.1),
106 UNI_RANGE(0.01),
107 BIP_RANGE(10),
108 BIP_RANGE(1),
109 BIP_RANGE(0.1),
110 BIP_RANGE(0.01)
111 }
112};
9ced1de6 113static const struct comedi_lrange range_acl8112dg_ai = { 9, {
456af201
DS
114 BIP_RANGE(5),
115 BIP_RANGE(2.5),
116 BIP_RANGE(1.25),
117 BIP_RANGE(0.625),
118 UNI_RANGE(10),
119 UNI_RANGE(5),
120 UNI_RANGE(2.5),
121 UNI_RANGE(1.25),
122 BIP_RANGE(10)
123 }
124};
125
126/*
127 * flags
128 */
129
130#define PCL711_TIMEOUT 100
131#define PCL711_DRDY 0x10
132
133static const int i8253_osc_base = 500; /* 2 Mhz */
134
6445296e
BP
135struct pcl711_board {
136
456af201
DS
137 const char *name;
138 int is_pcl711b;
139 int is_8112;
140 int is_dg;
141 int n_ranges;
142 int n_aichan;
143 int n_aochan;
144 int maxirq;
9ced1de6 145 const struct comedi_lrange *ai_range_type;
6445296e
BP
146};
147
456af201 148
6445296e 149static const struct pcl711_board boardtypes[] = {
456af201
DS
150 {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5},
151 {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai},
152 {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai},
153 {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai},
154};
155
6445296e
BP
156#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl711_board))
157#define this_board ((const struct pcl711_board *)dev->board_ptr)
456af201 158
da91b269
BP
159static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it);
160static int pcl711_detach(struct comedi_device *dev);
139dfbdf 161static struct comedi_driver driver_pcl711 = {
68c3dbff
BP
162 .driver_name = "pcl711",
163 .module = THIS_MODULE,
164 .attach = pcl711_attach,
165 .detach = pcl711_detach,
166 .board_name = &boardtypes[0].name,
167 .num_names = n_boardtypes,
168 .offset = sizeof(struct pcl711_board),
456af201
DS
169};
170
171COMEDI_INITCLEANUP(driver_pcl711);
172
e1c8638f
BP
173struct pcl711_private {
174
456af201
DS
175 int board;
176 int adchan;
177 int ntrig;
178 int aip[8];
179 int mode;
790c5541 180 unsigned int ao_readback[2];
456af201
DS
181 unsigned int divisor1;
182 unsigned int divisor2;
e1c8638f
BP
183};
184
456af201 185
e1c8638f 186#define devpriv ((struct pcl711_private *)dev->private)
456af201 187
70265d24 188static irqreturn_t pcl711_interrupt(int irq, void *d)
456af201
DS
189{
190 int lo, hi;
191 int data;
71b5f4f1 192 struct comedi_device *dev = d;
34c43922 193 struct comedi_subdevice *s = dev->subdevices + 0;
456af201
DS
194
195 if (!dev->attached) {
196 comedi_error(dev, "spurious interrupt");
197 return IRQ_HANDLED;
198 }
199
200 hi = inb(dev->iobase + PCL711_AD_HI);
201 lo = inb(dev->iobase + PCL711_AD_LO);
202 outb(0, dev->iobase + PCL711_CLRINTR);
203
204 data = (hi << 8) | lo;
205
206 /* FIXME! Nothing else sets ntrig! */
207 if (!(--devpriv->ntrig)) {
208 if (this_board->is_8112) {
209 outb(1, dev->iobase + PCL711_MODE);
210 } else {
211 outb(0, dev->iobase + PCL711_MODE);
212 }
213
214 s->async->events |= COMEDI_CB_EOA;
215 }
216 comedi_event(dev, s);
217 return IRQ_HANDLED;
218}
219
da91b269 220static void pcl711_set_changain(struct comedi_device *dev, int chan)
456af201
DS
221{
222 int chan_register;
223
224 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
225
226 chan_register = CR_CHAN(chan);
227
228 if (this_board->is_8112) {
229
230 /*
231 * Set the correct channel. The two channel banks are switched
232 * using the mask value.
233 * NB: To use differential channels, you should use mask = 0x30,
234 * but I haven't written the support for this yet. /JJ
235 */
236
237 if (chan_register >= 8) {
238 chan_register = 0x20 | (chan_register & 0x7);
239 } else {
240 chan_register |= 0x10;
241 }
242 } else {
243 outb(chan_register, dev->iobase + PCL711_MUX);
244 }
245}
246
da91b269
BP
247static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
248 struct comedi_insn *insn, unsigned int *data)
456af201
DS
249{
250 int i, n;
251 int hi, lo;
252
253 pcl711_set_changain(dev, insn->chanspec);
254
255 for (n = 0; n < insn->n; n++) {
256 /*
257 * Write the correct mode (software polling) and start polling by writing
258 * to the trigger register
259 */
260 outb(1, dev->iobase + PCL711_MODE);
261
262 if (this_board->is_8112) {
263 } else {
264 outb(0, dev->iobase + PCL711_SOFTTRIG);
265 }
266
267 i = PCL711_TIMEOUT;
268 while (--i) {
269 hi = inb(dev->iobase + PCL711_AD_HI);
270 if (!(hi & PCL711_DRDY))
271 goto ok;
5f74ea14 272 udelay(1);
456af201 273 }
5f74ea14 274 printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
456af201
DS
275 return -ETIME;
276
277 ok:
278 lo = inb(dev->iobase + PCL711_AD_LO);
279
280 data[n] = ((hi & 0xf) << 8) | lo;
281 }
282
283 return n;
284}
285
da91b269
BP
286static int pcl711_ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
287 struct comedi_cmd *cmd)
456af201
DS
288{
289 int tmp;
290 int err = 0;
291
292 /* step 1 */
293 tmp = cmd->start_src;
294 cmd->start_src &= TRIG_NOW;
295 if (!cmd->start_src || tmp != cmd->start_src)
296 err++;
297
298 tmp = cmd->scan_begin_src;
299 cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT;
300 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
301 err++;
302
303 tmp = cmd->convert_src;
304 cmd->convert_src &= TRIG_NOW;
305 if (!cmd->convert_src || tmp != cmd->convert_src)
306 err++;
307
308 tmp = cmd->scan_end_src;
309 cmd->scan_end_src &= TRIG_COUNT;
310 if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
311 err++;
312
313 tmp = cmd->stop_src;
314 cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
315 if (!cmd->stop_src || tmp != cmd->stop_src)
316 err++;
317
318 if (err)
319 return 1;
320
321 /* step 2 */
322
323 if (cmd->scan_begin_src != TRIG_TIMER &&
324 cmd->scan_begin_src != TRIG_EXT)
325 err++;
326 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
327 err++;
328
329 if (err)
330 return 2;
331
332 /* step 3 */
333
334 if (cmd->start_arg != 0) {
335 cmd->start_arg = 0;
336 err++;
337 }
338 if (cmd->scan_begin_src == TRIG_EXT) {
339 if (cmd->scan_begin_arg != 0) {
340 cmd->scan_begin_arg = 0;
341 err++;
342 }
343 } else {
344#define MAX_SPEED 1000
345#define TIMER_BASE 100
346 if (cmd->scan_begin_arg < MAX_SPEED) {
347 cmd->scan_begin_arg = MAX_SPEED;
348 err++;
349 }
350 }
351 if (cmd->convert_arg != 0) {
352 cmd->convert_arg = 0;
353 err++;
354 }
355 if (cmd->scan_end_arg != cmd->chanlist_len) {
356 cmd->scan_end_arg = cmd->chanlist_len;
357 err++;
358 }
359 if (cmd->stop_src == TRIG_NONE) {
360 if (cmd->stop_arg != 0) {
361 cmd->stop_arg = 0;
362 err++;
363 }
364 } else {
365 /* ignore */
366 }
367
368 if (err)
369 return 3;
370
371 /* step 4 */
372
373 if (cmd->scan_begin_src == TRIG_TIMER) {
374 tmp = cmd->scan_begin_arg;
375 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
376 &devpriv->divisor1, &devpriv->divisor2,
377 &cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK);
378 if (tmp != cmd->scan_begin_arg)
379 err++;
380 }
381
382 if (err)
383 return 4;
384
385 return 0;
386}
387
da91b269 388static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
456af201
DS
389{
390 int timer1, timer2;
ea6d0d4c 391 struct comedi_cmd *cmd = &s->async->cmd;
456af201
DS
392
393 pcl711_set_changain(dev, cmd->chanlist[0]);
394
395 if (cmd->scan_begin_src == TRIG_TIMER) {
396 /*
397 * Set timers
398 * timer chip is an 8253, with timers 1 and 2
399 * cascaded
400 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
401 * Mode 2 = Rate generator
402 *
403 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
404 */
405
406 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
407 &cmd->scan_begin_arg, TRIG_ROUND_NEAREST);
408
409 outb(0x74, dev->iobase + PCL711_CTRCTL);
410 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
411 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
412 outb(0xb4, dev->iobase + PCL711_CTRCTL);
413 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
414 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
415
416 /* clear pending interrupts (just in case) */
417 outb(0, dev->iobase + PCL711_CLRINTR);
418
419 /*
420 * Set mode to IRQ transfer
421 */
422 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
423 } else {
424 /* external trigger */
425 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
426 }
427
428 return 0;
429}
430
431/*
432 analog output
433*/
da91b269
BP
434static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
435 struct comedi_insn *insn, unsigned int *data)
456af201
DS
436{
437 int n;
438 int chan = CR_CHAN(insn->chanspec);
439
440 for (n = 0; n < insn->n; n++) {
441 outb((data[n] & 0xff),
442 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
443 outb((data[n] >> 8),
444 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
445
446 devpriv->ao_readback[chan] = data[n];
447 }
448
449 return n;
450}
451
da91b269
BP
452static int pcl711_ao_insn_read(struct comedi_device *dev, struct comedi_subdevice *s,
453 struct comedi_insn *insn, unsigned int *data)
456af201
DS
454{
455 int n;
456 int chan = CR_CHAN(insn->chanspec);
457
458 for (n = 0; n < insn->n; n++) {
459 data[n] = devpriv->ao_readback[chan];
460 }
461
462 return n;
463
464}
465
466/* Digital port read - Untested on 8112 */
da91b269
BP
467static int pcl711_di_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s,
468 struct comedi_insn *insn, unsigned int *data)
456af201
DS
469{
470 if (insn->n != 2)
471 return -EINVAL;
472
473 data[1] = inb(dev->iobase + PCL711_DI_LO) |
474 (inb(dev->iobase + PCL711_DI_HI) << 8);
475
476 return 2;
477}
478
479/* Digital port write - Untested on 8112 */
da91b269
BP
480static int pcl711_do_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s,
481 struct comedi_insn *insn, unsigned int *data)
456af201
DS
482{
483 if (insn->n != 2)
484 return -EINVAL;
485
486 if (data[0]) {
487 s->state &= ~data[0];
488 s->state |= data[0] & data[1];
489 }
490 if (data[0] & 0x00ff)
491 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
492 if (data[0] & 0xff00)
493 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
494
495 data[1] = s->state;
496
497 return 2;
498}
499
500/* Free any resources that we have claimed */
da91b269 501static int pcl711_detach(struct comedi_device *dev)
456af201
DS
502{
503 printk("comedi%d: pcl711: remove\n", dev->minor);
504
505 if (dev->irq)
5f74ea14 506 free_irq(dev->irq, dev);
456af201
DS
507
508 if (dev->iobase)
509 release_region(dev->iobase, PCL711_SIZE);
510
511 return 0;
512}
513
514/* Initialization */
da91b269 515static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
456af201
DS
516{
517 int ret;
518 unsigned long iobase;
519 unsigned int irq;
34c43922 520 struct comedi_subdevice *s;
456af201
DS
521
522 /* claim our I/O space */
523
524 iobase = it->options[0];
525 printk("comedi%d: pcl711: 0x%04lx ", dev->minor, iobase);
526 if (!request_region(iobase, PCL711_SIZE, "pcl711")) {
527 printk("I/O port conflict\n");
528 return -EIO;
529 }
530 dev->iobase = iobase;
531
532 /* there should be a sanity check here */
533
534 /* set up some name stuff */
535 dev->board_name = this_board->name;
536
537 /* grab our IRQ */
538 irq = it->options[1];
539 if (irq > this_board->maxirq) {
540 printk("irq out of range\n");
541 return -EINVAL;
542 }
543 if (irq) {
5f74ea14 544 if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
456af201
DS
545 printk("unable to allocate irq %u\n", irq);
546 return -EINVAL;
547 } else {
548 printk("( irq = %u )\n", irq);
549 }
550 }
551 dev->irq = irq;
552
c3744138
BP
553 ret = alloc_subdevices(dev, 4);
554 if (ret < 0)
456af201 555 return ret;
c3744138
BP
556
557 ret = alloc_private(dev, sizeof(struct pcl711_private));
558 if (ret < 0)
456af201
DS
559 return ret;
560
561 s = dev->subdevices + 0;
562 /* AI subdevice */
563 s->type = COMEDI_SUBD_AI;
564 s->subdev_flags = SDF_READABLE | SDF_GROUND;
565 s->n_chan = this_board->n_aichan;
566 s->maxdata = 0xfff;
567 s->len_chanlist = 1;
568 s->range_table = this_board->ai_range_type;
569 s->insn_read = pcl711_ai_insn;
570 if (irq) {
571 dev->read_subdev = s;
572 s->subdev_flags |= SDF_CMD_READ;
573 s->do_cmdtest = pcl711_ai_cmdtest;
574 s->do_cmd = pcl711_ai_cmd;
575 }
576
577 s++;
578 /* AO subdevice */
579 s->type = COMEDI_SUBD_AO;
580 s->subdev_flags = SDF_WRITABLE;
581 s->n_chan = this_board->n_aochan;
582 s->maxdata = 0xfff;
583 s->len_chanlist = 1;
584 s->range_table = &range_bipolar5;
585 s->insn_write = pcl711_ao_insn;
586 s->insn_read = pcl711_ao_insn_read;
587
588 s++;
589 /* 16-bit digital input */
590 s->type = COMEDI_SUBD_DI;
591 s->subdev_flags = SDF_READABLE;
592 s->n_chan = 16;
593 s->maxdata = 1;
594 s->len_chanlist = 16;
595 s->range_table = &range_digital;
596 s->insn_bits = pcl711_di_insn_bits;
597
598 s++;
599 /* 16-bit digital out */
600 s->type = COMEDI_SUBD_DO;
601 s->subdev_flags = SDF_WRITABLE;
602 s->n_chan = 16;
603 s->maxdata = 1;
604 s->len_chanlist = 16;
605 s->range_table = &range_digital;
606 s->state = 0;
607 s->insn_bits = pcl711_do_insn_bits;
608
609 /*
610 this is the "base value" for the mode register, which is
611 used for the irq on the PCL711
612 */
613 if (this_board->is_pcl711b) {
614 devpriv->mode = (dev->irq << 4);
615 }
616
617 /* clear DAC */
618 outb(0, dev->iobase + PCL711_DA0_LO);
619 outb(0, dev->iobase + PCL711_DA0_HI);
620 outb(0, dev->iobase + PCL711_DA1_LO);
621 outb(0, dev->iobase + PCL711_DA1_HI);
622
623 printk("\n");
624
625 return 0;
626}
This page took 0.082358 seconds and 5 git commands to generate.