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