| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * COMEDI driver for Advantech PCI-1720U |
| * Copyright (c) 2015 H Hartley Sweeten <hsweeten@visionengravers.com> |
| * |
| * Separated from the adv_pci1710 driver written by: |
| * Michal Dobes <dobes@tesnet.cz> |
| * |
| * COMEDI - Linux Control and Measurement Device Interface |
| * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
| */ |
| |
| /* |
| * Driver: adv_pci1720 |
| * Description: 4-channel Isolated D/A Output board |
| * Devices: [Advantech] PCI-7120U (adv_pci1720) |
| * Author: H Hartley Sweeten <hsweeten@visionengravers.com> |
| * Updated: Fri, 29 Oct 2015 17:19:35 -0700 |
| * Status: untested |
| * |
| * Configuration options: not applicable, uses PCI auto config |
| * |
| * The PCI-1720 has 4 isolated 12-bit analog output channels with multiple |
| * output ranges. It also has a BoardID switch to allow differentiating |
| * multiple boards in the system. |
| * |
| * The analog outputs can operate in two modes, immediate and synchronized. |
| * This driver currently does not support the synchronized output mode. |
| * |
| * Jumpers JP1 to JP4 are used to set the current sink ranges for each |
| * analog output channel. In order to use the current sink ranges, the |
| * unipolar 5V range must be used. The voltage output and sink output for |
| * each channel is available on the connector as separate pins. |
| * |
| * Jumper JP5 controls the "hot" reset state of the analog outputs. |
| * Depending on its setting, the analog outputs will either keep the |
| * last settings and output values or reset to the default state after |
| * a "hot" reset. The default state for all channels is uniploar 5V range |
| * and all the output values are 0V. To allow this feature to work, the |
| * analog outputs are not "reset" when the driver attaches. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/delay.h> |
| |
| #include "../comedi_pci.h" |
| |
| /* |
| * PCI BAR2 Register map (dev->iobase) |
| */ |
| #define PCI1720_AO_LSB_REG(x) (0x00 + ((x) * 2)) |
| #define PCI1720_AO_MSB_REG(x) (0x01 + ((x) * 2)) |
| #define PCI1720_AO_RANGE_REG 0x08 |
| #define PCI1720_AO_RANGE(c, r) (((r) & 0x3) << ((c) * 2)) |
| #define PCI1720_AO_RANGE_MASK(c) PCI1720_AO_RANGE((c), 0x3) |
| #define PCI1720_SYNC_REG 0x09 |
| #define PCI1720_SYNC_CTRL_REG 0x0f |
| #define PCI1720_SYNC_CTRL_SC0 BIT(0) |
| #define PCI1720_BOARDID_REG 0x14 |
| |
| static const struct comedi_lrange pci1720_ao_range = { |
| 4, { |
| UNI_RANGE(5), |
| UNI_RANGE(10), |
| BIP_RANGE(5), |
| BIP_RANGE(10) |
| } |
| }; |
| |
| static int pci1720_ao_insn_write(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int chan = CR_CHAN(insn->chanspec); |
| unsigned int range = CR_RANGE(insn->chanspec); |
| unsigned int val; |
| int i; |
| |
| /* set the channel range and polarity */ |
| val = inb(dev->iobase + PCI1720_AO_RANGE_REG); |
| val &= ~PCI1720_AO_RANGE_MASK(chan); |
| val |= PCI1720_AO_RANGE(chan, range); |
| outb(val, dev->iobase + PCI1720_AO_RANGE_REG); |
| |
| val = s->readback[chan]; |
| for (i = 0; i < insn->n; i++) { |
| val = data[i]; |
| |
| outb(val & 0xff, dev->iobase + PCI1720_AO_LSB_REG(chan)); |
| outb((val >> 8) & 0xff, dev->iobase + PCI1720_AO_MSB_REG(chan)); |
| |
| /* conversion time is 2us (500 kHz throughput) */ |
| usleep_range(2, 100); |
| } |
| |
| s->readback[chan] = val; |
| |
| return insn->n; |
| } |
| |
| static int pci1720_di_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| data[1] = inb(dev->iobase + PCI1720_BOARDID_REG); |
| |
| return insn->n; |
| } |
| |
| static int pci1720_auto_attach(struct comedi_device *dev, |
| unsigned long context) |
| { |
| struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
| struct comedi_subdevice *s; |
| int ret; |
| |
| ret = comedi_pci_enable(dev); |
| if (ret) |
| return ret; |
| dev->iobase = pci_resource_start(pcidev, 2); |
| |
| ret = comedi_alloc_subdevices(dev, 2); |
| if (ret) |
| return ret; |
| |
| /* Analog Output subdevice */ |
| s = &dev->subdevices[0]; |
| s->type = COMEDI_SUBD_AO; |
| s->subdev_flags = SDF_WRITABLE; |
| s->n_chan = 4; |
| s->maxdata = 0x0fff; |
| s->range_table = &pci1720_ao_range; |
| s->insn_write = pci1720_ao_insn_write; |
| |
| ret = comedi_alloc_subdev_readback(s); |
| if (ret) |
| return ret; |
| |
| /* Digital Input subdevice (BoardID SW1) */ |
| s = &dev->subdevices[1]; |
| s->type = COMEDI_SUBD_DI; |
| s->subdev_flags = SDF_READABLE; |
| s->n_chan = 4; |
| s->maxdata = 1; |
| s->range_table = &range_digital; |
| s->insn_bits = pci1720_di_insn_bits; |
| |
| /* disable synchronized output, channels update when written */ |
| outb(0, dev->iobase + PCI1720_SYNC_CTRL_REG); |
| |
| return 0; |
| } |
| |
| static struct comedi_driver adv_pci1720_driver = { |
| .driver_name = "adv_pci1720", |
| .module = THIS_MODULE, |
| .auto_attach = pci1720_auto_attach, |
| .detach = comedi_pci_detach, |
| }; |
| |
| static int adv_pci1720_pci_probe(struct pci_dev *dev, |
| const struct pci_device_id *id) |
| { |
| return comedi_pci_auto_config(dev, &adv_pci1720_driver, |
| id->driver_data); |
| } |
| |
| static const struct pci_device_id adv_pci1720_pci_table[] = { |
| { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1720) }, |
| { 0 } |
| }; |
| MODULE_DEVICE_TABLE(pci, adv_pci1720_pci_table); |
| |
| static struct pci_driver adv_pci1720_pci_driver = { |
| .name = "adv_pci1720", |
| .id_table = adv_pci1720_pci_table, |
| .probe = adv_pci1720_pci_probe, |
| .remove = comedi_pci_auto_unconfig, |
| }; |
| module_comedi_pci_driver(adv_pci1720_driver, adv_pci1720_pci_driver); |
| |
| MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>"); |
| MODULE_DESCRIPTION("Comedi driver for Advantech PCI-1720 Analog Output board"); |
| MODULE_LICENSE("GPL"); |