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