Migrating drivers from trig to insn
[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@stm.lbl.gov>
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 /*
28    Dave Andruczyk <dave@tech.buffalostate.edu> also wrote a
29    driver for the PCL-711.  I used a few ideas from his driver
30    here.  His driver also has more comments, if you are
31    interested in understanding how this driver works.
32    http://tech.buffalostate.edu/~dave/driver/
33
34    The ACL-8112 driver was hacked from the sources of the PCL-711
35    driver (the 744 chip used on the 8112 is almost the same as
36    the 711b chip, but it has more I/O channels) by
37    Janne Jalkanen (jalkanen@cs.hut.fi) and
38    Erik Bunn (ebu@cs.hut.fi).  Remerged with the PCL-711 driver
39    by ds.
40
41    [acl-8112]
42    This driver supports both TRIGNOW and TRIGCLK,
43    but does not yet support DMA transfers.  It also supports
44    both high (HG) and low (DG) versions of the card, though
45    the HG version has been untested.
46
47  */
48
49 #include <linux/kernel.h>
50 #include <linux/module.h>
51 #include <linux/sched.h>
52 #include <linux/mm.h>
53 #include <linux/malloc.h>
54 #include <linux/errno.h>
55 #include <linux/ioport.h>
56 #include <linux/delay.h>
57 #include <linux/interrupt.h>
58 #include <linux/timex.h>
59 #include <linux/timer.h>
60 #include <asm/io.h>
61 #include <linux/comedidev.h>
62 #include <8253.h>
63
64
65
66 #define PCL711_SIZE 16
67
68 #define PCL711_CTR0 0
69 #define PCL711_CTR1 1
70 #define PCL711_CTR2 2
71 #define PCL711_CTRCTL 3
72 #define PCL711_AD_LO 4
73 #define PCL711_DA0_LO 4
74 #define PCL711_AD_HI 5
75 #define PCL711_DA0_HI 5
76 #define PCL711_DI_LO 6
77 #define PCL711_DA1_LO 6
78 #define PCL711_DI_HI 7
79 #define PCL711_DA1_HI 7
80 #define PCL711_CLRINTR 8
81 #define PCL711_GAIN 9
82 #define PCL711_MUX 10
83 #define PCL711_MODE 11
84 #define PCL711_SOFTTRIG 12
85 #define PCL711_DO_LO 13
86 #define PCL711_DO_HI 14
87
88 static comedi_lrange range_pcl711b_ai = { 5, {
89         BIP_RANGE( 5 ),
90         BIP_RANGE( 2.5 ),
91         BIP_RANGE( 1.25 ),
92         BIP_RANGE( 0.625 ),
93         BIP_RANGE( 0.3125 )
94 }};
95 static comedi_lrange range_acl8112hg_ai = { 12, {
96         BIP_RANGE( 5 ),
97         BIP_RANGE( 0.5 ),
98         BIP_RANGE( 0.05 ),
99         BIP_RANGE( 0.005 ),
100         UNI_RANGE( 10 ),
101         UNI_RANGE( 1 ),
102         UNI_RANGE( 0.1 ),
103         UNI_RANGE( 0.01 ),
104         BIP_RANGE( 10 ),
105         BIP_RANGE( 1 ),
106         BIP_RANGE( 0.1 ),
107         BIP_RANGE( 0.01 )
108 }};
109 static comedi_lrange range_acl8112dg_ai = { 9, {
110         BIP_RANGE( 5 ),
111         BIP_RANGE( 2.5 ),
112         BIP_RANGE( 1.25 ),
113         BIP_RANGE( 0.625 ),
114         UNI_RANGE( 10 ),
115         UNI_RANGE( 5 ),
116         UNI_RANGE( 2.5 ),
117         UNI_RANGE( 1.25 ),
118         BIP_RANGE( 10 )
119 }};
120
121 typedef int bool;
122
123 /*
124  * flags
125  */
126
127 #define PCL711_TIMEOUT 100
128 #define PCL711_DRDY 0x10
129
130 int i8253_osc_base = 500;       /* 2 Mhz */
131
132 typedef struct {
133         char *name;
134         int is_pcl711b;
135         int is_8112;
136         int is_dg;
137         int n_ranges;
138         int n_aichan;
139         int n_aochan;
140         int maxirq;
141         comedi_lrange * ai_range_type;
142 } boardtype;
143
144 static boardtype boardtypes[] =
145 {
146         {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5},
147         {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai},
148         {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai},
149         {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai},
150 };
151 #define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype))
152 #define this_board ((boardtype *)dev->board_ptr)
153
154 static int pcl711_attach(comedi_device *dev,comedi_devconfig *it);
155 static int pcl711_detach(comedi_device *dev);
156 comedi_driver driver_pcl711={
157         driver_name:    "pcl711",
158         module:         THIS_MODULE,
159         attach:         pcl711_attach,
160         detach:         pcl711_detach,
161         board_name:     boardtypes,
162         num_names:      n_boardtypes,
163         offset:         sizeof(boardtype),
164 };
165 COMEDI_INITCLEANUP(driver_pcl711);
166
167 typedef struct {
168         int board;
169         int adchan;
170         int ntrig;
171         int aip[8];
172         int mode;
173         lsampl_t ao_readback[2];
174 } pcl711_private;
175
176 #define devpriv ((pcl711_private *)dev->private)
177
178 static void pcl711_interrupt(int irq, void *d, struct pt_regs *regs)
179 {
180         int lo, hi;
181         int data;
182         comedi_device *dev = d;
183         comedi_subdevice *s = dev->subdevices + 0;
184
185         hi = inb(dev->iobase + PCL711_AD_HI);
186         lo = inb(dev->iobase + PCL711_AD_LO);
187         outb(0, dev->iobase + PCL711_CLRINTR);
188
189         data = (hi << 8) | lo;
190
191         if (!(--devpriv->ntrig)) {
192                 if (this_board->is_8112) {
193                         outb(1, dev->iobase + PCL711_MODE);
194                 } else {
195                         outb(0, dev->iobase + PCL711_MODE);
196                 }
197                 s->busy = 0;
198
199                 comedi_done(dev,s);
200         }
201 }
202
203 static void pcl711_set_changain(comedi_device * dev, int chan)
204 {
205         int chan_register;
206
207         outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
208         
209         chan_register=CR_CHAN(chan);
210
211         if (this_board->is_8112) {
212
213                 /*
214                  *  Set the correct channel.  The two channel banks are switched
215                  *  using the mask value.
216                  *  NB: To use differential channels, you should use mask = 0x30,
217                  *  but I haven't written the support for this yet. /JJ
218                  */
219
220                 if (chan_register >= 8){
221                         chan_register = 0x20 | (chan_register & 0x7);
222                 }else{
223                         chan_register |= 0x10;
224                 }
225         } else {
226                 outb(chan_register, dev->iobase + PCL711_MUX);
227         }
228 }
229
230 static int pcl711_ai_insn(comedi_device *dev,comedi_subdevice *s,
231         comedi_insn *insn,lsampl_t *data)
232 {
233         int i,n;
234         int hi,lo;
235
236         pcl711_set_changain(dev,insn->chanspec);
237
238         /*
239            a sensible precaution to wait for the mux to
240            settle here.  is 10us enough?
241         */
242         udelay(10);
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                 }
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_mode4(comedi_device * dev, comedi_subdevice * s, comedi_trig * it)
275 {
276         if (!this_board->is_pcl711b || dev->irq == 0)
277                 return -EINVAL;
278
279         pcl711_set_changain(dev,it->chanlist[0]);
280
281         /*
282          *  Set mode to "no internal trigger"
283          */
284         outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
285
286         return 0;
287 }
288
289
290 static int pcl711_ai_mode1(comedi_device * dev, comedi_subdevice * s, comedi_trig * it)
291 {
292         int timer1,timer2;
293
294         if (!dev->irq)
295                 return -EINVAL;
296
297         pcl711_set_changain(dev,it->chanlist[0]);
298
299         /*
300          *  Set timers
301          *      timer chip is an 8253, with timers 1 and 2
302          *      cascaded
303          *  0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
304          *        Mode 2 = Rate generator
305          *
306          *  0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
307          */
308
309         i8253_cascade_ns_to_timer(i8253_osc_base,&timer1,&timer2,&it->trigvar,TRIG_ROUND_NEAREST);
310
311         outb(0x74, dev->iobase + PCL711_CTRCTL);
312         outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
313         outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
314         outb(0xb4, dev->iobase + PCL711_CTRCTL);
315         outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
316         outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
317
318         /* clear pending interrupts (just in case) */
319         outb(0, dev->iobase + PCL711_CLRINTR);
320
321         /*
322          *  Set mode to IRQ transfer
323          */
324         outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
325
326         return 0;
327 }
328
329 /*
330    analog output
331 */
332 static int pcl711_ao_insn(comedi_device *dev,comedi_subdevice *s,
333         comedi_insn *insn,lsampl_t *data)
334 {
335         int n;
336         int chan = CR_CHAN(insn->chanspec);
337
338         for(n=0;n<insn->n;n++){
339                 outb((data[n] & 0xff), dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
340                 outb((data[n] >> 8), dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
341
342                 devpriv->ao_readback[chan] = data[n];
343         }
344
345         return n;
346 }
347
348 static int pcl711_ao_insn_read(comedi_device *dev,comedi_subdevice *s,
349         comedi_insn *insn,lsampl_t *data)
350 {
351         int n;
352         int chan = CR_CHAN(insn->chanspec);
353
354         for(n=0;n<insn->n;n++){
355                 data[n] = devpriv->ao_readback[chan];
356         }
357
358         return n;
359
360 }
361
362 /* Digital port read - Untested on 8112 */
363 static int pcl711_di_insn_bits(comedi_device * dev, comedi_subdevice * s,
364         comedi_insn *insn,lsampl_t *data)
365 {
366         if(insn->n!=2)return -EINVAL;
367
368         data[1] = inb(dev->iobase + PCL711_DI_LO) |
369             (inb(dev->iobase + PCL711_DI_HI) << 8);
370
371         return 2;
372 }
373
374 /* Digital port write - Untested on 8112 */
375 static int pcl711_do_insn_bits(comedi_device * dev, comedi_subdevice * s,
376         comedi_insn *insn,lsampl_t *data)
377 {
378         if(insn->n!=2)return -EINVAL;
379
380         if(data[0]){
381                 s->state &= ~data[0];
382                 s->state |= data[0]&data[1];
383         }
384         if(data[0]&0x00ff)
385                 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
386         if(data[0]&0xff00)
387                 outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
388
389         data[1]=s->state;
390
391         return 2;
392 }
393
394 /*  Free any resources that we have claimed  */
395 static int pcl711_detach(comedi_device * dev)
396 {
397         printk("comedi%d: pcl711: remove\n", dev->minor);
398
399         if (dev->irq)
400                 free_irq(dev->irq, dev);
401
402         if (dev->iobase)
403                 release_region(dev->iobase, PCL711_SIZE);
404 }
405
406 /*  Initialization */
407 static int pcl711_attach(comedi_device * dev, comedi_devconfig * it)
408 {
409         int ret;
410         int iobase;
411         int irq;
412         comedi_subdevice *s;
413
414         /* claim our I/O space */
415
416         iobase = it->options[0];
417         printk("comedi%d: pcl711: 0x%04x ", dev->minor, iobase);
418         if (check_region(iobase, PCL711_SIZE) < 0) {
419                 printk("I/O port conflict\n");
420                 return -EIO;
421         }
422         request_region(dev->iobase, PCL711_SIZE, "pcl711");
423         dev->iobase = iobase;
424
425         /* there should be a sanity check here */
426
427         /* set up some name stuff */
428         dev->board_name = this_board->name;
429
430         /* grab our IRQ */
431         irq = it->options[1];
432         if (irq < 0 || irq > this_board->maxirq) {
433                 printk("irq out of range\n");
434                 free_resources(dev);
435                 return -EINVAL;
436         }
437         if (irq) {
438                 if (comedi_request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) {
439                         printk("unable to allocate irq %d\n", irq);
440                         free_resources(dev);
441                         return -EINVAL;
442                 } else {
443                         printk("( irq = %d )\n", irq);
444                 }
445         }
446         dev->irq = irq;
447
448         dev->n_subdevices = 4;
449         if((ret=alloc_subdevices(dev))<0)
450                 return ret;
451         if((ret=alloc_private(dev,sizeof(pcl711_private)))<0)
452                 return ret;
453
454         s = dev->subdevices + 0;
455         /* AI subdevice */
456         s->type = COMEDI_SUBD_AI;
457         s->subdev_flags = SDF_READABLE;
458         s->n_chan = this_board->n_aichan;
459         s->maxdata = 0xfff;
460         s->len_chanlist = 1;
461         s->range_table = this_board->ai_range_type;
462         s->insn_read = pcl711_ai_insn;
463         s->trig[1] = pcl711_ai_mode1;
464         s->trig[4] = pcl711_ai_mode4;
465
466         s++;
467         /* AO subdevice */
468         s->type = COMEDI_SUBD_AO;
469         s->subdev_flags = SDF_WRITEABLE;
470         s->n_chan = this_board->n_aochan;
471         s->maxdata = 0xfff;
472         s->len_chanlist = 1;
473         s->range_table = &range_bipolar5;
474         s->insn_write = pcl711_ao_insn;
475         s->insn_read = pcl711_ao_insn_read;
476
477         s++;
478         /* 16-bit digital input */
479         s->type = COMEDI_SUBD_DI;
480         s->subdev_flags = SDF_READABLE;
481         s->n_chan = 16;
482         s->maxdata = 1;
483         s->len_chanlist = 16;
484         s->range_table = &range_digital;
485         s->insn_bits = pcl711_di_insn_bits;
486
487         s++;
488         /* 16-bit digital out */
489         s->type = COMEDI_SUBD_DO;
490         s->subdev_flags = SDF_WRITEABLE;
491         s->n_chan = 16;
492         s->maxdata = 1;
493         s->len_chanlist = 16;
494         s->range_table = &range_digital;
495         s->state=0;
496         s->insn_bits = pcl711_do_insn_bits;
497
498         /*
499            this is the "base value" for the mode register, which is
500            used for the irq on the PCL711
501          */
502         if(this_board->is_pcl711b){
503                 devpriv->mode=(dev->irq<<4);
504         }
505
506         /* clear DAC */
507         outb(0, dev->iobase + PCL711_DA0_LO);
508         outb(0, dev->iobase + PCL711_DA0_HI);
509         outb(0, dev->iobase + PCL711_DA1_LO);
510         outb(0, dev->iobase + PCL711_DA1_HI);
511
512         printk("\n");
513
514         return 0;
515 }
516