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/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 inputs */
  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] = s->state;
 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
 499        ret = comedi_alloc_subdev_readback(s);
 500        if (ret)
 501                return ret;
 502
 503        s = &dev->subdevices[2];
 504        s->type = COMEDI_SUBD_DI;
 505        s->subdev_flags = SDF_READABLE;
 506        s->n_chan = 16;
 507        s->maxdata = 1;
 508        s->len_chanlist = 16;
 509        s->range_table = &range_digital;
 510        s->insn_bits = icp_multi_insn_bits_di;
 511
 512        s = &dev->subdevices[3];
 513        s->type = COMEDI_SUBD_DO;
 514        s->subdev_flags = SDF_WRITABLE;
 515        s->n_chan = 8;
 516        s->maxdata = 1;
 517        s->len_chanlist = 8;
 518        s->range_table = &range_digital;
 519        s->insn_bits = icp_multi_insn_bits_do;
 520
 521        s = &dev->subdevices[4];
 522        s->type = COMEDI_SUBD_COUNTER;
 523        s->subdev_flags = SDF_WRITABLE;
 524        s->n_chan = 4;
 525        s->maxdata = 0xffff;
 526        s->len_chanlist = 4;
 527        s->state = 0;
 528        s->insn_read = icp_multi_insn_read_ctr;
 529        s->insn_write = icp_multi_insn_write_ctr;
 530
 531        return 0;
 532}
 533
 534static void icp_multi_detach(struct comedi_device *dev)
 535{
 536        if (dev->mmio)
 537                icp_multi_reset(dev);
 538        comedi_pci_detach(dev);
 539}
 540
 541static struct comedi_driver icp_multi_driver = {
 542        .driver_name    = "icp_multi",
 543        .module         = THIS_MODULE,
 544        .auto_attach    = icp_multi_auto_attach,
 545        .detach         = icp_multi_detach,
 546};
 547
 548static int icp_multi_pci_probe(struct pci_dev *dev,
 549                               const struct pci_device_id *id)
 550{
 551        return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
 552}
 553
 554static const struct pci_device_id icp_multi_pci_table[] = {
 555        { PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) },
 556        { 0 }
 557};
 558MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
 559
 560static struct pci_driver icp_multi_pci_driver = {
 561        .name           = "icp_multi",
 562        .id_table       = icp_multi_pci_table,
 563        .probe          = icp_multi_pci_probe,
 564        .remove         = comedi_pci_auto_unconfig,
 565};
 566module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
 567
 568MODULE_AUTHOR("Comedi http://www.comedi.org");
 569MODULE_DESCRIPTION("Comedi low-level driver");
 570MODULE_LICENSE("GPL");
 571
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.