linux/drivers/staging/comedi/drivers/adv_pci1724.c
<<
>>
Prefs
   1/*
   2    comedi/drivers/adv_pci1724.c
   3    This is a driver for the Advantech PCI-1724U card.
   4
   5    Author:  Frank Mori Hess <fmh6jj@gmail.com>
   6    Copyright (C) 2013 GnuBIO Inc
   7
   8    COMEDI - Linux Control and Measurement Device Interface
   9    Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
  10
  11    This program is free software; you can redistribute it and/or modify
  12    it under the terms of the GNU General Public License as published by
  13    the Free Software Foundation; either version 2 of the License, or
  14    (at your option) any later version.
  15
  16    This program is distributed in the hope that it will be useful,
  17    but WITHOUT ANY WARRANTY; without even the implied warranty of
  18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  19    GNU General Public License for more details.
  20*/
  21
  22/*
  23
  24Driver: adv_1724
  25Description: Advantech PCI-1724U
  26Author: Frank Mori Hess <fmh6jj@gmail.com>
  27Status: works
  28Updated: 2013-02-09
  29Devices: [Advantech] PCI-1724U (adv_pci1724)
  30
  31Subdevice 0 is the analog output.
  32Subdevice 1 is the offset calibration for the analog output.
  33Subdevice 2 is the gain calibration for the analog output.
  34
  35The calibration offset and gains have quite a large effect
  36on the analog output, so it is possible to adjust the analog output to
  37have an output range significantly different from the board's
  38nominal output ranges.  For a calibrated +/- 10V range, the analog
  39output's offset will be set somewhere near mid-range (0x2000) and its
  40gain will be near maximum (0x3fff).
  41
  42There is really no difference between the board's documented 0-20mA
  43versus 4-20mA output ranges.  To pick one or the other is simply a matter
  44of adjusting the offset and gain calibration until the board outputs in
  45the desired range.
  46
  47Configuration options:
  48   None
  49
  50Manual configuration of comedi devices is not supported by this driver;
  51supported PCI devices are configured as comedi devices automatically.
  52
  53*/
  54
  55#include <linux/module.h>
  56#include <linux/delay.h>
  57#include <linux/pci.h>
  58
  59#include "../comedidev.h"
  60
  61#define PCI_VENDOR_ID_ADVANTECH 0x13fe
  62
  63#define NUM_AO_CHANNELS 32
  64
  65/* register offsets */
  66enum board_registers {
  67        DAC_CONTROL_REG = 0x0,
  68        SYNC_OUTPUT_REG = 0x4,
  69        EEPROM_CONTROL_REG = 0x8,
  70        SYNC_OUTPUT_TRIGGER_REG = 0xc,
  71        BOARD_ID_REG = 0x10
  72};
  73
  74/* bit definitions for registers */
  75enum dac_control_contents {
  76        DAC_DATA_MASK = 0x3fff,
  77        DAC_DESTINATION_MASK = 0xc000,
  78        DAC_NORMAL_MODE = 0xc000,
  79        DAC_OFFSET_MODE = 0x8000,
  80        DAC_GAIN_MODE = 0x4000,
  81        DAC_CHANNEL_SELECT_MASK = 0xf0000,
  82        DAC_GROUP_SELECT_MASK = 0xf00000
  83};
  84
  85static uint32_t dac_data_bits(uint16_t dac_data)
  86{
  87        return dac_data & DAC_DATA_MASK;
  88}
  89
  90static uint32_t dac_channel_select_bits(unsigned channel)
  91{
  92        return (channel << 16) & DAC_CHANNEL_SELECT_MASK;
  93}
  94
  95static uint32_t dac_group_select_bits(unsigned group)
  96{
  97        return (1 << (20 + group)) & DAC_GROUP_SELECT_MASK;
  98}
  99
 100static uint32_t dac_channel_and_group_select_bits(unsigned comedi_channel)
 101{
 102        return dac_channel_select_bits(comedi_channel % 8) |
 103                dac_group_select_bits(comedi_channel / 8);
 104}
 105
 106enum sync_output_contents {
 107        SYNC_MODE = 0x1,
 108        DAC_BUSY = 0x2, /* dac state machine is not ready */
 109};
 110
 111enum sync_output_trigger_contents {
 112        SYNC_TRIGGER_BITS = 0x0 /* any value works */
 113};
 114
 115enum board_id_contents {
 116        BOARD_ID_MASK = 0xf
 117};
 118
 119static const struct comedi_lrange ao_ranges_1724 = {
 120        4, {
 121                BIP_RANGE(10),
 122                RANGE_mA(0, 20),
 123                RANGE_mA(4, 20),
 124                RANGE_unitless(0, 1)
 125        }
 126};
 127
 128/* this structure is for data unique to this hardware driver. */
 129struct adv_pci1724_private {
 130        int ao_value[NUM_AO_CHANNELS];
 131        int offset_value[NUM_AO_CHANNELS];
 132        int gain_value[NUM_AO_CHANNELS];
 133};
 134
 135static int wait_for_dac_idle(struct comedi_device *dev)
 136{
 137        static const int timeout = 10000;
 138        int i;
 139
 140        for (i = 0; i < timeout; ++i) {
 141                if ((inl(dev->iobase + SYNC_OUTPUT_REG) & DAC_BUSY) == 0)
 142                        break;
 143                udelay(1);
 144        }
 145        if (i == timeout) {
 146                dev_err(dev->class_dev,
 147                        "Timed out waiting for dac to become idle\n");
 148                return -EIO;
 149        }
 150        return 0;
 151}
 152
 153static int set_dac(struct comedi_device *dev, unsigned mode, unsigned channel,
 154                   unsigned data)
 155{
 156        int retval;
 157        unsigned control_bits;
 158
 159        retval = wait_for_dac_idle(dev);
 160        if (retval < 0)
 161                return retval;
 162
 163        control_bits = mode;
 164        control_bits |= dac_channel_and_group_select_bits(channel);
 165        control_bits |= dac_data_bits(data);
 166        outl(control_bits, dev->iobase + DAC_CONTROL_REG);
 167        return 0;
 168}
 169
 170static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
 171                    struct comedi_insn *insn, unsigned int *data)
 172{
 173        struct adv_pci1724_private *devpriv = dev->private;
 174        int channel = CR_CHAN(insn->chanspec);
 175        int retval;
 176        int i;
 177
 178        /* turn off synchronous mode */
 179        outl(0, dev->iobase + SYNC_OUTPUT_REG);
 180
 181        for (i = 0; i < insn->n; ++i) {
 182                retval = set_dac(dev, DAC_NORMAL_MODE, channel, data[i]);
 183                if (retval < 0)
 184                        return retval;
 185                devpriv->ao_value[channel] = data[i];
 186        }
 187        return insn->n;
 188}
 189
 190static int ao_readback_insn(struct comedi_device *dev,
 191                            struct comedi_subdevice *s,
 192                            struct comedi_insn *insn, unsigned int *data)
 193{
 194        struct adv_pci1724_private *devpriv = dev->private;
 195        int channel = CR_CHAN(insn->chanspec);
 196        int i;
 197
 198        if (devpriv->ao_value[channel] < 0) {
 199                dev_err(dev->class_dev,
 200                        "Cannot read back channels which have not yet been written to\n");
 201                return -EIO;
 202        }
 203        for (i = 0; i < insn->n; i++)
 204                data[i] = devpriv->ao_value[channel];
 205
 206        return insn->n;
 207}
 208
 209static int offset_write_insn(struct comedi_device *dev,
 210                             struct comedi_subdevice *s,
 211                             struct comedi_insn *insn, unsigned int *data)
 212{
 213        struct adv_pci1724_private *devpriv = dev->private;
 214        int channel = CR_CHAN(insn->chanspec);
 215        int retval;
 216        int i;
 217
 218        /* turn off synchronous mode */
 219        outl(0, dev->iobase + SYNC_OUTPUT_REG);
 220
 221        for (i = 0; i < insn->n; ++i) {
 222                retval = set_dac(dev, DAC_OFFSET_MODE, channel, data[i]);
 223                if (retval < 0)
 224                        return retval;
 225                devpriv->offset_value[channel] = data[i];
 226        }
 227
 228        return insn->n;
 229}
 230
 231static int offset_read_insn(struct comedi_device *dev,
 232                            struct comedi_subdevice *s,
 233                            struct comedi_insn *insn, unsigned int *data)
 234{
 235        struct adv_pci1724_private *devpriv = dev->private;
 236        unsigned int channel = CR_CHAN(insn->chanspec);
 237        int i;
 238
 239        if (devpriv->offset_value[channel] < 0) {
 240                dev_err(dev->class_dev,
 241                        "Cannot read back channels which have not yet been written to\n");
 242                return -EIO;
 243        }
 244        for (i = 0; i < insn->n; i++)
 245                data[i] = devpriv->offset_value[channel];
 246
 247        return insn->n;
 248}
 249
 250static int gain_write_insn(struct comedi_device *dev,
 251                           struct comedi_subdevice *s,
 252                           struct comedi_insn *insn, unsigned int *data)
 253{
 254        struct adv_pci1724_private *devpriv = dev->private;
 255        int channel = CR_CHAN(insn->chanspec);
 256        int retval;
 257        int i;
 258
 259        /* turn off synchronous mode */
 260        outl(0, dev->iobase + SYNC_OUTPUT_REG);
 261
 262        for (i = 0; i < insn->n; ++i) {
 263                retval = set_dac(dev, DAC_GAIN_MODE, channel, data[i]);
 264                if (retval < 0)
 265                        return retval;
 266                devpriv->gain_value[channel] = data[i];
 267        }
 268
 269        return insn->n;
 270}
 271
 272static int gain_read_insn(struct comedi_device *dev,
 273                          struct comedi_subdevice *s, struct comedi_insn *insn,
 274                          unsigned int *data)
 275{
 276        struct adv_pci1724_private *devpriv = dev->private;
 277        unsigned int channel = CR_CHAN(insn->chanspec);
 278        int i;
 279
 280        if (devpriv->gain_value[channel] < 0) {
 281                dev_err(dev->class_dev,
 282                        "Cannot read back channels which have not yet been written to\n");
 283                return -EIO;
 284        }
 285        for (i = 0; i < insn->n; i++)
 286                data[i] = devpriv->gain_value[channel];
 287
 288        return insn->n;
 289}
 290
 291/* Allocate and initialize the subdevice structures.
 292 */
 293static int setup_subdevices(struct comedi_device *dev)
 294{
 295        struct comedi_subdevice *s;
 296        int ret;
 297
 298        ret = comedi_alloc_subdevices(dev, 3);
 299        if (ret)
 300                return ret;
 301
 302        /* analog output subdevice */
 303        s = &dev->subdevices[0];
 304        s->type = COMEDI_SUBD_AO;
 305        s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND;
 306        s->n_chan = NUM_AO_CHANNELS;
 307        s->maxdata = 0x3fff;
 308        s->range_table = &ao_ranges_1724;
 309        s->insn_read = ao_readback_insn;
 310        s->insn_write = ao_winsn;
 311
 312        /* offset calibration */
 313        s = &dev->subdevices[1];
 314        s->type = COMEDI_SUBD_CALIB;
 315        s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
 316        s->n_chan = NUM_AO_CHANNELS;
 317        s->insn_read = offset_read_insn;
 318        s->insn_write = offset_write_insn;
 319        s->maxdata = 0x3fff;
 320
 321        /* gain calibration */
 322        s = &dev->subdevices[2];
 323        s->type = COMEDI_SUBD_CALIB;
 324        s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
 325        s->n_chan = NUM_AO_CHANNELS;
 326        s->insn_read = gain_read_insn;
 327        s->insn_write = gain_write_insn;
 328        s->maxdata = 0x3fff;
 329
 330        return 0;
 331}
 332
 333static int adv_pci1724_auto_attach(struct comedi_device *dev,
 334                                   unsigned long context_unused)
 335{
 336        struct pci_dev *pcidev = comedi_to_pci_dev(dev);
 337        struct adv_pci1724_private *devpriv;
 338        int i;
 339        int retval;
 340        unsigned int board_id;
 341
 342        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 343        if (!devpriv)
 344                return -ENOMEM;
 345
 346        /* init software copies of output values to indicate we don't know
 347         * what the output value is since it has never been written. */
 348        for (i = 0; i < NUM_AO_CHANNELS; ++i) {
 349                devpriv->ao_value[i] = -1;
 350                devpriv->offset_value[i] = -1;
 351                devpriv->gain_value[i] = -1;
 352        }
 353
 354        retval = comedi_pci_enable(dev);
 355        if (retval)
 356                return retval;
 357
 358        dev->iobase = pci_resource_start(pcidev, 2);
 359        board_id = inl(dev->iobase + BOARD_ID_REG) & BOARD_ID_MASK;
 360        dev_info(dev->class_dev, "board id: %d\n", board_id);
 361
 362        retval = setup_subdevices(dev);
 363        if (retval < 0)
 364                return retval;
 365
 366        dev_info(dev->class_dev, "%s (pci %s) attached, board id: %u\n",
 367                 dev->board_name, pci_name(pcidev), board_id);
 368        return 0;
 369}
 370
 371static struct comedi_driver adv_pci1724_driver = {
 372        .driver_name = "adv_pci1724",
 373        .module = THIS_MODULE,
 374        .auto_attach = adv_pci1724_auto_attach,
 375        .detach = comedi_pci_disable,
 376};
 377
 378static int adv_pci1724_pci_probe(struct pci_dev *dev,
 379                                 const struct pci_device_id *id)
 380{
 381        return comedi_pci_auto_config(dev, &adv_pci1724_driver,
 382                                      id->driver_data);
 383}
 384
 385static const struct pci_device_id adv_pci1724_pci_table[] = {
 386        { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1724) },
 387        { 0 }
 388};
 389MODULE_DEVICE_TABLE(pci, adv_pci1724_pci_table);
 390
 391static struct pci_driver adv_pci1724_pci_driver = {
 392        .name = "adv_pci1724",
 393        .id_table = adv_pci1724_pci_table,
 394        .probe = adv_pci1724_pci_probe,
 395        .remove = comedi_pci_auto_unconfig,
 396};
 397
 398module_comedi_pci_driver(adv_pci1724_driver, adv_pci1724_pci_driver);
 399
 400MODULE_AUTHOR("Frank Mori Hess <fmh6jj@gmail.com>");
 401MODULE_DESCRIPTION("Advantech PCI-1724U Comedi driver");
 402MODULE_LICENSE("GPL");
 403