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