Changed prototype of alloc_subdevices() so it doesn't require dev->n_subdevices
[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 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 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 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 typedef int bool;
126
127 /*
128  * flags
129  */
130
131 #define PCL711_TIMEOUT 100
132 #define PCL711_DRDY 0x10
133
134 static int i8253_osc_base = 500;        /* 2 Mhz */
135
136 typedef struct {
137         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;
145         comedi_lrange * ai_range_type;
146 } boardtype;
147
148 static boardtype boardtypes[] =
149 {
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 #define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype))
156 #define this_board ((boardtype *)dev->board_ptr)
157
158 static int pcl711_attach(comedi_device *dev,comedi_devconfig *it);
159 static int pcl711_detach(comedi_device *dev);
160 static comedi_driver driver_pcl711={
161         driver_name:    "pcl711",
162         module:         THIS_MODULE,
163         attach:         pcl711_attach,
164         detach:         pcl711_detach,
165         board_name:     boardtypes,
166         num_names:      n_boardtypes,
167         offset:         sizeof(boardtype),
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         lsampl_t 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 void pcl711_interrupt(int irq, void *d, struct pt_regs *regs)
185 {
186         int lo, hi;
187         int data;
188         comedi_device *dev = d;
189         comedi_subdevice *s = dev->subdevices + 0;
190
191         hi = inb(dev->iobase + PCL711_AD_HI);
192         lo = inb(dev->iobase + PCL711_AD_LO);
193         outb(0, dev->iobase + PCL711_CLRINTR);
194
195         data = (hi << 8) | lo;
196
197         if (!(--devpriv->ntrig)) {
198                 if (this_board->is_8112) {
199                         outb(1, dev->iobase + PCL711_MODE);
200                 } else {
201                         outb(0, dev->iobase + PCL711_MODE);
202                 }
203
204                 s->async->events |= COMEDI_CB_EOA;
205         }
206         comedi_event(dev, s, s->async->events);
207 }
208
209 static void pcl711_set_changain(comedi_device * dev, int chan)
210 {
211         int chan_register;
212
213         outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
214         
215         chan_register=CR_CHAN(chan);
216
217         if (this_board->is_8112) {
218
219                 /*
220                  *  Set the correct channel.  The two channel banks are switched
221                  *  using the mask value.
222                  *  NB: To use differential channels, you should use mask = 0x30,
223                  *  but I haven't written the support for this yet. /JJ
224                  */
225
226                 if (chan_register >= 8){
227                         chan_register = 0x20 | (chan_register & 0x7);
228                 }else{
229                         chan_register |= 0x10;
230                 }
231         } else {
232                 outb(chan_register, dev->iobase + PCL711_MUX);
233         }
234 }
235
236 static int pcl711_ai_insn(comedi_device *dev,comedi_subdevice *s,
237         comedi_insn *insn,lsampl_t *data)
238 {
239         int i,n;
240         int hi,lo;
241
242         pcl711_set_changain(dev,insn->chanspec);
243
244         for(n=0;n<insn->n;n++){
245                 /*
246                  *  Write the correct mode (software polling) and start polling by writing
247                  *  to the trigger register
248                  */
249                 outb(1, dev->iobase + PCL711_MODE);
250
251                 if (this_board->is_8112) {
252                 }else{
253                         outb(0, dev->iobase + PCL711_SOFTTRIG);
254                 }
255
256                 i=PCL711_TIMEOUT;
257                 while(--i){
258                         hi = inb(dev->iobase + PCL711_AD_HI);
259                         if (!(hi & PCL711_DRDY))
260                                 goto ok;
261                         udelay(1);
262                 }
263                 rt_printk("comedi%d: pcl711: A/D timeout\n", dev->minor);
264                 return -ETIME;
265         
266 ok:
267                 lo = inb(dev->iobase + PCL711_AD_LO);
268
269                 data[n] = ((hi & 0xf) << 8) | lo;
270         }
271
272         return n;
273 }
274
275 static int pcl711_ai_cmdtest(comedi_device *dev, comedi_subdevice *s,
276         comedi_cmd *cmd)
277 {
278         int tmp;
279         int err = 0;
280
281         /* step 1 */
282         tmp=cmd->start_src;
283         cmd->start_src &= TRIG_NOW;
284         if(!cmd->start_src || tmp!=cmd->start_src)err++;
285
286         tmp=cmd->scan_begin_src;
287         cmd->scan_begin_src &= TRIG_TIMER|TRIG_EXT;
288         if(!cmd->scan_begin_src || tmp!=cmd->scan_begin_src)err++;
289
290         tmp=cmd->convert_src;
291         cmd->convert_src &= TRIG_NOW;
292         if(!cmd->convert_src || tmp!=cmd->convert_src)err++;
293
294         tmp=cmd->scan_end_src;
295         cmd->scan_end_src &= TRIG_COUNT;
296         if(!cmd->scan_end_src || tmp!=cmd->scan_end_src)err++;
297
298         tmp=cmd->stop_src;
299         cmd->stop_src &= TRIG_COUNT|TRIG_NONE;
300         if(!cmd->stop_src || tmp!=cmd->stop_src)err++;
301
302         if(err)return 1;
303
304         /* step 2 */
305
306         if(cmd->scan_begin_src!=TRIG_TIMER &&
307            cmd->scan_begin_src!=TRIG_EXT)err++;
308         if(cmd->stop_src!=TRIG_COUNT &&
309            cmd->stop_src!=TRIG_NONE)err++;
310
311         if(err)return 2;
312
313         /* step 3 */
314
315         if(cmd->start_arg!=0){
316                 cmd->start_arg=0;
317                 err++;
318         }
319         if(cmd->scan_begin_src==TRIG_EXT){
320                 if(cmd->scan_begin_arg!=0){
321                         cmd->scan_begin_arg=0;
322                         err++;
323                 }
324         }else{
325 #define MAX_SPEED 1000
326 #define TIMER_BASE 100
327                 if(cmd->scan_begin_arg<MAX_SPEED){
328                         cmd->scan_begin_arg=MAX_SPEED;
329                         err++;
330                 }
331         }
332         if(cmd->convert_arg!=0){
333                 cmd->convert_arg=0;
334                 err++;
335         }
336         if(cmd->scan_end_arg!=cmd->chanlist_len){
337                 cmd->scan_end_arg=cmd->chanlist_len;
338                 err++;
339         }
340         if(cmd->stop_src==TRIG_NONE){
341                 if(cmd->stop_arg!=0){
342                         cmd->stop_arg=0;
343                         err++;
344                 }
345         }else{
346                 /* ignore */
347         }
348
349         if(err)return 3;
350
351         /* step 4 */
352
353         if(cmd->scan_begin_src==TRIG_TIMER){
354                 tmp = cmd->scan_begin_arg;
355                 i8253_cascade_ns_to_timer_2div(TIMER_BASE,
356                         &devpriv->divisor1,&devpriv->divisor2,
357                         &cmd->scan_begin_arg, cmd->flags&TRIG_ROUND_MASK);
358                 if(tmp!=cmd->scan_begin_arg)
359                         err++;
360         }
361
362         if(err)return 4;
363
364         return 0;
365 }
366
367 static int pcl711_ai_cmd(comedi_device *dev, comedi_subdevice *s)
368 {
369         int timer1,timer2;
370         comedi_cmd *cmd = &s->async->cmd;
371
372         pcl711_set_changain(dev,cmd->chanlist[0]);
373
374         if(cmd->scan_begin_src==TRIG_TIMER){
375                 /*
376                  *  Set timers
377                  *      timer chip is an 8253, with timers 1 and 2
378                  *      cascaded
379                  *  0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
380                  *        Mode 2 = Rate generator
381                  *
382                  *  0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
383                  */
384
385                 i8253_cascade_ns_to_timer(i8253_osc_base,&timer1,&timer2,
386                         &cmd->scan_begin_arg,TRIG_ROUND_NEAREST);
387
388                 outb(0x74, dev->iobase + PCL711_CTRCTL);
389                 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
390                 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
391                 outb(0xb4, dev->iobase + PCL711_CTRCTL);
392                 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
393                 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
394
395                 /* clear pending interrupts (just in case) */
396                 outb(0, dev->iobase + PCL711_CLRINTR);
397
398                 /*
399                  *  Set mode to IRQ transfer
400                  */
401                 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
402         }else{
403                 /* external trigger */
404                 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
405         }
406
407         return 0;
408 }
409
410 /*
411    analog output
412 */
413 static int pcl711_ao_insn(comedi_device *dev,comedi_subdevice *s,
414         comedi_insn *insn,lsampl_t *data)
415 {
416         int n;
417         int chan = CR_CHAN(insn->chanspec);
418
419         for(n=0;n<insn->n;n++){
420                 outb((data[n] & 0xff), dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
421                 outb((data[n] >> 8), dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
422
423                 devpriv->ao_readback[chan] = data[n];
424         }
425
426         return n;
427 }
428
429 static int pcl711_ao_insn_read(comedi_device *dev,comedi_subdevice *s,
430         comedi_insn *insn,lsampl_t *data)
431 {
432         int n;
433         int chan = CR_CHAN(insn->chanspec);
434
435         for(n=0;n<insn->n;n++){
436                 data[n] = devpriv->ao_readback[chan];
437         }
438
439         return n;
440
441 }
442
443 /* Digital port read - Untested on 8112 */
444 static int pcl711_di_insn_bits(comedi_device * dev, comedi_subdevice * s,
445         comedi_insn *insn,lsampl_t *data)
446 {
447         if(insn->n!=2)return -EINVAL;
448
449         data[1] = inb(dev->iobase + PCL711_DI_LO) |
450             (inb(dev->iobase + PCL711_DI_HI) << 8);
451
452         return 2;
453 }
454
455 /* Digital port write - Untested on 8112 */
456 static int pcl711_do_insn_bits(comedi_device * dev, comedi_subdevice * s,
457         comedi_insn *insn,lsampl_t *data)
458 {
459         if(insn->n!=2)return -EINVAL;
460
461         if(data[0]){
462                 s->state &= ~data[0];
463                 s->state |= data[0]&data[1];
464         }
465         if(data[0]&0x00ff)
466                 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
467         if(data[0]&0xff00)
468                 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
469
470         data[1]=s->state;
471
472         return 2;
473 }
474
475 /*  Free any resources that we have claimed  */
476 static int pcl711_detach(comedi_device * dev)
477 {
478         printk("comedi%d: pcl711: remove\n", dev->minor);
479
480         if (dev->irq)
481                 comedi_free_irq(dev->irq, dev);
482
483         if (dev->iobase)
484                 release_region(dev->iobase, PCL711_SIZE);
485
486         return 0;
487 }
488
489 /*  Initialization */
490 static int pcl711_attach(comedi_device * dev, comedi_devconfig * it)
491 {
492         int ret;
493         int iobase;
494         int irq;
495         comedi_subdevice *s;
496
497         /* claim our I/O space */
498
499         iobase = it->options[0];
500         printk("comedi%d: pcl711: 0x%04x ", dev->minor, iobase);
501         if (check_region(iobase, PCL711_SIZE) < 0) {
502                 printk("I/O port conflict\n");
503                 return -EIO;
504         }
505         request_region(iobase, PCL711_SIZE, "pcl711");
506         dev->iobase = iobase;
507
508         /* there should be a sanity check here */
509
510         /* set up some name stuff */
511         dev->board_name = this_board->name;
512
513         /* grab our IRQ */
514         irq = it->options[1];
515         if (irq < 0 || irq > this_board->maxirq) {
516                 printk("irq out of range\n");
517                 return -EINVAL;
518         }
519         if (irq) {
520                 if (comedi_request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
521                         printk("unable to allocate irq %d\n", irq);
522                         return -EINVAL;
523                 } else {
524                         printk("( irq = %d )\n", irq);
525                 }
526         }
527         dev->irq = irq;
528
529         if((ret=alloc_subdevices(dev, 4))<0)
530                 return ret;
531         if((ret=alloc_private(dev,sizeof(pcl711_private)))<0)
532                 return ret;
533
534         s = dev->subdevices + 0;
535         /* AI subdevice */
536         s->type = COMEDI_SUBD_AI;
537         s->subdev_flags = SDF_READABLE|SDF_GROUND;
538         s->n_chan = this_board->n_aichan;
539         s->maxdata = 0xfff;
540         s->len_chanlist = 1;
541         s->range_table = this_board->ai_range_type;
542         s->insn_read = pcl711_ai_insn;
543         if(irq){
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