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