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