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 "comedi_fc.h"
  68#include "8253.h"
  69
  70#define PCL711_SIZE 16
  71
  72#define PCL711_CTR0 0
  73#define PCL711_CTR1 1
  74#define PCL711_CTR2 2
  75#define PCL711_CTRCTL 3
  76#define PCL711_AD_LO 4
  77#define PCL711_DA0_LO 4
  78#define PCL711_AD_HI 5
  79#define PCL711_DA0_HI 5
  80#define PCL711_DI_LO 6
  81#define PCL711_DA1_LO 6
  82#define PCL711_DI_HI 7
  83#define PCL711_DA1_HI 7
  84#define PCL711_CLRINTR 8
  85#define PCL711_GAIN 9
  86#define PCL711_MUX 10
  87#define PCL711_MODE 11
  88#define PCL711_SOFTTRIG 12
  89#define PCL711_DO_LO 13
  90#define PCL711_DO_HI 14
  91
  92static const struct comedi_lrange range_pcl711b_ai = { 5, {
  93                                                           BIP_RANGE(5),
  94                                                           BIP_RANGE(2.5),
  95                                                           BIP_RANGE(1.25),
  96                                                           BIP_RANGE(0.625),
  97                                                           BIP_RANGE(0.3125)
  98                                                           }
  99};
 100
 101static const struct comedi_lrange range_acl8112hg_ai = { 12, {
 102                                                              BIP_RANGE(5),
 103                                                              BIP_RANGE(0.5),
 104                                                              BIP_RANGE(0.05),
 105                                                              BIP_RANGE(0.005),
 106                                                              UNI_RANGE(10),
 107                                                              UNI_RANGE(1),
 108                                                              UNI_RANGE(0.1),
 109                                                              UNI_RANGE(0.01),
 110                                                              BIP_RANGE(10),
 111                                                              BIP_RANGE(1),
 112                                                              BIP_RANGE(0.1),
 113                                                              BIP_RANGE(0.01)
 114                                                              }
 115};
 116
 117static const struct comedi_lrange range_acl8112dg_ai = { 9, {
 118                                                             BIP_RANGE(5),
 119                                                             BIP_RANGE(2.5),
 120                                                             BIP_RANGE(1.25),
 121                                                             BIP_RANGE(0.625),
 122                                                             UNI_RANGE(10),
 123                                                             UNI_RANGE(5),
 124                                                             UNI_RANGE(2.5),
 125                                                             UNI_RANGE(1.25),
 126                                                             BIP_RANGE(10)
 127                                                             }
 128};
 129
 130/*
 131 * flags
 132 */
 133
 134#define PCL711_TIMEOUT 100
 135#define PCL711_DRDY 0x10
 136
 137static const int i8253_osc_base = 500;  /* 2 Mhz */
 138
 139struct pcl711_board {
 140
 141        const char *name;
 142        int is_pcl711b;
 143        int is_8112;
 144        int is_dg;
 145        int n_ranges;
 146        int n_aichan;
 147        int n_aochan;
 148        int maxirq;
 149        const struct comedi_lrange *ai_range_type;
 150};
 151
 152struct pcl711_private {
 153
 154        int board;
 155        int adchan;
 156        int ntrig;
 157        int aip[8];
 158        int mode;
 159        unsigned int ao_readback[2];
 160        unsigned int divisor1;
 161        unsigned int divisor2;
 162};
 163
 164static irqreturn_t pcl711_interrupt(int irq, void *d)
 165{
 166        int lo, hi;
 167        int data;
 168        struct comedi_device *dev = d;
 169        const struct pcl711_board *board = comedi_board(dev);
 170        struct pcl711_private *devpriv = dev->private;
 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        struct pcl711_private *devpriv = dev->private;
 267        int tmp;
 268        int err = 0;
 269
 270        /* Step 1 : check if triggers are trivially valid */
 271
 272        err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
 273        err |= cfc_check_trigger_src(&cmd->scan_begin_src,
 274                                        TRIG_TIMER | TRIG_EXT);
 275        err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
 276        err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 277        err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
 278
 279        if (err)
 280                return 1;
 281
 282        /* Step 2a : make sure trigger sources are unique */
 283
 284        err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
 285        err |= cfc_check_trigger_is_unique(cmd->stop_src);
 286
 287        /* Step 2b : and mutually compatible */
 288
 289        if (err)
 290                return 2;
 291
 292        /* Step 3: check if arguments are trivially valid */
 293
 294        err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
 295
 296        if (cmd->scan_begin_src == TRIG_EXT) {
 297                err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
 298        } else {
 299#define MAX_SPEED 1000
 300#define TIMER_BASE 100
 301                err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
 302                                                 MAX_SPEED);
 303        }
 304
 305        err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
 306        err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
 307
 308        if (cmd->stop_src == TRIG_NONE) {
 309                err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
 310        } else {
 311                /* ignore */
 312        }
 313
 314        if (err)
 315                return 3;
 316
 317        /* step 4 */
 318
 319        if (cmd->scan_begin_src == TRIG_TIMER) {
 320                tmp = cmd->scan_begin_arg;
 321                i8253_cascade_ns_to_timer_2div(TIMER_BASE,
 322                                               &devpriv->divisor1,
 323                                               &devpriv->divisor2,
 324                                               &cmd->scan_begin_arg,
 325                                               cmd->flags & TRIG_ROUND_MASK);
 326                if (tmp != cmd->scan_begin_arg)
 327                        err++;
 328        }
 329
 330        if (err)
 331                return 4;
 332
 333        return 0;
 334}
 335
 336static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
 337{
 338        struct pcl711_private *devpriv = dev->private;
 339        int timer1, timer2;
 340        struct comedi_cmd *cmd = &s->async->cmd;
 341
 342        pcl711_set_changain(dev, cmd->chanlist[0]);
 343
 344        if (cmd->scan_begin_src == TRIG_TIMER) {
 345                /*
 346                 *  Set timers
 347                 *      timer chip is an 8253, with timers 1 and 2
 348                 *      cascaded
 349                 *  0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
 350                 *        Mode 2 = Rate generator
 351                 *
 352                 *  0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
 353                 */
 354
 355                timer1 = timer2 = 0;
 356                i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
 357                                          &cmd->scan_begin_arg,
 358                                          TRIG_ROUND_NEAREST);
 359
 360                outb(0x74, dev->iobase + PCL711_CTRCTL);
 361                outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
 362                outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
 363                outb(0xb4, dev->iobase + PCL711_CTRCTL);
 364                outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
 365                outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
 366
 367                /* clear pending interrupts (just in case) */
 368                outb(0, dev->iobase + PCL711_CLRINTR);
 369
 370                /*
 371                 *  Set mode to IRQ transfer
 372                 */
 373                outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
 374        } else {
 375                /* external trigger */
 376                outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
 377        }
 378
 379        return 0;
 380}
 381
 382/*
 383   analog output
 384*/
 385static int pcl711_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
 386                          struct comedi_insn *insn, unsigned int *data)
 387{
 388        struct pcl711_private *devpriv = dev->private;
 389        int n;
 390        int chan = CR_CHAN(insn->chanspec);
 391
 392        for (n = 0; n < insn->n; n++) {
 393                outb((data[n] & 0xff),
 394                     dev->iobase + (chan ? PCL711_DA1_LO : PCL711_DA0_LO));
 395                outb((data[n] >> 8),
 396                     dev->iobase + (chan ? PCL711_DA1_HI : PCL711_DA0_HI));
 397
 398                devpriv->ao_readback[chan] = data[n];
 399        }
 400
 401        return n;
 402}
 403
 404static int pcl711_ao_insn_read(struct comedi_device *dev,
 405                               struct comedi_subdevice *s,
 406                               struct comedi_insn *insn, unsigned int *data)
 407{
 408        struct pcl711_private *devpriv = dev->private;
 409        int n;
 410        int chan = CR_CHAN(insn->chanspec);
 411
 412        for (n = 0; n < insn->n; n++)
 413                data[n] = devpriv->ao_readback[chan];
 414
 415        return n;
 416
 417}
 418
 419/* Digital port read - Untested on 8112 */
 420static int pcl711_di_insn_bits(struct comedi_device *dev,
 421                               struct comedi_subdevice *s,
 422                               struct comedi_insn *insn, unsigned int *data)
 423{
 424        data[1] = inb(dev->iobase + PCL711_DI_LO) |
 425            (inb(dev->iobase + PCL711_DI_HI) << 8);
 426
 427        return insn->n;
 428}
 429
 430/* Digital port write - Untested on 8112 */
 431static int pcl711_do_insn_bits(struct comedi_device *dev,
 432                               struct comedi_subdevice *s,
 433                               struct comedi_insn *insn, unsigned int *data)
 434{
 435        if (data[0]) {
 436                s->state &= ~data[0];
 437                s->state |= data[0] & data[1];
 438        }
 439        if (data[0] & 0x00ff)
 440                outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
 441        if (data[0] & 0xff00)
 442                outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
 443
 444        data[1] = s->state;
 445
 446        return insn->n;
 447}
 448
 449static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 450{
 451        const struct pcl711_board *board = comedi_board(dev);
 452        struct pcl711_private *devpriv;
 453        int ret;
 454        unsigned int irq;
 455        struct comedi_subdevice *s;
 456
 457        ret = comedi_request_region(dev, it->options[0], PCL711_SIZE);
 458        if (ret)
 459                return ret;
 460
 461        /* grab our IRQ */
 462        irq = it->options[1];
 463        if (irq > board->maxirq) {
 464                printk(KERN_ERR "irq out of range\n");
 465                return -EINVAL;
 466        }
 467        if (irq) {
 468                if (request_irq(irq, pcl711_interrupt, 0, dev->board_name,
 469                                dev)) {
 470                        printk(KERN_ERR "unable to allocate irq %u\n", irq);
 471                        return -EINVAL;
 472                } else {
 473                        printk(KERN_INFO "( irq = %u )\n", irq);
 474                }
 475        }
 476        dev->irq = irq;
 477
 478        ret = comedi_alloc_subdevices(dev, 4);
 479        if (ret)
 480                return ret;
 481
 482        devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
 483        if (!devpriv)
 484                return -ENOMEM;
 485        dev->private = devpriv;
 486
 487        s = &dev->subdevices[0];
 488        /* AI subdevice */
 489        s->type = COMEDI_SUBD_AI;
 490        s->subdev_flags = SDF_READABLE | SDF_GROUND;
 491        s->n_chan = board->n_aichan;
 492        s->maxdata = 0xfff;
 493        s->len_chanlist = 1;
 494        s->range_table = board->ai_range_type;
 495        s->insn_read = pcl711_ai_insn;
 496        if (irq) {
 497                dev->read_subdev = s;
 498                s->subdev_flags |= SDF_CMD_READ;
 499                s->do_cmdtest = pcl711_ai_cmdtest;
 500                s->do_cmd = pcl711_ai_cmd;
 501        }
 502
 503        s = &dev->subdevices[1];
 504        /* AO subdevice */
 505        s->type = COMEDI_SUBD_AO;
 506        s->subdev_flags = SDF_WRITABLE;
 507        s->n_chan = board->n_aochan;
 508        s->maxdata = 0xfff;
 509        s->len_chanlist = 1;
 510        s->range_table = &range_bipolar5;
 511        s->insn_write = pcl711_ao_insn;
 512        s->insn_read = pcl711_ao_insn_read;
 513
 514        s = &dev->subdevices[2];
 515        /* 16-bit digital input */
 516        s->type = COMEDI_SUBD_DI;
 517        s->subdev_flags = SDF_READABLE;
 518        s->n_chan = 16;
 519        s->maxdata = 1;
 520        s->len_chanlist = 16;
 521        s->range_table = &range_digital;
 522        s->insn_bits = pcl711_di_insn_bits;
 523
 524        s = &dev->subdevices[3];
 525        /* 16-bit digital out */
 526        s->type = COMEDI_SUBD_DO;
 527        s->subdev_flags = SDF_WRITABLE;
 528        s->n_chan = 16;
 529        s->maxdata = 1;
 530        s->len_chanlist = 16;
 531        s->range_table = &range_digital;
 532        s->state = 0;
 533        s->insn_bits = pcl711_do_insn_bits;
 534
 535        /*
 536           this is the "base value" for the mode register, which is
 537           used for the irq on the PCL711
 538         */
 539        if (board->is_pcl711b)
 540                devpriv->mode = (dev->irq << 4);
 541
 542        /* clear DAC */
 543        outb(0, dev->iobase + PCL711_DA0_LO);
 544        outb(0, dev->iobase + PCL711_DA0_HI);
 545        outb(0, dev->iobase + PCL711_DA1_LO);
 546        outb(0, dev->iobase + PCL711_DA1_HI);
 547
 548        printk(KERN_INFO "\n");
 549
 550        return 0;
 551}
 552
 553static const struct pcl711_board boardtypes[] = {
 554        { "pcl711", 0, 0, 0, 5, 8, 1, 0, &range_bipolar5 },
 555        { "pcl711b", 1, 0, 0, 5, 8, 1, 7, &range_pcl711b_ai },
 556        { "acl8112hg", 0, 1, 0, 12, 16, 2, 15, &range_acl8112hg_ai },
 557        { "acl8112dg", 0, 1, 1, 9, 16, 2, 15, &range_acl8112dg_ai },
 558};
 559
 560static struct comedi_driver pcl711_driver = {
 561        .driver_name    = "pcl711",
 562        .module         = THIS_MODULE,
 563        .attach         = pcl711_attach,
 564        .detach         = comedi_legacy_detach,
 565        .board_name     = &boardtypes[0].name,
 566        .num_names      = ARRAY_SIZE(boardtypes),
 567        .offset         = sizeof(struct pcl711_board),
 568};
 569module_comedi_driver(pcl711_driver);
 570
 571MODULE_AUTHOR("Comedi http://www.comedi.org");
 572MODULE_DESCRIPTION("Comedi low-level driver");
 573MODULE_LICENSE("GPL");
 574