pcl711.c revision 34c43922e62708d45e9660eee4b4f1fb7b4bf2c7
1/* 2 comedi/drivers/pcl711.c 3 hardware driver for PC-LabCard PCL-711 and AdSys ACL-8112 4 and compatibles 5 6 COMEDI - Linux Control and Measurement Device Interface 7 Copyright (C) 1998 David A. Schleef <ds@schleef.org> 8 Janne Jalkanen <jalkanen@cs.hut.fi> 9 Eric Bunn <ebu@cs.hut.fi> 10 11 This program is free software; you can redistribute it and/or modify 12 it under the terms of the GNU General Public License as published by 13 the Free Software Foundation; either version 2 of the License, or 14 (at your option) any later version. 15 16 This program is distributed in the hope that it will be useful, 17 but WITHOUT ANY WARRANTY; without even the implied warranty of 18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 GNU General Public License for more details. 20 21 You should have received a copy of the GNU General Public License 22 along with this program; if not, write to the Free Software 23 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 24 25 */ 26/* 27Driver: pcl711 28Description: Advantech PCL-711 and 711b, ADLink ACL-8112 29Author: ds, Janne Jalkanen <jalkanen@cs.hut.fi>, Eric Bunn <ebu@cs.hut.fi> 30Status: mostly complete 31Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b), 32 [AdLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg) 33 34Since these boards do not have DMA or FIFOs, only immediate mode is 35supported. 36 37*/ 38 39/* 40 Dave Andruczyk <dave@tech.buffalostate.edu> also wrote a 41 driver for the PCL-711. I used a few ideas from his driver 42 here. His driver also has more comments, if you are 43 interested in understanding how this driver works. 44 http://tech.buffalostate.edu/~dave/driver/ 45 46 The ACL-8112 driver was hacked from the sources of the PCL-711 47 driver (the 744 chip used on the 8112 is almost the same as 48 the 711b chip, but it has more I/O channels) by 49 Janne Jalkanen (jalkanen@cs.hut.fi) and 50 Erik Bunn (ebu@cs.hut.fi). Remerged with the PCL-711 driver 51 by ds. 52 53 [acl-8112] 54 This driver supports both TRIGNOW and TRIGCLK, 55 but does not yet support DMA transfers. It also supports 56 both high (HG) and low (DG) versions of the card, though 57 the HG version has been untested. 58 59 */ 60 61#include "../comedidev.h" 62 63#include <linux/ioport.h> 64#include <linux/delay.h> 65 66#include "8253.h" 67 68#define PCL711_SIZE 16 69 70#define PCL711_CTR0 0 71#define PCL711_CTR1 1 72#define PCL711_CTR2 2 73#define PCL711_CTRCTL 3 74#define PCL711_AD_LO 4 75#define PCL711_DA0_LO 4 76#define PCL711_AD_HI 5 77#define PCL711_DA0_HI 5 78#define PCL711_DI_LO 6 79#define PCL711_DA1_LO 6 80#define PCL711_DI_HI 7 81#define PCL711_DA1_HI 7 82#define PCL711_CLRINTR 8 83#define PCL711_GAIN 9 84#define PCL711_MUX 10 85#define PCL711_MODE 11 86#define PCL711_SOFTTRIG 12 87#define PCL711_DO_LO 13 88#define PCL711_DO_HI 14 89 90static const comedi_lrange range_pcl711b_ai = { 5, { 91 BIP_RANGE(5), 92 BIP_RANGE(2.5), 93 BIP_RANGE(1.25), 94 BIP_RANGE(0.625), 95 BIP_RANGE(0.3125) 96 } 97}; 98static const comedi_lrange range_acl8112hg_ai = { 12, { 99 BIP_RANGE(5), 100 BIP_RANGE(0.5), 101 BIP_RANGE(0.05), 102 BIP_RANGE(0.005), 103 UNI_RANGE(10), 104 UNI_RANGE(1), 105 UNI_RANGE(0.1), 106 UNI_RANGE(0.01), 107 BIP_RANGE(10), 108 BIP_RANGE(1), 109 BIP_RANGE(0.1), 110 BIP_RANGE(0.01) 111 } 112}; 113static const comedi_lrange range_acl8112dg_ai = { 9, { 114 BIP_RANGE(5), 115 BIP_RANGE(2.5), 116 BIP_RANGE(1.25), 117 BIP_RANGE(0.625), 118 UNI_RANGE(10), 119 UNI_RANGE(5), 120 UNI_RANGE(2.5), 121 UNI_RANGE(1.25), 122 BIP_RANGE(10) 123 } 124}; 125 126/* 127 * flags 128 */ 129 130#define PCL711_TIMEOUT 100 131#define PCL711_DRDY 0x10 132 133static const int i8253_osc_base = 500; /* 2 Mhz */ 134 135typedef struct { 136 const char *name; 137 int is_pcl711b; 138 int is_8112; 139 int is_dg; 140 int n_ranges; 141 int n_aichan; 142 int n_aochan; 143 int maxirq; 144 const comedi_lrange *ai_range_type; 145} boardtype; 146 147static const boardtype boardtypes[] = { 148 {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5}, 149 {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai}, 150 {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai}, 151 {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai}, 152}; 153 154#define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype)) 155#define this_board ((const boardtype *)dev->board_ptr) 156 157static int pcl711_attach(struct comedi_device * dev, comedi_devconfig * it); 158static int pcl711_detach(struct comedi_device * dev); 159static comedi_driver driver_pcl711 = { 160 driver_name:"pcl711", 161 module:THIS_MODULE, 162 attach:pcl711_attach, 163 detach:pcl711_detach, 164 board_name:&boardtypes[0].name, 165 num_names:n_boardtypes, 166 offset:sizeof(boardtype), 167}; 168 169COMEDI_INITCLEANUP(driver_pcl711); 170 171typedef struct { 172 int board; 173 int adchan; 174 int ntrig; 175 int aip[8]; 176 int mode; 177 unsigned int ao_readback[2]; 178 unsigned int divisor1; 179 unsigned int divisor2; 180} pcl711_private; 181 182#define devpriv ((pcl711_private *)dev->private) 183 184static irqreturn_t pcl711_interrupt(int irq, void *d PT_REGS_ARG) 185{ 186 int lo, hi; 187 int data; 188 struct comedi_device *dev = d; 189 struct comedi_subdevice *s = dev->subdevices + 0; 190 191 if (!dev->attached) { 192 comedi_error(dev, "spurious interrupt"); 193 return IRQ_HANDLED; 194 } 195 196 hi = inb(dev->iobase + PCL711_AD_HI); 197 lo = inb(dev->iobase + PCL711_AD_LO); 198 outb(0, dev->iobase + PCL711_CLRINTR); 199 200 data = (hi << 8) | lo; 201 202 /* FIXME! Nothing else sets ntrig! */ 203 if (!(--devpriv->ntrig)) { 204 if (this_board->is_8112) { 205 outb(1, dev->iobase + PCL711_MODE); 206 } else { 207 outb(0, dev->iobase + PCL711_MODE); 208 } 209 210 s->async->events |= COMEDI_CB_EOA; 211 } 212 comedi_event(dev, s); 213 return IRQ_HANDLED; 214} 215 216static void pcl711_set_changain(struct comedi_device * dev, int chan) 217{ 218 int chan_register; 219 220 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN); 221 222 chan_register = CR_CHAN(chan); 223 224 if (this_board->is_8112) { 225 226 /* 227 * Set the correct channel. The two channel banks are switched 228 * using the mask value. 229 * NB: To use differential channels, you should use mask = 0x30, 230 * but I haven't written the support for this yet. /JJ 231 */ 232 233 if (chan_register >= 8) { 234 chan_register = 0x20 | (chan_register & 0x7); 235 } else { 236 chan_register |= 0x10; 237 } 238 } else { 239 outb(chan_register, dev->iobase + PCL711_MUX); 240 } 241} 242 243static int pcl711_ai_insn(struct comedi_device * dev, struct comedi_subdevice * s, 244 comedi_insn * insn, unsigned int * data) 245{ 246 int i, n; 247 int hi, lo; 248 249 pcl711_set_changain(dev, insn->chanspec); 250 251 for (n = 0; n < insn->n; n++) { 252 /* 253 * Write the correct mode (software polling) and start polling by writing 254 * to the trigger register 255 */ 256 outb(1, dev->iobase + PCL711_MODE); 257 258 if (this_board->is_8112) { 259 } else { 260 outb(0, dev->iobase + PCL711_SOFTTRIG); 261 } 262 263 i = PCL711_TIMEOUT; 264 while (--i) { 265 hi = inb(dev->iobase + PCL711_AD_HI); 266 if (!(hi & PCL711_DRDY)) 267 goto ok; 268 comedi_udelay(1); 269 } 270 rt_printk("comedi%d: pcl711: A/D timeout\n", dev->minor); 271 return -ETIME; 272 273 ok: 274 lo = inb(dev->iobase + PCL711_AD_LO); 275 276 data[n] = ((hi & 0xf) << 8) | lo; 277 } 278 279 return n; 280} 281 282static int pcl711_ai_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, 283 comedi_cmd * cmd) 284{ 285 int tmp; 286 int err = 0; 287 288 /* step 1 */ 289 tmp = cmd->start_src; 290 cmd->start_src &= TRIG_NOW; 291 if (!cmd->start_src || tmp != cmd->start_src) 292 err++; 293 294 tmp = cmd->scan_begin_src; 295 cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; 296 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) 297 err++; 298 299 tmp = cmd->convert_src; 300 cmd->convert_src &= TRIG_NOW; 301 if (!cmd->convert_src || tmp != cmd->convert_src) 302 err++; 303 304 tmp = cmd->scan_end_src; 305 cmd->scan_end_src &= TRIG_COUNT; 306 if (!cmd->scan_end_src || tmp != cmd->scan_end_src) 307 err++; 308 309 tmp = cmd->stop_src; 310 cmd->stop_src &= TRIG_COUNT | TRIG_NONE; 311 if (!cmd->stop_src || tmp != cmd->stop_src) 312 err++; 313 314 if (err) 315 return 1; 316 317 /* step 2 */ 318 319 if (cmd->scan_begin_src != TRIG_TIMER && 320 cmd->scan_begin_src != TRIG_EXT) 321 err++; 322 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) 323 err++; 324 325 if (err) 326 return 2; 327 328 /* step 3 */ 329 330 if (cmd->start_arg != 0) { 331 cmd->start_arg = 0; 332 err++; 333 } 334 if (cmd->scan_begin_src == TRIG_EXT) { 335 if (cmd->scan_begin_arg != 0) { 336 cmd->scan_begin_arg = 0; 337 err++; 338 } 339 } else { 340#define MAX_SPEED 1000 341#define TIMER_BASE 100 342 if (cmd->scan_begin_arg < MAX_SPEED) { 343 cmd->scan_begin_arg = MAX_SPEED; 344 err++; 345 } 346 } 347 if (cmd->convert_arg != 0) { 348 cmd->convert_arg = 0; 349 err++; 350 } 351 if (cmd->scan_end_arg != cmd->chanlist_len) { 352 cmd->scan_end_arg = cmd->chanlist_len; 353 err++; 354 } 355 if (cmd->stop_src == TRIG_NONE) { 356 if (cmd->stop_arg != 0) { 357 cmd->stop_arg = 0; 358 err++; 359 } 360 } else { 361 /* ignore */ 362 } 363 364 if (err) 365 return 3; 366 367 /* step 4 */ 368 369 if (cmd->scan_begin_src == TRIG_TIMER) { 370 tmp = cmd->scan_begin_arg; 371 i8253_cascade_ns_to_timer_2div(TIMER_BASE, 372 &devpriv->divisor1, &devpriv->divisor2, 373 &cmd->scan_begin_arg, cmd->flags & TRIG_ROUND_MASK); 374 if (tmp != cmd->scan_begin_arg) 375 err++; 376 } 377 378 if (err) 379 return 4; 380 381 return 0; 382} 383 384static int pcl711_ai_cmd(struct comedi_device * dev, struct comedi_subdevice * s) 385{ 386 int timer1, timer2; 387 comedi_cmd *cmd = &s->async->cmd; 388 389 pcl711_set_changain(dev, cmd->chanlist[0]); 390 391 if (cmd->scan_begin_src == TRIG_TIMER) { 392 /* 393 * Set timers 394 * timer chip is an 8253, with timers 1 and 2 395 * cascaded 396 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary 397 * Mode 2 = Rate generator 398 * 399 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary 400 */ 401 402 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2, 403 &cmd->scan_begin_arg, TRIG_ROUND_NEAREST); 404 405 outb(0x74, dev->iobase + PCL711_CTRCTL); 406 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1); 407 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1); 408 outb(0xb4, dev->iobase + PCL711_CTRCTL); 409 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2); 410 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2); 411 412 /* clear pending interrupts (just in case) */ 413 outb(0, dev->iobase + PCL711_CLRINTR); 414 415 /* 416 * Set mode to IRQ transfer 417 */ 418 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE); 419 } else { 420 /* external trigger */ 421 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE); 422 } 423 424 return 0; 425} 426 427/* 428 analog output 429*/ 430static int pcl711_ao_insn(struct comedi_device * dev, struct comedi_subdevice * s, 431 comedi_insn * insn, unsigned int * data) 432{ 433 int n; 434 int chan = CR_CHAN(insn->chanspec); 435 436 for (n = 0; n < insn->n; n++) { 437 outb((data[n] & 0xff), 438 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO)); 439 outb((data[n] >> 8), 440 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI)); 441 442 devpriv->ao_readback[chan] = data[n]; 443 } 444 445 return n; 446} 447 448static int pcl711_ao_insn_read(struct comedi_device * dev, struct comedi_subdevice * s, 449 comedi_insn * insn, unsigned int * data) 450{ 451 int n; 452 int chan = CR_CHAN(insn->chanspec); 453 454 for (n = 0; n < insn->n; n++) { 455 data[n] = devpriv->ao_readback[chan]; 456 } 457 458 return n; 459 460} 461 462/* Digital port read - Untested on 8112 */ 463static int pcl711_di_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, 464 comedi_insn * insn, unsigned int * data) 465{ 466 if (insn->n != 2) 467 return -EINVAL; 468 469 data[1] = inb(dev->iobase + PCL711_DI_LO) | 470 (inb(dev->iobase + PCL711_DI_HI) << 8); 471 472 return 2; 473} 474 475/* Digital port write - Untested on 8112 */ 476static int pcl711_do_insn_bits(struct comedi_device * dev, struct comedi_subdevice * s, 477 comedi_insn * insn, unsigned int * data) 478{ 479 if (insn->n != 2) 480 return -EINVAL; 481 482 if (data[0]) { 483 s->state &= ~data[0]; 484 s->state |= data[0] & data[1]; 485 } 486 if (data[0] & 0x00ff) 487 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO); 488 if (data[0] & 0xff00) 489 outb((s->state >> 8), dev->iobase + PCL711_DO_HI); 490 491 data[1] = s->state; 492 493 return 2; 494} 495 496/* Free any resources that we have claimed */ 497static int pcl711_detach(struct comedi_device * dev) 498{ 499 printk("comedi%d: pcl711: remove\n", dev->minor); 500 501 if (dev->irq) 502 comedi_free_irq(dev->irq, dev); 503 504 if (dev->iobase) 505 release_region(dev->iobase, PCL711_SIZE); 506 507 return 0; 508} 509 510/* Initialization */ 511static int pcl711_attach(struct comedi_device * dev, comedi_devconfig * it) 512{ 513 int ret; 514 unsigned long iobase; 515 unsigned int irq; 516 struct comedi_subdevice *s; 517 518 /* claim our I/O space */ 519 520 iobase = it->options[0]; 521 printk("comedi%d: pcl711: 0x%04lx ", dev->minor, iobase); 522 if (!request_region(iobase, PCL711_SIZE, "pcl711")) { 523 printk("I/O port conflict\n"); 524 return -EIO; 525 } 526 dev->iobase = iobase; 527 528 /* there should be a sanity check here */ 529 530 /* set up some name stuff */ 531 dev->board_name = this_board->name; 532 533 /* grab our IRQ */ 534 irq = it->options[1]; 535 if (irq > this_board->maxirq) { 536 printk("irq out of range\n"); 537 return -EINVAL; 538 } 539 if (irq) { 540 if (comedi_request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) { 541 printk("unable to allocate irq %u\n", irq); 542 return -EINVAL; 543 } else { 544 printk("( irq = %u )\n", irq); 545 } 546 } 547 dev->irq = irq; 548 549 if ((ret = alloc_subdevices(dev, 4)) < 0) 550 return ret; 551 if ((ret = alloc_private(dev, sizeof(pcl711_private))) < 0) 552 return ret; 553 554 s = dev->subdevices + 0; 555 /* AI subdevice */ 556 s->type = COMEDI_SUBD_AI; 557 s->subdev_flags = SDF_READABLE | SDF_GROUND; 558 s->n_chan = this_board->n_aichan; 559 s->maxdata = 0xfff; 560 s->len_chanlist = 1; 561 s->range_table = this_board->ai_range_type; 562 s->insn_read = pcl711_ai_insn; 563 if (irq) { 564 dev->read_subdev = s; 565 s->subdev_flags |= SDF_CMD_READ; 566 s->do_cmdtest = pcl711_ai_cmdtest; 567 s->do_cmd = pcl711_ai_cmd; 568 } 569 570 s++; 571 /* AO subdevice */ 572 s->type = COMEDI_SUBD_AO; 573 s->subdev_flags = SDF_WRITABLE; 574 s->n_chan = this_board->n_aochan; 575 s->maxdata = 0xfff; 576 s->len_chanlist = 1; 577 s->range_table = &range_bipolar5; 578 s->insn_write = pcl711_ao_insn; 579 s->insn_read = pcl711_ao_insn_read; 580 581 s++; 582 /* 16-bit digital input */ 583 s->type = COMEDI_SUBD_DI; 584 s->subdev_flags = SDF_READABLE; 585 s->n_chan = 16; 586 s->maxdata = 1; 587 s->len_chanlist = 16; 588 s->range_table = &range_digital; 589 s->insn_bits = pcl711_di_insn_bits; 590 591 s++; 592 /* 16-bit digital out */ 593 s->type = COMEDI_SUBD_DO; 594 s->subdev_flags = SDF_WRITABLE; 595 s->n_chan = 16; 596 s->maxdata = 1; 597 s->len_chanlist = 16; 598 s->range_table = &range_digital; 599 s->state = 0; 600 s->insn_bits = pcl711_do_insn_bits; 601 602 /* 603 this is the "base value" for the mode register, which is 604 used for the irq on the PCL711 605 */ 606 if (this_board->is_pcl711b) { 607 devpriv->mode = (dev->irq << 4); 608 } 609 610 /* clear DAC */ 611 outb(0, dev->iobase + PCL711_DA0_LO); 612 outb(0, dev->iobase + PCL711_DA0_HI); 613 outb(0, dev->iobase + PCL711_DA1_LO); 614 outb(0, dev->iobase + PCL711_DA1_HI); 615 616 printk("\n"); 617 618 return 0; 619} 620