linux/drivers/staging/comedi/drivers/pcl711.c
<<
>>
Prefs
   1/*
   2 * pcl711.c
   3 * Comedi driver for PC-LabCard PCL-711 and AdSys ACL-8112 and compatibles
   4 * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
   5 *                    Janne Jalkanen <jalkanen@cs.hut.fi>
   6 *                    Eric Bunn <ebu@cs.hut.fi>
   7 *
   8 * COMEDI - Linux Control and Measurement Device Interface
   9 * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
  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
  22/*
  23 * Driver: pcl711
  24 * Description: Advantech PCL-711 and 711b, ADLink ACL-8112
  25 * Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
  26 *   [ADLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
  27 * Author: David A. Schleef <ds@schleef.org>
  28 *         Janne Jalkanen <jalkanen@cs.hut.fi>
  29 *         Eric Bunn <ebu@cs.hut.fi>
  30 * Updated:
  31 * Status: mostly complete
  32 *
  33 * Configuration Options:
  34 *   [0] - I/O port base
  35 *   [1] - IRQ, optional
  36 */
  37
  38#include <linux/module.h>
  39#include <linux/delay.h>
  40#include <linux/interrupt.h>
  41
  42#include "../comedidev.h"
  43
  44#include "comedi_fc.h"
  45#include "8253.h"
  46
  47/*
  48 * I/O port register map
  49 */
  50#define PCL711_TIMER_BASE       0x00
  51#define PCL711_AI_LSB_REG       0x04
  52#define PCL711_AI_MSB_REG       0x05
  53#define PCL711_AI_MSB_DRDY      (1 << 4)
  54#define PCL711_AO_LSB_REG(x)    (0x04 + ((x) * 2))
  55#define PCL711_AO_MSB_REG(x)    (0x05 + ((x) * 2))
  56#define PCL711_DI_LSB_REG       0x06
  57#define PCL711_DI_MSB_REG       0x07
  58#define PCL711_INT_STAT_REG     0x08
  59#define PCL711_INT_STAT_CLR     (0 << 0)  /* any value will work */
  60#define PCL711_AI_GAIN_REG      0x09
  61#define PCL711_AI_GAIN(x)       (((x) & 0xf) << 0)
  62#define PCL711_MUX_REG          0x0a
  63#define PCL711_MUX_CHAN(x)      (((x) & 0xf) << 0)
  64#define PCL711_MUX_CS0          (1 << 4)
  65#define PCL711_MUX_CS1          (1 << 5)
  66#define PCL711_MUX_DIFF         (PCL711_MUX_CS0 | PCL711_MUX_CS1)
  67#define PCL711_MODE_REG         0x0b
  68#define PCL711_MODE_DEFAULT     (0 << 0)
  69#define PCL711_MODE_SOFTTRIG    (1 << 0)
  70#define PCL711_MODE_EXT         (2 << 0)
  71#define PCL711_MODE_EXT_IRQ     (3 << 0)
  72#define PCL711_MODE_PACER       (4 << 0)
  73#define PCL711_MODE_PACER_IRQ   (6 << 0)
  74#define PCL711_MODE_IRQ(x)      (((x) & 0x7) << 4)
  75#define PCL711_SOFTTRIG_REG     0x0c
  76#define PCL711_SOFTTRIG         (0 << 0)  /* any value will work */
  77#define PCL711_DO_LSB_REG       0x0d
  78#define PCL711_DO_MSB_REG       0x0e
  79
  80static const struct comedi_lrange range_pcl711b_ai = {
  81        5, {
  82                BIP_RANGE(5),
  83                BIP_RANGE(2.5),
  84                BIP_RANGE(1.25),
  85                BIP_RANGE(0.625),
  86                BIP_RANGE(0.3125)
  87        }
  88};
  89
  90static const struct comedi_lrange range_acl8112hg_ai = {
  91        12, {
  92                BIP_RANGE(5),
  93                BIP_RANGE(0.5),
  94                BIP_RANGE(0.05),
  95                BIP_RANGE(0.005),
  96                UNI_RANGE(10),
  97                UNI_RANGE(1),
  98                UNI_RANGE(0.1),
  99                UNI_RANGE(0.01),
 100                BIP_RANGE(10),
 101                BIP_RANGE(1),
 102                BIP_RANGE(0.1),
 103                BIP_RANGE(0.01)
 104        }
 105};
 106
 107static const struct comedi_lrange range_acl8112dg_ai = {
 108        9, {
 109                BIP_RANGE(5),
 110                BIP_RANGE(2.5),
 111                BIP_RANGE(1.25),
 112                BIP_RANGE(0.625),
 113                UNI_RANGE(10),
 114                UNI_RANGE(5),
 115                UNI_RANGE(2.5),
 116                UNI_RANGE(1.25),
 117                BIP_RANGE(10)
 118        }
 119};
 120
 121struct pcl711_board {
 122        const char *name;
 123        int n_aichan;
 124        int n_aochan;
 125        int maxirq;
 126        const struct comedi_lrange *ai_range_type;
 127};
 128
 129static const struct pcl711_board boardtypes[] = {
 130        {
 131                .name           = "pcl711",
 132                .n_aichan       = 8,
 133                .n_aochan       = 1,
 134                .ai_range_type  = &range_bipolar5,
 135        }, {
 136                .name           = "pcl711b",
 137                .n_aichan       = 8,
 138                .n_aochan       = 1,
 139                .maxirq         = 7,
 140                .ai_range_type  = &range_pcl711b_ai,
 141        }, {
 142                .name           = "acl8112hg",
 143                .n_aichan       = 16,
 144                .n_aochan       = 2,
 145                .maxirq         = 15,
 146                .ai_range_type  = &range_acl8112hg_ai,
 147        }, {
 148                .name           = "acl8112dg",
 149                .n_aichan       = 16,
 150                .n_aochan       = 2,
 151                .maxirq         = 15,
 152                .ai_range_type  = &range_acl8112dg_ai,
 153        },
 154};
 155
 156struct pcl711_private {
 157        unsigned int divisor1;
 158        unsigned int divisor2;
 159};
 160
 161static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
 162{
 163        /*
 164         * The pcl711b board uses bits in the mode register to select the
 165         * interrupt. The other boards supported by this driver all use
 166         * jumpers on the board.
 167         *
 168         * Enables the interrupt when needed on the pcl711b board. These
 169         * bits do nothing on the other boards.
 170         */
 171        if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
 172                mode |= PCL711_MODE_IRQ(dev->irq);
 173
 174        outb(mode, dev->iobase + PCL711_MODE_REG);
 175}
 176
 177static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
 178                                         struct comedi_subdevice *s)
 179{
 180        unsigned int val;
 181
 182        val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
 183        val |= inb(dev->iobase + PCL711_AI_LSB_REG);
 184
 185        return val & s->maxdata;
 186}
 187
 188static int pcl711_ai_cancel(struct comedi_device *dev,
 189                            struct comedi_subdevice *s)
 190{
 191        outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
 192        pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
 193        return 0;
 194}
 195
 196static irqreturn_t pcl711_interrupt(int irq, void *d)
 197{
 198        struct comedi_device *dev = d;
 199        struct comedi_subdevice *s = dev->read_subdev;
 200        struct comedi_cmd *cmd = &s->async->cmd;
 201        unsigned int data;
 202
 203        if (!dev->attached) {
 204                dev_err(dev->class_dev, "spurious interrupt\n");
 205                return IRQ_HANDLED;
 206        }
 207
 208        data = pcl711_ai_get_sample(dev, s);
 209
 210        outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
 211
 212        comedi_buf_write_samples(s, &data, 1);
 213
 214        if (cmd->stop_src == TRIG_COUNT &&
 215            s->async->scans_done >= cmd->stop_arg)
 216                s->async->events |= COMEDI_CB_EOA;
 217
 218        comedi_handle_events(dev, s);
 219
 220        return IRQ_HANDLED;
 221}
 222
 223static void pcl711_set_changain(struct comedi_device *dev,
 224                                struct comedi_subdevice *s,
 225                                unsigned int chanspec)
 226{
 227        unsigned int chan = CR_CHAN(chanspec);
 228        unsigned int range = CR_RANGE(chanspec);
 229        unsigned int aref = CR_AREF(chanspec);
 230        unsigned int mux = 0;
 231
 232        outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG);
 233
 234        if (s->n_chan > 8) {
 235                /* Select the correct MPC508A chip */
 236                if (aref == AREF_DIFF) {
 237                        chan &= 0x7;
 238                        mux |= PCL711_MUX_DIFF;
 239                } else {
 240                        if (chan < 8)
 241                                mux |= PCL711_MUX_CS0;
 242                        else
 243                                mux |= PCL711_MUX_CS1;
 244                }
 245        }
 246        outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
 247}
 248
 249static int pcl711_ai_eoc(struct comedi_device *dev,
 250                         struct comedi_subdevice *s,
 251                         struct comedi_insn *insn,
 252                         unsigned long context)
 253{
 254        unsigned int status;
 255
 256        status = inb(dev->iobase + PCL711_AI_MSB_REG);
 257        if ((status & PCL711_AI_MSB_DRDY) == 0)
 258                return 0;
 259        return -EBUSY;
 260}
 261
 262static int pcl711_ai_insn_read(struct comedi_device *dev,
 263                               struct comedi_subdevice *s,
 264                               struct comedi_insn *insn,
 265                               unsigned int *data)
 266{
 267        int ret;
 268        int i;
 269
 270        pcl711_set_changain(dev, s, insn->chanspec);
 271
 272        pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
 273
 274        for (i = 0; i < insn->n; i++) {
 275                outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
 276
 277                ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0);
 278                if (ret)
 279                        return ret;
 280
 281                data[i] = pcl711_ai_get_sample(dev, s);
 282        }
 283
 284        return insn->n;
 285}
 286
 287static int pcl711_ai_cmdtest(struct comedi_device *dev,
 288                             struct comedi_subdevice *s, struct comedi_cmd *cmd)
 289{
 290        struct pcl711_private *devpriv = dev->private;
 291        int err = 0;
 292        unsigned int arg;
 293
 294        /* Step 1 : check if triggers are trivially valid */
 295
 296        err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
 297        err |= cfc_check_trigger_src(&cmd->scan_begin_src,
 298                                        TRIG_TIMER | TRIG_EXT);
 299        err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
 300        err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 301        err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
 302
 303        if (err)
 304                return 1;
 305
 306        /* Step 2a : make sure trigger sources are unique */
 307
 308        err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
 309        err |= cfc_check_trigger_is_unique(cmd->stop_src);
 310
 311        /* Step 2b : and mutually compatible */
 312
 313        if (err)
 314                return 2;
 315
 316        /* Step 3: check if arguments are trivially valid */
 317
 318        err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
 319
 320        if (cmd->scan_begin_src == TRIG_EXT) {
 321                err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
 322        } else {
 323#define MAX_SPEED 1000
 324                err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
 325                                                 MAX_SPEED);
 326        }
 327
 328        err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
 329        err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
 330
 331        if (cmd->stop_src == TRIG_COUNT)
 332                err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
 333        else    /* TRIG_NONE */
 334                err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
 335
 336        if (err)
 337                return 3;
 338
 339        /* step 4 */
 340
 341        if (cmd->scan_begin_src == TRIG_TIMER) {
 342                arg = cmd->scan_begin_arg;
 343                i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ,
 344                                          &devpriv->divisor1,
 345                                          &devpriv->divisor2,
 346                                          &arg, cmd->flags);
 347                err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
 348        }
 349
 350        if (err)
 351                return 4;
 352
 353        return 0;
 354}
 355
 356static void pcl711_ai_load_counters(struct comedi_device *dev)
 357{
 358        struct pcl711_private *devpriv = dev->private;
 359        unsigned long timer_base = dev->iobase + PCL711_TIMER_BASE;
 360
 361        i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
 362        i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
 363
 364        i8254_write(timer_base, 0, 1, devpriv->divisor1);
 365        i8254_write(timer_base, 0, 2, devpriv->divisor2);
 366}
 367
 368static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
 369{
 370        struct comedi_cmd *cmd = &s->async->cmd;
 371
 372        pcl711_set_changain(dev, s, cmd->chanlist[0]);
 373
 374        if (cmd->scan_begin_src == TRIG_TIMER) {
 375                pcl711_ai_load_counters(dev);
 376                outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
 377                pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
 378        } else {
 379                pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
 380        }
 381
 382        return 0;
 383}
 384
 385static void pcl711_ao_write(struct comedi_device *dev,
 386                            unsigned int chan, unsigned int val)
 387{
 388        outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
 389        outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
 390}
 391
 392static int pcl711_ao_insn_write(struct comedi_device *dev,
 393                                struct comedi_subdevice *s,
 394                                struct comedi_insn *insn,
 395                                unsigned int *data)
 396{
 397        unsigned int chan = CR_CHAN(insn->chanspec);
 398        unsigned int val = s->readback[chan];
 399        int i;
 400
 401        for (i = 0; i < insn->n; i++) {
 402                val = data[i];
 403                pcl711_ao_write(dev, chan, val);
 404        }
 405        s->readback[chan] = val;
 406
 407        return insn->n;
 408}
 409
 410static int pcl711_di_insn_bits(struct comedi_device *dev,
 411                               struct comedi_subdevice *s,
 412                               struct comedi_insn *insn,
 413                               unsigned int *data)
 414{
 415        unsigned int val;
 416
 417        val = inb(dev->iobase + PCL711_DI_LSB_REG);
 418        val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8);
 419
 420        data[1] = val;
 421
 422        return insn->n;
 423}
 424
 425static int pcl711_do_insn_bits(struct comedi_device *dev,
 426                               struct comedi_subdevice *s,
 427                               struct comedi_insn *insn,
 428                               unsigned int *data)
 429{
 430        unsigned int mask;
 431
 432        mask = comedi_dio_update_state(s, data);
 433        if (mask) {
 434                if (mask & 0x00ff)
 435                        outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG);
 436                if (mask & 0xff00)
 437                        outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG);
 438        }
 439
 440        data[1] = s->state;
 441
 442        return insn->n;
 443}
 444
 445static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 446{
 447        const struct pcl711_board *board = dev->board_ptr;
 448        struct pcl711_private *devpriv;
 449        struct comedi_subdevice *s;
 450        int ret;
 451
 452        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 453        if (!devpriv)
 454                return -ENOMEM;
 455
 456        ret = comedi_request_region(dev, it->options[0], 0x10);
 457        if (ret)
 458                return ret;
 459
 460        if (it->options[1] && it->options[1] <= board->maxirq) {
 461                ret = request_irq(it->options[1], pcl711_interrupt, 0,
 462                                  dev->board_name, dev);
 463                if (ret == 0)
 464                        dev->irq = it->options[1];
 465        }
 466
 467        ret = comedi_alloc_subdevices(dev, 4);
 468        if (ret)
 469                return ret;
 470
 471        /* Analog Input subdevice */
 472        s = &dev->subdevices[0];
 473        s->type         = COMEDI_SUBD_AI;
 474        s->subdev_flags = SDF_READABLE | SDF_GROUND;
 475        if (board->n_aichan > 8)
 476                s->subdev_flags |= SDF_DIFF;
 477        s->n_chan       = board->n_aichan;
 478        s->maxdata      = 0xfff;
 479        s->range_table  = board->ai_range_type;
 480        s->insn_read    = pcl711_ai_insn_read;
 481        if (dev->irq) {
 482                dev->read_subdev = s;
 483                s->subdev_flags |= SDF_CMD_READ;
 484                s->len_chanlist = 1;
 485                s->do_cmdtest   = pcl711_ai_cmdtest;
 486                s->do_cmd       = pcl711_ai_cmd;
 487                s->cancel       = pcl711_ai_cancel;
 488        }
 489
 490        /* Analog Output subdevice */
 491        s = &dev->subdevices[1];
 492        s->type         = COMEDI_SUBD_AO;
 493        s->subdev_flags = SDF_WRITABLE;
 494        s->n_chan       = board->n_aochan;
 495        s->maxdata      = 0xfff;
 496        s->range_table  = &range_bipolar5;
 497        s->insn_write   = pcl711_ao_insn_write;
 498
 499        ret = comedi_alloc_subdev_readback(s);
 500        if (ret)
 501                return ret;
 502
 503        /* Digital Input subdevice */
 504        s = &dev->subdevices[2];
 505        s->type         = COMEDI_SUBD_DI;
 506        s->subdev_flags = SDF_READABLE;
 507        s->n_chan       = 16;
 508        s->maxdata      = 1;
 509        s->range_table  = &range_digital;
 510        s->insn_bits    = pcl711_di_insn_bits;
 511
 512        /* Digital Output subdevice */
 513        s = &dev->subdevices[3];
 514        s->type         = COMEDI_SUBD_DO;
 515        s->subdev_flags = SDF_WRITABLE;
 516        s->n_chan       = 16;
 517        s->maxdata      = 1;
 518        s->range_table  = &range_digital;
 519        s->insn_bits    = pcl711_do_insn_bits;
 520
 521        /* clear DAC */
 522        pcl711_ao_write(dev, 0, 0x0);
 523        pcl711_ao_write(dev, 1, 0x0);
 524
 525        return 0;
 526}
 527
 528static struct comedi_driver pcl711_driver = {
 529        .driver_name    = "pcl711",
 530        .module         = THIS_MODULE,
 531        .attach         = pcl711_attach,
 532        .detach         = comedi_legacy_detach,
 533        .board_name     = &boardtypes[0].name,
 534        .num_names      = ARRAY_SIZE(boardtypes),
 535        .offset         = sizeof(struct pcl711_board),
 536};
 537module_comedi_driver(pcl711_driver);
 538
 539MODULE_AUTHOR("Comedi http://www.comedi.org");
 540MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards");
 541MODULE_LICENSE("GPL");
 542