[PATCH 01/13 v2] staging: comedi: adv_pci1724: new driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



New comedi driver for Advantech PCI-1724U, submitted by Frank Mori Hess.
He based it against Ubuntu 12.04's kernel, so I've made minimal changes
to get it to compile against the latest kernel and cleaned up the errors
reported by checkpatch.pl.

Other changes to fit in with the current comedi style and to make it
work with the current auto-configuration mechanism will be done in
subsequenit patches.  (The old-style auto-configuration using the
`attach()` method in the `struct comedi_driver` no longer works.)

Signed-off-by: Ian Abbott <abbotti@xxxxxxxxx>
Cc: Frank Mori Hess <fmh6jj@xxxxxxxxx>
---
v2: Fix missing `if (ret)` after call to `comedi_alloc_subdevices()`.
---
 drivers/staging/comedi/Kconfig               |  10 +
 drivers/staging/comedi/drivers/Makefile      |   1 +
 drivers/staging/comedi/drivers/adv_pci1724.c | 522 +++++++++++++++++++++++++++
 3 files changed, 533 insertions(+)
 create mode 100644 drivers/staging/comedi/drivers/adv_pci1724.c

diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig
index 1967852..109168c 100644
--- a/drivers/staging/comedi/Kconfig
+++ b/drivers/staging/comedi/Kconfig
@@ -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
diff --git a/drivers/staging/comedi/drivers/Makefile b/drivers/staging/comedi/drivers/Makefile
index 315e836..6cdef45 100644
--- a/drivers/staging/comedi/drivers/Makefile
+++ b/drivers/staging/comedi/drivers/Makefile
@@ -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
index 0000000..ebc1feb
--- /dev/null
+++ b/drivers/staging/comedi/drivers/adv_pci1724.c
@@ -0,0 +1,522 @@
+/*
+    comedi/drivers/adv_pci1724.c
+    This is a driver for the Advantech PCI-1724U card.
+
+    Author:  Frank Mori Hess <fmh6jj@xxxxxxxxx>
+    Copyright (C) 2013 GnuBIO Inc
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1997-8 David A. Schleef <ds@xxxxxxxxxxx>
+
+    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@xxxxxxxxx>
+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:
+   [0] - PCI bus of device (optional)
+   [1] - PCI slot of device (optional)
+
+*/
+
+#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 inline uint32_t dac_data_bits(uint16_t dac_data)
+{
+	return dac_data & DAC_DATA_MASK;
+}
+
+static inline uint32_t dac_channel_select_bits(unsigned channel)
+{
+	return (channel << 16) & DAC_CHANNEL_SELECT_MASK;
+}
+
+static inline uint32_t dac_group_select_bits(unsigned group)
+{
+	return (1 << (20 + group)) & DAC_GROUP_SELECT_MASK;
+}
+
+static inline 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
+};
+
+struct adv_pci1724_board {
+	const char *name;
+	int device_id;		/*  pci device id */
+};
+
+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 adv_pci1724_board adv_pci1724_boards[] = {
+	{
+	 .name = "pci-1724u",
+	 .device_id = 0x1724,
+	}
+};
+
+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 inline struct adv_pci1724_board *board(const struct comedi_device *dev)
+{
+	return (struct adv_pci1724_board *)dev->board_ptr;
+}
+
+/* this structure is for data unique to this hardware driver. */
+struct adv_pci1724_private {
+
+	struct pci_dev *hw_dev;
+	resource_size_t main_iobase;
+	unsigned board_id; /* 4 bit number settable via dip switches on board */
+	int ao_value[NUM_AO_CHANNELS];
+	int offset_value[NUM_AO_CHANNELS];
+	int gain_value[NUM_AO_CHANNELS];
+};
+
+/* inline function that makes it easier to
+ * access the private structure.
+ */
+static inline struct adv_pci1724_private *priv(struct comedi_device *dev)
+{
+	return dev->private;
+}
+
+static int attach(struct comedi_device *dev, struct comedi_devconfig *it);
+static void detach(struct comedi_device *dev);
+static struct comedi_driver driver_adv_pci1724 = {
+	.driver_name = "adv_pci1724",
+	.module = THIS_MODULE,
+	.attach = attach,
+	.detach = detach,
+};
+
+static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
+		    struct comedi_insn *insn, unsigned int *data);
+static int ao_readback_insn(struct comedi_device *dev,
+			    struct comedi_subdevice *s,
+			    struct comedi_insn *insn, unsigned int *data);
+static int gain_read_insn(struct comedi_device *dev,
+			   struct comedi_subdevice *s, struct comedi_insn *insn,
+			   unsigned int *data);
+static int gain_write_insn(struct comedi_device *dev,
+			    struct comedi_subdevice *s,
+			    struct comedi_insn *insn, unsigned int *data);
+static int offset_read_insn(struct comedi_device *dev,
+			   struct comedi_subdevice *s, struct comedi_insn *insn,
+			   unsigned int *data);
+static int offset_write_insn(struct comedi_device *dev,
+			    struct comedi_subdevice *s,
+			    struct comedi_insn *insn, unsigned int *data);
+
+static int driver_adv_pci1724_pci_probe(struct pci_dev *dev,
+					const struct pci_device_id *ent)
+{
+	return comedi_pci_auto_config(dev, &driver_adv_pci1724);
+}
+
+static void driver_adv_pci1724_pci_remove(struct pci_dev *dev)
+{
+	comedi_pci_auto_unconfig(dev);
+}
+
+static struct pci_driver driver_adv_pci1724_pci_driver = {
+	.id_table = adv_pci1724_pci_table,
+	.probe = &driver_adv_pci1724_pci_probe,
+	.remove = &driver_adv_pci1724_pci_remove
+};
+
+static int __init driver_adv_pci1724_init_module(void)
+{
+	int retval;
+
+	retval = comedi_driver_register(&driver_adv_pci1724);
+	if (retval < 0)
+		return retval;
+
+	driver_adv_pci1724_pci_driver.name =
+		(char *)driver_adv_pci1724.driver_name;
+	return pci_register_driver(&driver_adv_pci1724_pci_driver);
+}
+
+static void __exit driver_adv_pci1724_cleanup_module(void)
+{
+	pci_unregister_driver(&driver_adv_pci1724_pci_driver);
+	comedi_driver_unregister(&driver_adv_pci1724);
+}
+
+module_init(driver_adv_pci1724_init_module);
+module_exit(driver_adv_pci1724_cleanup_module);
+
+/* 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 = &ao_ranges_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 attach(struct comedi_device *dev, struct comedi_devconfig *it)
+{
+	struct pci_dev *pcidev = NULL;
+	int i;
+	int retval;
+
+	pr_info("comedi%d: adv_pci1724\n", dev->minor);
+
+	/*
+	 * Allocate the private structure area.
+	 */
+	dev->private = kzalloc(sizeof(struct adv_pci1724_private), GFP_KERNEL);
+	if (!dev->private)
+		return -ENOMEM;
+	/* 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) {
+		priv(dev)->ao_value[i] = -1;
+		priv(dev)->offset_value[i] = -1;
+		priv(dev)->gain_value[i] = -1;
+	}
+
+	/*
+	 * Probe the device to determine what device in the series it is.
+	 */
+	for_each_pci_dev(pcidev) {
+		/*  is it not a computer boards card? */
+		if (pcidev->vendor != PCI_VENDOR_ID_ADVANTECH)
+			continue;
+		/*  loop through cards supported by this driver */
+		for (i = 0; i < ARRAY_SIZE(adv_pci1724_boards); ++i) {
+			if (adv_pci1724_boards[i].device_id != pcidev->device)
+				continue;
+			/*  was a particular bus/slot requested? */
+			if (it->options[0] || it->options[1]) {
+				/*  are we on the wrong bus/slot? */
+				if (pcidev->bus->number != it->options[0] ||
+				    PCI_SLOT(pcidev->devfn) != it->options[1]) {
+					continue;
+				}
+			}
+			priv(dev)->hw_dev = pcidev;
+			dev->board_ptr = adv_pci1724_boards + i;
+			break;
+		}
+		if (dev->board_ptr)
+			break;
+	}
+
+	if (dev->board_ptr == NULL) {
+		pr_info("No supported Advantech 1724 card found\n");
+		return -EIO;
+	}
+
+	pr_info("Found %s on bus %i, slot %i\n", board(dev)->name,
+		pcidev->bus->number, PCI_SLOT(pcidev->devfn));
+
+	if (comedi_pci_enable(pcidev, driver_adv_pci1724.driver_name)) {
+		pr_warn(" failed to enable PCI device and request regions\n");
+		return -EIO;
+	}
+
+	/* Initialize dev->board_name */
+	dev->board_name = board(dev)->name;
+
+	priv(dev)->main_iobase =
+	    pci_resource_start(pcidev, 2);
+
+	priv(dev)->board_id = inl(priv(dev)->main_iobase + BOARD_ID_REG) &
+			      BOARD_ID_MASK;
+	pr_info("board id: %d\n", priv(dev)->board_id);
+
+	retval = setup_subdevices(dev);
+	if (retval < 0)
+		return retval;
+
+	return 0;
+}
+
+static void detach(struct comedi_device *dev)
+{
+	pr_info("comedi%d: adv_pci1724: remove\n", dev->minor);
+
+	if (priv(dev)) {
+		if (priv(dev)->hw_dev) {
+			if (priv(dev)->main_iobase)
+				comedi_pci_disable(priv(dev)->hw_dev);
+
+			pci_dev_put(priv(dev)->hw_dev);
+		}
+	}
+}
+
+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(priv(dev)->main_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 |= data & DAC_DATA_MASK;
+	outl(control_bits, priv(dev)->main_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)
+{
+	int channel = CR_CHAN(insn->chanspec);
+	int retval;
+	int i;
+
+	/* turn off synchronous mode */
+	outl(0, priv(dev)->main_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;
+		priv(dev)->ao_value[channel] = data[i];
+	}
+	return 1;
+}
+
+static int ao_readback_insn(struct comedi_device *dev,
+			    struct comedi_subdevice *s,
+			    struct comedi_insn *insn, unsigned int *data)
+{
+	int channel = CR_CHAN(insn->chanspec);
+
+	if (priv(dev)->ao_value[channel] < 0) {
+		comedi_error(dev,
+			     "Cannot read back channels which have not yet been written to.");
+		return -EIO;
+	}
+	data[0] = priv(dev)->ao_value[channel];
+
+	return 1;
+}
+
+static int offset_write_insn(struct comedi_device *dev,
+			     struct comedi_subdevice *s,
+			     struct comedi_insn *insn, unsigned int *data)
+{
+	int channel = CR_CHAN(insn->chanspec);
+	int retval;
+	int i;
+
+	/* turn off synchronous mode */
+	outl(0, priv(dev)->main_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;
+		priv(dev)->offset_value[channel] = data[i];
+	}
+
+	return 1;
+}
+
+static int offset_read_insn(struct comedi_device *dev,
+			    struct comedi_subdevice *s,
+			    struct comedi_insn *insn, unsigned int *data)
+{
+	unsigned int channel = CR_CHAN(insn->chanspec);
+
+	if (priv(dev)->offset_value[channel] < 0) {
+		comedi_error(dev,
+			     "Cannot read back channels which have not yet been written to.");
+		return -EIO;
+	}
+	data[0] = priv(dev)->offset_value[channel];
+
+	return 1;
+}
+
+static int gain_write_insn(struct comedi_device *dev,
+			   struct comedi_subdevice *s,
+			   struct comedi_insn *insn, unsigned int *data)
+{
+	int channel = CR_CHAN(insn->chanspec);
+	int retval;
+	int i;
+
+	/* turn off synchronous mode */
+	outl(0, priv(dev)->main_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;
+		priv(dev)->gain_value[channel] = data[i];
+	}
+
+	return 1;
+}
+
+static int gain_read_insn(struct comedi_device *dev,
+			  struct comedi_subdevice *s, struct comedi_insn *insn,
+			  unsigned int *data)
+{
+	unsigned int channel = CR_CHAN(insn->chanspec);
+
+	if (priv(dev)->gain_value[channel] < 0) {
+		comedi_error(dev,
+			     "Cannot read back channels which have not yet been written to.");
+		return -EIO;
+	}
+	data[0] = priv(dev)->gain_value[channel];
+
+	return 1;
+}
+
+MODULE_AUTHOR("Comedi http://www.comedi.org";);
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");
-- 
1.8.1.2

_______________________________________________
devel mailing list
devel@xxxxxxxxxxxxxxxxxxxxxx
http://driverdev.linuxdriverproject.org/mailman/listinfo/devel


[Index of Archives]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux