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