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