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