dt2814.c revision 3f3ba29c78c4039a9fd746065ff89afec8bbc19a
1/* 2 comedi/drivers/dt2814.c 3 Hardware driver for Data Translation DT2814 4 5 COMEDI - Linux Control and Measurement Device Interface 6 Copyright (C) 1998 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 You should have received a copy of the GNU General Public License 19 along with this program; if not, write to the Free Software 20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 21 22*/ 23/* 24Driver: dt2814 25Description: Data Translation DT2814 26Author: ds 27Status: complete 28Devices: [Data Translation] DT2814 (dt2814) 29 30Configuration options: 31 [0] - I/O port base address 32 [1] - IRQ 33 34This card has 16 analog inputs multiplexed onto a 12 bit ADC. There 35is a minimally useful onboard clock. The base frequency for the 36clock is selected by jumpers, and the clock divider can be selected 37via programmed I/O. Unfortunately, the clock divider can only be 38a power of 10, from 1 to 10^7, of which only 3 or 4 are useful. In 39addition, the clock does not seem to be very accurate. 40*/ 41 42#include <linux/interrupt.h> 43#include "../comedidev.h" 44 45#include <linux/ioport.h> 46#include <linux/delay.h> 47 48#define DT2814_SIZE 2 49 50#define DT2814_CSR 0 51#define DT2814_DATA 1 52 53/* 54 * flags 55 */ 56 57#define DT2814_FINISH 0x80 58#define DT2814_ERR 0x40 59#define DT2814_BUSY 0x20 60#define DT2814_ENB 0x10 61#define DT2814_CHANMASK 0x0f 62 63static int dt2814_attach(struct comedi_device *dev, 64 struct comedi_devconfig *it); 65static int dt2814_detach(struct comedi_device *dev); 66static struct comedi_driver driver_dt2814 = { 67 .driver_name = "dt2814", 68 .module = THIS_MODULE, 69 .attach = dt2814_attach, 70 .detach = dt2814_detach, 71}; 72 73COMEDI_INITCLEANUP(driver_dt2814); 74 75static irqreturn_t dt2814_interrupt(int irq, void *dev); 76 77struct dt2814_private { 78 79 int ntrig; 80 int curadchan; 81}; 82 83#define devpriv ((struct dt2814_private *)dev->private) 84 85#define DT2814_TIMEOUT 10 86#define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */ 87 88static int dt2814_ai_insn_read(struct comedi_device *dev, 89 struct comedi_subdevice *s, 90 struct comedi_insn *insn, unsigned int *data) 91{ 92 int n, i, hi, lo; 93 int chan; 94 int status = 0; 95 96 for (n = 0; n < insn->n; n++) { 97 chan = CR_CHAN(insn->chanspec); 98 99 outb(chan, dev->iobase + DT2814_CSR); 100 for (i = 0; i < DT2814_TIMEOUT; i++) { 101 status = inb(dev->iobase + DT2814_CSR); 102 printk(KERN_INFO "dt2814: status: %02x\n", status); 103 udelay(10); 104 if (status & DT2814_FINISH) 105 break; 106 } 107 if (i >= DT2814_TIMEOUT) { 108 printk(KERN_INFO "dt2814: status: %02x\n", status); 109 return -ETIMEDOUT; 110 } 111 112 hi = inb(dev->iobase + DT2814_DATA); 113 lo = inb(dev->iobase + DT2814_DATA); 114 115 data[n] = (hi << 4) | (lo >> 4); 116 } 117 118 return n; 119} 120 121static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags) 122{ 123 int i; 124 unsigned int f; 125 126 /* XXX ignores flags */ 127 128 f = 10000; /* ns */ 129 for (i = 0; i < 8; i++) { 130 if ((2 * (*ns)) < (f * 11)) 131 break; 132 f *= 10; 133 } 134 135 *ns = f; 136 137 return i; 138} 139 140static int dt2814_ai_cmdtest(struct comedi_device *dev, 141 struct comedi_subdevice *s, struct comedi_cmd *cmd) 142{ 143 int err = 0; 144 int tmp; 145 146 /* step 1: make sure trigger sources are trivially valid */ 147 148 tmp = cmd->start_src; 149 cmd->start_src &= TRIG_NOW; 150 if (!cmd->start_src || tmp != cmd->start_src) 151 err++; 152 153 tmp = cmd->scan_begin_src; 154 cmd->scan_begin_src &= TRIG_TIMER; 155 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) 156 err++; 157 158 tmp = cmd->convert_src; 159 cmd->convert_src &= TRIG_NOW; 160 if (!cmd->convert_src || tmp != cmd->convert_src) 161 err++; 162 163 tmp = cmd->scan_end_src; 164 cmd->scan_end_src &= TRIG_COUNT; 165 if (!cmd->scan_end_src || tmp != cmd->scan_end_src) 166 err++; 167 168 tmp = cmd->stop_src; 169 cmd->stop_src &= TRIG_COUNT | TRIG_NONE; 170 if (!cmd->stop_src || tmp != cmd->stop_src) 171 err++; 172 173 if (err) 174 return 1; 175 176 /* step 2: make sure trigger sources are 177 * unique and mutually compatible */ 178 179 /* note that mutual compatibility is not an issue here */ 180 if (cmd->stop_src != TRIG_TIMER && cmd->stop_src != TRIG_EXT) 181 err++; 182 183 if (err) 184 return 2; 185 186 /* step 3: make sure arguments are trivially compatible */ 187 188 if (cmd->start_arg != 0) { 189 cmd->start_arg = 0; 190 err++; 191 } 192 if (cmd->scan_begin_arg > 1000000000) { 193 cmd->scan_begin_arg = 1000000000; 194 err++; 195 } 196 if (cmd->scan_begin_arg < DT2814_MAX_SPEED) { 197 cmd->scan_begin_arg = DT2814_MAX_SPEED; 198 err++; 199 } 200 if (cmd->scan_end_arg != cmd->chanlist_len) { 201 cmd->scan_end_arg = cmd->chanlist_len; 202 err++; 203 } 204 if (cmd->stop_src == TRIG_COUNT) { 205 if (cmd->stop_arg < 2) { 206 cmd->stop_arg = 2; 207 err++; 208 } 209 } else { 210 /* TRIG_NONE */ 211 if (cmd->stop_arg != 0) { 212 cmd->stop_arg = 0; 213 err++; 214 } 215 } 216 217 if (err) 218 return 3; 219 220 /* step 4: fix up any arguments */ 221 222 tmp = cmd->scan_begin_arg; 223 dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK); 224 if (tmp != cmd->scan_begin_arg) 225 err++; 226 227 if (err) 228 return 4; 229 230 return 0; 231} 232 233static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 234{ 235 struct comedi_cmd *cmd = &s->async->cmd; 236 int chan; 237 int trigvar; 238 239 trigvar = 240 dt2814_ns_to_timer(&cmd->scan_begin_arg, 241 cmd->flags & TRIG_ROUND_MASK); 242 243 chan = CR_CHAN(cmd->chanlist[0]); 244 245 devpriv->ntrig = cmd->stop_arg; 246 outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR); 247 248 return 0; 249 250} 251 252static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it) 253{ 254 int i, irq; 255 int ret; 256 struct comedi_subdevice *s; 257 unsigned long iobase; 258 259 iobase = it->options[0]; 260 printk(KERN_INFO "comedi%d: dt2814: 0x%04lx ", dev->minor, iobase); 261 if (!request_region(iobase, DT2814_SIZE, "dt2814")) { 262 printk(KERN_ERR "I/O port conflict\n"); 263 return -EIO; 264 } 265 dev->iobase = iobase; 266 dev->board_name = "dt2814"; 267 268 outb(0, dev->iobase + DT2814_CSR); 269 udelay(100); 270 if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) { 271 printk(KERN_ERR "reset error (fatal)\n"); 272 return -EIO; 273 } 274 i = inb(dev->iobase + DT2814_DATA); 275 i = inb(dev->iobase + DT2814_DATA); 276 277 irq = it->options[1]; 278#if 0 279 if (irq < 0) { 280 save_flags(flags); 281 sti(); 282 irqs = probe_irq_on(); 283 284 outb(0, dev->iobase + DT2814_CSR); 285 286 udelay(100); 287 288 irq = probe_irq_off(irqs); 289 restore_flags(flags); 290 if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) 291 printk(KERN_DEBUG "error probing irq (bad)\n"); 292 293 294 i = inb(dev->iobase + DT2814_DATA); 295 i = inb(dev->iobase + DT2814_DATA); 296 } 297#endif 298 dev->irq = 0; 299 if (irq > 0) { 300 if (request_irq(irq, dt2814_interrupt, 0, "dt2814", dev)) { 301 printk(KERN_WARNING "(irq %d unavailable)\n", irq); 302 } else { 303 printk(KERN_INFO "( irq = %d )\n", irq); 304 dev->irq = irq; 305 } 306 } else if (irq == 0) { 307 printk(KERN_WARNING "(no irq)\n"); 308 } else { 309#if 0 310 printk(KERN_DEBUG "(probe returned multiple irqs--bad)\n"); 311#else 312 printk(KERN_WARNING "(irq probe not implemented)\n"); 313#endif 314 } 315 316 ret = alloc_subdevices(dev, 1); 317 if (ret < 0) 318 return ret; 319 320 ret = alloc_private(dev, sizeof(struct dt2814_private)); 321 if (ret < 0) 322 return ret; 323 324 s = dev->subdevices + 0; 325 dev->read_subdev = s; 326 s->type = COMEDI_SUBD_AI; 327 s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; 328 s->n_chan = 16; /* XXX */ 329 s->len_chanlist = 1; 330 s->insn_read = dt2814_ai_insn_read; 331 s->do_cmd = dt2814_ai_cmd; 332 s->do_cmdtest = dt2814_ai_cmdtest; 333 s->maxdata = 0xfff; 334 s->range_table = &range_unknown; /* XXX */ 335 336 return 0; 337} 338 339static int dt2814_detach(struct comedi_device *dev) 340{ 341 printk(KERN_INFO "comedi%d: dt2814: remove\n", dev->minor); 342 343 if (dev->irq) 344 free_irq(dev->irq, dev); 345 346 if (dev->iobase) 347 release_region(dev->iobase, DT2814_SIZE); 348 349 return 0; 350} 351 352static irqreturn_t dt2814_interrupt(int irq, void *d) 353{ 354 int lo, hi; 355 struct comedi_device *dev = d; 356 struct comedi_subdevice *s; 357 int data; 358 359 if (!dev->attached) { 360 comedi_error(dev, "spurious interrupt"); 361 return IRQ_HANDLED; 362 } 363 364 s = dev->subdevices + 0; 365 366 hi = inb(dev->iobase + DT2814_DATA); 367 lo = inb(dev->iobase + DT2814_DATA); 368 369 data = (hi << 4) | (lo >> 4); 370 371 if (!(--devpriv->ntrig)) { 372 int i; 373 374 outb(0, dev->iobase + DT2814_CSR); 375 /* note: turning off timed mode triggers another 376 sample. */ 377 378 for (i = 0; i < DT2814_TIMEOUT; i++) { 379 if (inb(dev->iobase + DT2814_CSR) & DT2814_FINISH) 380 break; 381 } 382 inb(dev->iobase + DT2814_DATA); 383 inb(dev->iobase + DT2814_DATA); 384 385 s->async->events |= COMEDI_CB_EOA; 386 } 387 comedi_event(dev, s); 388 return IRQ_HANDLED; 389} 390