staging: comedi: adv_pci1724: new driver
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>
This commit is contained in:
Родитель
94cc409be7
Коммит
f165d815d5
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
Загрузка…
Ссылка в новой задаче