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