| 1 | /* |
| 2 | comedi/drivers/dmm32at.c |
| 3 | Diamond Systems mm32at code for a Comedi driver |
| 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 | 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 | /* |
| 24 | Driver: dmm32at |
| 25 | Description: Diamond Systems mm32at driver. |
| 26 | Devices: |
| 27 | Author: Perry J. Piplani <perry.j.piplani@nasa.gov> |
| 28 | Updated: Fri Jun 4 09:13:24 CDT 2004 |
| 29 | Status: experimental |
| 30 | |
| 31 | This driver is for the Diamond Systems MM-32-AT board |
| 32 | http://www.diamondsystems.com/products/diamondmm32at It is being used |
| 33 | on serveral projects inside NASA, without problems so far. For analog |
| 34 | input commands, TRIG_EXT is not yet supported at all.. |
| 35 | |
| 36 | Configuration Options: |
| 37 | comedi_config /dev/comedi0 dmm32at baseaddr,irq |
| 38 | */ |
| 39 | |
| 40 | #include <linux/interrupt.h> |
| 41 | #include "../comedidev.h" |
| 42 | #include <linux/ioport.h> |
| 43 | |
| 44 | #include "comedi_fc.h" |
| 45 | |
| 46 | /* Board register addresses */ |
| 47 | |
| 48 | #define DMM32AT_MEMSIZE 0x10 |
| 49 | |
| 50 | #define DMM32AT_CONV 0x00 |
| 51 | #define DMM32AT_AILSB 0x00 |
| 52 | #define DMM32AT_AUXDOUT 0x01 |
| 53 | #define DMM32AT_AIMSB 0x01 |
| 54 | #define DMM32AT_AILOW 0x02 |
| 55 | #define DMM32AT_AIHIGH 0x03 |
| 56 | |
| 57 | #define DMM32AT_DACLSB 0x04 |
| 58 | #define DMM32AT_DACSTAT 0x04 |
| 59 | #define DMM32AT_DACMSB 0x05 |
| 60 | |
| 61 | #define DMM32AT_FIFOCNTRL 0x07 |
| 62 | #define DMM32AT_FIFOSTAT 0x07 |
| 63 | |
| 64 | #define DMM32AT_CNTRL 0x08 |
| 65 | #define DMM32AT_AISTAT 0x08 |
| 66 | |
| 67 | #define DMM32AT_INTCLOCK 0x09 |
| 68 | |
| 69 | #define DMM32AT_CNTRDIO 0x0a |
| 70 | |
| 71 | #define DMM32AT_AICONF 0x0b |
| 72 | #define DMM32AT_AIRBACK 0x0b |
| 73 | |
| 74 | #define DMM32AT_CLK1 0x0d |
| 75 | #define DMM32AT_CLK2 0x0e |
| 76 | #define DMM32AT_CLKCT 0x0f |
| 77 | |
| 78 | #define DMM32AT_DIOA 0x0c |
| 79 | #define DMM32AT_DIOB 0x0d |
| 80 | #define DMM32AT_DIOC 0x0e |
| 81 | #define DMM32AT_DIOCONF 0x0f |
| 82 | |
| 83 | /* Board register values. */ |
| 84 | |
| 85 | /* DMM32AT_DACSTAT 0x04 */ |
| 86 | #define DMM32AT_DACBUSY 0x80 |
| 87 | |
| 88 | /* DMM32AT_FIFOCNTRL 0x07 */ |
| 89 | #define DMM32AT_FIFORESET 0x02 |
| 90 | #define DMM32AT_SCANENABLE 0x04 |
| 91 | |
| 92 | /* DMM32AT_CNTRL 0x08 */ |
| 93 | #define DMM32AT_RESET 0x20 |
| 94 | #define DMM32AT_INTRESET 0x08 |
| 95 | #define DMM32AT_CLKACC 0x00 |
| 96 | #define DMM32AT_DIOACC 0x01 |
| 97 | |
| 98 | /* DMM32AT_AISTAT 0x08 */ |
| 99 | #define DMM32AT_STATUS 0x80 |
| 100 | |
| 101 | /* DMM32AT_INTCLOCK 0x09 */ |
| 102 | #define DMM32AT_ADINT 0x80 |
| 103 | #define DMM32AT_CLKSEL 0x03 |
| 104 | |
| 105 | /* DMM32AT_CNTRDIO 0x0a */ |
| 106 | #define DMM32AT_FREQ12 0x80 |
| 107 | |
| 108 | /* DMM32AT_AICONF 0x0b */ |
| 109 | #define DMM32AT_RANGE_U10 0x0c |
| 110 | #define DMM32AT_RANGE_U5 0x0d |
| 111 | #define DMM32AT_RANGE_B10 0x08 |
| 112 | #define DMM32AT_RANGE_B5 0x00 |
| 113 | #define DMM32AT_SCINT_20 0x00 |
| 114 | #define DMM32AT_SCINT_15 0x10 |
| 115 | #define DMM32AT_SCINT_10 0x20 |
| 116 | #define DMM32AT_SCINT_5 0x30 |
| 117 | |
| 118 | /* DMM32AT_CLKCT 0x0f */ |
| 119 | #define DMM32AT_CLKCT1 0x56 /* mode3 counter 1 - write low byte only */ |
| 120 | #define DMM32AT_CLKCT2 0xb6 /* mode3 counter 2 - write high and low byte */ |
| 121 | |
| 122 | /* DMM32AT_DIOCONF 0x0f */ |
| 123 | #define DMM32AT_DIENABLE 0x80 |
| 124 | #define DMM32AT_DIRA 0x10 |
| 125 | #define DMM32AT_DIRB 0x02 |
| 126 | #define DMM32AT_DIRCL 0x01 |
| 127 | #define DMM32AT_DIRCH 0x08 |
| 128 | |
| 129 | /* board AI ranges in comedi structure */ |
| 130 | static const struct comedi_lrange dmm32at_airanges = { |
| 131 | 4, |
| 132 | { |
| 133 | UNI_RANGE(10), |
| 134 | UNI_RANGE(5), |
| 135 | BIP_RANGE(10), |
| 136 | BIP_RANGE(5), |
| 137 | } |
| 138 | }; |
| 139 | |
| 140 | /* register values for above ranges */ |
| 141 | static const unsigned char dmm32at_rangebits[] = { |
| 142 | DMM32AT_RANGE_U10, |
| 143 | DMM32AT_RANGE_U5, |
| 144 | DMM32AT_RANGE_B10, |
| 145 | DMM32AT_RANGE_B5, |
| 146 | }; |
| 147 | |
| 148 | /* only one of these ranges is valid, as set by a jumper on the |
| 149 | * board. The application should only use the range set by the jumper |
| 150 | */ |
| 151 | static const struct comedi_lrange dmm32at_aoranges = { |
| 152 | 4, |
| 153 | { |
| 154 | UNI_RANGE(10), |
| 155 | UNI_RANGE(5), |
| 156 | BIP_RANGE(10), |
| 157 | BIP_RANGE(5), |
| 158 | } |
| 159 | }; |
| 160 | |
| 161 | struct dmm32at_board { |
| 162 | const char *name; |
| 163 | }; |
| 164 | |
| 165 | struct dmm32at_private { |
| 166 | |
| 167 | int data; |
| 168 | int ai_inuse; |
| 169 | unsigned int ai_scans_left; |
| 170 | |
| 171 | /* Used for AO readback */ |
| 172 | unsigned int ao_readback[4]; |
| 173 | unsigned char dio_config; |
| 174 | |
| 175 | }; |
| 176 | |
| 177 | static int dmm32at_ai_rinsn(struct comedi_device *dev, |
| 178 | struct comedi_subdevice *s, |
| 179 | struct comedi_insn *insn, unsigned int *data) |
| 180 | { |
| 181 | int n, i; |
| 182 | unsigned int d; |
| 183 | unsigned char status; |
| 184 | unsigned short msb, lsb; |
| 185 | unsigned char chan; |
| 186 | int range; |
| 187 | |
| 188 | /* get the channel and range number */ |
| 189 | |
| 190 | chan = CR_CHAN(insn->chanspec) & (s->n_chan - 1); |
| 191 | range = CR_RANGE(insn->chanspec); |
| 192 | |
| 193 | /* printk("channel=0x%02x, range=%d\n",chan,range); */ |
| 194 | |
| 195 | /* zero scan and fifo control and reset fifo */ |
| 196 | outb(DMM32AT_FIFORESET, dev->iobase + DMM32AT_FIFOCNTRL); |
| 197 | |
| 198 | /* write the ai channel range regs */ |
| 199 | outb(chan, dev->iobase + DMM32AT_AILOW); |
| 200 | outb(chan, dev->iobase + DMM32AT_AIHIGH); |
| 201 | /* set the range bits */ |
| 202 | outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AICONF); |
| 203 | |
| 204 | /* wait for circuit to settle */ |
| 205 | for (i = 0; i < 40000; i++) { |
| 206 | status = inb(dev->iobase + DMM32AT_AIRBACK); |
| 207 | if ((status & DMM32AT_STATUS) == 0) |
| 208 | break; |
| 209 | } |
| 210 | if (i == 40000) { |
| 211 | printk(KERN_WARNING "dmm32at: timeout\n"); |
| 212 | return -ETIMEDOUT; |
| 213 | } |
| 214 | |
| 215 | /* convert n samples */ |
| 216 | for (n = 0; n < insn->n; n++) { |
| 217 | /* trigger conversion */ |
| 218 | outb(0xff, dev->iobase + DMM32AT_CONV); |
| 219 | /* wait for conversion to end */ |
| 220 | for (i = 0; i < 40000; i++) { |
| 221 | status = inb(dev->iobase + DMM32AT_AISTAT); |
| 222 | if ((status & DMM32AT_STATUS) == 0) |
| 223 | break; |
| 224 | } |
| 225 | if (i == 40000) { |
| 226 | printk(KERN_WARNING "dmm32at: timeout\n"); |
| 227 | return -ETIMEDOUT; |
| 228 | } |
| 229 | |
| 230 | /* read data */ |
| 231 | lsb = inb(dev->iobase + DMM32AT_AILSB); |
| 232 | msb = inb(dev->iobase + DMM32AT_AIMSB); |
| 233 | |
| 234 | /* invert sign bit to make range unsigned, this is an |
| 235 | idiosyncrasy of the diamond board, it return |
| 236 | conversions as a signed value, i.e. -32768 to |
| 237 | 32767, flipping the bit and interpreting it as |
| 238 | signed gives you a range of 0 to 65535 which is |
| 239 | used by comedi */ |
| 240 | d = ((msb ^ 0x0080) << 8) + lsb; |
| 241 | |
| 242 | data[n] = d; |
| 243 | } |
| 244 | |
| 245 | /* return the number of samples read/written */ |
| 246 | return n; |
| 247 | } |
| 248 | |
| 249 | static int dmm32at_ns_to_timer(unsigned int *ns, int round) |
| 250 | { |
| 251 | /* trivial timer */ |
| 252 | return *ns; |
| 253 | } |
| 254 | |
| 255 | static int dmm32at_ai_cmdtest(struct comedi_device *dev, |
| 256 | struct comedi_subdevice *s, |
| 257 | struct comedi_cmd *cmd) |
| 258 | { |
| 259 | int err = 0; |
| 260 | int tmp; |
| 261 | int start_chan, gain, i; |
| 262 | |
| 263 | /* Step 1 : check if triggers are trivially valid */ |
| 264 | |
| 265 | err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); |
| 266 | err |= cfc_check_trigger_src(&cmd->scan_begin_src, |
| 267 | TRIG_TIMER /*| TRIG_EXT */); |
| 268 | err |= cfc_check_trigger_src(&cmd->convert_src, |
| 269 | TRIG_TIMER /*| TRIG_EXT */); |
| 270 | err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); |
| 271 | err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
| 272 | |
| 273 | if (err) |
| 274 | return 1; |
| 275 | |
| 276 | /* Step 2a : make sure trigger sources are unique */ |
| 277 | |
| 278 | err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); |
| 279 | err |= cfc_check_trigger_is_unique(cmd->convert_src); |
| 280 | err |= cfc_check_trigger_is_unique(cmd->stop_src); |
| 281 | |
| 282 | /* Step 2b : and mutually compatible */ |
| 283 | |
| 284 | if (err) |
| 285 | return 2; |
| 286 | |
| 287 | /* step 3: make sure arguments are trivially compatible */ |
| 288 | |
| 289 | if (cmd->start_arg != 0) { |
| 290 | cmd->start_arg = 0; |
| 291 | err++; |
| 292 | } |
| 293 | #define MAX_SCAN_SPEED 1000000 /* in nanoseconds */ |
| 294 | #define MIN_SCAN_SPEED 1000000000 /* in nanoseconds */ |
| 295 | |
| 296 | if (cmd->scan_begin_src == TRIG_TIMER) { |
| 297 | if (cmd->scan_begin_arg < MAX_SCAN_SPEED) { |
| 298 | cmd->scan_begin_arg = MAX_SCAN_SPEED; |
| 299 | err++; |
| 300 | } |
| 301 | if (cmd->scan_begin_arg > MIN_SCAN_SPEED) { |
| 302 | cmd->scan_begin_arg = MIN_SCAN_SPEED; |
| 303 | err++; |
| 304 | } |
| 305 | } else { |
| 306 | /* external trigger */ |
| 307 | /* should be level/edge, hi/lo specification here */ |
| 308 | /* should specify multiple external triggers */ |
| 309 | if (cmd->scan_begin_arg > 9) { |
| 310 | cmd->scan_begin_arg = 9; |
| 311 | err++; |
| 312 | } |
| 313 | } |
| 314 | if (cmd->convert_src == TRIG_TIMER) { |
| 315 | if (cmd->convert_arg >= 17500) |
| 316 | cmd->convert_arg = 20000; |
| 317 | else if (cmd->convert_arg >= 12500) |
| 318 | cmd->convert_arg = 15000; |
| 319 | else if (cmd->convert_arg >= 7500) |
| 320 | cmd->convert_arg = 10000; |
| 321 | else |
| 322 | cmd->convert_arg = 5000; |
| 323 | |
| 324 | } else { |
| 325 | /* external trigger */ |
| 326 | /* see above */ |
| 327 | if (cmd->convert_arg > 9) { |
| 328 | cmd->convert_arg = 9; |
| 329 | err++; |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | if (cmd->scan_end_arg != cmd->chanlist_len) { |
| 334 | cmd->scan_end_arg = cmd->chanlist_len; |
| 335 | err++; |
| 336 | } |
| 337 | if (cmd->stop_src == TRIG_COUNT) { |
| 338 | if (cmd->stop_arg > 0xfffffff0) { |
| 339 | cmd->stop_arg = 0xfffffff0; |
| 340 | err++; |
| 341 | } |
| 342 | if (cmd->stop_arg == 0) { |
| 343 | cmd->stop_arg = 1; |
| 344 | err++; |
| 345 | } |
| 346 | } else { |
| 347 | /* TRIG_NONE */ |
| 348 | if (cmd->stop_arg != 0) { |
| 349 | cmd->stop_arg = 0; |
| 350 | err++; |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | if (err) |
| 355 | return 3; |
| 356 | |
| 357 | /* step 4: fix up any arguments */ |
| 358 | |
| 359 | if (cmd->scan_begin_src == TRIG_TIMER) { |
| 360 | tmp = cmd->scan_begin_arg; |
| 361 | dmm32at_ns_to_timer(&cmd->scan_begin_arg, |
| 362 | cmd->flags & TRIG_ROUND_MASK); |
| 363 | if (tmp != cmd->scan_begin_arg) |
| 364 | err++; |
| 365 | } |
| 366 | if (cmd->convert_src == TRIG_TIMER) { |
| 367 | tmp = cmd->convert_arg; |
| 368 | dmm32at_ns_to_timer(&cmd->convert_arg, |
| 369 | cmd->flags & TRIG_ROUND_MASK); |
| 370 | if (tmp != cmd->convert_arg) |
| 371 | err++; |
| 372 | if (cmd->scan_begin_src == TRIG_TIMER && |
| 373 | cmd->scan_begin_arg < |
| 374 | cmd->convert_arg * cmd->scan_end_arg) { |
| 375 | cmd->scan_begin_arg = |
| 376 | cmd->convert_arg * cmd->scan_end_arg; |
| 377 | err++; |
| 378 | } |
| 379 | } |
| 380 | |
| 381 | if (err) |
| 382 | return 4; |
| 383 | |
| 384 | /* step 5 check the channel list, the channel list for this |
| 385 | board must be consecutive and gains must be the same */ |
| 386 | |
| 387 | if (cmd->chanlist) { |
| 388 | gain = CR_RANGE(cmd->chanlist[0]); |
| 389 | start_chan = CR_CHAN(cmd->chanlist[0]); |
| 390 | for (i = 1; i < cmd->chanlist_len; i++) { |
| 391 | if (CR_CHAN(cmd->chanlist[i]) != |
| 392 | (start_chan + i) % s->n_chan) { |
| 393 | comedi_error(dev, |
| 394 | "entries in chanlist must be consecutive channels, counting upwards\n"); |
| 395 | err++; |
| 396 | } |
| 397 | if (CR_RANGE(cmd->chanlist[i]) != gain) { |
| 398 | comedi_error(dev, |
| 399 | "entries in chanlist must all have the same gain\n"); |
| 400 | err++; |
| 401 | } |
| 402 | } |
| 403 | } |
| 404 | |
| 405 | if (err) |
| 406 | return 5; |
| 407 | |
| 408 | return 0; |
| 409 | } |
| 410 | |
| 411 | static void dmm32at_setaitimer(struct comedi_device *dev, unsigned int nansec) |
| 412 | { |
| 413 | unsigned char lo1, lo2, hi2; |
| 414 | unsigned short both2; |
| 415 | |
| 416 | /* based on 10mhz clock */ |
| 417 | lo1 = 200; |
| 418 | both2 = nansec / 20000; |
| 419 | hi2 = (both2 & 0xff00) >> 8; |
| 420 | lo2 = both2 & 0x00ff; |
| 421 | |
| 422 | /* set the counter frequency to 10mhz */ |
| 423 | outb(0, dev->iobase + DMM32AT_CNTRDIO); |
| 424 | |
| 425 | /* get access to the clock regs */ |
| 426 | outb(DMM32AT_CLKACC, dev->iobase + DMM32AT_CNTRL); |
| 427 | |
| 428 | /* write the counter 1 control word and low byte to counter */ |
| 429 | outb(DMM32AT_CLKCT1, dev->iobase + DMM32AT_CLKCT); |
| 430 | outb(lo1, dev->iobase + DMM32AT_CLK1); |
| 431 | |
| 432 | /* write the counter 2 control word and low byte then to counter */ |
| 433 | outb(DMM32AT_CLKCT2, dev->iobase + DMM32AT_CLKCT); |
| 434 | outb(lo2, dev->iobase + DMM32AT_CLK2); |
| 435 | outb(hi2, dev->iobase + DMM32AT_CLK2); |
| 436 | |
| 437 | /* enable the ai conversion interrupt and the clock to start scans */ |
| 438 | outb(DMM32AT_ADINT | DMM32AT_CLKSEL, dev->iobase + DMM32AT_INTCLOCK); |
| 439 | } |
| 440 | |
| 441 | static int dmm32at_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
| 442 | { |
| 443 | struct dmm32at_private *devpriv = dev->private; |
| 444 | struct comedi_cmd *cmd = &s->async->cmd; |
| 445 | int i, range; |
| 446 | unsigned char chanlo, chanhi, status; |
| 447 | |
| 448 | if (!cmd->chanlist) |
| 449 | return -EINVAL; |
| 450 | |
| 451 | /* get the channel list and range */ |
| 452 | chanlo = CR_CHAN(cmd->chanlist[0]) & (s->n_chan - 1); |
| 453 | chanhi = chanlo + cmd->chanlist_len - 1; |
| 454 | if (chanhi >= s->n_chan) |
| 455 | return -EINVAL; |
| 456 | range = CR_RANGE(cmd->chanlist[0]); |
| 457 | |
| 458 | /* reset fifo */ |
| 459 | outb(DMM32AT_FIFORESET, dev->iobase + DMM32AT_FIFOCNTRL); |
| 460 | |
| 461 | /* set scan enable */ |
| 462 | outb(DMM32AT_SCANENABLE, dev->iobase + DMM32AT_FIFOCNTRL); |
| 463 | |
| 464 | /* write the ai channel range regs */ |
| 465 | outb(chanlo, dev->iobase + DMM32AT_AILOW); |
| 466 | outb(chanhi, dev->iobase + DMM32AT_AIHIGH); |
| 467 | |
| 468 | /* set the range bits */ |
| 469 | outb(dmm32at_rangebits[range], dev->iobase + DMM32AT_AICONF); |
| 470 | |
| 471 | /* reset the interrupt just in case */ |
| 472 | outb(DMM32AT_INTRESET, dev->iobase + DMM32AT_CNTRL); |
| 473 | |
| 474 | if (cmd->stop_src == TRIG_COUNT) |
| 475 | devpriv->ai_scans_left = cmd->stop_arg; |
| 476 | else { /* TRIG_NONE */ |
| 477 | devpriv->ai_scans_left = 0xffffffff; /* indicates TRIG_NONE to |
| 478 | * isr */ |
| 479 | } |
| 480 | |
| 481 | /* wait for circuit to settle */ |
| 482 | for (i = 0; i < 40000; i++) { |
| 483 | status = inb(dev->iobase + DMM32AT_AIRBACK); |
| 484 | if ((status & DMM32AT_STATUS) == 0) |
| 485 | break; |
| 486 | } |
| 487 | if (i == 40000) { |
| 488 | printk(KERN_WARNING "dmm32at: timeout\n"); |
| 489 | return -ETIMEDOUT; |
| 490 | } |
| 491 | |
| 492 | if (devpriv->ai_scans_left > 1) { |
| 493 | /* start the clock and enable the interrupts */ |
| 494 | dmm32at_setaitimer(dev, cmd->scan_begin_arg); |
| 495 | } else { |
| 496 | /* start the interrups and initiate a single scan */ |
| 497 | outb(DMM32AT_ADINT, dev->iobase + DMM32AT_INTCLOCK); |
| 498 | outb(0xff, dev->iobase + DMM32AT_CONV); |
| 499 | } |
| 500 | |
| 501 | /* printk("dmmat32 in command\n"); */ |
| 502 | |
| 503 | /* for(i=0;i<cmd->chanlist_len;i++) */ |
| 504 | /* comedi_buf_put(s->async,i*100); */ |
| 505 | |
| 506 | /* s->async->events |= COMEDI_CB_EOA; */ |
| 507 | /* comedi_event(dev, s); */ |
| 508 | |
| 509 | return 0; |
| 510 | |
| 511 | } |
| 512 | |
| 513 | static int dmm32at_ai_cancel(struct comedi_device *dev, |
| 514 | struct comedi_subdevice *s) |
| 515 | { |
| 516 | struct dmm32at_private *devpriv = dev->private; |
| 517 | |
| 518 | devpriv->ai_scans_left = 1; |
| 519 | return 0; |
| 520 | } |
| 521 | |
| 522 | static irqreturn_t dmm32at_isr(int irq, void *d) |
| 523 | { |
| 524 | struct comedi_device *dev = d; |
| 525 | struct dmm32at_private *devpriv = dev->private; |
| 526 | unsigned char intstat; |
| 527 | unsigned int samp; |
| 528 | unsigned short msb, lsb; |
| 529 | int i; |
| 530 | |
| 531 | if (!dev->attached) { |
| 532 | comedi_error(dev, "spurious interrupt"); |
| 533 | return IRQ_HANDLED; |
| 534 | } |
| 535 | |
| 536 | intstat = inb(dev->iobase + DMM32AT_INTCLOCK); |
| 537 | |
| 538 | if (intstat & DMM32AT_ADINT) { |
| 539 | struct comedi_subdevice *s = dev->read_subdev; |
| 540 | struct comedi_cmd *cmd = &s->async->cmd; |
| 541 | |
| 542 | for (i = 0; i < cmd->chanlist_len; i++) { |
| 543 | /* read data */ |
| 544 | lsb = inb(dev->iobase + DMM32AT_AILSB); |
| 545 | msb = inb(dev->iobase + DMM32AT_AIMSB); |
| 546 | |
| 547 | /* invert sign bit to make range unsigned */ |
| 548 | samp = ((msb ^ 0x0080) << 8) + lsb; |
| 549 | comedi_buf_put(s->async, samp); |
| 550 | } |
| 551 | |
| 552 | if (devpriv->ai_scans_left != 0xffffffff) { /* TRIG_COUNT */ |
| 553 | devpriv->ai_scans_left--; |
| 554 | if (devpriv->ai_scans_left == 0) { |
| 555 | /* disable further interrupts and clocks */ |
| 556 | outb(0x0, dev->iobase + DMM32AT_INTCLOCK); |
| 557 | /* set the buffer to be flushed with an EOF */ |
| 558 | s->async->events |= COMEDI_CB_EOA; |
| 559 | } |
| 560 | |
| 561 | } |
| 562 | /* flush the buffer */ |
| 563 | comedi_event(dev, s); |
| 564 | } |
| 565 | |
| 566 | /* reset the interrupt */ |
| 567 | outb(DMM32AT_INTRESET, dev->iobase + DMM32AT_CNTRL); |
| 568 | return IRQ_HANDLED; |
| 569 | } |
| 570 | |
| 571 | static int dmm32at_ao_winsn(struct comedi_device *dev, |
| 572 | struct comedi_subdevice *s, |
| 573 | struct comedi_insn *insn, unsigned int *data) |
| 574 | { |
| 575 | struct dmm32at_private *devpriv = dev->private; |
| 576 | int i; |
| 577 | int chan = CR_CHAN(insn->chanspec); |
| 578 | unsigned char hi, lo, status; |
| 579 | |
| 580 | /* Writing a list of values to an AO channel is probably not |
| 581 | * very useful, but that's how the interface is defined. */ |
| 582 | for (i = 0; i < insn->n; i++) { |
| 583 | |
| 584 | devpriv->ao_readback[chan] = data[i]; |
| 585 | |
| 586 | /* get the low byte */ |
| 587 | lo = data[i] & 0x00ff; |
| 588 | /* high byte also contains channel number */ |
| 589 | hi = (data[i] >> 8) + chan * (1 << 6); |
| 590 | /* printk("writing 0x%02x 0x%02x\n",hi,lo); */ |
| 591 | /* write the low and high values to the board */ |
| 592 | outb(lo, dev->iobase + DMM32AT_DACLSB); |
| 593 | outb(hi, dev->iobase + DMM32AT_DACMSB); |
| 594 | |
| 595 | /* wait for circuit to settle */ |
| 596 | for (i = 0; i < 40000; i++) { |
| 597 | status = inb(dev->iobase + DMM32AT_DACSTAT); |
| 598 | if ((status & DMM32AT_DACBUSY) == 0) |
| 599 | break; |
| 600 | } |
| 601 | if (i == 40000) { |
| 602 | printk(KERN_WARNING "dmm32at: timeout\n"); |
| 603 | return -ETIMEDOUT; |
| 604 | } |
| 605 | /* dummy read to update trigger the output */ |
| 606 | status = inb(dev->iobase + DMM32AT_DACMSB); |
| 607 | |
| 608 | } |
| 609 | |
| 610 | /* return the number of samples read/written */ |
| 611 | return i; |
| 612 | } |
| 613 | |
| 614 | static int dmm32at_ao_rinsn(struct comedi_device *dev, |
| 615 | struct comedi_subdevice *s, |
| 616 | struct comedi_insn *insn, unsigned int *data) |
| 617 | { |
| 618 | struct dmm32at_private *devpriv = dev->private; |
| 619 | int i; |
| 620 | int chan = CR_CHAN(insn->chanspec); |
| 621 | |
| 622 | for (i = 0; i < insn->n; i++) |
| 623 | data[i] = devpriv->ao_readback[chan]; |
| 624 | |
| 625 | return i; |
| 626 | } |
| 627 | |
| 628 | static int dmm32at_dio_insn_bits(struct comedi_device *dev, |
| 629 | struct comedi_subdevice *s, |
| 630 | struct comedi_insn *insn, unsigned int *data) |
| 631 | { |
| 632 | struct dmm32at_private *devpriv = dev->private; |
| 633 | unsigned char diobits; |
| 634 | |
| 635 | /* The insn data is a mask in data[0] and the new data |
| 636 | * in data[1], each channel cooresponding to a bit. */ |
| 637 | if (data[0]) { |
| 638 | s->state &= ~data[0]; |
| 639 | s->state |= data[0] & data[1]; |
| 640 | /* Write out the new digital output lines */ |
| 641 | /* outw(s->state,dev->iobase + DMM32AT_DIO); */ |
| 642 | } |
| 643 | |
| 644 | /* get access to the DIO regs */ |
| 645 | outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL); |
| 646 | |
| 647 | /* if either part of dio is set for output */ |
| 648 | if (((devpriv->dio_config & DMM32AT_DIRCL) == 0) || |
| 649 | ((devpriv->dio_config & DMM32AT_DIRCH) == 0)) { |
| 650 | diobits = (s->state & 0x00ff0000) >> 16; |
| 651 | outb(diobits, dev->iobase + DMM32AT_DIOC); |
| 652 | } |
| 653 | if ((devpriv->dio_config & DMM32AT_DIRB) == 0) { |
| 654 | diobits = (s->state & 0x0000ff00) >> 8; |
| 655 | outb(diobits, dev->iobase + DMM32AT_DIOB); |
| 656 | } |
| 657 | if ((devpriv->dio_config & DMM32AT_DIRA) == 0) { |
| 658 | diobits = (s->state & 0x000000ff); |
| 659 | outb(diobits, dev->iobase + DMM32AT_DIOA); |
| 660 | } |
| 661 | |
| 662 | /* now read the state back in */ |
| 663 | s->state = inb(dev->iobase + DMM32AT_DIOC); |
| 664 | s->state <<= 8; |
| 665 | s->state |= inb(dev->iobase + DMM32AT_DIOB); |
| 666 | s->state <<= 8; |
| 667 | s->state |= inb(dev->iobase + DMM32AT_DIOA); |
| 668 | data[1] = s->state; |
| 669 | |
| 670 | /* on return, data[1] contains the value of the digital |
| 671 | * input and output lines. */ |
| 672 | /* data[1]=inw(dev->iobase + DMM32AT_DIO); */ |
| 673 | /* or we could just return the software copy of the output values if |
| 674 | * it was a purely digital output subdevice */ |
| 675 | /* data[1]=s->state; */ |
| 676 | |
| 677 | return insn->n; |
| 678 | } |
| 679 | |
| 680 | static int dmm32at_dio_insn_config(struct comedi_device *dev, |
| 681 | struct comedi_subdevice *s, |
| 682 | struct comedi_insn *insn, unsigned int *data) |
| 683 | { |
| 684 | struct dmm32at_private *devpriv = dev->private; |
| 685 | unsigned char chanbit; |
| 686 | int chan = CR_CHAN(insn->chanspec); |
| 687 | |
| 688 | if (insn->n != 1) |
| 689 | return -EINVAL; |
| 690 | |
| 691 | if (chan < 8) |
| 692 | chanbit = DMM32AT_DIRA; |
| 693 | else if (chan < 16) |
| 694 | chanbit = DMM32AT_DIRB; |
| 695 | else if (chan < 20) |
| 696 | chanbit = DMM32AT_DIRCL; |
| 697 | else |
| 698 | chanbit = DMM32AT_DIRCH; |
| 699 | |
| 700 | /* The input or output configuration of each digital line is |
| 701 | * configured by a special insn_config instruction. chanspec |
| 702 | * contains the channel to be changed, and data[0] contains the |
| 703 | * value COMEDI_INPUT or COMEDI_OUTPUT. */ |
| 704 | |
| 705 | /* if output clear the bit, otherwise set it */ |
| 706 | if (data[0] == COMEDI_OUTPUT) |
| 707 | devpriv->dio_config &= ~chanbit; |
| 708 | else |
| 709 | devpriv->dio_config |= chanbit; |
| 710 | /* get access to the DIO regs */ |
| 711 | outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL); |
| 712 | /* set the DIO's to the new configuration setting */ |
| 713 | outb(devpriv->dio_config, dev->iobase + DMM32AT_DIOCONF); |
| 714 | |
| 715 | return 1; |
| 716 | } |
| 717 | |
| 718 | static int dmm32at_attach(struct comedi_device *dev, |
| 719 | struct comedi_devconfig *it) |
| 720 | { |
| 721 | const struct dmm32at_board *board = comedi_board(dev); |
| 722 | struct dmm32at_private *devpriv; |
| 723 | int ret; |
| 724 | struct comedi_subdevice *s; |
| 725 | unsigned char aihi, ailo, fifostat, aistat, intstat, airback; |
| 726 | unsigned long iobase; |
| 727 | unsigned int irq; |
| 728 | |
| 729 | iobase = it->options[0]; |
| 730 | irq = it->options[1]; |
| 731 | |
| 732 | printk(KERN_INFO "comedi%d: dmm32at: attaching\n", dev->minor); |
| 733 | printk(KERN_DEBUG "dmm32at: probing at address 0x%04lx, irq %u\n", |
| 734 | iobase, irq); |
| 735 | |
| 736 | /* register address space */ |
| 737 | if (!request_region(iobase, DMM32AT_MEMSIZE, board->name)) { |
| 738 | printk(KERN_ERR "comedi%d: dmm32at: I/O port conflict\n", |
| 739 | dev->minor); |
| 740 | return -EIO; |
| 741 | } |
| 742 | dev->iobase = iobase; |
| 743 | |
| 744 | /* the following just makes sure the board is there and gets |
| 745 | it to a known state */ |
| 746 | |
| 747 | /* reset the board */ |
| 748 | outb(DMM32AT_RESET, dev->iobase + DMM32AT_CNTRL); |
| 749 | |
| 750 | /* allow a millisecond to reset */ |
| 751 | udelay(1000); |
| 752 | |
| 753 | /* zero scan and fifo control */ |
| 754 | outb(0x0, dev->iobase + DMM32AT_FIFOCNTRL); |
| 755 | |
| 756 | /* zero interrupt and clock control */ |
| 757 | outb(0x0, dev->iobase + DMM32AT_INTCLOCK); |
| 758 | |
| 759 | /* write a test channel range, the high 3 bits should drop */ |
| 760 | outb(0x80, dev->iobase + DMM32AT_AILOW); |
| 761 | outb(0xff, dev->iobase + DMM32AT_AIHIGH); |
| 762 | |
| 763 | /* set the range at 10v unipolar */ |
| 764 | outb(DMM32AT_RANGE_U10, dev->iobase + DMM32AT_AICONF); |
| 765 | |
| 766 | /* should take 10 us to settle, here's a hundred */ |
| 767 | udelay(100); |
| 768 | |
| 769 | /* read back the values */ |
| 770 | ailo = inb(dev->iobase + DMM32AT_AILOW); |
| 771 | aihi = inb(dev->iobase + DMM32AT_AIHIGH); |
| 772 | fifostat = inb(dev->iobase + DMM32AT_FIFOSTAT); |
| 773 | aistat = inb(dev->iobase + DMM32AT_AISTAT); |
| 774 | intstat = inb(dev->iobase + DMM32AT_INTCLOCK); |
| 775 | airback = inb(dev->iobase + DMM32AT_AIRBACK); |
| 776 | |
| 777 | printk(KERN_DEBUG "dmm32at: lo=0x%02x hi=0x%02x fifostat=0x%02x\n", |
| 778 | ailo, aihi, fifostat); |
| 779 | printk(KERN_DEBUG |
| 780 | "dmm32at: aistat=0x%02x intstat=0x%02x airback=0x%02x\n", |
| 781 | aistat, intstat, airback); |
| 782 | |
| 783 | if ((ailo != 0x00) || (aihi != 0x1f) || (fifostat != 0x80) || |
| 784 | (aistat != 0x60 || (intstat != 0x00) || airback != 0x0c)) { |
| 785 | printk(KERN_ERR "dmmat32: board detection failed\n"); |
| 786 | return -EIO; |
| 787 | } |
| 788 | |
| 789 | /* board is there, register interrupt */ |
| 790 | if (irq) { |
| 791 | ret = request_irq(irq, dmm32at_isr, 0, board->name, dev); |
| 792 | if (ret < 0) { |
| 793 | printk(KERN_ERR "dmm32at: irq conflict\n"); |
| 794 | return ret; |
| 795 | } |
| 796 | dev->irq = irq; |
| 797 | } |
| 798 | |
| 799 | dev->board_name = board->name; |
| 800 | |
| 801 | if (alloc_private(dev, sizeof(*devpriv)) < 0) |
| 802 | return -ENOMEM; |
| 803 | devpriv = dev->private; |
| 804 | |
| 805 | ret = comedi_alloc_subdevices(dev, 3); |
| 806 | if (ret) |
| 807 | return ret; |
| 808 | |
| 809 | s = &dev->subdevices[0]; |
| 810 | dev->read_subdev = s; |
| 811 | /* analog input subdevice */ |
| 812 | s->type = COMEDI_SUBD_AI; |
| 813 | /* we support single-ended (ground) and differential */ |
| 814 | s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DIFF | SDF_CMD_READ; |
| 815 | s->n_chan = 32; |
| 816 | s->maxdata = 0xffff; |
| 817 | s->range_table = &dmm32at_airanges; |
| 818 | s->len_chanlist = 32; /* This is the maximum chanlist length that |
| 819 | the board can handle */ |
| 820 | s->insn_read = dmm32at_ai_rinsn; |
| 821 | s->do_cmd = dmm32at_ai_cmd; |
| 822 | s->do_cmdtest = dmm32at_ai_cmdtest; |
| 823 | s->cancel = dmm32at_ai_cancel; |
| 824 | |
| 825 | s = &dev->subdevices[1]; |
| 826 | /* analog output subdevice */ |
| 827 | s->type = COMEDI_SUBD_AO; |
| 828 | s->subdev_flags = SDF_WRITABLE; |
| 829 | s->n_chan = 4; |
| 830 | s->maxdata = 0x0fff; |
| 831 | s->range_table = &dmm32at_aoranges; |
| 832 | s->insn_write = dmm32at_ao_winsn; |
| 833 | s->insn_read = dmm32at_ao_rinsn; |
| 834 | |
| 835 | s = &dev->subdevices[2]; |
| 836 | /* digital i/o subdevice */ |
| 837 | |
| 838 | /* get access to the DIO regs */ |
| 839 | outb(DMM32AT_DIOACC, dev->iobase + DMM32AT_CNTRL); |
| 840 | /* set the DIO's to the defualt input setting */ |
| 841 | devpriv->dio_config = DMM32AT_DIRA | DMM32AT_DIRB | |
| 842 | DMM32AT_DIRCL | DMM32AT_DIRCH | DMM32AT_DIENABLE; |
| 843 | outb(devpriv->dio_config, dev->iobase + DMM32AT_DIOCONF); |
| 844 | |
| 845 | /* set up the subdevice */ |
| 846 | s->type = COMEDI_SUBD_DIO; |
| 847 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
| 848 | s->n_chan = 24; |
| 849 | s->maxdata = 1; |
| 850 | s->state = 0; |
| 851 | s->range_table = &range_digital; |
| 852 | s->insn_bits = dmm32at_dio_insn_bits; |
| 853 | s->insn_config = dmm32at_dio_insn_config; |
| 854 | |
| 855 | /* success */ |
| 856 | printk(KERN_INFO "comedi%d: dmm32at: attached\n", dev->minor); |
| 857 | |
| 858 | return 1; |
| 859 | |
| 860 | } |
| 861 | |
| 862 | static void dmm32at_detach(struct comedi_device *dev) |
| 863 | { |
| 864 | if (dev->irq) |
| 865 | free_irq(dev->irq, dev); |
| 866 | if (dev->iobase) |
| 867 | release_region(dev->iobase, DMM32AT_MEMSIZE); |
| 868 | } |
| 869 | |
| 870 | static const struct dmm32at_board dmm32at_boards[] = { |
| 871 | { |
| 872 | .name = "dmm32at", |
| 873 | }, |
| 874 | }; |
| 875 | |
| 876 | static struct comedi_driver dmm32at_driver = { |
| 877 | .driver_name = "dmm32at", |
| 878 | .module = THIS_MODULE, |
| 879 | .attach = dmm32at_attach, |
| 880 | .detach = dmm32at_detach, |
| 881 | .board_name = &dmm32at_boards[0].name, |
| 882 | .offset = sizeof(struct dmm32at_board), |
| 883 | .num_names = ARRAY_SIZE(dmm32at_boards), |
| 884 | }; |
| 885 | module_comedi_driver(dmm32at_driver); |
| 886 | |
| 887 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
| 888 | MODULE_DESCRIPTION("Comedi low-level driver"); |
| 889 | MODULE_LICENSE("GPL"); |