diff options
Diffstat (limited to 'drivers/staging/comedi/drivers/adv_pci1724.c')
-rw-r--r-- | drivers/staging/comedi/drivers/adv_pci1724.c | 471 |
1 files changed, 145 insertions, 326 deletions
diff --git a/drivers/staging/comedi/drivers/adv_pci1724.c b/drivers/staging/comedi/drivers/adv_pci1724.c index 2697758b1ed9..a8d28403262e 100644 --- a/drivers/staging/comedi/drivers/adv_pci1724.c +++ b/drivers/staging/comedi/drivers/adv_pci1724.c @@ -1,122 +1,76 @@ /* - 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. -*/ + * adv_pci1724.c + * Comedi 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. + */ /* - -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. - -*/ + * Driver: adv_pci1724 + * Description: Advantech PCI-1724U + * Devices: (Advantech) PCI-1724U [adv_pci1724] + * Author: Frank Mori Hess <fmh6jj@gmail.com> + * Updated: 2013-02-09 + * Status: works + * + * Configuration Options: not applicable, uses comedi PCI auto config + * + * 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. + */ #include <linux/module.h> -#include <linux/delay.h> #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 = { +/* + * PCI bar 2 Register I/O map (dev->iobase) + */ +#define PCI1724_DAC_CTRL_REG 0x00 +#define PCI1724_DAC_CTRL_GX(x) (1 << (20 + ((x) / 8))) +#define PCI1724_DAC_CTRL_CX(x) (((x) % 8) << 16) +#define PCI1724_DAC_CTRL_MODE_GAIN (1 << 14) +#define PCI1724_DAC_CTRL_MODE_OFFSET (2 << 14) +#define PCI1724_DAC_CTRL_MODE_NORMAL (3 << 14) +#define PCI1724_DAC_CTRL_MODE_MASK (3 << 14) +#define PCI1724_DAC_CTRL_DATA(x) (((x) & 0x3fff) << 0) +#define PCI1724_SYNC_CTRL_REG 0x04 +#define PCI1724_SYNC_CTRL_DACSTAT (1 << 1) +#define PCI1724_SYNC_CTRL_SYN (1 << 0) +#define PCI1724_EEPROM_CTRL_REG 0x08 +#define PCI1724_SYNC_TRIG_REG 0x0c /* any value works */ +#define PCI1724_BOARD_ID_REG 0x10 +#define PCI1724_BOARD_ID_MASK (0xf << 0) + +static const struct comedi_lrange adv_pci1724_ao_ranges = { 4, { BIP_RANGE(10), RANGE_mA(0, 20), @@ -125,254 +79,120 @@ static const struct comedi_lrange 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) { - dev_err(dev->class_dev, - "Timed out waiting for dac to become idle\n"); - 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) +static int adv_pci1724_dac_idle(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned long context) { - struct adv_pci1724_private *devpriv = dev->private; - int channel = CR_CHAN(insn->chanspec); - int retval; - int i; + unsigned int status; - /* 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; + status = inl(dev->iobase + PCI1724_SYNC_CTRL_REG); + if ((status & PCI1724_SYNC_CTRL_DACSTAT) == 0) + return 0; + return -EBUSY; } -static int ao_readback_insn(struct comedi_device *dev, - struct comedi_subdevice *s, - struct comedi_insn *insn, unsigned int *data) +static int adv_pci1724_insn_write(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) { - dev_err(dev->class_dev, - "Cannot read back channels which have not yet been written to\n"); - 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); + unsigned long mode = (unsigned long)s->private; + unsigned int chan = CR_CHAN(insn->chanspec); + unsigned int ctrl; + int ret; int i; - if (devpriv->offset_value[channel] < 0) { - dev_err(dev->class_dev, - "Cannot read back channels which have not yet been written to\n"); - 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; + ctrl = PCI1724_DAC_CTRL_GX(chan) | PCI1724_DAC_CTRL_CX(chan) | mode; /* turn off synchronous mode */ - outl(0, dev->iobase + SYNC_OUTPUT_REG); + outl(0, dev->iobase + PCI1724_SYNC_CTRL_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]; - } + unsigned int val = data[i]; - return insn->n; -} + ret = comedi_timeout(dev, s, insn, adv_pci1724_dac_idle, 0); + if (ret) + return ret; -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; + outl(ctrl | PCI1724_DAC_CTRL_DATA(val), + dev->iobase + PCI1724_DAC_CTRL_REG); - if (devpriv->gain_value[channel] < 0) { - dev_err(dev->class_dev, - "Cannot read back channels which have not yet been written to\n"); - return -EIO; + s->readback[chan] = val; } - 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) +static int adv_pci1724_auto_attach(struct comedi_device *dev, + unsigned long context_unused) { + struct pci_dev *pcidev = comedi_to_pci_dev(dev); struct comedi_subdevice *s; + unsigned int board_id; int ret; + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + dev->iobase = pci_resource_start(pcidev, 2); + board_id = inl(dev->iobase + PCI1724_BOARD_ID_REG); + dev_info(dev->class_dev, "board id: %d\n", + board_id & PCI1724_BOARD_ID_MASK); + ret = comedi_alloc_subdevices(dev, 3); if (ret) return ret; - /* analog output subdevice */ + /* 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; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND; + s->n_chan = 32; + s->maxdata = 0x3fff; + s->range_table = &adv_pci1724_ao_ranges; + s->insn_write = adv_pci1724_insn_write; + s->private = (void *)PCI1724_DAC_CTRL_MODE_NORMAL; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; - /* offset calibration */ + /* Offset Calibration subdevice */ 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; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 32; + s->maxdata = 0x3fff; + s->insn_write = adv_pci1724_insn_write; + s->private = (void *)PCI1724_DAC_CTRL_MODE_OFFSET; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; - /* gain calibration */ + /* Gain Calibration subdevice */ 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 = comedi_alloc_devpriv(dev, sizeof(*devpriv)); - if (!devpriv) - 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) { - devpriv->ao_value[i] = -1; - devpriv->offset_value[i] = -1; - devpriv->gain_value[i] = -1; - } - - retval = comedi_pci_enable(dev); - 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; + s->type = COMEDI_SUBD_CALIB; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; + s->n_chan = 32; + s->maxdata = 0x3fff; + s->insn_write = adv_pci1724_insn_write; + s->private = (void *)PCI1724_DAC_CTRL_MODE_GAIN; + + ret = comedi_alloc_subdev_readback(s); + if (ret) + return ret; - dev_info(dev->class_dev, "%s (pci %s) attached, board id: %u\n", - dev->board_name, pci_name(pcidev), board_id); return 0; } static struct comedi_driver adv_pci1724_driver = { - .driver_name = "adv_pci1724", - .module = THIS_MODULE, - .auto_attach = adv_pci1724_auto_attach, - .detach = comedi_pci_detach, + .driver_name = "adv_pci1724", + .module = THIS_MODULE, + .auto_attach = adv_pci1724_auto_attach, + .detach = comedi_pci_detach, }; static int adv_pci1724_pci_probe(struct pci_dev *dev, @@ -389,12 +209,11 @@ static const struct pci_device_id adv_pci1724_pci_table[] = { 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, + .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>"); |