icp_multi.c revision ce157f8032bbd46d9427034c335b0afd751da25d
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 PCI_DEVICE_ID_ICP_MULTI 0x8000 53 54#define ICP_MULTI_ADC_CSR 0 /* R/W: ADC command/status register */ 55#define ICP_MULTI_AI 2 /* R: Analogue input data */ 56#define ICP_MULTI_DAC_CSR 4 /* R/W: DAC command/status register */ 57#define ICP_MULTI_AO 6 /* R/W: Analogue output data */ 58#define ICP_MULTI_DI 8 /* R/W: Digital inouts */ 59#define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */ 60#define ICP_MULTI_INT_EN 0x0C /* R/W: Interrupt enable register */ 61#define ICP_MULTI_INT_STAT 0x0E /* R/W: Interrupt status register */ 62#define ICP_MULTI_CNTR0 0x10 /* R/W: Counter 0 */ 63#define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */ 64#define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */ 65#define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */ 66 67/* Define bits from ADC command/status register */ 68#define ADC_ST 0x0001 /* Start ADC */ 69#define ADC_BSY 0x0001 /* ADC busy */ 70#define ADC_BI 0x0010 /* Bipolar input range 1 = bipolar */ 71#define ADC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ 72#define ADC_DI 0x0040 /* Differential input mode 1 = differential */ 73 74/* Define bits from DAC command/status register */ 75#define DAC_ST 0x0001 /* Start DAC */ 76#define DAC_BSY 0x0001 /* DAC busy */ 77#define DAC_BI 0x0010 /* Bipolar input range 1 = bipolar */ 78#define DAC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ 79 80/* Define bits from interrupt enable/status registers */ 81#define ADC_READY 0x0001 /* A/d conversion ready interrupt */ 82#define DAC_READY 0x0002 /* D/a conversion ready interrupt */ 83#define DOUT_ERROR 0x0004 /* Digital output error interrupt */ 84#define DIN_STATUS 0x0008 /* Digital input status change interrupt */ 85#define CIE0 0x0010 /* Counter 0 overrun interrupt */ 86#define CIE1 0x0020 /* Counter 1 overrun interrupt */ 87#define CIE2 0x0040 /* Counter 2 overrun interrupt */ 88#define CIE3 0x0080 /* Counter 3 overrun interrupt */ 89 90/* Useful definitions */ 91#define Status_IRQ 0x00ff /* All interrupts */ 92 93/* Define analogue range */ 94static const struct comedi_lrange range_analog = { 4, { 95 UNI_RANGE(5), 96 UNI_RANGE(10), 97 BIP_RANGE(5), 98 BIP_RANGE(10) 99 } 100}; 101 102static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 }; 103 104/* 105============================================================================== 106 Data & Structure declarations 107============================================================================== 108*/ 109 110struct icp_multi_private { 111 char valid; /* card is usable */ 112 void __iomem *io_addr; /* Pointer to mapped io address */ 113 unsigned int AdcCmdStatus; /* ADC Command/Status register */ 114 unsigned int DacCmdStatus; /* DAC Command/Status register */ 115 unsigned int IntEnable; /* Interrupt Enable register */ 116 unsigned int IntStatus; /* Interrupt Status register */ 117 unsigned int act_chanlist[32]; /* list of scanned channel */ 118 unsigned char act_chanlist_len; /* len of scanlist */ 119 unsigned char act_chanlist_pos; /* actual position in MUX list */ 120 unsigned int *ai_chanlist; /* actaul chanlist */ 121 short *ai_data; /* data buffer */ 122 short ao_data[4]; /* data output buffer */ 123 short di_data; /* Digital input data */ 124 unsigned int do_data; /* Remember digital output data */ 125}; 126 127static void setup_channel_list(struct comedi_device *dev, 128 struct comedi_subdevice *s, 129 unsigned int *chanlist, unsigned int n_chan) 130{ 131 struct icp_multi_private *devpriv = dev->private; 132 unsigned int i, range, chanprog; 133 unsigned int diff; 134 135 devpriv->act_chanlist_len = n_chan; 136 devpriv->act_chanlist_pos = 0; 137 138 for (i = 0; i < n_chan; i++) { 139 /* Get channel */ 140 chanprog = CR_CHAN(chanlist[i]); 141 142 /* Determine if it is a differential channel (Bit 15 = 1) */ 143 if (CR_AREF(chanlist[i]) == AREF_DIFF) { 144 diff = 1; 145 chanprog &= 0x0007; 146 } else { 147 diff = 0; 148 chanprog &= 0x000f; 149 } 150 151 /* Clear channel, range and input mode bits 152 * in A/D command/status register */ 153 devpriv->AdcCmdStatus &= 0xf00f; 154 155 /* Set channel number and differential mode status bit */ 156 if (diff) { 157 /* Set channel number, bits 9-11 & mode, bit 6 */ 158 devpriv->AdcCmdStatus |= (chanprog << 9); 159 devpriv->AdcCmdStatus |= ADC_DI; 160 } else 161 /* Set channel number, bits 8-11 */ 162 devpriv->AdcCmdStatus |= (chanprog << 8); 163 164 /* Get range for current channel */ 165 range = range_codes_analog[CR_RANGE(chanlist[i])]; 166 /* Set range. bits 4-5 */ 167 devpriv->AdcCmdStatus |= range; 168 169 /* Output channel, range, mode to ICP Multi */ 170 writew(devpriv->AdcCmdStatus, 171 devpriv->io_addr + ICP_MULTI_ADC_CSR); 172 } 173} 174 175static int icp_multi_insn_read_ai(struct comedi_device *dev, 176 struct comedi_subdevice *s, 177 struct comedi_insn *insn, unsigned int *data) 178{ 179 struct icp_multi_private *devpriv = dev->private; 180 int n, timeout; 181 182 /* Disable A/D conversion ready interrupt */ 183 devpriv->IntEnable &= ~ADC_READY; 184 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); 185 186 /* Clear interrupt status */ 187 devpriv->IntStatus |= ADC_READY; 188 writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); 189 190 /* Set up appropriate channel, mode and range data, for specified ch */ 191 setup_channel_list(dev, s, &insn->chanspec, 1); 192 193 for (n = 0; n < insn->n; n++) { 194 /* Set start ADC bit */ 195 devpriv->AdcCmdStatus |= ADC_ST; 196 writew(devpriv->AdcCmdStatus, 197 devpriv->io_addr + ICP_MULTI_ADC_CSR); 198 devpriv->AdcCmdStatus &= ~ADC_ST; 199 200 udelay(1); 201 202 /* Wait for conversion to complete, or get fed up waiting */ 203 timeout = 100; 204 while (timeout--) { 205 if (!(readw(devpriv->io_addr + 206 ICP_MULTI_ADC_CSR) & ADC_BSY)) 207 goto conv_finish; 208 209 udelay(1); 210 } 211 212 /* If we reach here, a timeout has occurred */ 213 comedi_error(dev, "A/D insn timeout"); 214 215 /* Disable interrupt */ 216 devpriv->IntEnable &= ~ADC_READY; 217 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); 218 219 /* Clear interrupt status */ 220 devpriv->IntStatus |= ADC_READY; 221 writew(devpriv->IntStatus, 222 devpriv->io_addr + ICP_MULTI_INT_STAT); 223 224 /* Clear data received */ 225 data[n] = 0; 226 227 return -ETIME; 228 229conv_finish: 230 data[n] = 231 (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff; 232 } 233 234 /* Disable interrupt */ 235 devpriv->IntEnable &= ~ADC_READY; 236 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); 237 238 /* Clear interrupt status */ 239 devpriv->IntStatus |= ADC_READY; 240 writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); 241 242 return n; 243} 244 245static int icp_multi_insn_write_ao(struct comedi_device *dev, 246 struct comedi_subdevice *s, 247 struct comedi_insn *insn, unsigned int *data) 248{ 249 struct icp_multi_private *devpriv = dev->private; 250 int n, chan, range, timeout; 251 252 /* Disable D/A conversion ready interrupt */ 253 devpriv->IntEnable &= ~DAC_READY; 254 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); 255 256 /* Clear interrupt status */ 257 devpriv->IntStatus |= DAC_READY; 258 writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); 259 260 /* Get channel number and range */ 261 chan = CR_CHAN(insn->chanspec); 262 range = CR_RANGE(insn->chanspec); 263 264 /* Set up range and channel data */ 265 /* Bit 4 = 1 : Bipolar */ 266 /* Bit 5 = 0 : 5V */ 267 /* Bit 5 = 1 : 10V */ 268 /* Bits 8-9 : Channel number */ 269 devpriv->DacCmdStatus &= 0xfccf; 270 devpriv->DacCmdStatus |= range_codes_analog[range]; 271 devpriv->DacCmdStatus |= (chan << 8); 272 273 writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR); 274 275 for (n = 0; n < insn->n; n++) { 276 /* Wait for analogue output data register to be 277 * ready for new data, or get fed up waiting */ 278 timeout = 100; 279 while (timeout--) { 280 if (!(readw(devpriv->io_addr + 281 ICP_MULTI_DAC_CSR) & DAC_BSY)) 282 goto dac_ready; 283 284 udelay(1); 285 } 286 287 /* If we reach here, a timeout has occurred */ 288 comedi_error(dev, "D/A insn timeout"); 289 290 /* Disable interrupt */ 291 devpriv->IntEnable &= ~DAC_READY; 292 writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); 293 294 /* Clear interrupt status */ 295 devpriv->IntStatus |= DAC_READY; 296 writew(devpriv->IntStatus, 297 devpriv->io_addr + ICP_MULTI_INT_STAT); 298 299 /* Clear data received */ 300 devpriv->ao_data[chan] = 0; 301 302 return -ETIME; 303 304dac_ready: 305 /* Write data to analogue output data register */ 306 writew(data[n], devpriv->io_addr + ICP_MULTI_AO); 307 308 /* Set DAC_ST bit to write the data to selected channel */ 309 devpriv->DacCmdStatus |= DAC_ST; 310 writew(devpriv->DacCmdStatus, 311 devpriv->io_addr + ICP_MULTI_DAC_CSR); 312 devpriv->DacCmdStatus &= ~DAC_ST; 313 314 /* Save analogue output data */ 315 devpriv->ao_data[chan] = data[n]; 316 } 317 318 return n; 319} 320 321static int icp_multi_insn_read_ao(struct comedi_device *dev, 322 struct comedi_subdevice *s, 323 struct comedi_insn *insn, unsigned int *data) 324{ 325 struct icp_multi_private *devpriv = dev->private; 326 int n, chan; 327 328 /* Get channel number */ 329 chan = CR_CHAN(insn->chanspec); 330 331 /* Read analogue outputs */ 332 for (n = 0; n < insn->n; n++) 333 data[n] = devpriv->ao_data[chan]; 334 335 return n; 336} 337 338static int icp_multi_insn_bits_di(struct comedi_device *dev, 339 struct comedi_subdevice *s, 340 struct comedi_insn *insn, unsigned int *data) 341{ 342 struct icp_multi_private *devpriv = dev->private; 343 344 data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); 345 346 return insn->n; 347} 348 349static int icp_multi_insn_bits_do(struct comedi_device *dev, 350 struct comedi_subdevice *s, 351 struct comedi_insn *insn, unsigned int *data) 352{ 353 struct icp_multi_private *devpriv = dev->private; 354 355 if (data[0]) { 356 s->state &= ~data[0]; 357 s->state |= (data[0] & data[1]); 358 359 printk(KERN_DEBUG "Digital outputs = %4x \n", s->state); 360 361 writew(s->state, devpriv->io_addr + ICP_MULTI_DO); 362 } 363 364 data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); 365 366 return insn->n; 367} 368 369static int icp_multi_insn_read_ctr(struct comedi_device *dev, 370 struct comedi_subdevice *s, 371 struct comedi_insn *insn, unsigned int *data) 372{ 373 return 0; 374} 375 376static int icp_multi_insn_write_ctr(struct comedi_device *dev, 377 struct comedi_subdevice *s, 378 struct comedi_insn *insn, 379 unsigned int *data) 380{ 381 return 0; 382} 383 384static irqreturn_t interrupt_service_icp_multi(int irq, void *d) 385{ 386 struct comedi_device *dev = d; 387 struct icp_multi_private *devpriv = dev->private; 388 int int_no; 389 390 /* Is this interrupt from our board? */ 391 int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ; 392 if (!int_no) 393 /* No, exit */ 394 return IRQ_NONE; 395 396 /* Determine which interrupt is active & handle it */ 397 switch (int_no) { 398 case ADC_READY: 399 break; 400 case DAC_READY: 401 break; 402 case DOUT_ERROR: 403 break; 404 case DIN_STATUS: 405 break; 406 case CIE0: 407 break; 408 case CIE1: 409 break; 410 case CIE2: 411 break; 412 case CIE3: 413 break; 414 default: 415 break; 416 417 } 418 419 return IRQ_HANDLED; 420} 421 422#if 0 423static int check_channel_list(struct comedi_device *dev, 424 struct comedi_subdevice *s, 425 unsigned int *chanlist, unsigned int n_chan) 426{ 427 unsigned int i; 428 429 /* Check that we at least have one channel to check */ 430 if (n_chan < 1) { 431 comedi_error(dev, "range/channel list is empty!"); 432 return 0; 433 } 434 /* Check all channels */ 435 for (i = 0; i < n_chan; i++) { 436 /* Check that channel number is < maximum */ 437 if (CR_AREF(chanlist[i]) == AREF_DIFF) { 438 if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) { 439 comedi_error(dev, 440 "Incorrect differential ai ch-nr"); 441 return 0; 442 } 443 } else { 444 if (CR_CHAN(chanlist[i]) > s->n_chan) { 445 comedi_error(dev, 446 "Incorrect ai channel number"); 447 return 0; 448 } 449 } 450 } 451 return 1; 452} 453#endif 454 455static int icp_multi_reset(struct comedi_device *dev) 456{ 457 struct icp_multi_private *devpriv = dev->private; 458 unsigned int i; 459 460 /* Clear INT enables and requests */ 461 writew(0, devpriv->io_addr + ICP_MULTI_INT_EN); 462 writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT); 463 464 /* Set DACs to 0..5V range and 0V output */ 465 for (i = 0; i < 4; i++) { 466 devpriv->DacCmdStatus &= 0xfcce; 467 468 /* Set channel number */ 469 devpriv->DacCmdStatus |= (i << 8); 470 471 /* Output 0V */ 472 writew(0, devpriv->io_addr + ICP_MULTI_AO); 473 474 /* Set start conversion bit */ 475 devpriv->DacCmdStatus |= DAC_ST; 476 477 /* Output to command / status register */ 478 writew(devpriv->DacCmdStatus, 479 devpriv->io_addr + ICP_MULTI_DAC_CSR); 480 481 /* Delay to allow DAC time to recover */ 482 udelay(1); 483 } 484 485 /* Digital outputs to 0 */ 486 writew(0, devpriv->io_addr + ICP_MULTI_DO); 487 488 return 0; 489} 490 491static int icp_multi_auto_attach(struct comedi_device *dev, 492 unsigned long context_unused) 493{ 494 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 495 struct icp_multi_private *devpriv; 496 struct comedi_subdevice *s; 497 int ret; 498 499 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 500 if (!devpriv) 501 return -ENOMEM; 502 503 ret = comedi_pci_enable(dev); 504 if (ret) 505 return ret; 506 507 devpriv->io_addr = pci_ioremap_bar(pcidev, 2); 508 if (!devpriv->io_addr) 509 return -ENOMEM; 510 511 ret = comedi_alloc_subdevices(dev, 5); 512 if (ret) 513 return ret; 514 515 icp_multi_reset(dev); 516 517 if (pcidev->irq) { 518 ret = request_irq(pcidev->irq, interrupt_service_icp_multi, 519 IRQF_SHARED, dev->board_name, dev); 520 if (ret == 0) 521 dev->irq = pcidev->irq; 522 } 523 524 s = &dev->subdevices[0]; 525 dev->read_subdev = s; 526 s->type = COMEDI_SUBD_AI; 527 s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; 528 s->n_chan = 16; 529 s->maxdata = 0x0fff; 530 s->len_chanlist = 16; 531 s->range_table = &range_analog; 532 s->insn_read = icp_multi_insn_read_ai; 533 534 s = &dev->subdevices[1]; 535 s->type = COMEDI_SUBD_AO; 536 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; 537 s->n_chan = 4; 538 s->maxdata = 0x0fff; 539 s->len_chanlist = 4; 540 s->range_table = &range_analog; 541 s->insn_write = icp_multi_insn_write_ao; 542 s->insn_read = icp_multi_insn_read_ao; 543 544 s = &dev->subdevices[2]; 545 s->type = COMEDI_SUBD_DI; 546 s->subdev_flags = SDF_READABLE; 547 s->n_chan = 16; 548 s->maxdata = 1; 549 s->len_chanlist = 16; 550 s->range_table = &range_digital; 551 s->io_bits = 0; 552 s->insn_bits = icp_multi_insn_bits_di; 553 554 s = &dev->subdevices[3]; 555 s->type = COMEDI_SUBD_DO; 556 s->subdev_flags = SDF_WRITABLE | SDF_READABLE; 557 s->n_chan = 8; 558 s->maxdata = 1; 559 s->len_chanlist = 8; 560 s->range_table = &range_digital; 561 s->io_bits = 0xff; 562 s->state = 0; 563 s->insn_bits = icp_multi_insn_bits_do; 564 565 s = &dev->subdevices[4]; 566 s->type = COMEDI_SUBD_COUNTER; 567 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; 568 s->n_chan = 4; 569 s->maxdata = 0xffff; 570 s->len_chanlist = 4; 571 s->state = 0; 572 s->insn_read = icp_multi_insn_read_ctr; 573 s->insn_write = icp_multi_insn_write_ctr; 574 575 devpriv->valid = 1; 576 577 dev_info(dev->class_dev, "%s attached, irq %sabled\n", 578 dev->board_name, dev->irq ? "en" : "dis"); 579 580 return 0; 581} 582 583static void icp_multi_detach(struct comedi_device *dev) 584{ 585 struct icp_multi_private *devpriv = dev->private; 586 587 if (devpriv) 588 if (devpriv->valid) 589 icp_multi_reset(dev); 590 if (dev->irq) 591 free_irq(dev->irq, dev); 592 if (devpriv && devpriv->io_addr) 593 iounmap(devpriv->io_addr); 594 comedi_pci_disable(dev); 595} 596 597static struct comedi_driver icp_multi_driver = { 598 .driver_name = "icp_multi", 599 .module = THIS_MODULE, 600 .auto_attach = icp_multi_auto_attach, 601 .detach = icp_multi_detach, 602}; 603 604static int icp_multi_pci_probe(struct pci_dev *dev, 605 const struct pci_device_id *id) 606{ 607 return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data); 608} 609 610static DEFINE_PCI_DEVICE_TABLE(icp_multi_pci_table) = { 611 { PCI_DEVICE(PCI_VENDOR_ID_ICP, PCI_DEVICE_ID_ICP_MULTI) }, 612 { 0 } 613}; 614MODULE_DEVICE_TABLE(pci, icp_multi_pci_table); 615 616static struct pci_driver icp_multi_pci_driver = { 617 .name = "icp_multi", 618 .id_table = icp_multi_pci_table, 619 .probe = icp_multi_pci_probe, 620 .remove = comedi_pci_auto_unconfig, 621}; 622module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver); 623 624MODULE_AUTHOR("Comedi http://www.comedi.org"); 625MODULE_DESCRIPTION("Comedi low-level driver"); 626MODULE_LICENSE("GPL"); 627