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