1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * adv_pci1724.c 4 * Comedi driver for the Advantech PCI-1724U card. 5 * 6 * Author: Frank Mori Hess <fmh6jj@gmail.com> 7 * Copyright (C) 2013 GnuBIO Inc 8 * 9 * COMEDI - Linux Control and Measurement Device Interface 10 * Copyright (C) 1997-8 David A. Schleef <ds@schleef.org> 11 */ 12 13 /* 14 * Driver: adv_pci1724 15 * Description: Advantech PCI-1724U 16 * Devices: [Advantech] PCI-1724U (adv_pci1724) 17 * Author: Frank Mori Hess <fmh6jj@gmail.com> 18 * Updated: 2013-02-09 19 * Status: works 20 * 21 * Configuration Options: not applicable, uses comedi PCI auto config 22 * 23 * Subdevice 0 is the analog output. 24 * Subdevice 1 is the offset calibration for the analog output. 25 * Subdevice 2 is the gain calibration for the analog output. 26 * 27 * The calibration offset and gains have quite a large effect on the 28 * analog output, so it is possible to adjust the analog output to 29 * have an output range significantly different from the board's 30 * nominal output ranges. For a calibrated +/-10V range, the analog 31 * output's offset will be set somewhere near mid-range (0x2000) and 32 * its gain will be near maximum (0x3fff). 33 * 34 * There is really no difference between the board's documented 0-20mA 35 * versus 4-20mA output ranges. To pick one or the other is simply a 36 * matter of adjusting the offset and gain calibration until the board 37 * outputs in the desired range. 38 */ 39 40 #include <linux/module.h> 41 #include <linux/comedi/comedi_pci.h> 42 43 /* 44 * PCI bar 2 Register I/O map (dev->iobase) 45 */ 46 #define PCI1724_DAC_CTRL_REG 0x00 47 #define PCI1724_DAC_CTRL_GX(x) BIT(20 + ((x) / 8)) 48 #define PCI1724_DAC_CTRL_CX(x) (((x) % 8) << 16) 49 #define PCI1724_DAC_CTRL_MODE(x) (((x) & 0x3) << 14) 50 #define PCI1724_DAC_CTRL_MODE_GAIN PCI1724_DAC_CTRL_MODE(1) 51 #define PCI1724_DAC_CTRL_MODE_OFFSET PCI1724_DAC_CTRL_MODE(2) 52 #define PCI1724_DAC_CTRL_MODE_NORMAL PCI1724_DAC_CTRL_MODE(3) 53 #define PCI1724_DAC_CTRL_MODE_MASK PCI1724_DAC_CTRL_MODE(3) 54 #define PCI1724_DAC_CTRL_DATA(x) (((x) & 0x3fff) << 0) 55 #define PCI1724_SYNC_CTRL_REG 0x04 56 #define PCI1724_SYNC_CTRL_DACSTAT BIT(1) 57 #define PCI1724_SYNC_CTRL_SYN BIT(0) 58 #define PCI1724_EEPROM_CTRL_REG 0x08 59 #define PCI1724_SYNC_TRIG_REG 0x0c /* any value works */ 60 #define PCI1724_BOARD_ID_REG 0x10 61 #define PCI1724_BOARD_ID_MASK (0xf << 0) 62 63 static const struct comedi_lrange adv_pci1724_ao_ranges = { 64 4, { 65 BIP_RANGE(10), 66 RANGE_mA(0, 20), 67 RANGE_mA(4, 20), 68 RANGE_unitless(0, 1) 69 } 70 }; 71 72 static int adv_pci1724_dac_idle(struct comedi_device *dev, 73 struct comedi_subdevice *s, 74 struct comedi_insn *insn, 75 unsigned long context) 76 { 77 unsigned int status; 78 79 status = inl(dev->iobase + PCI1724_SYNC_CTRL_REG); 80 if ((status & PCI1724_SYNC_CTRL_DACSTAT) == 0) 81 return 0; 82 return -EBUSY; 83 } 84 85 static int adv_pci1724_insn_write(struct comedi_device *dev, 86 struct comedi_subdevice *s, 87 struct comedi_insn *insn, 88 unsigned int *data) 89 { 90 unsigned long mode = (unsigned long)s->private; 91 unsigned int chan = CR_CHAN(insn->chanspec); 92 unsigned int ctrl; 93 int ret; 94 int i; 95 96 ctrl = PCI1724_DAC_CTRL_GX(chan) | PCI1724_DAC_CTRL_CX(chan) | mode; 97 98 /* turn off synchronous mode */ 99 outl(0, dev->iobase + PCI1724_SYNC_CTRL_REG); 100 101 for (i = 0; i < insn->n; ++i) { 102 unsigned int val = data[i]; 103 104 ret = comedi_timeout(dev, s, insn, adv_pci1724_dac_idle, 0); 105 if (ret) 106 return ret; 107 108 outl(ctrl | PCI1724_DAC_CTRL_DATA(val), 109 dev->iobase + PCI1724_DAC_CTRL_REG); 110 111 s->readback[chan] = val; 112 } 113 114 return insn->n; 115 } 116 117 static int adv_pci1724_auto_attach(struct comedi_device *dev, 118 unsigned long context_unused) 119 { 120 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 121 struct comedi_subdevice *s; 122 unsigned int board_id; 123 int ret; 124 125 ret = comedi_pci_enable(dev); 126 if (ret) 127 return ret; 128 129 dev->iobase = pci_resource_start(pcidev, 2); 130 board_id = inl(dev->iobase + PCI1724_BOARD_ID_REG); 131 dev_info(dev->class_dev, "board id: %d\n", 132 board_id & PCI1724_BOARD_ID_MASK); 133 134 ret = comedi_alloc_subdevices(dev, 3); 135 if (ret) 136 return ret; 137 138 /* Analog Output subdevice */ 139 s = &dev->subdevices[0]; 140 s->type = COMEDI_SUBD_AO; 141 s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND; 142 s->n_chan = 32; 143 s->maxdata = 0x3fff; 144 s->range_table = &adv_pci1724_ao_ranges; 145 s->insn_write = adv_pci1724_insn_write; 146 s->private = (void *)PCI1724_DAC_CTRL_MODE_NORMAL; 147 148 ret = comedi_alloc_subdev_readback(s); 149 if (ret) 150 return ret; 151 152 /* Offset Calibration subdevice */ 153 s = &dev->subdevices[1]; 154 s->type = COMEDI_SUBD_CALIB; 155 s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; 156 s->n_chan = 32; 157 s->maxdata = 0x3fff; 158 s->insn_write = adv_pci1724_insn_write; 159 s->private = (void *)PCI1724_DAC_CTRL_MODE_OFFSET; 160 161 ret = comedi_alloc_subdev_readback(s); 162 if (ret) 163 return ret; 164 165 /* Gain Calibration subdevice */ 166 s = &dev->subdevices[2]; 167 s->type = COMEDI_SUBD_CALIB; 168 s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL; 169 s->n_chan = 32; 170 s->maxdata = 0x3fff; 171 s->insn_write = adv_pci1724_insn_write; 172 s->private = (void *)PCI1724_DAC_CTRL_MODE_GAIN; 173 174 return comedi_alloc_subdev_readback(s); 175 } 176 177 static struct comedi_driver adv_pci1724_driver = { 178 .driver_name = "adv_pci1724", 179 .module = THIS_MODULE, 180 .auto_attach = adv_pci1724_auto_attach, 181 .detach = comedi_pci_detach, 182 }; 183 184 static int adv_pci1724_pci_probe(struct pci_dev *dev, 185 const struct pci_device_id *id) 186 { 187 return comedi_pci_auto_config(dev, &adv_pci1724_driver, 188 id->driver_data); 189 } 190 191 static const struct pci_device_id adv_pci1724_pci_table[] = { 192 { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1724) }, 193 { 0 } 194 }; 195 MODULE_DEVICE_TABLE(pci, adv_pci1724_pci_table); 196 197 static struct pci_driver adv_pci1724_pci_driver = { 198 .name = "adv_pci1724", 199 .id_table = adv_pci1724_pci_table, 200 .probe = adv_pci1724_pci_probe, 201 .remove = comedi_pci_auto_unconfig, 202 }; 203 module_comedi_pci_driver(adv_pci1724_driver, adv_pci1724_pci_driver); 204 205 MODULE_AUTHOR("Frank Mori Hess <fmh6jj@gmail.com>"); 206 MODULE_DESCRIPTION("Advantech PCI-1724U Comedi driver"); 207 MODULE_LICENSE("GPL"); 208