Merge branch 'next' into for-linus
[firefly-linux-kernel-4.4.55.git] / drivers / staging / comedi / drivers / unioxx5.c
1 /***************************************************************************
2  *                                                                         *
3  *  comedi/drivers/unioxx5.c                                               *
4  *  Driver for Fastwel UNIOxx-5 (analog and digital i/o) boards.           *
5  *                                                                         *
6  *  Copyright (C) 2006 Kruchinin Daniil (asgard) [asgard@etersoft.ru]      *
7  *                                                                         *
8  *  COMEDI - Linux Control and Measurement Device Interface                *
9  *  Copyright (C) 1998,2000 David A. Schleef <ds@schleef.org>              *
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  ***************************************************************************/
22 /*
23
24 Driver: unioxx5
25 Description: Driver for Fastwel UNIOxx-5 (analog and digital i/o) boards.
26 Author: Kruchinin Daniil (asgard) <asgard@etersoft.ru>
27 Status: unknown
28 Updated: 2006-10-09
29 Devices: [Fastwel] UNIOxx-5 (unioxx5),
30
31  This card supports digital and analog I/O. It written for g01
32  subdevices only.
33  channels range: 0 .. 23 dio channels
34  and 0 .. 11 analog modules range
35  During attaching unioxx5 module displays modules identifiers
36  (see dmesg after comedi_config) in format:
37  | [module_number] module_id |
38
39 */
40
41
42 #include <linux/module.h>
43 #include <linux/delay.h>
44 #include "../comedidev.h"
45
46 #define UNIOXX5_SIZE 0x10
47 #define UNIOXX5_SUBDEV_BASE 0xA000      /* base addr of first subdev */
48 #define UNIOXX5_SUBDEV_ODDS 0x400
49
50 /* modules types */
51 #define MODULE_DIGITAL 0
52 #define MODULE_OUTPUT_MASK 0x80 /* analog input/output */
53
54 /* constants for digital i/o */
55 #define UNIOXX5_NUM_OF_CHANS 24
56
57 /* constants for analog i/o */
58 #define TxBE  0x10              /* transmit buffer enable */
59 #define RxCA  0x20              /* 1 receive character available */
60 #define Rx2CA 0x40              /* 2 receive character available */
61 #define Rx4CA 0x80              /* 4 receive character available */
62
63 /* bytes mask errors */
64 #define Rx2CA_ERR_MASK 0x04     /* 2 bytes receiving error */
65 #define Rx4CA_ERR_MASK 0x08     /* 4 bytes receiving error */
66
67 /* channel modes */
68 #define ALL_2_INPUT  0          /* config all digital channels to input */
69 #define ALL_2_OUTPUT 1          /* config all digital channels to output */
70
71 /* 'private' structure for each subdevice */
72 struct unioxx5_subd_priv {
73         int usp_iobase;
74         /* 12 modules. each can be 70L or 73L */
75         unsigned char usp_module_type[12];
76         /* for saving previous written value for analog modules */
77         unsigned char usp_extra_data[12][4];
78         unsigned char usp_prev_wr_val[3];       /* previous written value */
79         unsigned char usp_prev_cn_val[3];       /* previous channel value */
80 };
81
82 static int __unioxx5_define_chan_offset(int chan_num)
83 {
84
85         if (chan_num < 0 || chan_num > 23)
86                 return -1;
87
88         return (chan_num >> 3) + 1;
89 }
90
91 #if 0                           /* not used? */
92 static void __unioxx5_digital_config(struct comedi_subdevice *s, int mode)
93 {
94         struct unioxx5_subd_priv *usp = s->private;
95         struct device *csdev = s->device->class_dev;
96         int i, mask;
97
98         mask = (mode == ALL_2_OUTPUT) ? 0xFF : 0x00;
99         dev_dbg(csdev, "mode = %d\n", mask);
100
101         outb(1, usp->usp_iobase + 0);
102
103         for (i = 0; i < 3; i++)
104                 outb(mask, usp->usp_iobase + i);
105
106         outb(0, usp->usp_iobase + 0);
107 }
108 #endif
109
110 /* configure channels for analog i/o (even to output, odd to input) */
111 static void __unioxx5_analog_config(struct unioxx5_subd_priv *usp, int channel)
112 {
113         int chan_a, chan_b, conf, channel_offset;
114
115         channel_offset = __unioxx5_define_chan_offset(channel);
116         conf = usp->usp_prev_cn_val[channel_offset - 1];
117         chan_a = chan_b = 1;
118
119         /* setting channel A and channel B mask */
120         if (channel % 2 == 0) {
121                 chan_a <<= channel & 0x07;
122                 chan_b <<= (channel + 1) & 0x07;
123         } else {
124                 chan_a <<= (channel - 1) & 0x07;
125                 chan_b <<= channel & 0x07;
126         }
127
128         conf |= chan_a;         /* even channel ot output */
129         conf &= ~chan_b;        /* odd channel to input */
130
131         outb(1, usp->usp_iobase + 0);
132         outb(conf, usp->usp_iobase + channel_offset);
133         outb(0, usp->usp_iobase + 0);
134
135         usp->usp_prev_cn_val[channel_offset - 1] = conf;
136 }
137
138 static int __unioxx5_digital_read(struct comedi_subdevice *s,
139                                   unsigned int *data, int channel, int minor)
140 {
141         struct unioxx5_subd_priv *usp = s->private;
142         struct device *csdev = s->device->class_dev;
143         int channel_offset, mask = 1 << (channel & 0x07);
144
145         channel_offset = __unioxx5_define_chan_offset(channel);
146         if (channel_offset < 0) {
147                 dev_err(csdev,
148                         "undefined channel %d. channel range is 0 .. 23\n",
149                         channel);
150                 return 0;
151         }
152
153         *data = inb(usp->usp_iobase + channel_offset);
154         *data &= mask;
155
156         /* correct the read value to 0 or 1 */
157         if (channel_offset > 1)
158                 channel -= 2 << channel_offset;
159         *data >>= channel;
160         return 1;
161 }
162
163 static int __unioxx5_analog_read(struct comedi_subdevice *s,
164                                  unsigned int *data, int channel, int minor)
165 {
166         struct unioxx5_subd_priv *usp = s->private;
167         struct device *csdev = s->device->class_dev;
168         int module_no, read_ch;
169         char control;
170
171         module_no = channel / 2;
172         read_ch = channel % 2;  /* depend on type of channel (A or B) */
173
174         /* defining if given module can work on input */
175         if (usp->usp_module_type[module_no] & MODULE_OUTPUT_MASK) {
176                 dev_err(csdev,
177                         "module in position %d with id 0x%02x is for output only",
178                         module_no, usp->usp_module_type[module_no]);
179                 return 0;
180         }
181
182         __unioxx5_analog_config(usp, channel);
183         /* sends module number to card(1 .. 12) */
184         outb(module_no + 1, usp->usp_iobase + 5);
185         outb('V', usp->usp_iobase + 6); /* sends to module (V)erify command */
186         control = inb(usp->usp_iobase); /* get control register byte */
187
188         /* waits while reading four bytes will be allowed */
189         while (!((control = inb(usp->usp_iobase + 0)) & Rx4CA))
190                 ;
191
192         /* if four bytes readding error occurs - return 0(false) */
193         if ((control & Rx4CA_ERR_MASK)) {
194                 dev_err(csdev, "4 bytes error\n");
195                 return 0;
196         }
197
198         if (read_ch)
199                 *data = inw(usp->usp_iobase + 6);       /* channel B */
200         else
201                 *data = inw(usp->usp_iobase + 4);       /* channel A */
202
203         return 1;
204 }
205
206 static int __unioxx5_digital_write(struct comedi_subdevice *s,
207                                    unsigned int *data, int channel, int minor)
208 {
209         struct unioxx5_subd_priv *usp = s->private;
210         struct device *csdev = s->device->class_dev;
211         int channel_offset, val;
212         int mask = 1 << (channel & 0x07);
213
214         channel_offset = __unioxx5_define_chan_offset(channel);
215         if (channel_offset < 0) {
216                 dev_err(csdev,
217                         "undefined channel %d. channel range is 0 .. 23\n",
218                         channel);
219                 return 0;
220         }
221
222         /* getting previous written value */
223         val = usp->usp_prev_wr_val[channel_offset - 1];
224
225         if (*data)
226                 val |= mask;
227         else
228                 val &= ~mask;
229
230         outb(val, usp->usp_iobase + channel_offset);
231         /* saving new written value */
232         usp->usp_prev_wr_val[channel_offset - 1] = val;
233
234         return 1;
235 }
236
237 static int __unioxx5_analog_write(struct comedi_subdevice *s,
238                                   unsigned int *data, int channel, int minor)
239 {
240         struct unioxx5_subd_priv *usp = s->private;
241         struct device *csdev = s->device->class_dev;
242         int module, i;
243
244         module = channel / 2;   /* definig module number(0 .. 11) */
245         i = (channel % 2) << 1; /* depends on type of channel (A or B) */
246
247         /* defining if given module can work on output */
248         if (!(usp->usp_module_type[module] & MODULE_OUTPUT_MASK)) {
249                 dev_err(csdev,
250                         "module in position %d with id 0x%0x is for input only!\n",
251                         module, usp->usp_module_type[module]);
252                 return 0;
253         }
254
255         __unioxx5_analog_config(usp, channel);
256         /* saving minor byte */
257         usp->usp_extra_data[module][i++] = (unsigned char)(*data & 0x00FF);
258         /* saving major byte */
259         usp->usp_extra_data[module][i] = (unsigned char)((*data & 0xFF00) >> 8);
260
261         /* while(!((inb(usp->usp_iobase + 0)) & TxBE)); */
262         /* sending module number to card(1 .. 12) */
263         outb(module + 1, usp->usp_iobase + 5);
264         outb('W', usp->usp_iobase + 6); /* sends (W)rite command to module */
265
266         /* sending for bytes to module(one byte per cycle iteration) */
267         for (i = 0; i < 4; i++) {
268                 while (!((inb(usp->usp_iobase + 0)) & TxBE))
269                         ;       /* waits while writting will be allowed */
270                 outb(usp->usp_extra_data[module][i], usp->usp_iobase + 6);
271         }
272
273         return 1;
274 }
275
276 static int unioxx5_subdev_read(struct comedi_device *dev,
277                                struct comedi_subdevice *subdev,
278                                struct comedi_insn *insn, unsigned int *data)
279 {
280         struct unioxx5_subd_priv *usp = subdev->private;
281         int channel, type;
282
283         channel = CR_CHAN(insn->chanspec);
284         /* defining module type(analog or digital) */
285         type = usp->usp_module_type[channel / 2];
286
287         if (type == MODULE_DIGITAL) {
288                 if (!__unioxx5_digital_read(subdev, data, channel, dev->minor))
289                         return -1;
290         } else {
291                 if (!__unioxx5_analog_read(subdev, data, channel, dev->minor))
292                         return -1;
293         }
294
295         return 1;
296 }
297
298 static int unioxx5_subdev_write(struct comedi_device *dev,
299                                 struct comedi_subdevice *subdev,
300                                 struct comedi_insn *insn, unsigned int *data)
301 {
302         struct unioxx5_subd_priv *usp = subdev->private;
303         int channel, type;
304
305         channel = CR_CHAN(insn->chanspec);
306         /* defining module type(analog or digital) */
307         type = usp->usp_module_type[channel / 2];
308
309         if (type == MODULE_DIGITAL) {
310                 if (!__unioxx5_digital_write(subdev, data, channel, dev->minor))
311                         return -1;
312         } else {
313                 if (!__unioxx5_analog_write(subdev, data, channel, dev->minor))
314                         return -1;
315         }
316
317         return 1;
318 }
319
320 /* for digital modules only */
321 static int unioxx5_insn_config(struct comedi_device *dev,
322                                struct comedi_subdevice *subdev,
323                                struct comedi_insn *insn, unsigned int *data)
324 {
325         int channel_offset, flags, channel = CR_CHAN(insn->chanspec), type;
326         struct unioxx5_subd_priv *usp = subdev->private;
327         int mask = 1 << (channel & 0x07);
328
329         type = usp->usp_module_type[channel / 2];
330
331         if (type != MODULE_DIGITAL) {
332                 dev_err(dev->class_dev,
333                         "channel configuration accessible only for digital modules\n");
334                 return -1;
335         }
336
337         channel_offset = __unioxx5_define_chan_offset(channel);
338         if (channel_offset < 0) {
339                 dev_err(dev->class_dev,
340                         "undefined channel %d. channel range is 0 .. 23\n",
341                         channel);
342                 return -1;
343         }
344
345         /* gets previously written value */
346         flags = usp->usp_prev_cn_val[channel_offset - 1];
347
348         switch (*data) {
349         case COMEDI_INPUT:
350                 flags &= ~mask;
351                 break;
352         case COMEDI_OUTPUT:
353                 flags |= mask;
354                 break;
355         default:
356                 dev_err(dev->class_dev, "unknown flag\n");
357                 return -1;
358         }
359
360         /*                                                        *\
361          * sets channels buffer to 1(after this we are allowed to *
362          * change channel type on input or output)                *
363          \*                                                        */
364         outb(1, usp->usp_iobase + 0);
365         /* changes type of _one_ channel */
366         outb(flags, usp->usp_iobase + channel_offset);
367         /* sets channels bank to 0(allows directly input/output) */
368         outb(0, usp->usp_iobase + 0);
369         /* saves written value */
370         usp->usp_prev_cn_val[channel_offset - 1] = flags;
371
372         return 0;
373 }
374
375 /* initializing subdevice with given address */
376 static int __unioxx5_subdev_init(struct comedi_device *dev,
377                                  struct comedi_subdevice *s,
378                                  int iobase)
379 {
380         struct unioxx5_subd_priv *usp;
381         int i, to, ndef_flag = 0;
382         int ret;
383
384         usp = comedi_alloc_spriv(s, sizeof(*usp));
385         if (!usp)
386                 return -ENOMEM;
387
388         ret = __comedi_request_region(dev, iobase, UNIOXX5_SIZE);
389         if (ret)
390                 return ret;
391         usp->usp_iobase = iobase;
392
393         /* defining modules types */
394         for (i = 0; i < 12; i++) {
395                 to = 10000;
396
397                 __unioxx5_analog_config(usp, i * 2);
398                 /* sends channel number to card */
399                 outb(i + 1, iobase + 5);
400                 outb('H', iobase + 6);  /* requests EEPROM world */
401                 while (!(inb(iobase + 0) & TxBE))
402                         ;       /* waits while writting will be allowed */
403                 outb(0, iobase + 6);
404
405                 /* waits while reading of two bytes will be allowed */
406                 while (!(inb(iobase + 0) & Rx2CA)) {
407                         if (--to <= 0) {
408                                 ndef_flag = 1;
409                                 break;
410                         }
411                 }
412
413                 if (ndef_flag) {
414                         usp->usp_module_type[i] = 0;
415                         ndef_flag = 0;
416                 } else
417                         usp->usp_module_type[i] = inb(iobase + 6);
418
419                 udelay(1);
420         }
421
422         /* initial subdevice for digital or analog i/o */
423         s->type = COMEDI_SUBD_DIO;
424         s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
425         s->n_chan = UNIOXX5_NUM_OF_CHANS;
426         s->maxdata = 0xFFF;
427         s->range_table = &range_digital;
428         s->insn_read = unioxx5_subdev_read;
429         s->insn_write = unioxx5_subdev_write;
430         /* for digital modules only!!! */
431         s->insn_config = unioxx5_insn_config;
432
433         return 0;
434 }
435
436 static int unioxx5_attach(struct comedi_device *dev,
437                           struct comedi_devconfig *it)
438 {
439         struct comedi_subdevice *s;
440         int iobase, i, n_subd;
441         int id, num, ba;
442         int ret;
443
444         iobase = it->options[0];
445
446         dev->iobase = iobase;
447         iobase += UNIOXX5_SUBDEV_BASE;
448         n_subd = 0;
449
450         /* getting number of subdevices with types 'g01' */
451         for (i = 0, ba = iobase; i < 4; i++, ba += UNIOXX5_SUBDEV_ODDS) {
452                 id = inb(ba + 0xE);
453                 num = inb(ba + 0xF);
454
455                 if (id != 'g' || num != 1)
456                         continue;
457
458                 n_subd++;
459         }
460
461         /* unioxx5 can has from two to four subdevices */
462         if (n_subd < 2) {
463                 dev_err(dev->class_dev,
464                         "your card must has at least 2 'g01' subdevices\n");
465                 return -1;
466         }
467
468         ret = comedi_alloc_subdevices(dev, n_subd);
469         if (ret)
470                 return ret;
471
472         /* initializing each of for same subdevices */
473         for (i = 0; i < n_subd; i++, iobase += UNIOXX5_SUBDEV_ODDS) {
474                 s = &dev->subdevices[i];
475                 ret = __unioxx5_subdev_init(dev, s, iobase);
476                 if (ret)
477                         return ret;
478         }
479
480         return 0;
481 }
482
483 static void unioxx5_detach(struct comedi_device *dev)
484 {
485         struct comedi_subdevice *s;
486         struct unioxx5_subd_priv *spriv;
487         int i;
488
489         for (i = 0; i < dev->n_subdevices; i++) {
490                 s = &dev->subdevices[i];
491                 spriv = s->private;
492                 if (spriv && spriv->usp_iobase)
493                         release_region(spriv->usp_iobase, UNIOXX5_SIZE);
494         }
495 }
496
497 static struct comedi_driver unioxx5_driver = {
498         .driver_name    = "unioxx5",
499         .module         = THIS_MODULE,
500         .attach         = unioxx5_attach,
501         .detach         = unioxx5_detach,
502 };
503 module_comedi_driver(unioxx5_driver);
504
505 MODULE_AUTHOR("Comedi http://www.comedi.org");
506 MODULE_DESCRIPTION("Comedi low-level driver");
507 MODULE_LICENSE("GPL");