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