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