linux/drivers/staging/comedi/drivers/adv_pci1724.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * adv_pci1724.c
   4 * Comedi driver for the Advantech PCI-1724U card.
   5 *
   6 * Author:  Frank Mori Hess <fmh6jj@gmail.com>
   7 * Copyright (C) 2013 GnuBIO Inc
   8 *
   9 * COMEDI - Linux Control and Measurement Device Interface
  10 * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
  11 */
  12
  13/*
  14 * Driver: adv_pci1724
  15 * Description: Advantech PCI-1724U
  16 * Devices: [Advantech] PCI-1724U (adv_pci1724)
  17 * Author: Frank Mori Hess <fmh6jj@gmail.com>
  18 * Updated: 2013-02-09
  19 * Status: works
  20 *
  21 * Configuration Options: not applicable, uses comedi PCI auto config
  22 *
  23 * Subdevice 0 is the analog output.
  24 * Subdevice 1 is the offset calibration for the analog output.
  25 * Subdevice 2 is the gain calibration for the analog output.
  26 *
  27 * The calibration offset and gains have quite a large effect on the
  28 * analog output, so it is possible to adjust the analog output to
  29 * have an output range significantly different from the board's
  30 * nominal output ranges. For a calibrated +/-10V range, the analog
  31 * output's offset will be set somewhere near mid-range (0x2000) and
  32 * its gain will be near maximum (0x3fff).
  33 *
  34 * There is really no difference between the board's documented 0-20mA
  35 * versus 4-20mA output ranges. To pick one or the other is simply a
  36 * matter of adjusting the offset and gain calibration until the board
  37 * outputs in the desired range.
  38 */
  39
  40#include <linux/module.h>
  41
  42#include "../comedi_pci.h"
  43
  44/*
  45 * PCI bar 2 Register I/O map (dev->iobase)
  46 */
  47#define PCI1724_DAC_CTRL_REG            0x00
  48#define PCI1724_DAC_CTRL_GX(x)          BIT(20 + ((x) / 8))
  49#define PCI1724_DAC_CTRL_CX(x)          (((x) % 8) << 16)
  50#define PCI1724_DAC_CTRL_MODE(x)        (((x) & 0x3) << 14)
  51#define PCI1724_DAC_CTRL_MODE_GAIN      PCI1724_DAC_CTRL_MODE(1)
  52#define PCI1724_DAC_CTRL_MODE_OFFSET    PCI1724_DAC_CTRL_MODE(2)
  53#define PCI1724_DAC_CTRL_MODE_NORMAL    PCI1724_DAC_CTRL_MODE(3)
  54#define PCI1724_DAC_CTRL_MODE_MASK      PCI1724_DAC_CTRL_MODE(3)
  55#define PCI1724_DAC_CTRL_DATA(x)        (((x) & 0x3fff) << 0)
  56#define PCI1724_SYNC_CTRL_REG           0x04
  57#define PCI1724_SYNC_CTRL_DACSTAT       BIT(1)
  58#define PCI1724_SYNC_CTRL_SYN           BIT(0)
  59#define PCI1724_EEPROM_CTRL_REG         0x08
  60#define PCI1724_SYNC_TRIG_REG           0x0c  /* any value works */
  61#define PCI1724_BOARD_ID_REG            0x10
  62#define PCI1724_BOARD_ID_MASK           (0xf << 0)
  63
  64static const struct comedi_lrange adv_pci1724_ao_ranges = {
  65        4, {
  66                BIP_RANGE(10),
  67                RANGE_mA(0, 20),
  68                RANGE_mA(4, 20),
  69                RANGE_unitless(0, 1)
  70        }
  71};
  72
  73static int adv_pci1724_dac_idle(struct comedi_device *dev,
  74                                struct comedi_subdevice *s,
  75                                struct comedi_insn *insn,
  76                                unsigned long context)
  77{
  78        unsigned int status;
  79
  80        status = inl(dev->iobase + PCI1724_SYNC_CTRL_REG);
  81        if ((status & PCI1724_SYNC_CTRL_DACSTAT) == 0)
  82                return 0;
  83        return -EBUSY;
  84}
  85
  86static int adv_pci1724_insn_write(struct comedi_device *dev,
  87                                  struct comedi_subdevice *s,
  88                                  struct comedi_insn *insn,
  89                                  unsigned int *data)
  90{
  91        unsigned long mode = (unsigned long)s->private;
  92        unsigned int chan = CR_CHAN(insn->chanspec);
  93        unsigned int ctrl;
  94        int ret;
  95        int i;
  96
  97        ctrl = PCI1724_DAC_CTRL_GX(chan) | PCI1724_DAC_CTRL_CX(chan) | mode;
  98
  99        /* turn off synchronous mode */
 100        outl(0, dev->iobase + PCI1724_SYNC_CTRL_REG);
 101
 102        for (i = 0; i < insn->n; ++i) {
 103                unsigned int val = data[i];
 104
 105                ret = comedi_timeout(dev, s, insn, adv_pci1724_dac_idle, 0);
 106                if (ret)
 107                        return ret;
 108
 109                outl(ctrl | PCI1724_DAC_CTRL_DATA(val),
 110                     dev->iobase + PCI1724_DAC_CTRL_REG);
 111
 112                s->readback[chan] = val;
 113        }
 114
 115        return insn->n;
 116}
 117
 118static int adv_pci1724_auto_attach(struct comedi_device *dev,
 119                                   unsigned long context_unused)
 120{
 121        struct pci_dev *pcidev = comedi_to_pci_dev(dev);
 122        struct comedi_subdevice *s;
 123        unsigned int board_id;
 124        int ret;
 125
 126        ret = comedi_pci_enable(dev);
 127        if (ret)
 128                return ret;
 129
 130        dev->iobase = pci_resource_start(pcidev, 2);
 131        board_id = inl(dev->iobase + PCI1724_BOARD_ID_REG);
 132        dev_info(dev->class_dev, "board id: %d\n",
 133                 board_id & PCI1724_BOARD_ID_MASK);
 134
 135        ret = comedi_alloc_subdevices(dev, 3);
 136        if (ret)
 137                return ret;
 138
 139        /* Analog Output subdevice */
 140        s = &dev->subdevices[0];
 141        s->type         = COMEDI_SUBD_AO;
 142        s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND;
 143        s->n_chan       = 32;
 144        s->maxdata      = 0x3fff;
 145        s->range_table  = &adv_pci1724_ao_ranges;
 146        s->insn_write   = adv_pci1724_insn_write;
 147        s->private      = (void *)PCI1724_DAC_CTRL_MODE_NORMAL;
 148
 149        ret = comedi_alloc_subdev_readback(s);
 150        if (ret)
 151                return ret;
 152
 153        /* Offset Calibration subdevice */
 154        s = &dev->subdevices[1];
 155        s->type         = COMEDI_SUBD_CALIB;
 156        s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
 157        s->n_chan       = 32;
 158        s->maxdata      = 0x3fff;
 159        s->insn_write   = adv_pci1724_insn_write;
 160        s->private      = (void *)PCI1724_DAC_CTRL_MODE_OFFSET;
 161
 162        ret = comedi_alloc_subdev_readback(s);
 163        if (ret)
 164                return ret;
 165
 166        /* Gain Calibration subdevice */
 167        s = &dev->subdevices[2];
 168        s->type         = COMEDI_SUBD_CALIB;
 169        s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
 170        s->n_chan       = 32;
 171        s->maxdata      = 0x3fff;
 172        s->insn_write   = adv_pci1724_insn_write;
 173        s->private      = (void *)PCI1724_DAC_CTRL_MODE_GAIN;
 174
 175        return comedi_alloc_subdev_readback(s);
 176}
 177
 178static struct comedi_driver adv_pci1724_driver = {
 179        .driver_name    = "adv_pci1724",
 180        .module         = THIS_MODULE,
 181        .auto_attach    = adv_pci1724_auto_attach,
 182        .detach         = comedi_pci_detach,
 183};
 184
 185static int adv_pci1724_pci_probe(struct pci_dev *dev,
 186                                 const struct pci_device_id *id)
 187{
 188        return comedi_pci_auto_config(dev, &adv_pci1724_driver,
 189                                      id->driver_data);
 190}
 191
 192static const struct pci_device_id adv_pci1724_pci_table[] = {
 193        { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1724) },
 194        { 0 }
 195};
 196MODULE_DEVICE_TABLE(pci, adv_pci1724_pci_table);
 197
 198static struct pci_driver adv_pci1724_pci_driver = {
 199        .name           = "adv_pci1724",
 200        .id_table       = adv_pci1724_pci_table,
 201        .probe          = adv_pci1724_pci_probe,
 202        .remove         = comedi_pci_auto_unconfig,
 203};
 204module_comedi_pci_driver(adv_pci1724_driver, adv_pci1724_pci_driver);
 205
 206MODULE_AUTHOR("Frank Mori Hess <fmh6jj@gmail.com>");
 207MODULE_DESCRIPTION("Advantech PCI-1724U Comedi driver");
 208MODULE_LICENSE("GPL");
 209