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