1/* 2 comedi/drivers/multiq3.c 3 Hardware driver for Quanser Consulting MultiQ-3 board 4 5 COMEDI - Linux Control and Measurement Device Interface 6 Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se> 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/* 19Driver: multiq3 20Description: Quanser Consulting MultiQ-3 21Author: Anders Blomdell <anders.blomdell@control.lth.se> 22Status: works 23Devices: [Quanser Consulting] MultiQ-3 (multiq3) 24 25*/ 26 27#include <linux/module.h> 28#include <linux/interrupt.h> 29#include "../comedidev.h" 30 31/* 32 * MULTIQ-3 port offsets 33 */ 34#define MULTIQ3_DIGIN_PORT 0 35#define MULTIQ3_DIGOUT_PORT 0 36#define MULTIQ3_DAC_DATA 2 37#define MULTIQ3_AD_DATA 4 38#define MULTIQ3_AD_CS 4 39#define MULTIQ3_STATUS 6 40#define MULTIQ3_CONTROL 6 41#define MULTIQ3_CLK_DATA 8 42#define MULTIQ3_ENC_DATA 12 43#define MULTIQ3_ENC_CONTROL 14 44 45/* 46 * flags for CONTROL register 47 */ 48#define MULTIQ3_AD_MUX_EN 0x0040 49#define MULTIQ3_AD_AUTOZ 0x0080 50#define MULTIQ3_AD_AUTOCAL 0x0100 51#define MULTIQ3_AD_SH 0x0200 52#define MULTIQ3_AD_CLOCK_4M 0x0400 53#define MULTIQ3_DA_LOAD 0x1800 54 55#define MULTIQ3_CONTROL_MUST 0x0600 56 57/* 58 * flags for STATUS register 59 */ 60#define MULTIQ3_STATUS_EOC 0x008 61#define MULTIQ3_STATUS_EOC_I 0x010 62 63/* 64 * flags for encoder control 65 */ 66#define MULTIQ3_CLOCK_DATA 0x00 67#define MULTIQ3_CLOCK_SETUP 0x18 68#define MULTIQ3_INPUT_SETUP 0x41 69#define MULTIQ3_QUAD_X4 0x38 70#define MULTIQ3_BP_RESET 0x01 71#define MULTIQ3_CNTR_RESET 0x02 72#define MULTIQ3_TRSFRPR_CTR 0x08 73#define MULTIQ3_TRSFRCNTR_OL 0x10 74#define MULTIQ3_EFLAG_RESET 0x06 75 76#define MULTIQ3_TIMEOUT 30 77 78static int multiq3_ai_status(struct comedi_device *dev, 79 struct comedi_subdevice *s, 80 struct comedi_insn *insn, 81 unsigned long context) 82{ 83 unsigned int status; 84 85 status = inw(dev->iobase + MULTIQ3_STATUS); 86 if (status & context) 87 return 0; 88 return -EBUSY; 89} 90 91static int multiq3_ai_insn_read(struct comedi_device *dev, 92 struct comedi_subdevice *s, 93 struct comedi_insn *insn, unsigned int *data) 94{ 95 int n; 96 int chan; 97 unsigned int hi, lo; 98 int ret; 99 100 chan = CR_CHAN(insn->chanspec); 101 outw(MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3), 102 dev->iobase + MULTIQ3_CONTROL); 103 104 ret = comedi_timeout(dev, s, insn, multiq3_ai_status, 105 MULTIQ3_STATUS_EOC); 106 if (ret) 107 return ret; 108 109 for (n = 0; n < insn->n; n++) { 110 outw(0, dev->iobase + MULTIQ3_AD_CS); 111 112 ret = comedi_timeout(dev, s, insn, multiq3_ai_status, 113 MULTIQ3_STATUS_EOC_I); 114 if (ret) 115 return ret; 116 117 hi = inb(dev->iobase + MULTIQ3_AD_CS); 118 lo = inb(dev->iobase + MULTIQ3_AD_CS); 119 data[n] = (((hi << 8) | lo) + 0x1000) & 0x1fff; 120 } 121 122 return n; 123} 124 125static int multiq3_ao_insn_write(struct comedi_device *dev, 126 struct comedi_subdevice *s, 127 struct comedi_insn *insn, 128 unsigned int *data) 129{ 130 unsigned int chan = CR_CHAN(insn->chanspec); 131 unsigned int val = s->readback[chan]; 132 int i; 133 134 for (i = 0; i < insn->n; i++) { 135 val = data[i]; 136 outw(MULTIQ3_CONTROL_MUST | MULTIQ3_DA_LOAD | chan, 137 dev->iobase + MULTIQ3_CONTROL); 138 outw(val, dev->iobase + MULTIQ3_DAC_DATA); 139 outw(MULTIQ3_CONTROL_MUST, dev->iobase + MULTIQ3_CONTROL); 140 } 141 s->readback[chan] = val; 142 143 return insn->n; 144} 145 146static int multiq3_di_insn_bits(struct comedi_device *dev, 147 struct comedi_subdevice *s, 148 struct comedi_insn *insn, unsigned int *data) 149{ 150 data[1] = inw(dev->iobase + MULTIQ3_DIGIN_PORT); 151 152 return insn->n; 153} 154 155static int multiq3_do_insn_bits(struct comedi_device *dev, 156 struct comedi_subdevice *s, 157 struct comedi_insn *insn, 158 unsigned int *data) 159{ 160 if (comedi_dio_update_state(s, data)) 161 outw(s->state, dev->iobase + MULTIQ3_DIGOUT_PORT); 162 163 data[1] = s->state; 164 165 return insn->n; 166} 167 168static int multiq3_encoder_insn_read(struct comedi_device *dev, 169 struct comedi_subdevice *s, 170 struct comedi_insn *insn, 171 unsigned int *data) 172{ 173 int chan = CR_CHAN(insn->chanspec); 174 int control = MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3); 175 int value; 176 int n; 177 178 for (n = 0; n < insn->n; n++) { 179 outw(control, dev->iobase + MULTIQ3_CONTROL); 180 outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CONTROL); 181 outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CONTROL); 182 value = inb(dev->iobase + MULTIQ3_ENC_DATA); 183 value |= (inb(dev->iobase + MULTIQ3_ENC_DATA) << 8); 184 value |= (inb(dev->iobase + MULTIQ3_ENC_DATA) << 16); 185 data[n] = (value + 0x800000) & 0xffffff; 186 } 187 188 return n; 189} 190 191static void encoder_reset(struct comedi_device *dev) 192{ 193 struct comedi_subdevice *s = &dev->subdevices[4]; 194 int chan; 195 196 for (chan = 0; chan < s->n_chan; chan++) { 197 int control = 198 MULTIQ3_CONTROL_MUST | MULTIQ3_AD_MUX_EN | (chan << 3); 199 outw(control, dev->iobase + MULTIQ3_CONTROL); 200 outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CONTROL); 201 outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CONTROL); 202 outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA); 203 outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CONTROL); 204 outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CONTROL); 205 outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CONTROL); 206 outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CONTROL); 207 } 208} 209 210static int multiq3_attach(struct comedi_device *dev, 211 struct comedi_devconfig *it) 212{ 213 struct comedi_subdevice *s; 214 int ret; 215 216 ret = comedi_request_region(dev, it->options[0], 0x10); 217 if (ret) 218 return ret; 219 220 ret = comedi_alloc_subdevices(dev, 5); 221 if (ret) 222 return ret; 223 224 s = &dev->subdevices[0]; 225 /* ai subdevice */ 226 s->type = COMEDI_SUBD_AI; 227 s->subdev_flags = SDF_READABLE | SDF_GROUND; 228 s->n_chan = 8; 229 s->insn_read = multiq3_ai_insn_read; 230 s->maxdata = 0x1fff; 231 s->range_table = &range_bipolar5; 232 233 s = &dev->subdevices[1]; 234 /* ao subdevice */ 235 s->type = COMEDI_SUBD_AO; 236 s->subdev_flags = SDF_WRITABLE; 237 s->n_chan = 8; 238 s->maxdata = 0xfff; 239 s->range_table = &range_bipolar5; 240 s->insn_write = multiq3_ao_insn_write; 241 s->insn_read = comedi_readback_insn_read; 242 243 ret = comedi_alloc_subdev_readback(s); 244 if (ret) 245 return ret; 246 247 s = &dev->subdevices[2]; 248 /* di subdevice */ 249 s->type = COMEDI_SUBD_DI; 250 s->subdev_flags = SDF_READABLE; 251 s->n_chan = 16; 252 s->insn_bits = multiq3_di_insn_bits; 253 s->maxdata = 1; 254 s->range_table = &range_digital; 255 256 s = &dev->subdevices[3]; 257 /* do subdevice */ 258 s->type = COMEDI_SUBD_DO; 259 s->subdev_flags = SDF_WRITABLE; 260 s->n_chan = 16; 261 s->insn_bits = multiq3_do_insn_bits; 262 s->maxdata = 1; 263 s->range_table = &range_digital; 264 s->state = 0; 265 266 s = &dev->subdevices[4]; 267 /* encoder (counter) subdevice */ 268 s->type = COMEDI_SUBD_COUNTER; 269 s->subdev_flags = SDF_READABLE | SDF_LSAMPL; 270 s->n_chan = it->options[2] * 2; 271 s->insn_read = multiq3_encoder_insn_read; 272 s->maxdata = 0xffffff; 273 s->range_table = &range_unknown; 274 275 encoder_reset(dev); 276 277 return 0; 278} 279 280static struct comedi_driver multiq3_driver = { 281 .driver_name = "multiq3", 282 .module = THIS_MODULE, 283 .attach = multiq3_attach, 284 .detach = comedi_legacy_detach, 285}; 286module_comedi_driver(multiq3_driver); 287 288MODULE_AUTHOR("Comedi http://www.comedi.org"); 289MODULE_DESCRIPTION("Comedi low-level driver"); 290MODULE_LICENSE("GPL"); 291