icp_multi.c revision aac307f9dd5ce1fe651140a036ab4b0a0571b54a
1/* 2 comedi/drivers/icp_multi.c 3 4 COMEDI - Linux Control and Measurement Device Interface 5 Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org> 6 7 This program is free software; you can redistribute it and/or modify 8 it under the terms of the GNU General Public License as published by 9 the Free Software Foundation; either version 2 of the License, or 10 (at your option) any later version. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 GNU General Public License for more details. 16*/ 17 18/* 19Driver: icp_multi 20Description: Inova ICP_MULTI 21Author: Anne Smorthit <anne.smorthit@sfwte.ch> 22Devices: [Inova] ICP_MULTI (icp_multi) 23Status: works 24 25The driver works for analog input and output and digital input and output. 26It does not work with interrupts or with the counters. Currently no support 27for DMA. 28 29It has 16 single-ended or 8 differential Analogue Input channels with 12-bit 30resolution. Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA. Input 31ranges can be individually programmed for each channel. Voltage or current 32measurement is selected by jumper. 33 34There are 4 x 12-bit Analogue Outputs. Ranges : 5V, 10V, +/-5V, +/-10V 35 3616 x Digital Inputs, 24V 37 388 x Digital Outputs, 24V, 1A 39 404 x 16-bit counters 41 42Configuration options: not applicable, uses PCI auto config 43*/ 44 45#include <linux/module.h> 46#include <linux/pci.h> 47#include <linux/delay.h> 48#include <linux/interrupt.h> 49 50#include "../comedidev.h" 51 52#define ICP_MULTI_ADC_CSR 0 /* R/W: ADC command/status register */ 53#define ICP_MULTI_AI 2 /* R: Analogue input data */ 54#define ICP_MULTI_DAC_CSR 4 /* R/W: DAC command/status register */ 55#define ICP_MULTI_AO 6 /* R/W: Analogue output data */ 56#define ICP_MULTI_DI 8 /* R/W: Digital inouts */ 57#define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */ 58#define ICP_MULTI_INT_EN 0x0C /* R/W: Interrupt enable register */ 59#define ICP_MULTI_INT_STAT 0x0E /* R/W: Interrupt status register */ 60#define ICP_MULTI_CNTR0 0x10 /* R/W: Counter 0 */ 61#define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */ 62#define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */ 63#define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */ 64 65/* Define bits from ADC command/status register */ 66#define ADC_ST 0x0001 /* Start ADC */ 67#define ADC_BSY 0x0001 /* ADC busy */ 68#define ADC_BI 0x0010 /* Bipolar input range 1 = bipolar */ 69#define ADC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ 70#define ADC_DI 0x0040 /* Differential input mode 1 = differential */ 71 72/* Define bits from DAC command/status register */ 73#define DAC_ST 0x0001 /* Start DAC */ 74#define DAC_BSY 0x0001 /* DAC busy */ 75#define DAC_BI 0x0010 /* Bipolar input range 1 = bipolar */ 76#define DAC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ 77 78/* Define bits from interrupt enable/status registers */ 79#define ADC_READY 0x0001 /* A/d conversion ready interrupt */ 80#define DAC_READY 0x0002 /* D/a conversion ready interrupt */ 81#define DOUT_ERROR 0x0004 /* Digital output error interrupt */ 82#define DIN_STATUS 0x0008 /* Digital input status change interrupt */ 83#define CIE0 0x0010 /* Counter 0 overrun interrupt */ 84#define CIE1 0x0020 /* Counter 1 overrun interrupt */ 85#define CIE2 0x0040 /* Counter 2 overrun interrupt */ 86#define CIE3 0x0080 /* Counter 3 overrun interrupt */ 87 88/* Useful definitions */ 89#define Status_IRQ 0x00ff /* All interrupts */ 90 91/* Define analogue range */ 92static const struct comedi_lrange range_analog = { 93 4, { 94 UNI_RANGE(5), 95 UNI_RANGE(10), 96 BIP_RANGE(5), 97 BIP_RANGE(10) 98 } 99}; 100 101static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 }; 102 103/* 104============================================================================== 105 Data & Structure declarations 106============================================================================== 107*/ 108 109struct icp_multi_private { 110 char valid; /* card is usable */ 111 unsigned int AdcCmdStatus; /* ADC Command/Status register */ 112 unsigned int DacCmdStatus; /* DAC Command/Status register */ 113 unsigned int IntEnable; /* Interrupt Enable register */ 114 unsigned int IntStatus; /* Interrupt Status register */ 115 unsigned int act_chanlist[32]; /* list of scanned channel */ 116 unsigned char act_chanlist_len; /* len of scanlist */ 117 unsigned char act_chanlist_pos; /* actual position in MUX list */ 118 unsigned int *ai_chanlist; /* actaul chanlist */ 119 unsigned int do_data; /* Remember digital output data */ 120}; 121 122static void setup_channel_list(struct comedi_device *dev, 123 struct comedi_subdevice *s, 124 unsigned int *chanlist, unsigned int n_chan) 125{ 126 struct icp_multi_private *devpriv = dev->private; 127 unsigned int i, range, chanprog; 128 unsigned int diff; 129 130 devpriv->act_chanlist_len = n_chan; 131 devpriv->act_chanlist_pos = 0; 132 133 for (i = 0; i < n_chan; i++) { 134 /* Get channel */ 135 chanprog = CR_CHAN(chanlist[i]); 136 137 /* Determine if it is a differential channel (Bit 15 = 1) */ 138 if (CR_AREF(chanlist[i]) == AREF_DIFF) { 139 diff = 1; 140 chanprog &= 0x0007; 141 } else { 142 diff = 0; 143 chanprog &= 0x000f; 144 } 145 146 /* Clear channel, range and input mode bits 147 * in A/D command/status register */ 148 devpriv->AdcCmdStatus &= 0xf00f; 149 150 /* Set channel number and differential mode status bit */ 151 if (diff) { 152 /* Set channel number, bits 9-11 & mode, bit 6 */ 153 devpriv->AdcCmdStatus |= (chanprog << 9); 154 devpriv->AdcCmdStatus |= ADC_DI; 155 } else 156 /* Set channel number, bits 8-11 */ 157 devpriv->AdcCmdStatus |= (chanprog << 8); 158 159 /* Get range for current channel */ 160 range = range_codes_analog[CR_RANGE(chanlist[i])]; 161 /* Set range. bits 4-5 */ 162 devpriv->AdcCmdStatus |= range; 163 164 /* Output channel, range, mode to ICP Multi */ 165 writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR); 166 } 167} 168 169static int icp_multi_ai_eoc(struct comedi_device *dev, 170 struct comedi_subdevice *s, 171 struct comedi_insn *insn, 172 unsigned long context) 173{ 174 unsigned int status; 175 176 status = readw(dev->mmio + ICP_MULTI_ADC_CSR); 177 if ((status & ADC_BSY) == 0) 178 return 0; 179 return -EBUSY; 180} 181 182static int icp_multi_insn_read_ai(struct comedi_device *dev, 183 struct comedi_subdevice *s, 184 struct comedi_insn *insn, 185 unsigned int *data) 186{ 187 struct icp_multi_private *devpriv = dev->private; 188 int ret = 0; 189 int n; 190 191 /* Disable A/D conversion ready interrupt */ 192 devpriv->IntEnable &= ~ADC_READY; 193 writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN); 194 195 /* Clear interrupt status */ 196 devpriv->IntStatus |= ADC_READY; 197 writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT); 198 199 /* Set up appropriate channel, mode and range data, for specified ch */ 200 setup_channel_list(dev, s, &insn->chanspec, 1); 201 202 for (n = 0; n < insn->n; n++) { 203 /* Set start ADC bit */ 204 devpriv->AdcCmdStatus |= ADC_ST; 205 writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR); 206 devpriv->AdcCmdStatus &= ~ADC_ST; 207 208 udelay(1); 209 210 /* Wait for conversion to complete, or get fed up waiting */ 211 ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0); 212 if (ret) 213 break; 214 215 data[n] = (readw(dev->mmio + ICP_MULTI_AI) >> 4) & 0x0fff; 216 } 217 218 /* Disable interrupt */ 219 devpriv->IntEnable &= ~ADC_READY; 220 writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN); 221 222 /* Clear interrupt status */ 223 devpriv->IntStatus |= ADC_READY; 224 writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT); 225 226 return ret ? ret : n; 227} 228 229static int icp_multi_ao_eoc(struct comedi_device *dev, 230 struct comedi_subdevice *s, 231 struct comedi_insn *insn, 232 unsigned long context) 233{ 234 unsigned int status; 235 236 status = readw(dev->mmio + ICP_MULTI_DAC_CSR); 237 if ((status & DAC_BSY) == 0) 238 return 0; 239 return -EBUSY; 240} 241 242static int icp_multi_ao_insn_write(struct comedi_device *dev, 243 struct comedi_subdevice *s, 244 struct comedi_insn *insn, 245 unsigned int *data) 246{ 247 struct icp_multi_private *devpriv = dev->private; 248 unsigned int chan = CR_CHAN(insn->chanspec); 249 unsigned int range = CR_RANGE(insn->chanspec); 250 int i; 251 252 /* Disable D/A conversion ready interrupt */ 253 devpriv->IntEnable &= ~DAC_READY; 254 writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN); 255 256 /* Clear interrupt status */ 257 devpriv->IntStatus |= DAC_READY; 258 writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT); 259 260 /* Set up range and channel data */ 261 /* Bit 4 = 1 : Bipolar */ 262 /* Bit 5 = 0 : 5V */ 263 /* Bit 5 = 1 : 10V */ 264 /* Bits 8-9 : Channel number */ 265 devpriv->DacCmdStatus &= 0xfccf; 266 devpriv->DacCmdStatus |= range_codes_analog[range]; 267 devpriv->DacCmdStatus |= (chan << 8); 268 269 writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR); 270 271 for (i = 0; i < insn->n; i++) { 272 unsigned int val = data[i]; 273 int ret; 274 275 /* Wait for analogue output data register to be 276 * ready for new data, or get fed up waiting */ 277 ret = comedi_timeout(dev, s, insn, icp_multi_ao_eoc, 0); 278 if (ret) { 279 /* Disable interrupt */ 280 devpriv->IntEnable &= ~DAC_READY; 281 writew(devpriv->IntEnable, 282 dev->mmio + ICP_MULTI_INT_EN); 283 284 /* Clear interrupt status */ 285 devpriv->IntStatus |= DAC_READY; 286 writew(devpriv->IntStatus, 287 dev->mmio + ICP_MULTI_INT_STAT); 288 289 return ret; 290 } 291 292 writew(val, dev->mmio + ICP_MULTI_AO); 293 294 /* Set DAC_ST bit to write the data to selected channel */ 295 devpriv->DacCmdStatus |= DAC_ST; 296 writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR); 297 devpriv->DacCmdStatus &= ~DAC_ST; 298 299 s->readback[chan] = val; 300 } 301 302 return insn->n; 303} 304 305static int icp_multi_insn_bits_di(struct comedi_device *dev, 306 struct comedi_subdevice *s, 307 struct comedi_insn *insn, 308 unsigned int *data) 309{ 310 data[1] = readw(dev->mmio + ICP_MULTI_DI); 311 312 return insn->n; 313} 314 315static int icp_multi_insn_bits_do(struct comedi_device *dev, 316 struct comedi_subdevice *s, 317 struct comedi_insn *insn, 318 unsigned int *data) 319{ 320 if (comedi_dio_update_state(s, data)) 321 writew(s->state, dev->mmio + ICP_MULTI_DO); 322 323 data[1] = readw(dev->mmio + ICP_MULTI_DI); 324 325 return insn->n; 326} 327 328static int icp_multi_insn_read_ctr(struct comedi_device *dev, 329 struct comedi_subdevice *s, 330 struct comedi_insn *insn, unsigned int *data) 331{ 332 return 0; 333} 334 335static int icp_multi_insn_write_ctr(struct comedi_device *dev, 336 struct comedi_subdevice *s, 337 struct comedi_insn *insn, 338 unsigned int *data) 339{ 340 return 0; 341} 342 343static irqreturn_t interrupt_service_icp_multi(int irq, void *d) 344{ 345 struct comedi_device *dev = d; 346 int int_no; 347 348 /* Is this interrupt from our board? */ 349 int_no = readw(dev->mmio + ICP_MULTI_INT_STAT) & Status_IRQ; 350 if (!int_no) 351 /* No, exit */ 352 return IRQ_NONE; 353 354 /* Determine which interrupt is active & handle it */ 355 switch (int_no) { 356 case ADC_READY: 357 break; 358 case DAC_READY: 359 break; 360 case DOUT_ERROR: 361 break; 362 case DIN_STATUS: 363 break; 364 case CIE0: 365 break; 366 case CIE1: 367 break; 368 case CIE2: 369 break; 370 case CIE3: 371 break; 372 default: 373 break; 374 375 } 376 377 return IRQ_HANDLED; 378} 379 380#if 0 381static int check_channel_list(struct comedi_device *dev, 382 struct comedi_subdevice *s, 383 unsigned int *chanlist, unsigned int n_chan) 384{ 385 unsigned int i; 386 387 /* Check that we at least have one channel to check */ 388 if (n_chan < 1) { 389 dev_err(dev->class_dev, "range/channel list is empty!\n"); 390 return 0; 391 } 392 /* Check all channels */ 393 for (i = 0; i < n_chan; i++) { 394 /* Check that channel number is < maximum */ 395 if (CR_AREF(chanlist[i]) == AREF_DIFF) { 396 if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) { 397 dev_err(dev->class_dev, 398 "Incorrect differential ai ch-nr\n"); 399 return 0; 400 } 401 } else { 402 if (CR_CHAN(chanlist[i]) > s->n_chan) { 403 dev_err(dev->class_dev, 404 "Incorrect ai channel number\n"); 405 return 0; 406 } 407 } 408 } 409 return 1; 410} 411#endif 412 413static int icp_multi_reset(struct comedi_device *dev) 414{ 415 struct icp_multi_private *devpriv = dev->private; 416 unsigned int i; 417 418 /* Clear INT enables and requests */ 419 writew(0, dev->mmio + ICP_MULTI_INT_EN); 420 writew(0x00ff, dev->mmio + ICP_MULTI_INT_STAT); 421 422 /* Set DACs to 0..5V range and 0V output */ 423 for (i = 0; i < 4; i++) { 424 devpriv->DacCmdStatus &= 0xfcce; 425 426 /* Set channel number */ 427 devpriv->DacCmdStatus |= (i << 8); 428 429 /* Output 0V */ 430 writew(0, dev->mmio + ICP_MULTI_AO); 431 432 /* Set start conversion bit */ 433 devpriv->DacCmdStatus |= DAC_ST; 434 435 /* Output to command / status register */ 436 writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR); 437 438 /* Delay to allow DAC time to recover */ 439 udelay(1); 440 } 441 442 /* Digital outputs to 0 */ 443 writew(0, dev->mmio + ICP_MULTI_DO); 444 445 return 0; 446} 447 448static int icp_multi_auto_attach(struct comedi_device *dev, 449 unsigned long context_unused) 450{ 451 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 452 struct icp_multi_private *devpriv; 453 struct comedi_subdevice *s; 454 int ret; 455 456 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 457 if (!devpriv) 458 return -ENOMEM; 459 460 ret = comedi_pci_enable(dev); 461 if (ret) 462 return ret; 463 464 dev->mmio = pci_ioremap_bar(pcidev, 2); 465 if (!dev->mmio) 466 return -ENOMEM; 467 468 ret = comedi_alloc_subdevices(dev, 5); 469 if (ret) 470 return ret; 471 472 icp_multi_reset(dev); 473 474 if (pcidev->irq) { 475 ret = request_irq(pcidev->irq, interrupt_service_icp_multi, 476 IRQF_SHARED, dev->board_name, dev); 477 if (ret == 0) 478 dev->irq = pcidev->irq; 479 } 480 481 s = &dev->subdevices[0]; 482 dev->read_subdev = s; 483 s->type = COMEDI_SUBD_AI; 484 s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; 485 s->n_chan = 16; 486 s->maxdata = 0x0fff; 487 s->len_chanlist = 16; 488 s->range_table = &range_analog; 489 s->insn_read = icp_multi_insn_read_ai; 490 491 s = &dev->subdevices[1]; 492 s->type = COMEDI_SUBD_AO; 493 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; 494 s->n_chan = 4; 495 s->maxdata = 0x0fff; 496 s->len_chanlist = 4; 497 s->range_table = &range_analog; 498 s->insn_write = icp_multi_ao_insn_write; 499 s->insn_read = comedi_readback_insn_read; 500 501 ret = comedi_alloc_subdev_readback(s); 502 if (ret) 503 return ret; 504 505 s = &dev->subdevices[2]; 506 s->type = COMEDI_SUBD_DI; 507 s->subdev_flags = SDF_READABLE; 508 s->n_chan = 16; 509 s->maxdata = 1; 510 s->len_chanlist = 16; 511 s->range_table = &range_digital; 512 s->insn_bits = icp_multi_insn_bits_di; 513 514 s = &dev->subdevices[3]; 515 s->type = COMEDI_SUBD_DO; 516 s->subdev_flags = SDF_WRITABLE | SDF_READABLE; 517 s->n_chan = 8; 518 s->maxdata = 1; 519 s->len_chanlist = 8; 520 s->range_table = &range_digital; 521 s->insn_bits = icp_multi_insn_bits_do; 522 523 s = &dev->subdevices[4]; 524 s->type = COMEDI_SUBD_COUNTER; 525 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; 526 s->n_chan = 4; 527 s->maxdata = 0xffff; 528 s->len_chanlist = 4; 529 s->state = 0; 530 s->insn_read = icp_multi_insn_read_ctr; 531 s->insn_write = icp_multi_insn_write_ctr; 532 533 devpriv->valid = 1; 534 535 return 0; 536} 537 538static void icp_multi_detach(struct comedi_device *dev) 539{ 540 struct icp_multi_private *devpriv = dev->private; 541 542 if (devpriv) 543 if (devpriv->valid) 544 icp_multi_reset(dev); 545 comedi_pci_detach(dev); 546} 547 548static struct comedi_driver icp_multi_driver = { 549 .driver_name = "icp_multi", 550 .module = THIS_MODULE, 551 .auto_attach = icp_multi_auto_attach, 552 .detach = icp_multi_detach, 553}; 554 555static int icp_multi_pci_probe(struct pci_dev *dev, 556 const struct pci_device_id *id) 557{ 558 return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data); 559} 560 561static const struct pci_device_id icp_multi_pci_table[] = { 562 { PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) }, 563 { 0 } 564}; 565MODULE_DEVICE_TABLE(pci, icp_multi_pci_table); 566 567static struct pci_driver icp_multi_pci_driver = { 568 .name = "icp_multi", 569 .id_table = icp_multi_pci_table, 570 .probe = icp_multi_pci_probe, 571 .remove = comedi_pci_auto_unconfig, 572}; 573module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver); 574 575MODULE_AUTHOR("Comedi http://www.comedi.org"); 576MODULE_DESCRIPTION("Comedi low-level driver"); 577MODULE_LICENSE("GPL"); 578