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