pcl711.c revision 8f4e80af3aa509902bd2319c3b5512580f64868f
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 <linux/interrupt.h> 62#include "../comedidev.h" 63 64#include <linux/ioport.h> 65#include <linux/delay.h> 66 67#include "8253.h" 68 69#define PCL711_SIZE 16 70 71#define PCL711_CTR0 0 72#define PCL711_CTR1 1 73#define PCL711_CTR2 2 74#define PCL711_CTRCTL 3 75#define PCL711_AD_LO 4 76#define PCL711_DA0_LO 4 77#define PCL711_AD_HI 5 78#define PCL711_DA0_HI 5 79#define PCL711_DI_LO 6 80#define PCL711_DA1_LO 6 81#define PCL711_DI_HI 7 82#define PCL711_DA1_HI 7 83#define PCL711_CLRINTR 8 84#define PCL711_GAIN 9 85#define PCL711_MUX 10 86#define PCL711_MODE 11 87#define PCL711_SOFTTRIG 12 88#define PCL711_DO_LO 13 89#define PCL711_DO_HI 14 90 91static const struct comedi_lrange range_pcl711b_ai = { 5, { 92 BIP_RANGE(5), 93 BIP_RANGE(2.5), 94 BIP_RANGE(1.25), 95 BIP_RANGE(0.625), 96 BIP_RANGE(0.3125) 97 } 98}; 99 100static const struct comedi_lrange range_acl8112hg_ai = { 12, { 101 BIP_RANGE(5), 102 BIP_RANGE(0.5), 103 BIP_RANGE(0.05), 104 BIP_RANGE(0.005), 105 UNI_RANGE(10), 106 UNI_RANGE(1), 107 UNI_RANGE(0.1), 108 UNI_RANGE(0.01), 109 BIP_RANGE(10), 110 BIP_RANGE(1), 111 BIP_RANGE(0.1), 112 BIP_RANGE(0.01) 113 } 114}; 115 116static const struct comedi_lrange range_acl8112dg_ai = { 9, { 117 BIP_RANGE(5), 118 BIP_RANGE(2.5), 119 BIP_RANGE(1.25), 120 BIP_RANGE(0.625), 121 UNI_RANGE(10), 122 UNI_RANGE(5), 123 UNI_RANGE(2.5), 124 UNI_RANGE(1.25), 125 BIP_RANGE(10) 126 } 127}; 128 129/* 130 * flags 131 */ 132 133#define PCL711_TIMEOUT 100 134#define PCL711_DRDY 0x10 135 136static const int i8253_osc_base = 500; /* 2 Mhz */ 137 138struct pcl711_board { 139 140 const char *name; 141 int is_pcl711b; 142 int is_8112; 143 int is_dg; 144 int n_ranges; 145 int n_aichan; 146 int n_aochan; 147 int maxirq; 148 const struct comedi_lrange *ai_range_type; 149}; 150 151static const struct pcl711_board boardtypes[] = { 152 {"pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5}, 153 {"pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai}, 154 {"acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai}, 155 {"acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai}, 156}; 157 158#define n_boardtypes (sizeof(boardtypes)/sizeof(struct pcl711_board)) 159#define this_board ((const struct pcl711_board *)dev->board_ptr) 160 161static int pcl711_attach(struct comedi_device *dev, 162 struct comedi_devconfig *it); 163static int pcl711_detach(struct comedi_device *dev); 164static struct comedi_driver driver_pcl711 = { 165 .driver_name = "pcl711", 166 .module = THIS_MODULE, 167 .attach = pcl711_attach, 168 .detach = pcl711_detach, 169 .board_name = &boardtypes[0].name, 170 .num_names = n_boardtypes, 171 .offset = sizeof(struct pcl711_board), 172}; 173 174static int __init driver_pcl711_init_module(void) 175{ 176 return comedi_driver_register(&driver_pcl711); 177} 178 179static void __exit driver_pcl711_cleanup_module(void) 180{ 181 comedi_driver_unregister(&driver_pcl711); 182} 183 184module_init(driver_pcl711_init_module); 185module_exit(driver_pcl711_cleanup_module); 186 187struct pcl711_private { 188 189 int board; 190 int adchan; 191 int ntrig; 192 int aip[8]; 193 int mode; 194 unsigned int ao_readback[2]; 195 unsigned int divisor1; 196 unsigned int divisor2; 197}; 198 199#define devpriv ((struct pcl711_private *)dev->private) 200 201static irqreturn_t pcl711_interrupt(int irq, void *d) 202{ 203 int lo, hi; 204 int data; 205 struct comedi_device *dev = d; 206 struct comedi_subdevice *s = dev->subdevices + 0; 207 208 if (!dev->attached) { 209 comedi_error(dev, "spurious interrupt"); 210 return IRQ_HANDLED; 211 } 212 213 hi = inb(dev->iobase + PCL711_AD_HI); 214 lo = inb(dev->iobase + PCL711_AD_LO); 215 outb(0, dev->iobase + PCL711_CLRINTR); 216 217 data = (hi << 8) | lo; 218 219 /* FIXME! Nothing else sets ntrig! */ 220 if (!(--devpriv->ntrig)) { 221 if (this_board->is_8112) 222 outb(1, dev->iobase + PCL711_MODE); 223 else 224 outb(0, dev->iobase + PCL711_MODE); 225 226 s->async->events |= COMEDI_CB_EOA; 227 } 228 comedi_event(dev, s); 229 return IRQ_HANDLED; 230} 231 232static void pcl711_set_changain(struct comedi_device *dev, int chan) 233{ 234 int chan_register; 235 236 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN); 237 238 chan_register = CR_CHAN(chan); 239 240 if (this_board->is_8112) { 241 242 /* 243 * Set the correct channel. The two channel banks are switched 244 * using the mask value. 245 * NB: To use differential channels, you should use 246 * mask = 0x30, but I haven't written the support for this 247 * yet. /JJ 248 */ 249 250 if (chan_register >= 8) 251 chan_register = 0x20 | (chan_register & 0x7); 252 else 253 chan_register |= 0x10; 254 } else { 255 outb(chan_register, dev->iobase + PCL711_MUX); 256 } 257} 258 259static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s, 260 struct comedi_insn *insn, unsigned int *data) 261{ 262 int i, n; 263 int hi, lo; 264 265 pcl711_set_changain(dev, insn->chanspec); 266 267 for (n = 0; n < insn->n; n++) { 268 /* 269 * Write the correct mode (software polling) and start polling 270 * by writing to the trigger register 271 */ 272 outb(1, dev->iobase + PCL711_MODE); 273 274 if (!this_board->is_8112) 275 outb(0, dev->iobase + PCL711_SOFTTRIG); 276 277 i = PCL711_TIMEOUT; 278 while (--i) { 279 hi = inb(dev->iobase + PCL711_AD_HI); 280 if (!(hi & PCL711_DRDY)) 281 goto ok; 282 udelay(1); 283 } 284 printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor); 285 return -ETIME; 286 287ok: 288 lo = inb(dev->iobase + PCL711_AD_LO); 289 290 data[n] = ((hi & 0xf) << 8) | lo; 291 } 292 293 return n; 294} 295 296static int pcl711_ai_cmdtest(struct comedi_device *dev, 297 struct comedi_subdevice *s, struct comedi_cmd *cmd) 298{ 299 int tmp; 300 int err = 0; 301 302 /* step 1 */ 303 tmp = cmd->start_src; 304 cmd->start_src &= TRIG_NOW; 305 if (!cmd->start_src || tmp != cmd->start_src) 306 err++; 307 308 tmp = cmd->scan_begin_src; 309 cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; 310 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) 311 err++; 312 313 tmp = cmd->convert_src; 314 cmd->convert_src &= TRIG_NOW; 315 if (!cmd->convert_src || tmp != cmd->convert_src) 316 err++; 317 318 tmp = cmd->scan_end_src; 319 cmd->scan_end_src &= TRIG_COUNT; 320 if (!cmd->scan_end_src || tmp != cmd->scan_end_src) 321 err++; 322 323 tmp = cmd->stop_src; 324 cmd->stop_src &= TRIG_COUNT | TRIG_NONE; 325 if (!cmd->stop_src || tmp != cmd->stop_src) 326 err++; 327 328 if (err) 329 return 1; 330 331 /* step 2 */ 332 333 if (cmd->scan_begin_src != TRIG_TIMER && 334 cmd->scan_begin_src != TRIG_EXT) 335 err++; 336 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) 337 err++; 338 339 if (err) 340 return 2; 341 342 /* step 3 */ 343 344 if (cmd->start_arg != 0) { 345 cmd->start_arg = 0; 346 err++; 347 } 348 if (cmd->scan_begin_src == TRIG_EXT) { 349 if (cmd->scan_begin_arg != 0) { 350 cmd->scan_begin_arg = 0; 351 err++; 352 } 353 } else { 354#define MAX_SPEED 1000 355#define TIMER_BASE 100 356 if (cmd->scan_begin_arg < MAX_SPEED) { 357 cmd->scan_begin_arg = MAX_SPEED; 358 err++; 359 } 360 } 361 if (cmd->convert_arg != 0) { 362 cmd->convert_arg = 0; 363 err++; 364 } 365 if (cmd->scan_end_arg != cmd->chanlist_len) { 366 cmd->scan_end_arg = cmd->chanlist_len; 367 err++; 368 } 369 if (cmd->stop_src == TRIG_NONE) { 370 if (cmd->stop_arg != 0) { 371 cmd->stop_arg = 0; 372 err++; 373 } 374 } else { 375 /* ignore */ 376 } 377 378 if (err) 379 return 3; 380 381 /* step 4 */ 382 383 if (cmd->scan_begin_src == TRIG_TIMER) { 384 tmp = cmd->scan_begin_arg; 385 i8253_cascade_ns_to_timer_2div(TIMER_BASE, 386 &devpriv->divisor1, 387 &devpriv->divisor2, 388 &cmd->scan_begin_arg, 389 cmd->flags & TRIG_ROUND_MASK); 390 if (tmp != cmd->scan_begin_arg) 391 err++; 392 } 393 394 if (err) 395 return 4; 396 397 return 0; 398} 399 400static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 401{ 402 int timer1, timer2; 403 struct comedi_cmd *cmd = &s->async->cmd; 404 405 pcl711_set_changain(dev, cmd->chanlist[0]); 406 407 if (cmd->scan_begin_src == TRIG_TIMER) { 408 /* 409 * Set timers 410 * timer chip is an 8253, with timers 1 and 2 411 * cascaded 412 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary 413 * Mode 2 = Rate generator 414 * 415 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary 416 */ 417 418 timer1 = timer2 = 0; 419 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2, 420 &cmd->scan_begin_arg, 421 TRIG_ROUND_NEAREST); 422 423 outb(0x74, dev->iobase + PCL711_CTRCTL); 424 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1); 425 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1); 426 outb(0xb4, dev->iobase + PCL711_CTRCTL); 427 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2); 428 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2); 429 430 /* clear pending interrupts (just in case) */ 431 outb(0, dev->iobase + PCL711_CLRINTR); 432 433 /* 434 * Set mode to IRQ transfer 435 */ 436 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE); 437 } else { 438 /* external trigger */ 439 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE); 440 } 441 442 return 0; 443} 444 445/* 446 analog output 447*/ 448static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, 449 struct 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 outb((data[n] & 0xff), 456 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO)); 457 outb((data[n] >> 8), 458 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI)); 459 460 devpriv->ao_readback[chan] = data[n]; 461 } 462 463 return n; 464} 465 466static int pcl711_ao_insn_read(struct comedi_device *dev, 467 struct comedi_subdevice *s, 468 struct comedi_insn *insn, unsigned int *data) 469{ 470 int n; 471 int chan = CR_CHAN(insn->chanspec); 472 473 for (n = 0; n < insn->n; n++) 474 data[n] = devpriv->ao_readback[chan]; 475 476 return n; 477 478} 479 480/* Digital port read - Untested on 8112 */ 481static int pcl711_di_insn_bits(struct comedi_device *dev, 482 struct comedi_subdevice *s, 483 struct comedi_insn *insn, unsigned int *data) 484{ 485 if (insn->n != 2) 486 return -EINVAL; 487 488 data[1] = inb(dev->iobase + PCL711_DI_LO) | 489 (inb(dev->iobase + PCL711_DI_HI) << 8); 490 491 return 2; 492} 493 494/* Digital port write - Untested on 8112 */ 495static int pcl711_do_insn_bits(struct comedi_device *dev, 496 struct comedi_subdevice *s, 497 struct comedi_insn *insn, unsigned int *data) 498{ 499 if (insn->n != 2) 500 return -EINVAL; 501 502 if (data[0]) { 503 s->state &= ~data[0]; 504 s->state |= data[0] & data[1]; 505 } 506 if (data[0] & 0x00ff) 507 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO); 508 if (data[0] & 0xff00) 509 outb((s->state >> 8), dev->iobase + PCL711_DO_HI); 510 511 data[1] = s->state; 512 513 return 2; 514} 515 516/* Free any resources that we have claimed */ 517static int pcl711_detach(struct comedi_device *dev) 518{ 519 printk(KERN_INFO "comedi%d: pcl711: remove\n", dev->minor); 520 521 if (dev->irq) 522 free_irq(dev->irq, dev); 523 524 if (dev->iobase) 525 release_region(dev->iobase, PCL711_SIZE); 526 527 return 0; 528} 529 530/* Initialization */ 531static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it) 532{ 533 int ret; 534 unsigned long iobase; 535 unsigned int irq; 536 struct comedi_subdevice *s; 537 538 /* claim our I/O space */ 539 540 iobase = it->options[0]; 541 printk(KERN_INFO "comedi%d: pcl711: 0x%04lx ", dev->minor, iobase); 542 if (!request_region(iobase, PCL711_SIZE, "pcl711")) { 543 printk("I/O port conflict\n"); 544 return -EIO; 545 } 546 dev->iobase = iobase; 547 548 /* there should be a sanity check here */ 549 550 /* set up some name stuff */ 551 dev->board_name = this_board->name; 552 553 /* grab our IRQ */ 554 irq = it->options[1]; 555 if (irq > this_board->maxirq) { 556 printk(KERN_ERR "irq out of range\n"); 557 return -EINVAL; 558 } 559 if (irq) { 560 if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) { 561 printk(KERN_ERR "unable to allocate irq %u\n", irq); 562 return -EINVAL; 563 } else { 564 printk(KERN_INFO "( irq = %u )\n", irq); 565 } 566 } 567 dev->irq = irq; 568 569 ret = alloc_subdevices(dev, 4); 570 if (ret < 0) 571 return ret; 572 573 ret = alloc_private(dev, sizeof(struct pcl711_private)); 574 if (ret < 0) 575 return ret; 576 577 s = dev->subdevices + 0; 578 /* AI subdevice */ 579 s->type = COMEDI_SUBD_AI; 580 s->subdev_flags = SDF_READABLE | SDF_GROUND; 581 s->n_chan = this_board->n_aichan; 582 s->maxdata = 0xfff; 583 s->len_chanlist = 1; 584 s->range_table = this_board->ai_range_type; 585 s->insn_read = pcl711_ai_insn; 586 if (irq) { 587 dev->read_subdev = s; 588 s->subdev_flags |= SDF_CMD_READ; 589 s->do_cmdtest = pcl711_ai_cmdtest; 590 s->do_cmd = pcl711_ai_cmd; 591 } 592 593 s++; 594 /* AO subdevice */ 595 s->type = COMEDI_SUBD_AO; 596 s->subdev_flags = SDF_WRITABLE; 597 s->n_chan = this_board->n_aochan; 598 s->maxdata = 0xfff; 599 s->len_chanlist = 1; 600 s->range_table = &range_bipolar5; 601 s->insn_write = pcl711_ao_insn; 602 s->insn_read = pcl711_ao_insn_read; 603 604 s++; 605 /* 16-bit digital input */ 606 s->type = COMEDI_SUBD_DI; 607 s->subdev_flags = SDF_READABLE; 608 s->n_chan = 16; 609 s->maxdata = 1; 610 s->len_chanlist = 16; 611 s->range_table = &range_digital; 612 s->insn_bits = pcl711_di_insn_bits; 613 614 s++; 615 /* 16-bit digital out */ 616 s->type = COMEDI_SUBD_DO; 617 s->subdev_flags = SDF_WRITABLE; 618 s->n_chan = 16; 619 s->maxdata = 1; 620 s->len_chanlist = 16; 621 s->range_table = &range_digital; 622 s->state = 0; 623 s->insn_bits = pcl711_do_insn_bits; 624 625 /* 626 this is the "base value" for the mode register, which is 627 used for the irq on the PCL711 628 */ 629 if (this_board->is_pcl711b) 630 devpriv->mode = (dev->irq << 4); 631 632 /* clear DAC */ 633 outb(0, dev->iobase + PCL711_DA0_LO); 634 outb(0, dev->iobase + PCL711_DA0_HI); 635 outb(0, dev->iobase + PCL711_DA1_LO); 636 outb(0, dev->iobase + PCL711_DA1_HI); 637 638 printk(KERN_INFO "\n"); 639 640 return 0; 641} 642 643MODULE_AUTHOR("Comedi http://www.comedi.org"); 644MODULE_DESCRIPTION("Comedi low-level driver"); 645MODULE_LICENSE("GPL"); 646