Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
unioxx5.c
Go to the documentation of this file.
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) [[email protected]] *
7  * *
8  * COMEDI - Linux Control and Measurement Device Interface *
9  * Copyright (C) 1998,2000 David A. Schleef <[email protected]> *
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) <[email protected]>
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 */
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) {
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) {
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) {
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)) {
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) {
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) {
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;
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) {
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;
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");