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