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