pcl711.c revision 266bfbdd3eb7f080612977f22055c82b951b179e
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 174COMEDI_INITCLEANUP(driver_pcl711); 175 176struct pcl711_private { 177 178 int board; 179 int adchan; 180 int ntrig; 181 int aip[8]; 182 int mode; 183 unsigned int ao_readback[2]; 184 unsigned int divisor1; 185 unsigned int divisor2; 186}; 187 188#define devpriv ((struct pcl711_private *)dev->private) 189 190static irqreturn_t pcl711_interrupt(int irq, void *d) 191{ 192 int lo, hi; 193 int data; 194 struct comedi_device *dev = d; 195 struct comedi_subdevice *s = dev->subdevices + 0; 196 197 if (!dev->attached) { 198 comedi_error(dev, "spurious interrupt"); 199 return IRQ_HANDLED; 200 } 201 202 hi = inb(dev->iobase + PCL711_AD_HI); 203 lo = inb(dev->iobase + PCL711_AD_LO); 204 outb(0, dev->iobase + PCL711_CLRINTR); 205 206 data = (hi << 8) | lo; 207 208 /* FIXME! Nothing else sets ntrig! */ 209 if (!(--devpriv->ntrig)) { 210 if (this_board->is_8112) 211 outb(1, dev->iobase + PCL711_MODE); 212 else 213 outb(0, dev->iobase + PCL711_MODE); 214 215 s->async->events |= COMEDI_CB_EOA; 216 } 217 comedi_event(dev, s); 218 return IRQ_HANDLED; 219} 220 221static void pcl711_set_changain(struct comedi_device *dev, int chan) 222{ 223 int chan_register; 224 225 outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN); 226 227 chan_register = CR_CHAN(chan); 228 229 if (this_board->is_8112) { 230 231 /* 232 * Set the correct channel. The two channel banks are switched 233 * using the mask value. 234 * NB: To use differential channels, you should use 235 * mask = 0x30, but I haven't written the support for this 236 * yet. /JJ 237 */ 238 239 if (chan_register >= 8) 240 chan_register = 0x20 | (chan_register & 0x7); 241 else 242 chan_register |= 0x10; 243 } else { 244 outb(chan_register, dev->iobase + PCL711_MUX); 245 } 246} 247 248static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s, 249 struct comedi_insn *insn, unsigned int *data) 250{ 251 int i, n; 252 int hi, lo; 253 254 pcl711_set_changain(dev, insn->chanspec); 255 256 for (n = 0; n < insn->n; n++) { 257 /* 258 * Write the correct mode (software polling) and start polling 259 * by writing to the trigger register 260 */ 261 outb(1, dev->iobase + PCL711_MODE); 262 263 if (!this_board->is_8112) 264 outb(0, dev->iobase + PCL711_SOFTTRIG); 265 266 i = PCL711_TIMEOUT; 267 while (--i) { 268 hi = inb(dev->iobase + PCL711_AD_HI); 269 if (!(hi & PCL711_DRDY)) 270 goto ok; 271 udelay(1); 272 } 273 printk("comedi%d: pcl711: A/D timeout\n", dev->minor); 274 return -ETIME; 275 276ok: 277 lo = inb(dev->iobase + PCL711_AD_LO); 278 279 data[n] = ((hi & 0xf) << 8) | lo; 280 } 281 282 return n; 283} 284 285static int pcl711_ai_cmdtest(struct comedi_device *dev, 286 struct comedi_subdevice *s, struct comedi_cmd *cmd) 287{ 288 int tmp; 289 int err = 0; 290 291 /* step 1 */ 292 tmp = cmd->start_src; 293 cmd->start_src &= TRIG_NOW; 294 if (!cmd->start_src || tmp != cmd->start_src) 295 err++; 296 297 tmp = cmd->scan_begin_src; 298 cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT; 299 if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) 300 err++; 301 302 tmp = cmd->convert_src; 303 cmd->convert_src &= TRIG_NOW; 304 if (!cmd->convert_src || tmp != cmd->convert_src) 305 err++; 306 307 tmp = cmd->scan_end_src; 308 cmd->scan_end_src &= TRIG_COUNT; 309 if (!cmd->scan_end_src || tmp != cmd->scan_end_src) 310 err++; 311 312 tmp = cmd->stop_src; 313 cmd->stop_src &= TRIG_COUNT | TRIG_NONE; 314 if (!cmd->stop_src || tmp != cmd->stop_src) 315 err++; 316 317 if (err) 318 return 1; 319 320 /* step 2 */ 321 322 if (cmd->scan_begin_src != TRIG_TIMER && 323 cmd->scan_begin_src != TRIG_EXT) 324 err++; 325 if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) 326 err++; 327 328 if (err) 329 return 2; 330 331 /* step 3 */ 332 333 if (cmd->start_arg != 0) { 334 cmd->start_arg = 0; 335 err++; 336 } 337 if (cmd->scan_begin_src == TRIG_EXT) { 338 if (cmd->scan_begin_arg != 0) { 339 cmd->scan_begin_arg = 0; 340 err++; 341 } 342 } else { 343#define MAX_SPEED 1000 344#define TIMER_BASE 100 345 if (cmd->scan_begin_arg < MAX_SPEED) { 346 cmd->scan_begin_arg = MAX_SPEED; 347 err++; 348 } 349 } 350 if (cmd->convert_arg != 0) { 351 cmd->convert_arg = 0; 352 err++; 353 } 354 if (cmd->scan_end_arg != cmd->chanlist_len) { 355 cmd->scan_end_arg = cmd->chanlist_len; 356 err++; 357 } 358 if (cmd->stop_src == TRIG_NONE) { 359 if (cmd->stop_arg != 0) { 360 cmd->stop_arg = 0; 361 err++; 362 } 363 } else { 364 /* ignore */ 365 } 366 367 if (err) 368 return 3; 369 370 /* step 4 */ 371 372 if (cmd->scan_begin_src == TRIG_TIMER) { 373 tmp = cmd->scan_begin_arg; 374 i8253_cascade_ns_to_timer_2div(TIMER_BASE, 375 &devpriv->divisor1, 376 &devpriv->divisor2, 377 &cmd->scan_begin_arg, 378 cmd->flags & TRIG_ROUND_MASK); 379 if (tmp != cmd->scan_begin_arg) 380 err++; 381 } 382 383 if (err) 384 return 4; 385 386 return 0; 387} 388 389static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 390{ 391 int timer1, timer2; 392 struct comedi_cmd *cmd = &s->async->cmd; 393 394 pcl711_set_changain(dev, cmd->chanlist[0]); 395 396 if (cmd->scan_begin_src == TRIG_TIMER) { 397 /* 398 * Set timers 399 * timer chip is an 8253, with timers 1 and 2 400 * cascaded 401 * 0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary 402 * Mode 2 = Rate generator 403 * 404 * 0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary 405 */ 406 407 timer1 = timer2 = 0; 408 i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2, 409 &cmd->scan_begin_arg, 410 TRIG_ROUND_NEAREST); 411 412 outb(0x74, dev->iobase + PCL711_CTRCTL); 413 outb(timer1 & 0xff, dev->iobase + PCL711_CTR1); 414 outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1); 415 outb(0xb4, dev->iobase + PCL711_CTRCTL); 416 outb(timer2 & 0xff, dev->iobase + PCL711_CTR2); 417 outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2); 418 419 /* clear pending interrupts (just in case) */ 420 outb(0, dev->iobase + PCL711_CLRINTR); 421 422 /* 423 * Set mode to IRQ transfer 424 */ 425 outb(devpriv->mode | 6, dev->iobase + PCL711_MODE); 426 } else { 427 /* external trigger */ 428 outb(devpriv->mode | 3, dev->iobase + PCL711_MODE); 429 } 430 431 return 0; 432} 433 434/* 435 analog output 436*/ 437static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s, 438 struct comedi_insn *insn, unsigned int *data) 439{ 440 int n; 441 int chan = CR_CHAN(insn->chanspec); 442 443 for (n = 0; n < insn->n; n++) { 444 outb((data[n] & 0xff), 445 dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO)); 446 outb((data[n] >> 8), 447 dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI)); 448 449 devpriv->ao_readback[chan] = data[n]; 450 } 451 452 return n; 453} 454 455static int pcl711_ao_insn_read(struct comedi_device *dev, 456 struct comedi_subdevice *s, 457 struct comedi_insn *insn, unsigned int *data) 458{ 459 int n; 460 int chan = CR_CHAN(insn->chanspec); 461 462 for (n = 0; n < insn->n; n++) 463 data[n] = devpriv->ao_readback[chan]; 464 465 return n; 466 467} 468 469/* Digital port read - Untested on 8112 */ 470static int pcl711_di_insn_bits(struct comedi_device *dev, 471 struct comedi_subdevice *s, 472 struct comedi_insn *insn, unsigned int *data) 473{ 474 if (insn->n != 2) 475 return -EINVAL; 476 477 data[1] = inb(dev->iobase + PCL711_DI_LO) | 478 (inb(dev->iobase + PCL711_DI_HI) << 8); 479 480 return 2; 481} 482 483/* Digital port write - Untested on 8112 */ 484static int pcl711_do_insn_bits(struct comedi_device *dev, 485 struct comedi_subdevice *s, 486 struct comedi_insn *insn, unsigned int *data) 487{ 488 if (insn->n != 2) 489 return -EINVAL; 490 491 if (data[0]) { 492 s->state &= ~data[0]; 493 s->state |= data[0] & data[1]; 494 } 495 if (data[0] & 0x00ff) 496 outb(s->state & 0xff, dev->iobase + PCL711_DO_LO); 497 if (data[0] & 0xff00) 498 outb((s->state >> 8), dev->iobase + PCL711_DO_HI); 499 500 data[1] = s->state; 501 502 return 2; 503} 504 505/* Free any resources that we have claimed */ 506static int pcl711_detach(struct comedi_device *dev) 507{ 508 printk("comedi%d: pcl711: remove\n", dev->minor); 509 510 if (dev->irq) 511 free_irq(dev->irq, dev); 512 513 if (dev->iobase) 514 release_region(dev->iobase, PCL711_SIZE); 515 516 return 0; 517} 518 519/* Initialization */ 520static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it) 521{ 522 int ret; 523 unsigned long iobase; 524 unsigned int irq; 525 struct comedi_subdevice *s; 526 527 /* claim our I/O space */ 528 529 iobase = it->options[0]; 530 printk("comedi%d: pcl711: 0x%04lx ", dev->minor, iobase); 531 if (!request_region(iobase, PCL711_SIZE, "pcl711")) { 532 printk("I/O port conflict\n"); 533 return -EIO; 534 } 535 dev->iobase = iobase; 536 537 /* there should be a sanity check here */ 538 539 /* set up some name stuff */ 540 dev->board_name = this_board->name; 541 542 /* grab our IRQ */ 543 irq = it->options[1]; 544 if (irq > this_board->maxirq) { 545 printk("irq out of range\n"); 546 return -EINVAL; 547 } 548 if (irq) { 549 if (request_irq(irq, pcl711_interrupt, 0, "pcl711", dev)) { 550 printk("unable to allocate irq %u\n", irq); 551 return -EINVAL; 552 } else { 553 printk("( irq = %u )\n", irq); 554 } 555 } 556 dev->irq = irq; 557 558 ret = alloc_subdevices(dev, 4); 559 if (ret < 0) 560 return ret; 561 562 ret = alloc_private(dev, sizeof(struct pcl711_private)); 563 if (ret < 0) 564 return ret; 565 566 s = dev->subdevices + 0; 567 /* AI subdevice */ 568 s->type = COMEDI_SUBD_AI; 569 s->subdev_flags = SDF_READABLE | SDF_GROUND; 570 s->n_chan = this_board->n_aichan; 571 s->maxdata = 0xfff; 572 s->len_chanlist = 1; 573 s->range_table = this_board->ai_range_type; 574 s->insn_read = pcl711_ai_insn; 575 if (irq) { 576 dev->read_subdev = s; 577 s->subdev_flags |= SDF_CMD_READ; 578 s->do_cmdtest = pcl711_ai_cmdtest; 579 s->do_cmd = pcl711_ai_cmd; 580 } 581 582 s++; 583 /* AO subdevice */ 584 s->type = COMEDI_SUBD_AO; 585 s->subdev_flags = SDF_WRITABLE; 586 s->n_chan = this_board->n_aochan; 587 s->maxdata = 0xfff; 588 s->len_chanlist = 1; 589 s->range_table = &range_bipolar5; 590 s->insn_write = pcl711_ao_insn; 591 s->insn_read = pcl711_ao_insn_read; 592 593 s++; 594 /* 16-bit digital input */ 595 s->type = COMEDI_SUBD_DI; 596 s->subdev_flags = SDF_READABLE; 597 s->n_chan = 16; 598 s->maxdata = 1; 599 s->len_chanlist = 16; 600 s->range_table = &range_digital; 601 s->insn_bits = pcl711_di_insn_bits; 602 603 s++; 604 /* 16-bit digital out */ 605 s->type = COMEDI_SUBD_DO; 606 s->subdev_flags = SDF_WRITABLE; 607 s->n_chan = 16; 608 s->maxdata = 1; 609 s->len_chanlist = 16; 610 s->range_table = &range_digital; 611 s->state = 0; 612 s->insn_bits = pcl711_do_insn_bits; 613 614 /* 615 this is the "base value" for the mode register, which is 616 used for the irq on the PCL711 617 */ 618 if (this_board->is_pcl711b) 619 devpriv->mode = (dev->irq << 4); 620 621 /* clear DAC */ 622 outb(0, dev->iobase + PCL711_DA0_LO); 623 outb(0, dev->iobase + PCL711_DA0_HI); 624 outb(0, dev->iobase + PCL711_DA1_LO); 625 outb(0, dev->iobase + PCL711_DA1_HI); 626 627 printk("\n"); 628 629 return 0; 630} 631