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