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