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