staging: comedi: adv_pci1724: new driver
authorFrank Mori Hess <fmh6jj@gmail.com>
Tue, 12 Mar 2013 11:42:32 +0000 (11:42 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 12 Mar 2013 16:09:08 +0000 (09:09 -0700)
New comedi driver for Advantech PCI-1724U with modifications by Ian
Abbott <abbotti@mev.co.uk>.

Signed-off-by: Ian Abbott <abbotti@mev.co.uk>
Cc: Frank Mori Hess <fmh6jj@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/staging/comedi/Kconfig
drivers/staging/comedi/drivers/Makefile
drivers/staging/comedi/drivers/adv_pci1724.c [new file with mode: 0644]

index 1967852..109168c 100644 (file)
@@ -728,6 +728,16 @@ config COMEDI_ADV_PCI1723
          To compile this driver as a module, choose M here: the module will be
          called adv_pci1723.
 
+config COMEDI_ADV_PCI1724
+       tristate "Advantech PCI-1724U support"
+       ---help---
+         Enable support for Advantech PCI-1724U cards.  These are 32-channel
+         analog output cards with voltage and current loop output ranges and
+         14-bit resolution.
+
+         To compile this driver as a module, choose M here: the module will be
+         called adv_pci1724.
+
 config COMEDI_ADV_PCI_DIO
        tristate "Advantech PCI DIO card support"
        select COMEDI_8255
index 315e836..6cdef45 100644 (file)
@@ -75,6 +75,7 @@ obj-$(CONFIG_COMEDI_ADL_PCI9111)      += adl_pci9111.o
 obj-$(CONFIG_COMEDI_ADL_PCI9118)       += adl_pci9118.o
 obj-$(CONFIG_COMEDI_ADV_PCI1710)       += adv_pci1710.o
 obj-$(CONFIG_COMEDI_ADV_PCI1723)       += adv_pci1723.o
+obj-$(CONFIG_COMEDI_ADV_PCI1724)       += adv_pci1724.o
 obj-$(CONFIG_COMEDI_ADV_PCI_DIO)       += adv_pci_dio.o
 obj-$(CONFIG_COMEDI_AMPLC_DIO200)      += amplc_dio200.o
 obj-$(CONFIG_COMEDI_AMPLC_PC236)       += amplc_pc236.o
diff --git a/drivers/staging/comedi/drivers/adv_pci1724.c b/drivers/staging/comedi/drivers/adv_pci1724.c
new file mode 100644 (file)
index 0000000..f448e4d
--- /dev/null
@@ -0,0 +1,421 @@
+/*
+    comedi/drivers/adv_pci1724.c
+    This is a driver for the Advantech PCI-1724U card.
+
+    Author:  Frank Mori Hess <fmh6jj@gmail.com>
+    Copyright (C) 2013 GnuBIO Inc
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+************************************************************************/
+
+/*
+
+Driver: adv_1724
+Description: Advantech PCI-1724U
+Author: Frank Mori Hess <fmh6jj@gmail.com>
+Status: works
+Updated: 2013-02-09
+Devices: [Advantech] PCI-1724U (adv_pci1724)
+
+Subdevice 0 is the analog output.
+Subdevice 1 is the offset calibration for the analog output.
+Subdevice 2 is the gain calibration for the analog output.
+
+The calibration offset and gains have quite a large effect
+on the analog output, so it is possible to adjust the analog output to
+have an output range significantly different from the board's
+nominal output ranges.  For a calibrated +/- 10V range, the analog
+output's offset will be set somewhere near mid-range (0x2000) and its
+gain will be near maximum (0x3fff).
+
+There is really no difference between the board's documented 0-20mA
+versus 4-20mA output ranges.  To pick one or the other is simply a matter
+of adjusting the offset and gain calibration until the board outputs in
+the desired range.
+
+Configuration options:
+   None
+
+Manual configuration of comedi devices is not supported by this driver;
+supported PCI devices are configured as comedi devices automatically.
+
+*/
+
+#include <linux/pci.h>
+
+#include "../comedidev.h"
+
+#define PCI_VENDOR_ID_ADVANTECH        0x13fe
+
+#define NUM_AO_CHANNELS 32
+
+/* register offsets */
+enum board_registers {
+       DAC_CONTROL_REG = 0x0,
+       SYNC_OUTPUT_REG = 0x4,
+       EEPROM_CONTROL_REG = 0x8,
+       SYNC_OUTPUT_TRIGGER_REG = 0xc,
+       BOARD_ID_REG = 0x10
+};
+
+/* bit definitions for registers */
+enum dac_control_contents {
+       DAC_DATA_MASK = 0x3fff,
+       DAC_DESTINATION_MASK = 0xc000,
+       DAC_NORMAL_MODE = 0xc000,
+       DAC_OFFSET_MODE = 0x8000,
+       DAC_GAIN_MODE = 0x4000,
+       DAC_CHANNEL_SELECT_MASK = 0xf0000,
+       DAC_GROUP_SELECT_MASK = 0xf00000
+};
+
+static uint32_t dac_data_bits(uint16_t dac_data)
+{
+       return dac_data & DAC_DATA_MASK;
+}
+
+static uint32_t dac_channel_select_bits(unsigned channel)
+{
+       return (channel << 16) & DAC_CHANNEL_SELECT_MASK;
+}
+
+static uint32_t dac_group_select_bits(unsigned group)
+{
+       return (1 << (20 + group)) & DAC_GROUP_SELECT_MASK;
+}
+
+static uint32_t dac_channel_and_group_select_bits(unsigned comedi_channel)
+{
+       return dac_channel_select_bits(comedi_channel % 8) |
+               dac_group_select_bits(comedi_channel / 8);
+}
+
+enum sync_output_contents {
+       SYNC_MODE = 0x1,
+       DAC_BUSY = 0x2, /* dac state machine is not ready */
+};
+
+enum sync_output_trigger_contents {
+       SYNC_TRIGGER_BITS = 0x0 /* any value works */
+};
+
+enum board_id_contents {
+       BOARD_ID_MASK = 0xf
+};
+
+static const struct comedi_lrange ao_ranges_1724 = { 4,
+       {
+               BIP_RANGE(10),
+               RANGE_mA(0, 20),
+               RANGE_mA(4, 20),
+               RANGE_unitless(0, 1)
+       }
+};
+
+static const struct comedi_lrange *const ao_range_list_1724[NUM_AO_CHANNELS] = {
+       [0 ... NUM_AO_CHANNELS - 1] = &ao_ranges_1724,
+};
+
+/* this structure is for data unique to this hardware driver. */
+struct adv_pci1724_private {
+       int ao_value[NUM_AO_CHANNELS];
+       int offset_value[NUM_AO_CHANNELS];
+       int gain_value[NUM_AO_CHANNELS];
+};
+
+static int wait_for_dac_idle(struct comedi_device *dev)
+{
+       static const int timeout = 10000;
+       int i;
+
+       for (i = 0; i < timeout; ++i) {
+               if ((inl(dev->iobase + SYNC_OUTPUT_REG) & DAC_BUSY) == 0)
+                       break;
+               udelay(1);
+       }
+       if (i == timeout) {
+               comedi_error(dev, "Timed out waiting for dac to become idle.");
+               return -EIO;
+       }
+       return 0;
+}
+
+static int set_dac(struct comedi_device *dev, unsigned mode, unsigned channel,
+                  unsigned data)
+{
+       int retval;
+       unsigned control_bits;
+
+       retval = wait_for_dac_idle(dev);
+       if (retval < 0)
+               return retval;
+
+       control_bits = mode;
+       control_bits |= dac_channel_and_group_select_bits(channel);
+       control_bits |= dac_data_bits(data);
+       outl(control_bits, dev->iobase + DAC_CONTROL_REG);
+       return 0;
+}
+
+static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
+                   struct comedi_insn *insn, unsigned int *data)
+{
+       struct adv_pci1724_private *devpriv = dev->private;
+       int channel = CR_CHAN(insn->chanspec);
+       int retval;
+       int i;
+
+       /* turn off synchronous mode */
+       outl(0, dev->iobase + SYNC_OUTPUT_REG);
+
+       for (i = 0; i < insn->n; ++i) {
+               retval = set_dac(dev, DAC_NORMAL_MODE, channel, data[i]);
+               if (retval < 0)
+                       return retval;
+               devpriv->ao_value[channel] = data[i];
+       }
+       return insn->n;
+}
+
+static int ao_readback_insn(struct comedi_device *dev,
+                           struct comedi_subdevice *s,
+                           struct comedi_insn *insn, unsigned int *data)
+{
+       struct adv_pci1724_private *devpriv = dev->private;
+       int channel = CR_CHAN(insn->chanspec);
+       int i;
+
+       if (devpriv->ao_value[channel] < 0) {
+               comedi_error(dev,
+                            "Cannot read back channels which have not yet been written to.");
+               return -EIO;
+       }
+       for (i = 0; i < insn->n; i++)
+               data[i] = devpriv->ao_value[channel];
+
+       return insn->n;
+}
+
+static int offset_write_insn(struct comedi_device *dev,
+                            struct comedi_subdevice *s,
+                            struct comedi_insn *insn, unsigned int *data)
+{
+       struct adv_pci1724_private *devpriv = dev->private;
+       int channel = CR_CHAN(insn->chanspec);
+       int retval;
+       int i;
+
+       /* turn off synchronous mode */
+       outl(0, dev->iobase + SYNC_OUTPUT_REG);
+
+       for (i = 0; i < insn->n; ++i) {
+               retval = set_dac(dev, DAC_OFFSET_MODE, channel, data[i]);
+               if (retval < 0)
+                       return retval;
+               devpriv->offset_value[channel] = data[i];
+       }
+
+       return insn->n;
+}
+
+static int offset_read_insn(struct comedi_device *dev,
+                           struct comedi_subdevice *s,
+                           struct comedi_insn *insn, unsigned int *data)
+{
+       struct adv_pci1724_private *devpriv = dev->private;
+       unsigned int channel = CR_CHAN(insn->chanspec);
+       int i;
+
+       if (devpriv->offset_value[channel] < 0) {
+               comedi_error(dev,
+                            "Cannot read back channels which have not yet been written to.");
+               return -EIO;
+       }
+       for (i = 0; i < insn->n; i++)
+               data[i] = devpriv->offset_value[channel];
+
+       return insn->n;
+}
+
+static int gain_write_insn(struct comedi_device *dev,
+                          struct comedi_subdevice *s,
+                          struct comedi_insn *insn, unsigned int *data)
+{
+       struct adv_pci1724_private *devpriv = dev->private;
+       int channel = CR_CHAN(insn->chanspec);
+       int retval;
+       int i;
+
+       /* turn off synchronous mode */
+       outl(0, dev->iobase + SYNC_OUTPUT_REG);
+
+       for (i = 0; i < insn->n; ++i) {
+               retval = set_dac(dev, DAC_GAIN_MODE, channel, data[i]);
+               if (retval < 0)
+                       return retval;
+               devpriv->gain_value[channel] = data[i];
+       }
+
+       return insn->n;
+}
+
+static int gain_read_insn(struct comedi_device *dev,
+                         struct comedi_subdevice *s, struct comedi_insn *insn,
+                         unsigned int *data)
+{
+       struct adv_pci1724_private *devpriv = dev->private;
+       unsigned int channel = CR_CHAN(insn->chanspec);
+       int i;
+
+       if (devpriv->gain_value[channel] < 0) {
+               comedi_error(dev,
+                            "Cannot read back channels which have not yet been written to.");
+               return -EIO;
+       }
+       for (i = 0; i < insn->n; i++)
+               data[i] = devpriv->gain_value[channel];
+
+       return insn->n;
+}
+
+/* Allocate and initialize the subdevice structures.
+ */
+static int setup_subdevices(struct comedi_device *dev)
+{
+       struct comedi_subdevice *s;
+       int ret;
+
+       ret = comedi_alloc_subdevices(dev, 3);
+       if (ret)
+               return ret;
+
+       /* analog output subdevice */
+       s = &dev->subdevices[0];
+       s->type = COMEDI_SUBD_AO;
+       s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND;
+       s->n_chan = NUM_AO_CHANNELS;
+       s->maxdata = 0x3fff;
+       s->range_table_list = ao_range_list_1724;
+       s->insn_read = ao_readback_insn;
+       s->insn_write = ao_winsn;
+
+       /* offset calibration */
+       s = &dev->subdevices[1];
+       s->type = COMEDI_SUBD_CALIB;
+       s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+       s->n_chan = NUM_AO_CHANNELS;
+       s->insn_read = offset_read_insn;
+       s->insn_write = offset_write_insn;
+       s->maxdata = 0x3fff;
+
+       /* gain calibration */
+       s = &dev->subdevices[2];
+       s->type = COMEDI_SUBD_CALIB;
+       s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
+       s->n_chan = NUM_AO_CHANNELS;
+       s->insn_read = gain_read_insn;
+       s->insn_write = gain_write_insn;
+       s->maxdata = 0x3fff;
+
+       return 0;
+}
+
+static int adv_pci1724_auto_attach(struct comedi_device *dev,
+                                  unsigned long context_unused)
+{
+       struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+       struct adv_pci1724_private *devpriv;
+       int i;
+       int retval;
+       unsigned int board_id;
+
+       devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
+       if (!devpriv)
+               return -ENOMEM;
+       dev->private = devpriv;
+
+       /* init software copies of output values to indicate we don't know
+        * what the output value is since it has never been written. */
+       for (i = 0; i < NUM_AO_CHANNELS; ++i) {
+               devpriv->ao_value[i] = -1;
+               devpriv->offset_value[i] = -1;
+               devpriv->gain_value[i] = -1;
+       }
+
+       dev->board_name = dev->driver->driver_name;
+
+       retval = comedi_pci_enable(pcidev, dev->board_name);
+       if (retval)
+               return retval;
+
+       dev->iobase = pci_resource_start(pcidev, 2);
+       board_id = inl(dev->iobase + BOARD_ID_REG) & BOARD_ID_MASK;
+       dev_info(dev->class_dev, "board id: %d\n", board_id);
+
+       retval = setup_subdevices(dev);
+       if (retval < 0)
+               return retval;
+
+       dev_info(dev->class_dev, "%s (pci %s) attached, board id: %u\n",
+                dev->board_name, pci_name(pcidev), board_id);
+       return 0;
+}
+
+static void adv_pci1724_detach(struct comedi_device *dev)
+{
+       struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+
+       if (pcidev && dev->iobase) {
+               comedi_pci_disable(pcidev);
+               dev_info(dev->class_dev, "detached\n");
+       }
+}
+
+static struct comedi_driver adv_pci1724_driver = {
+       .driver_name = "adv_pci1724",
+       .module = THIS_MODULE,
+       .auto_attach = adv_pci1724_auto_attach,
+       .detach = adv_pci1724_detach,
+};
+
+static int adv_pci1724_pci_probe(struct pci_dev *dev,
+                                const struct pci_device_id *id)
+{
+       return comedi_pci_auto_config(dev, &adv_pci1724_driver,
+                                     id->driver_data);
+}
+
+static DEFINE_PCI_DEVICE_TABLE(adv_pci1724_pci_table) = {
+       { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1724) },
+       { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci1724_pci_table);
+
+static struct pci_driver adv_pci1724_pci_driver = {
+       .name = "adv_pci1724",
+       .id_table = adv_pci1724_pci_table,
+       .probe = adv_pci1724_pci_probe,
+       .remove = comedi_pci_auto_unconfig,
+};
+
+module_comedi_pci_driver(adv_pci1724_driver, adv_pci1724_pci_driver);
+
+MODULE_AUTHOR("Frank Mori Hess <fmh6jj@gmail.com>");
+MODULE_DESCRIPTION("Advantech PCI-1724U Comedi driver");
+MODULE_LICENSE("GPL");