Linux-libre 3.16.85-gnu
[librecmc/linux-libre.git] / drivers / staging / comedi / drivers / das08.c
1 /*
2  *  comedi/drivers/das08.c
3  *  comedi driver for common DAS08 support (used by ISA/PCI/PCMCIA drivers)
4  *
5  *  COMEDI - Linux Control and Measurement Device Interface
6  *  Copyright (C) 2000 David A. Schleef <ds@schleef.org>
7  *  Copyright (C) 2001,2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net>
8  *  Copyright (C) 2004 Salvador E. Tropea <set@users.sf.net> <set@ieee.org>
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  */
20
21 /*
22  * Driver: das08
23  * Description: DAS-08 compatible boards
24  * Devices: various, see das08_isa, das08_cs, and das08_pci drivers
25  * Author: Warren Jasper, ds, Frank Hess
26  * Updated: Fri, 31 Aug 2012 19:19:06 +0100
27  * Status: works
28  *
29  * This driver is used by the das08_isa, das08_cs, and das08_pci
30  * drivers to provide the common support for the DAS-08 hardware.
31  *
32  * The driver doesn't support asynchronous commands, since the
33  * cheap das08 hardware doesn't really support them.
34  */
35
36 #include <linux/module.h>
37
38 #include "../comedidev.h"
39
40 #include "8255.h"
41 #include "8253.h"
42 #include "das08.h"
43
44 /*
45     cio-das08.pdf
46
47   "isa-das08"
48
49   0     a/d bits 0-3            start 8 bit
50   1     a/d bits 4-11           start 12 bit
51   2     eoc, ip1-3, irq, mux    op1-4, inte, mux
52   3     unused                  unused
53   4567  8254
54   89ab  8255
55
56   requires hard-wiring for async ai
57
58 */
59
60 #define DAS08_LSB               0
61 #define DAS08_MSB               1
62 #define DAS08_TRIG_12BIT        1
63 #define DAS08_STATUS            2
64 #define   DAS08_EOC                     (1<<7)
65 #define   DAS08_IRQ                     (1<<3)
66 #define   DAS08_IP(x)                   (((x)>>4)&0x7)
67 #define DAS08_CONTROL           2
68 #define   DAS08_MUX_MASK        0x7
69 #define   DAS08_MUX(x)          ((x) & DAS08_MUX_MASK)
70 #define   DAS08_INTE                    (1<<3)
71 #define   DAS08_DO_MASK         0xf0
72 #define   DAS08_OP(x)           (((x) << 4) & DAS08_DO_MASK)
73
74 /*
75     cio-das08jr.pdf
76
77   "das08/jr-ao"
78
79   0     a/d bits 0-3            unused
80   1     a/d bits 4-11           start 12 bit
81   2     eoc, mux                mux
82   3     di                      do
83   4     unused                  ao0_lsb
84   5     unused                  ao0_msb
85   6     unused                  ao1_lsb
86   7     unused                  ao1_msb
87
88 */
89
90 #define DAS08JR_DIO             3
91 #define DAS08JR_AO_LSB(x)       ((x) ? 6 : 4)
92 #define DAS08JR_AO_MSB(x)       ((x) ? 7 : 5)
93
94 /*
95     cio-das08_aox.pdf
96
97   "das08-aoh"
98   "das08-aol"
99   "das08-aom"
100
101   0     a/d bits 0-3            start 8 bit
102   1     a/d bits 4-11           start 12 bit
103   2     eoc, ip1-3, irq, mux    op1-4, inte, mux
104   3     mux, gain status        gain control
105   4567  8254
106   8     unused                  ao0_lsb
107   9     unused                  ao0_msb
108   a     unused                  ao1_lsb
109   b     unused                  ao1_msb
110   89ab
111   cdef  8255
112 */
113
114 #define DAS08AO_GAIN_CONTROL    3
115 #define DAS08AO_GAIN_STATUS     3
116
117 #define DAS08AO_AO_LSB(x)       ((x) ? 0xa : 8)
118 #define DAS08AO_AO_MSB(x)       ((x) ? 0xb : 9)
119 #define DAS08AO_AO_UPDATE       8
120
121 /* gainlist same as _pgx_ below */
122
123 static const struct comedi_lrange range_das08_pgl = {
124         9, {
125                 BIP_RANGE(10),
126                 BIP_RANGE(5),
127                 BIP_RANGE(2.5),
128                 BIP_RANGE(1.25),
129                 BIP_RANGE(0.625),
130                 UNI_RANGE(10),
131                 UNI_RANGE(5),
132                 UNI_RANGE(2.5),
133                 UNI_RANGE(1.25)
134         }
135 };
136
137 static const struct comedi_lrange range_das08_pgh = {
138         12, {
139                 BIP_RANGE(10),
140                 BIP_RANGE(5),
141                 BIP_RANGE(1),
142                 BIP_RANGE(0.5),
143                 BIP_RANGE(0.1),
144                 BIP_RANGE(0.05),
145                 BIP_RANGE(0.01),
146                 BIP_RANGE(0.005),
147                 UNI_RANGE(10),
148                 UNI_RANGE(1),
149                 UNI_RANGE(0.1),
150                 UNI_RANGE(0.01)
151         }
152 };
153
154 static const struct comedi_lrange range_das08_pgm = {
155         9, {
156                 BIP_RANGE(10),
157                 BIP_RANGE(5),
158                 BIP_RANGE(0.5),
159                 BIP_RANGE(0.05),
160                 BIP_RANGE(0.01),
161                 UNI_RANGE(10),
162                 UNI_RANGE(1),
163                 UNI_RANGE(0.1),
164                 UNI_RANGE(0.01)
165         }
166 };                              /*
167                                    cio-das08jr.pdf
168
169                                    "das08/jr-ao"
170
171                                    0 a/d bits 0-3            unused
172                                    1 a/d bits 4-11           start 12 bit
173                                    2 eoc, mux                mux
174                                    3 di                      do
175                                    4 unused                  ao0_lsb
176                                    5 unused                  ao0_msb
177                                    6 unused                  ao1_lsb
178                                    7 unused                  ao1_msb
179
180                                  */
181
182 static const struct comedi_lrange *const das08_ai_lranges[] = {
183         &range_unknown,
184         &range_bipolar5,
185         &range_das08_pgh,
186         &range_das08_pgl,
187         &range_das08_pgm,
188 };
189
190 static const int das08_pgh_gainlist[] = {
191         8, 0, 10, 2, 12, 4, 14, 6, 1, 3, 5, 7
192 };
193 static const int das08_pgl_gainlist[] = { 8, 0, 2, 4, 6, 1, 3, 5, 7 };
194 static const int das08_pgm_gainlist[] = { 8, 0, 10, 12, 14, 9, 11, 13, 15 };
195
196 static const int *const das08_gainlists[] = {
197         NULL,
198         NULL,
199         das08_pgh_gainlist,
200         das08_pgl_gainlist,
201         das08_pgm_gainlist,
202 };
203
204 static int das08_ai_eoc(struct comedi_device *dev,
205                         struct comedi_subdevice *s,
206                         struct comedi_insn *insn,
207                         unsigned long context)
208 {
209         unsigned int status;
210
211         status = inb(dev->iobase + DAS08_STATUS);
212         if ((status & DAS08_EOC) == 0)
213                 return 0;
214         return -EBUSY;
215 }
216
217 static int das08_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
218                           struct comedi_insn *insn, unsigned int *data)
219 {
220         const struct das08_board_struct *thisboard = comedi_board(dev);
221         struct das08_private_struct *devpriv = dev->private;
222         int n;
223         int chan;
224         int range;
225         int lsb, msb;
226         int ret;
227
228         chan = CR_CHAN(insn->chanspec);
229         range = CR_RANGE(insn->chanspec);
230
231         /* clear crap */
232         inb(dev->iobase + DAS08_LSB);
233         inb(dev->iobase + DAS08_MSB);
234
235         /* set multiplexer */
236         /*  lock to prevent race with digital output */
237         spin_lock(&dev->spinlock);
238         devpriv->do_mux_bits &= ~DAS08_MUX_MASK;
239         devpriv->do_mux_bits |= DAS08_MUX(chan);
240         outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL);
241         spin_unlock(&dev->spinlock);
242
243         if (s->range_table->length > 1) {
244                 /* set gain/range */
245                 range = CR_RANGE(insn->chanspec);
246                 outb(devpriv->pg_gainlist[range],
247                      dev->iobase + DAS08AO_GAIN_CONTROL);
248         }
249
250         for (n = 0; n < insn->n; n++) {
251                 /* clear over-range bits for 16-bit boards */
252                 if (thisboard->ai_nbits == 16)
253                         if (inb(dev->iobase + DAS08_MSB) & 0x80)
254                                 dev_info(dev->class_dev, "over-range\n");
255
256                 /* trigger conversion */
257                 outb_p(0, dev->iobase + DAS08_TRIG_12BIT);
258
259                 ret = comedi_timeout(dev, s, insn, das08_ai_eoc, 0);
260                 if (ret)
261                         return ret;
262
263                 msb = inb(dev->iobase + DAS08_MSB);
264                 lsb = inb(dev->iobase + DAS08_LSB);
265                 if (thisboard->ai_encoding == das08_encode12) {
266                         data[n] = (lsb >> 4) | (msb << 4);
267                 } else if (thisboard->ai_encoding == das08_pcm_encode12) {
268                         data[n] = (msb << 8) + lsb;
269                 } else if (thisboard->ai_encoding == das08_encode16) {
270                         /* FPOS 16-bit boards are sign-magnitude */
271                         if (msb & 0x80)
272                                 data[n] = (1 << 15) | lsb | ((msb & 0x7f) << 8);
273                         else
274                                 data[n] = (1 << 15) - (lsb | (msb & 0x7f) << 8);
275                 } else {
276                         comedi_error(dev, "bug! unknown ai encoding");
277                         return -1;
278                 }
279         }
280
281         return n;
282 }
283
284 static int das08_di_rbits(struct comedi_device *dev, struct comedi_subdevice *s,
285                           struct comedi_insn *insn, unsigned int *data)
286 {
287         data[0] = 0;
288         data[1] = DAS08_IP(inb(dev->iobase + DAS08_STATUS));
289
290         return insn->n;
291 }
292
293 static int das08_do_wbits(struct comedi_device *dev,
294                           struct comedi_subdevice *s,
295                           struct comedi_insn *insn,
296                           unsigned int *data)
297 {
298         struct das08_private_struct *devpriv = dev->private;
299
300         if (comedi_dio_update_state(s, data)) {
301                 /* prevent race with setting of analog input mux */
302                 spin_lock(&dev->spinlock);
303                 devpriv->do_mux_bits &= ~DAS08_DO_MASK;
304                 devpriv->do_mux_bits |= DAS08_OP(s->state);
305                 outb(devpriv->do_mux_bits, dev->iobase + DAS08_CONTROL);
306                 spin_unlock(&dev->spinlock);
307         }
308
309         data[1] = s->state;
310
311         return insn->n;
312 }
313
314 static int das08jr_di_rbits(struct comedi_device *dev,
315                             struct comedi_subdevice *s,
316                             struct comedi_insn *insn, unsigned int *data)
317 {
318         data[0] = 0;
319         data[1] = inb(dev->iobase + DAS08JR_DIO);
320
321         return insn->n;
322 }
323
324 static int das08jr_do_wbits(struct comedi_device *dev,
325                             struct comedi_subdevice *s,
326                             struct comedi_insn *insn,
327                             unsigned int *data)
328 {
329         if (comedi_dio_update_state(s, data))
330                 outb(s->state, dev->iobase + DAS08JR_DIO);
331
332         data[1] = s->state;
333
334         return insn->n;
335 }
336
337 static void das08_ao_set_data(struct comedi_device *dev,
338                               unsigned int chan, unsigned int data)
339 {
340         const struct das08_board_struct *thisboard = comedi_board(dev);
341         struct das08_private_struct *devpriv = dev->private;
342         unsigned char lsb;
343         unsigned char msb;
344
345         lsb = data & 0xff;
346         msb = (data >> 8) & 0xff;
347         if (thisboard->is_jr) {
348                 outb(lsb, dev->iobase + DAS08JR_AO_LSB(chan));
349                 outb(msb, dev->iobase + DAS08JR_AO_MSB(chan));
350                 /* load DACs */
351                 inb(dev->iobase + DAS08JR_DIO);
352         } else {
353                 outb(lsb, dev->iobase + DAS08AO_AO_LSB(chan));
354                 outb(msb, dev->iobase + DAS08AO_AO_MSB(chan));
355                 /* load DACs */
356                 inb(dev->iobase + DAS08AO_AO_UPDATE);
357         }
358         devpriv->ao_readback[chan] = data;
359 }
360
361 static void das08_ao_initialize(struct comedi_device *dev,
362                                 struct comedi_subdevice *s)
363 {
364         int n;
365         unsigned int data;
366
367         data = s->maxdata / 2;  /* should be about 0 volts */
368         for (n = 0; n < s->n_chan; n++)
369                 das08_ao_set_data(dev, n, data);
370 }
371
372 static int das08_ao_winsn(struct comedi_device *dev,
373                           struct comedi_subdevice *s,
374                           struct comedi_insn *insn, unsigned int *data)
375 {
376         unsigned int n;
377         unsigned int chan;
378
379         chan = CR_CHAN(insn->chanspec);
380
381         for (n = 0; n < insn->n; n++)
382                 das08_ao_set_data(dev, chan, *data);
383
384         return n;
385 }
386
387 static int das08_ao_rinsn(struct comedi_device *dev,
388                           struct comedi_subdevice *s,
389                           struct comedi_insn *insn, unsigned int *data)
390 {
391         struct das08_private_struct *devpriv = dev->private;
392         unsigned int n;
393         unsigned int chan;
394
395         chan = CR_CHAN(insn->chanspec);
396
397         for (n = 0; n < insn->n; n++)
398                 data[n] = devpriv->ao_readback[chan];
399
400         return n;
401 }
402
403 static void i8254_initialize(struct comedi_device *dev)
404 {
405         const struct das08_board_struct *thisboard = comedi_board(dev);
406         unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset;
407         unsigned int mode = I8254_MODE0 | I8254_BINARY;
408         int i;
409
410         for (i = 0; i < 3; ++i)
411                 i8254_set_mode(i8254_iobase, 0, i, mode);
412 }
413
414 static int das08_counter_read(struct comedi_device *dev,
415                               struct comedi_subdevice *s,
416                               struct comedi_insn *insn, unsigned int *data)
417 {
418         const struct das08_board_struct *thisboard = comedi_board(dev);
419         unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset;
420         int chan = insn->chanspec;
421
422         data[0] = i8254_read(i8254_iobase, 0, chan);
423         return 1;
424 }
425
426 static int das08_counter_write(struct comedi_device *dev,
427                                struct comedi_subdevice *s,
428                                struct comedi_insn *insn, unsigned int *data)
429 {
430         const struct das08_board_struct *thisboard = comedi_board(dev);
431         unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset;
432         int chan = insn->chanspec;
433
434         i8254_write(i8254_iobase, 0, chan, data[0]);
435         return 1;
436 }
437
438 static int das08_counter_config(struct comedi_device *dev,
439                                 struct comedi_subdevice *s,
440                                 struct comedi_insn *insn, unsigned int *data)
441 {
442         const struct das08_board_struct *thisboard = comedi_board(dev);
443         unsigned long i8254_iobase = dev->iobase + thisboard->i8254_offset;
444         int chan = insn->chanspec;
445
446         switch (data[0]) {
447         case INSN_CONFIG_SET_COUNTER_MODE:
448                 i8254_set_mode(i8254_iobase, 0, chan, data[1]);
449                 break;
450         case INSN_CONFIG_8254_READ_STATUS:
451                 data[1] = i8254_status(i8254_iobase, 0, chan);
452                 break;
453         default:
454                 return -EINVAL;
455                 break;
456         }
457         return 2;
458 }
459
460 int das08_common_attach(struct comedi_device *dev, unsigned long iobase)
461 {
462         const struct das08_board_struct *thisboard = comedi_board(dev);
463         struct das08_private_struct *devpriv = dev->private;
464         struct comedi_subdevice *s;
465         int ret;
466
467         dev->iobase = iobase;
468
469         dev->board_name = thisboard->name;
470
471         ret = comedi_alloc_subdevices(dev, 6);
472         if (ret)
473                 return ret;
474
475         s = &dev->subdevices[0];
476         /* ai */
477         if (thisboard->ai_nbits) {
478                 s->type = COMEDI_SUBD_AI;
479                 /* XXX some boards actually have differential
480                  * inputs instead of single ended.
481                  * The driver does nothing with arefs though,
482                  * so it's no big deal.
483                  */
484                 s->subdev_flags = SDF_READABLE | SDF_GROUND;
485                 s->n_chan = 8;
486                 s->maxdata = (1 << thisboard->ai_nbits) - 1;
487                 s->range_table = das08_ai_lranges[thisboard->ai_pg];
488                 s->insn_read = das08_ai_rinsn;
489                 devpriv->pg_gainlist = das08_gainlists[thisboard->ai_pg];
490         } else {
491                 s->type = COMEDI_SUBD_UNUSED;
492         }
493
494         s = &dev->subdevices[1];
495         /* ao */
496         if (thisboard->ao_nbits) {
497                 s->type = COMEDI_SUBD_AO;
498                 s->subdev_flags = SDF_WRITABLE;
499                 s->n_chan = 2;
500                 s->maxdata = (1 << thisboard->ao_nbits) - 1;
501                 s->range_table = &range_bipolar5;
502                 s->insn_write = das08_ao_winsn;
503                 s->insn_read = das08_ao_rinsn;
504                 das08_ao_initialize(dev, s);
505         } else {
506                 s->type = COMEDI_SUBD_UNUSED;
507         }
508
509         s = &dev->subdevices[2];
510         /* di */
511         if (thisboard->di_nchan) {
512                 s->type = COMEDI_SUBD_DI;
513                 s->subdev_flags = SDF_READABLE;
514                 s->n_chan = thisboard->di_nchan;
515                 s->maxdata = 1;
516                 s->range_table = &range_digital;
517                 s->insn_bits =
518                         thisboard->is_jr ? das08jr_di_rbits : das08_di_rbits;
519         } else {
520                 s->type = COMEDI_SUBD_UNUSED;
521         }
522
523         s = &dev->subdevices[3];
524         /* do */
525         if (thisboard->do_nchan) {
526                 s->type = COMEDI_SUBD_DO;
527                 s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
528                 s->n_chan = thisboard->do_nchan;
529                 s->maxdata = 1;
530                 s->range_table = &range_digital;
531                 s->insn_bits =
532                         thisboard->is_jr ? das08jr_do_wbits : das08_do_wbits;
533         } else {
534                 s->type = COMEDI_SUBD_UNUSED;
535         }
536
537         s = &dev->subdevices[4];
538         /* 8255 */
539         if (thisboard->i8255_offset != 0) {
540                 ret = subdev_8255_init(dev, s, NULL,
541                                        dev->iobase + thisboard->i8255_offset);
542                 if (ret)
543                         return ret;
544         } else {
545                 s->type = COMEDI_SUBD_UNUSED;
546         }
547
548         s = &dev->subdevices[5];
549         /* 8254 */
550         if (thisboard->i8254_offset != 0) {
551                 s->type = COMEDI_SUBD_COUNTER;
552                 s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
553                 s->n_chan = 3;
554                 s->maxdata = 0xFFFF;
555                 s->insn_read = das08_counter_read;
556                 s->insn_write = das08_counter_write;
557                 s->insn_config = das08_counter_config;
558                 i8254_initialize(dev);
559         } else {
560                 s->type = COMEDI_SUBD_UNUSED;
561         }
562
563         return 0;
564 }
565 EXPORT_SYMBOL_GPL(das08_common_attach);
566
567 static int __init das08_init(void)
568 {
569         return 0;
570 }
571 module_init(das08_init);
572
573 static void __exit das08_exit(void)
574 {
575 }
576 module_exit(das08_exit);
577
578 MODULE_AUTHOR("Comedi http://www.comedi.org");
579 MODULE_DESCRIPTION("Comedi low-level driver");
580 MODULE_LICENSE("GPL");