1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * multiq3.c 4 * Hardware driver for Quanser Consulting MultiQ-3 board 5 * 6 * COMEDI - Linux Control and Measurement Device Interface 7 * Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se> 8 */ 9 10 /* 11 * Driver: multiq3 12 * Description: Quanser Consulting MultiQ-3 13 * Devices: [Quanser Consulting] MultiQ-3 (multiq3) 14 * Author: Anders Blomdell <anders.blomdell@control.lth.se> 15 * Status: works 16 * 17 * Configuration Options: 18 * [0] - I/O port base address 19 * [1] - IRQ (not used) 20 * [2] - Number of optional encoder chips installed on board 21 * 0 = none 22 * 1 = 2 inputs (Model -2E) 23 * 2 = 4 inputs (Model -4E) 24 * 3 = 6 inputs (Model -6E) 25 * 4 = 8 inputs (Model -8E) 26 */ 27 28 #include <linux/module.h> 29 #include <linux/comedi/comedidev.h> 30 31 /* 32 * Register map 33 */ 34 #define MULTIQ3_DI_REG 0x00 35 #define MULTIQ3_DO_REG 0x00 36 #define MULTIQ3_AO_REG 0x02 37 #define MULTIQ3_AI_REG 0x04 38 #define MULTIQ3_AI_CONV_REG 0x04 39 #define MULTIQ3_STATUS_REG 0x06 40 #define MULTIQ3_STATUS_EOC BIT(3) 41 #define MULTIQ3_STATUS_EOC_I BIT(4) 42 #define MULTIQ3_CTRL_REG 0x06 43 #define MULTIQ3_CTRL_AO_CHAN(x) (((x) & 0x7) << 0) 44 #define MULTIQ3_CTRL_RC(x) (((x) & 0x3) << 0) 45 #define MULTIQ3_CTRL_AI_CHAN(x) (((x) & 0x7) << 3) 46 #define MULTIQ3_CTRL_E_CHAN(x) (((x) & 0x7) << 3) 47 #define MULTIQ3_CTRL_EN BIT(6) 48 #define MULTIQ3_CTRL_AZ BIT(7) 49 #define MULTIQ3_CTRL_CAL BIT(8) 50 #define MULTIQ3_CTRL_SH BIT(9) 51 #define MULTIQ3_CTRL_CLK BIT(10) 52 #define MULTIQ3_CTRL_LD (3 << 11) 53 #define MULTIQ3_CLK_REG 0x08 54 #define MULTIQ3_ENC_DATA_REG 0x0c 55 #define MULTIQ3_ENC_CTRL_REG 0x0e 56 57 /* 58 * Encoder chip commands (from the programming manual) 59 */ 60 #define MULTIQ3_CLOCK_DATA 0x00 /* FCK frequency divider */ 61 #define MULTIQ3_CLOCK_SETUP 0x18 /* xfer PR0 to PSC */ 62 #define MULTIQ3_INPUT_SETUP 0x41 /* enable inputs A and B */ 63 #define MULTIQ3_QUAD_X4 0x38 /* quadrature */ 64 #define MULTIQ3_BP_RESET 0x01 /* reset byte pointer */ 65 #define MULTIQ3_CNTR_RESET 0x02 /* reset counter */ 66 #define MULTIQ3_TRSFRPR_CTR 0x08 /* xfre preset reg to counter */ 67 #define MULTIQ3_TRSFRCNTR_OL 0x10 /* xfer CNTR to OL (x and y) */ 68 #define MULTIQ3_EFLAG_RESET 0x06 /* reset E bit of flag reg */ 69 70 static void multiq3_set_ctrl(struct comedi_device *dev, unsigned int bits) 71 { 72 /* 73 * According to the programming manual, the SH and CLK bits should 74 * be kept high at all times. 75 */ 76 outw(MULTIQ3_CTRL_SH | MULTIQ3_CTRL_CLK | bits, 77 dev->iobase + MULTIQ3_CTRL_REG); 78 } 79 80 static int multiq3_ai_status(struct comedi_device *dev, 81 struct comedi_subdevice *s, 82 struct comedi_insn *insn, 83 unsigned long context) 84 { 85 unsigned int status; 86 87 status = inw(dev->iobase + MULTIQ3_STATUS_REG); 88 if (status & context) 89 return 0; 90 return -EBUSY; 91 } 92 93 static int multiq3_ai_insn_read(struct comedi_device *dev, 94 struct comedi_subdevice *s, 95 struct comedi_insn *insn, 96 unsigned int *data) 97 { 98 unsigned int chan = CR_CHAN(insn->chanspec); 99 unsigned int val; 100 int ret; 101 int i; 102 103 multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_AI_CHAN(chan)); 104 105 ret = comedi_timeout(dev, s, insn, multiq3_ai_status, 106 MULTIQ3_STATUS_EOC); 107 if (ret) 108 return ret; 109 110 for (i = 0; i < insn->n; i++) { 111 outw(0, dev->iobase + MULTIQ3_AI_CONV_REG); 112 113 ret = comedi_timeout(dev, s, insn, multiq3_ai_status, 114 MULTIQ3_STATUS_EOC_I); 115 if (ret) 116 return ret; 117 118 /* get a 16-bit sample; mask it to the subdevice resolution */ 119 val = inb(dev->iobase + MULTIQ3_AI_REG) << 8; 120 val |= inb(dev->iobase + MULTIQ3_AI_REG); 121 val &= s->maxdata; 122 123 /* munge the 2's complement value to offset binary */ 124 data[i] = comedi_offset_munge(s, val); 125 } 126 127 return insn->n; 128 } 129 130 static int multiq3_ao_insn_write(struct comedi_device *dev, 131 struct comedi_subdevice *s, 132 struct comedi_insn *insn, 133 unsigned int *data) 134 { 135 unsigned int chan = CR_CHAN(insn->chanspec); 136 unsigned int val = s->readback[chan]; 137 int i; 138 139 for (i = 0; i < insn->n; i++) { 140 val = data[i]; 141 multiq3_set_ctrl(dev, MULTIQ3_CTRL_LD | 142 MULTIQ3_CTRL_AO_CHAN(chan)); 143 outw(val, dev->iobase + MULTIQ3_AO_REG); 144 multiq3_set_ctrl(dev, 0); 145 } 146 s->readback[chan] = val; 147 148 return insn->n; 149 } 150 151 static int multiq3_di_insn_bits(struct comedi_device *dev, 152 struct comedi_subdevice *s, 153 struct comedi_insn *insn, unsigned int *data) 154 { 155 data[1] = inw(dev->iobase + MULTIQ3_DI_REG); 156 157 return insn->n; 158 } 159 160 static int multiq3_do_insn_bits(struct comedi_device *dev, 161 struct comedi_subdevice *s, 162 struct comedi_insn *insn, 163 unsigned int *data) 164 { 165 if (comedi_dio_update_state(s, data)) 166 outw(s->state, dev->iobase + MULTIQ3_DO_REG); 167 168 data[1] = s->state; 169 170 return insn->n; 171 } 172 173 static int multiq3_encoder_insn_read(struct comedi_device *dev, 174 struct comedi_subdevice *s, 175 struct comedi_insn *insn, 176 unsigned int *data) 177 { 178 unsigned int chan = CR_CHAN(insn->chanspec); 179 unsigned int val; 180 int i; 181 182 for (i = 0; i < insn->n; i++) { 183 /* select encoder channel */ 184 multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | 185 MULTIQ3_CTRL_E_CHAN(chan)); 186 187 /* reset the byte pointer */ 188 outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG); 189 190 /* latch the data */ 191 outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CTRL_REG); 192 193 /* read the 24-bit encoder data (lsb/mid/msb) */ 194 val = inb(dev->iobase + MULTIQ3_ENC_DATA_REG); 195 val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 8); 196 val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 16); 197 198 /* 199 * Munge the data so that the reset value is in the middle 200 * of the maxdata range, i.e.: 201 * 202 * real value comedi value 203 * 0xffffff 0x7fffff 1 negative count 204 * 0x000000 0x800000 reset value 205 * 0x000001 0x800001 1 positive count 206 * 207 * It's possible for the 24-bit counter to overflow but it 208 * would normally take _quite_ a few turns. A 2000 line 209 * encoder in quadrature results in 8000 counts/rev. So about 210 * 1048 turns in either direction can be measured without 211 * an overflow. 212 */ 213 data[i] = (val + ((s->maxdata + 1) >> 1)) & s->maxdata; 214 } 215 216 return insn->n; 217 } 218 219 static void multiq3_encoder_reset(struct comedi_device *dev, 220 unsigned int chan) 221 { 222 multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_E_CHAN(chan)); 223 outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG); 224 outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG); 225 outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA_REG); 226 outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG); 227 outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG); 228 outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CTRL_REG); 229 outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG); 230 } 231 232 static int multiq3_encoder_insn_config(struct comedi_device *dev, 233 struct comedi_subdevice *s, 234 struct comedi_insn *insn, 235 unsigned int *data) 236 { 237 unsigned int chan = CR_CHAN(insn->chanspec); 238 239 switch (data[0]) { 240 case INSN_CONFIG_RESET: 241 multiq3_encoder_reset(dev, chan); 242 break; 243 default: 244 return -EINVAL; 245 } 246 247 return insn->n; 248 } 249 250 static int multiq3_attach(struct comedi_device *dev, 251 struct comedi_devconfig *it) 252 { 253 struct comedi_subdevice *s; 254 int ret; 255 int i; 256 257 ret = comedi_request_region(dev, it->options[0], 0x10); 258 if (ret) 259 return ret; 260 261 ret = comedi_alloc_subdevices(dev, 5); 262 if (ret) 263 return ret; 264 265 /* Analog Input subdevice */ 266 s = &dev->subdevices[0]; 267 s->type = COMEDI_SUBD_AI; 268 s->subdev_flags = SDF_READABLE | SDF_GROUND; 269 s->n_chan = 8; 270 s->maxdata = 0x1fff; 271 s->range_table = &range_bipolar5; 272 s->insn_read = multiq3_ai_insn_read; 273 274 /* Analog Output subdevice */ 275 s = &dev->subdevices[1]; 276 s->type = COMEDI_SUBD_AO; 277 s->subdev_flags = SDF_WRITABLE; 278 s->n_chan = 8; 279 s->maxdata = 0x0fff; 280 s->range_table = &range_bipolar5; 281 s->insn_write = multiq3_ao_insn_write; 282 283 ret = comedi_alloc_subdev_readback(s); 284 if (ret) 285 return ret; 286 287 /* Digital Input subdevice */ 288 s = &dev->subdevices[2]; 289 s->type = COMEDI_SUBD_DI; 290 s->subdev_flags = SDF_READABLE; 291 s->n_chan = 16; 292 s->maxdata = 1; 293 s->range_table = &range_digital; 294 s->insn_bits = multiq3_di_insn_bits; 295 296 /* Digital Output subdevice */ 297 s = &dev->subdevices[3]; 298 s->type = COMEDI_SUBD_DO; 299 s->subdev_flags = SDF_WRITABLE; 300 s->n_chan = 16; 301 s->maxdata = 1; 302 s->range_table = &range_digital; 303 s->insn_bits = multiq3_do_insn_bits; 304 305 /* Encoder (Counter) subdevice */ 306 s = &dev->subdevices[4]; 307 s->type = COMEDI_SUBD_COUNTER; 308 s->subdev_flags = SDF_READABLE | SDF_LSAMPL; 309 s->n_chan = it->options[2] * 2; 310 s->maxdata = 0x00ffffff; 311 s->range_table = &range_unknown; 312 s->insn_read = multiq3_encoder_insn_read; 313 s->insn_config = multiq3_encoder_insn_config; 314 315 for (i = 0; i < s->n_chan; i++) 316 multiq3_encoder_reset(dev, i); 317 318 return 0; 319 } 320 321 static struct comedi_driver multiq3_driver = { 322 .driver_name = "multiq3", 323 .module = THIS_MODULE, 324 .attach = multiq3_attach, 325 .detach = comedi_legacy_detach, 326 }; 327 module_comedi_driver(multiq3_driver); 328 329 MODULE_AUTHOR("Comedi https://www.comedi.org"); 330 MODULE_DESCRIPTION("Comedi driver for Quanser Consulting MultiQ-3 board"); 331 MODULE_LICENSE("GPL"); 332