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 24Driver: unioxx5 25Description: Driver for Fastwel UNIOxx-5 (analog and digital i/o) boards. 26Author: Kruchinin Daniil (asgard) <asgard@etersoft.ru> 27Status: unknown 28Updated: 2006-10-09 29Devices: [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 */ 72struct 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 82static 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? */ 92static 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) */ 111static 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 138static 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 163static 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 206static 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 237static 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 276static 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 298static 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 */ 321static 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 */ 376static 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 436static 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 483static 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 497static struct comedi_driver unioxx5_driver = { 498 .driver_name = "unioxx5", 499 .module = THIS_MODULE, 500 .attach = unioxx5_attach, 501 .detach = unioxx5_detach, 502}; 503module_comedi_driver(unioxx5_driver); 504 505MODULE_AUTHOR("Comedi http://www.comedi.org"); 506MODULE_DESCRIPTION("Comedi low-level driver"); 507MODULE_LICENSE("GPL"); 508