1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * comedi/drivers/mf6x4.c 4 * Driver for Humusoft MF634 and MF624 Data acquisition cards 5 * 6 * COMEDI - Linux Control and Measurement Device Interface 7 * Copyright (C) 2000 David A. Schleef <ds@schleef.org> 8 */ 9 /* 10 * Driver: mf6x4 11 * Description: Humusoft MF634 and MF624 Data acquisition card driver 12 * Devices: [Humusoft] MF634 (mf634), MF624 (mf624) 13 * Author: Rostislav Lisovy <lisovy@gmail.com> 14 * Status: works 15 * Updated: 16 * Configuration Options: none 17 */ 18 19 #include <linux/module.h> 20 #include <linux/delay.h> 21 #include <linux/comedi/comedi_pci.h> 22 23 /* Registers present in BAR0 memory region */ 24 #define MF624_GPIOC_REG 0x54 25 26 #define MF6X4_GPIOC_EOLC BIT(17) /* End Of Last Conversion */ 27 #define MF6X4_GPIOC_LDAC BIT(23) /* Load DACs */ 28 #define MF6X4_GPIOC_DACEN BIT(26) 29 30 /* BAR1 registers */ 31 #define MF6X4_ADDATA_REG 0x00 32 #define MF6X4_ADCTRL_REG 0x00 33 #define MF6X4_ADCTRL_CHAN(x) BIT(chan) 34 #define MF6X4_DIN_REG 0x10 35 #define MF6X4_DIN_MASK 0xff 36 #define MF6X4_DOUT_REG 0x10 37 #define MF6X4_ADSTART_REG 0x20 38 #define MF6X4_DAC_REG(x) (0x20 + ((x) * 2)) 39 40 /* BAR2 registers */ 41 #define MF634_GPIOC_REG 0x68 42 43 enum mf6x4_boardid { 44 BOARD_MF634, 45 BOARD_MF624, 46 }; 47 48 struct mf6x4_board { 49 const char *name; 50 /* We need to keep track of the order of BARs used by the cards */ 51 unsigned int bar_nums[3]; 52 }; 53 54 static const struct mf6x4_board mf6x4_boards[] = { 55 [BOARD_MF634] = { 56 .name = "mf634", 57 .bar_nums = {0, 2, 3}, 58 }, 59 [BOARD_MF624] = { 60 .name = "mf624", 61 .bar_nums = {0, 2, 4}, 62 }, 63 }; 64 65 struct mf6x4_private { 66 /* 67 * Documentation for both MF634 and MF624 describes registers 68 * present in BAR0, 1 and 2 regions. 69 * The real (i.e. in HW) BAR numbers are different for MF624 70 * and MF634 yet we will call them 0, 1, 2 71 */ 72 void __iomem *bar0_mem; 73 void __iomem *bar2_mem; 74 75 /* 76 * This configuration register has the same function and fields 77 * for both cards however it lies in different BARs on different 78 * offsets -- this variable makes the access easier 79 */ 80 void __iomem *gpioc_reg; 81 }; 82 83 static int mf6x4_di_insn_bits(struct comedi_device *dev, 84 struct comedi_subdevice *s, 85 struct comedi_insn *insn, 86 unsigned int *data) 87 { 88 data[1] = ioread16(dev->mmio + MF6X4_DIN_REG) & MF6X4_DIN_MASK; 89 90 return insn->n; 91 } 92 93 static int mf6x4_do_insn_bits(struct comedi_device *dev, 94 struct comedi_subdevice *s, 95 struct comedi_insn *insn, 96 unsigned int *data) 97 { 98 if (comedi_dio_update_state(s, data)) 99 iowrite16(s->state, dev->mmio + MF6X4_DOUT_REG); 100 101 data[1] = s->state; 102 103 return insn->n; 104 } 105 106 static int mf6x4_ai_eoc(struct comedi_device *dev, 107 struct comedi_subdevice *s, 108 struct comedi_insn *insn, 109 unsigned long context) 110 { 111 struct mf6x4_private *devpriv = dev->private; 112 unsigned int status; 113 114 /* EOLC goes low at end of conversion. */ 115 status = ioread32(devpriv->gpioc_reg); 116 if ((status & MF6X4_GPIOC_EOLC) == 0) 117 return 0; 118 return -EBUSY; 119 } 120 121 static int mf6x4_ai_insn_read(struct comedi_device *dev, 122 struct comedi_subdevice *s, 123 struct comedi_insn *insn, 124 unsigned int *data) 125 { 126 unsigned int chan = CR_CHAN(insn->chanspec); 127 unsigned int d; 128 int ret; 129 int i; 130 131 /* Set the ADC channel number in the scan list */ 132 iowrite16(MF6X4_ADCTRL_CHAN(chan), dev->mmio + MF6X4_ADCTRL_REG); 133 134 for (i = 0; i < insn->n; i++) { 135 /* Trigger ADC conversion by reading ADSTART */ 136 ioread16(dev->mmio + MF6X4_ADSTART_REG); 137 138 ret = comedi_timeout(dev, s, insn, mf6x4_ai_eoc, 0); 139 if (ret) 140 return ret; 141 142 /* Read the actual value */ 143 d = ioread16(dev->mmio + MF6X4_ADDATA_REG); 144 d &= s->maxdata; 145 /* munge the 2's complement data to offset binary */ 146 data[i] = comedi_offset_munge(s, d); 147 } 148 149 iowrite16(0x0, dev->mmio + MF6X4_ADCTRL_REG); 150 151 return insn->n; 152 } 153 154 static int mf6x4_ao_insn_write(struct comedi_device *dev, 155 struct comedi_subdevice *s, 156 struct comedi_insn *insn, 157 unsigned int *data) 158 { 159 struct mf6x4_private *devpriv = dev->private; 160 unsigned int chan = CR_CHAN(insn->chanspec); 161 unsigned int val = s->readback[chan]; 162 unsigned int gpioc; 163 int i; 164 165 /* Enable instantaneous update of converters outputs + Enable DACs */ 166 gpioc = ioread32(devpriv->gpioc_reg); 167 iowrite32((gpioc & ~MF6X4_GPIOC_LDAC) | MF6X4_GPIOC_DACEN, 168 devpriv->gpioc_reg); 169 170 for (i = 0; i < insn->n; i++) { 171 val = data[i]; 172 iowrite16(val, dev->mmio + MF6X4_DAC_REG(chan)); 173 } 174 s->readback[chan] = val; 175 176 return insn->n; 177 } 178 179 static int mf6x4_auto_attach(struct comedi_device *dev, unsigned long context) 180 { 181 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 182 const struct mf6x4_board *board = NULL; 183 struct mf6x4_private *devpriv; 184 struct comedi_subdevice *s; 185 int ret; 186 187 if (context < ARRAY_SIZE(mf6x4_boards)) 188 board = &mf6x4_boards[context]; 189 else 190 return -ENODEV; 191 192 dev->board_ptr = board; 193 dev->board_name = board->name; 194 195 ret = comedi_pci_enable(dev); 196 if (ret) 197 return ret; 198 199 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 200 if (!devpriv) 201 return -ENOMEM; 202 203 devpriv->bar0_mem = pci_ioremap_bar(pcidev, board->bar_nums[0]); 204 if (!devpriv->bar0_mem) 205 return -ENODEV; 206 207 dev->mmio = pci_ioremap_bar(pcidev, board->bar_nums[1]); 208 if (!dev->mmio) 209 return -ENODEV; 210 211 devpriv->bar2_mem = pci_ioremap_bar(pcidev, board->bar_nums[2]); 212 if (!devpriv->bar2_mem) 213 return -ENODEV; 214 215 if (board == &mf6x4_boards[BOARD_MF634]) 216 devpriv->gpioc_reg = devpriv->bar2_mem + MF634_GPIOC_REG; 217 else 218 devpriv->gpioc_reg = devpriv->bar0_mem + MF624_GPIOC_REG; 219 220 ret = comedi_alloc_subdevices(dev, 4); 221 if (ret) 222 return ret; 223 224 /* Analog Input subdevice */ 225 s = &dev->subdevices[0]; 226 s->type = COMEDI_SUBD_AI; 227 s->subdev_flags = SDF_READABLE | SDF_GROUND; 228 s->n_chan = 8; 229 s->maxdata = 0x3fff; 230 s->range_table = &range_bipolar10; 231 s->insn_read = mf6x4_ai_insn_read; 232 233 /* Analog Output subdevice */ 234 s = &dev->subdevices[1]; 235 s->type = COMEDI_SUBD_AO; 236 s->subdev_flags = SDF_WRITABLE; 237 s->n_chan = 8; 238 s->maxdata = 0x3fff; 239 s->range_table = &range_bipolar10; 240 s->insn_write = mf6x4_ao_insn_write; 241 242 ret = comedi_alloc_subdev_readback(s); 243 if (ret) 244 return ret; 245 246 /* Digital Input subdevice */ 247 s = &dev->subdevices[2]; 248 s->type = COMEDI_SUBD_DI; 249 s->subdev_flags = SDF_READABLE; 250 s->n_chan = 8; 251 s->maxdata = 1; 252 s->range_table = &range_digital; 253 s->insn_bits = mf6x4_di_insn_bits; 254 255 /* Digital Output subdevice */ 256 s = &dev->subdevices[3]; 257 s->type = COMEDI_SUBD_DO; 258 s->subdev_flags = SDF_WRITABLE; 259 s->n_chan = 8; 260 s->maxdata = 1; 261 s->range_table = &range_digital; 262 s->insn_bits = mf6x4_do_insn_bits; 263 264 return 0; 265 } 266 267 static void mf6x4_detach(struct comedi_device *dev) 268 { 269 struct mf6x4_private *devpriv = dev->private; 270 271 if (devpriv) { 272 if (devpriv->bar0_mem) 273 iounmap(devpriv->bar0_mem); 274 if (devpriv->bar2_mem) 275 iounmap(devpriv->bar2_mem); 276 } 277 comedi_pci_detach(dev); 278 } 279 280 static struct comedi_driver mf6x4_driver = { 281 .driver_name = "mf6x4", 282 .module = THIS_MODULE, 283 .auto_attach = mf6x4_auto_attach, 284 .detach = mf6x4_detach, 285 }; 286 287 static int mf6x4_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) 288 { 289 return comedi_pci_auto_config(dev, &mf6x4_driver, id->driver_data); 290 } 291 292 static const struct pci_device_id mf6x4_pci_table[] = { 293 { PCI_VDEVICE(HUMUSOFT, 0x0634), BOARD_MF634 }, 294 { PCI_VDEVICE(HUMUSOFT, 0x0624), BOARD_MF624 }, 295 { 0 } 296 }; 297 MODULE_DEVICE_TABLE(pci, mf6x4_pci_table); 298 299 static struct pci_driver mf6x4_pci_driver = { 300 .name = "mf6x4", 301 .id_table = mf6x4_pci_table, 302 .probe = mf6x4_pci_probe, 303 .remove = comedi_pci_auto_unconfig, 304 }; 305 306 module_comedi_pci_driver(mf6x4_driver, mf6x4_pci_driver); 307 308 MODULE_AUTHOR("Rostislav Lisovy <lisovy@gmail.com>"); 309 MODULE_DESCRIPTION("Comedi MF634 and MF624 DAQ cards driver"); 310 MODULE_LICENSE("GPL"); 311