| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * 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> |
| */ |
| |
| /* |
| * 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 "../comedi_pci.h" |
| |
| /* |
| * PCI bar 2 Register I/O map (dev->iobase) |
| */ |
| #define PCI1724_DAC_CTRL_REG 0x00 |
| #define PCI1724_DAC_CTRL_GX(x) BIT(20 + ((x) / 8)) |
| #define PCI1724_DAC_CTRL_CX(x) (((x) % 8) << 16) |
| #define PCI1724_DAC_CTRL_MODE(x) (((x) & 0x3) << 14) |
| #define PCI1724_DAC_CTRL_MODE_GAIN PCI1724_DAC_CTRL_MODE(1) |
| #define PCI1724_DAC_CTRL_MODE_OFFSET PCI1724_DAC_CTRL_MODE(2) |
| #define PCI1724_DAC_CTRL_MODE_NORMAL PCI1724_DAC_CTRL_MODE(3) |
| #define PCI1724_DAC_CTRL_MODE_MASK PCI1724_DAC_CTRL_MODE(3) |
| #define PCI1724_DAC_CTRL_DATA(x) (((x) & 0x3fff) << 0) |
| #define PCI1724_SYNC_CTRL_REG 0x04 |
| #define PCI1724_SYNC_CTRL_DACSTAT BIT(1) |
| #define PCI1724_SYNC_CTRL_SYN BIT(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), |
| RANGE_mA(4, 20), |
| RANGE_unitless(0, 1) |
| } |
| }; |
| |
| static int adv_pci1724_dac_idle(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned long context) |
| { |
| unsigned int status; |
| |
| status = inl(dev->iobase + PCI1724_SYNC_CTRL_REG); |
| if ((status & PCI1724_SYNC_CTRL_DACSTAT) == 0) |
| return 0; |
| return -EBUSY; |
| } |
| |
| static int adv_pci1724_insn_write(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned long mode = (unsigned long)s->private; |
| unsigned int chan = CR_CHAN(insn->chanspec); |
| unsigned int ctrl; |
| int ret; |
| int i; |
| |
| ctrl = PCI1724_DAC_CTRL_GX(chan) | PCI1724_DAC_CTRL_CX(chan) | mode; |
| |
| /* turn off synchronous mode */ |
| outl(0, dev->iobase + PCI1724_SYNC_CTRL_REG); |
| |
| for (i = 0; i < insn->n; ++i) { |
| unsigned int val = data[i]; |
| |
| ret = comedi_timeout(dev, s, insn, adv_pci1724_dac_idle, 0); |
| if (ret) |
| return ret; |
| |
| outl(ctrl | PCI1724_DAC_CTRL_DATA(val), |
| dev->iobase + PCI1724_DAC_CTRL_REG); |
| |
| s->readback[chan] = val; |
| } |
| |
| return insn->n; |
| } |
| |
| 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 */ |
| s = &dev->subdevices[0]; |
| 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 subdevice */ |
| s = &dev->subdevices[1]; |
| 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 subdevice */ |
| s = &dev->subdevices[2]; |
| 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; |
| |
| return comedi_alloc_subdev_readback(s); |
| } |
| |
| static struct comedi_driver adv_pci1724_driver = { |
| .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, |
| const struct pci_device_id *id) |
| { |
| return comedi_pci_auto_config(dev, &adv_pci1724_driver, |
| id->driver_data); |
| } |
| |
| static const struct pci_device_id 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"); |