1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * comedi/drivers/dt2814.c 4 * Hardware driver for Data Translation DT2814 5 * 6 * COMEDI - Linux Control and Measurement Device Interface 7 * Copyright (C) 1998 David A. Schleef <ds@schleef.org> 8 */ 9 /* 10 * Driver: dt2814 11 * Description: Data Translation DT2814 12 * Author: ds 13 * Status: complete 14 * Devices: [Data Translation] DT2814 (dt2814) 15 * 16 * Configuration options: 17 * [0] - I/O port base address 18 * [1] - IRQ 19 * 20 * This card has 16 analog inputs multiplexed onto a 12 bit ADC. There 21 * is a minimally useful onboard clock. The base frequency for the 22 * clock is selected by jumpers, and the clock divider can be selected 23 * via programmed I/O. Unfortunately, the clock divider can only be 24 * a power of 10, from 1 to 10^7, of which only 3 or 4 are useful. In 25 * addition, the clock does not seem to be very accurate. 26 */ 27 28 #include <linux/module.h> 29 #include <linux/interrupt.h> 30 #include "../comedidev.h" 31 32 #include <linux/delay.h> 33 34 #define DT2814_CSR 0 35 #define DT2814_DATA 1 36 37 /* 38 * flags 39 */ 40 41 #define DT2814_FINISH 0x80 42 #define DT2814_ERR 0x40 43 #define DT2814_BUSY 0x20 44 #define DT2814_ENB 0x10 45 #define DT2814_CHANMASK 0x0f 46 47 #define DT2814_TIMEOUT 10 48 #define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */ 49 50 static int dt2814_ai_notbusy(struct comedi_device *dev, 51 struct comedi_subdevice *s, 52 struct comedi_insn *insn, 53 unsigned long context) 54 { 55 unsigned int status; 56 57 status = inb(dev->iobase + DT2814_CSR); 58 if (context) 59 *(unsigned int *)context = status; 60 if (status & DT2814_BUSY) 61 return -EBUSY; 62 return 0; 63 } 64 65 static int dt2814_ai_clear(struct comedi_device *dev) 66 { 67 unsigned int status = 0; 68 int ret; 69 70 /* Wait until not busy and get status register value. */ 71 ret = comedi_timeout(dev, NULL, NULL, dt2814_ai_notbusy, 72 (unsigned long)&status); 73 if (ret) 74 return ret; 75 76 if (status & (DT2814_FINISH | DT2814_ERR)) { 77 /* 78 * There unread data, or the error flag is set. 79 * Read the data register twice to clear the condition. 80 */ 81 inb(dev->iobase + DT2814_DATA); 82 inb(dev->iobase + DT2814_DATA); 83 } 84 return 0; 85 } 86 87 static int dt2814_ai_eoc(struct comedi_device *dev, 88 struct comedi_subdevice *s, 89 struct comedi_insn *insn, 90 unsigned long context) 91 { 92 unsigned int status; 93 94 status = inb(dev->iobase + DT2814_CSR); 95 if (status & DT2814_FINISH) 96 return 0; 97 return -EBUSY; 98 } 99 100 static int dt2814_ai_insn_read(struct comedi_device *dev, 101 struct comedi_subdevice *s, 102 struct comedi_insn *insn, unsigned int *data) 103 { 104 int n, hi, lo; 105 int chan; 106 int ret; 107 108 dt2814_ai_clear(dev); /* clear stale data or error */ 109 for (n = 0; n < insn->n; n++) { 110 chan = CR_CHAN(insn->chanspec); 111 112 outb(chan, dev->iobase + DT2814_CSR); 113 114 ret = comedi_timeout(dev, s, insn, dt2814_ai_eoc, 0); 115 if (ret) 116 return ret; 117 118 hi = inb(dev->iobase + DT2814_DATA); 119 lo = inb(dev->iobase + DT2814_DATA); 120 121 data[n] = (hi << 4) | (lo >> 4); 122 } 123 124 return n; 125 } 126 127 static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags) 128 { 129 int i; 130 unsigned int f; 131 132 /* XXX ignores flags */ 133 134 f = 10000; /* ns */ 135 for (i = 0; i < 8; i++) { 136 if ((2 * (*ns)) < (f * 11)) 137 break; 138 f *= 10; 139 } 140 141 *ns = f; 142 143 return i; 144 } 145 146 static int dt2814_ai_cmdtest(struct comedi_device *dev, 147 struct comedi_subdevice *s, struct comedi_cmd *cmd) 148 { 149 int err = 0; 150 unsigned int arg; 151 152 /* Step 1 : check if triggers are trivially valid */ 153 154 err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); 155 err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER); 156 err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW); 157 err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 158 err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); 159 160 if (err) 161 return 1; 162 163 /* Step 2a : make sure trigger sources are unique */ 164 165 err |= comedi_check_trigger_is_unique(cmd->stop_src); 166 167 /* Step 2b : and mutually compatible */ 168 169 if (err) 170 return 2; 171 172 /* Step 3: check if arguments are trivially valid */ 173 174 err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); 175 176 err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000); 177 err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, 178 DT2814_MAX_SPEED); 179 180 err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, 181 cmd->chanlist_len); 182 183 if (cmd->stop_src == TRIG_COUNT) 184 err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 2); 185 else /* TRIG_NONE */ 186 err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); 187 188 if (err) 189 return 3; 190 191 /* step 4: fix up any arguments */ 192 193 arg = cmd->scan_begin_arg; 194 dt2814_ns_to_timer(&arg, cmd->flags); 195 err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg); 196 197 if (err) 198 return 4; 199 200 return 0; 201 } 202 203 static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 204 { 205 struct comedi_cmd *cmd = &s->async->cmd; 206 int chan; 207 int trigvar; 208 209 dt2814_ai_clear(dev); /* clear stale data or error */ 210 trigvar = dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags); 211 212 chan = CR_CHAN(cmd->chanlist[0]); 213 214 outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR); 215 216 return 0; 217 } 218 219 static int dt2814_ai_cancel(struct comedi_device *dev, 220 struct comedi_subdevice *s) 221 { 222 unsigned int status; 223 unsigned long flags; 224 225 spin_lock_irqsave(&dev->spinlock, flags); 226 status = inb(dev->iobase + DT2814_CSR); 227 if (status & DT2814_ENB) { 228 /* 229 * Clear the timed trigger enable bit. 230 * 231 * Note: turning off timed mode triggers another 232 * sample. This will be mopped up by the calls to 233 * dt2814_ai_clear(). 234 */ 235 outb(status & DT2814_CHANMASK, dev->iobase + DT2814_CSR); 236 } 237 spin_unlock_irqrestore(&dev->spinlock, flags); 238 return 0; 239 } 240 241 static irqreturn_t dt2814_interrupt(int irq, void *d) 242 { 243 struct comedi_device *dev = d; 244 struct comedi_subdevice *s = dev->read_subdev; 245 struct comedi_async *async; 246 unsigned int lo, hi; 247 unsigned short data; 248 unsigned int status; 249 250 if (!dev->attached) { 251 dev_err(dev->class_dev, "spurious interrupt\n"); 252 return IRQ_HANDLED; 253 } 254 255 async = s->async; 256 257 spin_lock(&dev->spinlock); 258 259 status = inb(dev->iobase + DT2814_CSR); 260 if (!(status & DT2814_ENB)) { 261 /* Timed acquisition not enabled. Nothing to do. */ 262 spin_unlock(&dev->spinlock); 263 return IRQ_HANDLED; 264 } 265 266 if (!(status & (DT2814_FINISH | DT2814_ERR))) { 267 /* Spurious interrupt? */ 268 spin_unlock(&dev->spinlock); 269 return IRQ_HANDLED; 270 } 271 272 /* Read data or clear error. */ 273 hi = inb(dev->iobase + DT2814_DATA); 274 lo = inb(dev->iobase + DT2814_DATA); 275 276 data = (hi << 4) | (lo >> 4); 277 278 if (status & DT2814_ERR) { 279 async->events |= COMEDI_CB_ERROR; 280 } else { 281 comedi_buf_write_samples(s, &data, 1); 282 if (async->cmd.stop_src == TRIG_COUNT && 283 async->scans_done >= async->cmd.stop_arg) { 284 async->events |= COMEDI_CB_EOA; 285 } 286 } 287 if (async->events & COMEDI_CB_CANCEL_MASK) { 288 /* 289 * Disable timed mode. 290 * 291 * Note: turning off timed mode triggers another 292 * sample. This will be mopped up by the calls to 293 * dt2814_ai_clear(). 294 */ 295 outb(status & DT2814_CHANMASK, dev->iobase + DT2814_CSR); 296 } 297 298 spin_unlock(&dev->spinlock); 299 300 comedi_handle_events(dev, s); 301 return IRQ_HANDLED; 302 } 303 304 static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it) 305 { 306 struct comedi_subdevice *s; 307 int ret; 308 309 ret = comedi_request_region(dev, it->options[0], 0x2); 310 if (ret) 311 return ret; 312 313 outb(0, dev->iobase + DT2814_CSR); 314 if (dt2814_ai_clear(dev)) { 315 dev_err(dev->class_dev, "reset error (fatal)\n"); 316 return -EIO; 317 } 318 319 if (it->options[1]) { 320 ret = request_irq(it->options[1], dt2814_interrupt, 0, 321 dev->board_name, dev); 322 if (ret == 0) 323 dev->irq = it->options[1]; 324 } 325 326 ret = comedi_alloc_subdevices(dev, 1); 327 if (ret) 328 return ret; 329 330 s = &dev->subdevices[0]; 331 s->type = COMEDI_SUBD_AI; 332 s->subdev_flags = SDF_READABLE | SDF_GROUND; 333 s->n_chan = 16; /* XXX */ 334 s->insn_read = dt2814_ai_insn_read; 335 s->maxdata = 0xfff; 336 s->range_table = &range_unknown; /* XXX */ 337 if (dev->irq) { 338 dev->read_subdev = s; 339 s->subdev_flags |= SDF_CMD_READ; 340 s->len_chanlist = 1; 341 s->do_cmd = dt2814_ai_cmd; 342 s->do_cmdtest = dt2814_ai_cmdtest; 343 s->cancel = dt2814_ai_cancel; 344 } 345 346 return 0; 347 } 348 349 static void dt2814_detach(struct comedi_device *dev) 350 { 351 if (dev->irq) { 352 /* 353 * An extra conversion triggered on termination of an 354 * asynchronous command may still be in progress. Wait for 355 * it to finish and clear the data or error status. 356 */ 357 dt2814_ai_clear(dev); 358 } 359 comedi_legacy_detach(dev); 360 } 361 362 static struct comedi_driver dt2814_driver = { 363 .driver_name = "dt2814", 364 .module = THIS_MODULE, 365 .attach = dt2814_attach, 366 .detach = dt2814_detach, 367 }; 368 module_comedi_driver(dt2814_driver); 369 370 MODULE_AUTHOR("Comedi https://www.comedi.org"); 371 MODULE_DESCRIPTION("Comedi low-level driver"); 372 MODULE_LICENSE("GPL"); 373