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_8254.h"
  45
  46/*
  47 * I/O port register map
  48 */
  49#define PCL711_TIMER_BASE       0x00
  50#define PCL711_AI_LSB_REG       0x04
  51#define PCL711_AI_MSB_REG       0x05
  52#define PCL711_AI_MSB_DRDY      BIT(4)
  53#define PCL711_AO_LSB_REG(x)    (0x04 + ((x) * 2))
  54#define PCL711_AO_MSB_REG(x)    (0x05 + ((x) * 2))
  55#define PCL711_DI_LSB_REG       0x06
  56#define PCL711_DI_MSB_REG       0x07
  57#define PCL711_INT_STAT_REG     0x08
  58#define PCL711_INT_STAT_CLR     (0 << 0)  /* any value will work */
  59#define PCL711_AI_GAIN_REG      0x09
  60#define PCL711_AI_GAIN(x)       (((x) & 0xf) << 0)
  61#define PCL711_MUX_REG          0x0a
  62#define PCL711_MUX_CHAN(x)      (((x) & 0xf) << 0)
  63#define PCL711_MUX_CS0          BIT(4)
  64#define PCL711_MUX_CS1          BIT(5)
  65#define PCL711_MUX_DIFF         (PCL711_MUX_CS0 | PCL711_MUX_CS1)
  66#define PCL711_MODE_REG         0x0b
  67#define PCL711_MODE(x)          (((x) & 0x7) << 0)
  68#define PCL711_MODE_DEFAULT     PCL711_MODE(0)
  69#define PCL711_MODE_SOFTTRIG    PCL711_MODE(1)
  70#define PCL711_MODE_EXT         PCL711_MODE(2)
  71#define PCL711_MODE_EXT_IRQ     PCL711_MODE(3)
  72#define PCL711_MODE_PACER       PCL711_MODE(4)
  73#define PCL711_MODE_PACER_IRQ   PCL711_MODE(6)
  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
 156static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
 157{
 158        /*
 159         * The pcl711b board uses bits in the mode register to select the
 160         * interrupt. The other boards supported by this driver all use
 161         * jumpers on the board.
 162         *
 163         * Enables the interrupt when needed on the pcl711b board. These
 164         * bits do nothing on the other boards.
 165         */
 166        if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
 167                mode |= PCL711_MODE_IRQ(dev->irq);
 168
 169        outb(mode, dev->iobase + PCL711_MODE_REG);
 170}
 171
 172static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
 173                                         struct comedi_subdevice *s)
 174{
 175        unsigned int val;
 176
 177        val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
 178        val |= inb(dev->iobase + PCL711_AI_LSB_REG);
 179
 180        return val & s->maxdata;
 181}
 182
 183static int pcl711_ai_cancel(struct comedi_device *dev,
 184                            struct comedi_subdevice *s)
 185{
 186        outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
 187        pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
 188        return 0;
 189}
 190
 191static irqreturn_t pcl711_interrupt(int irq, void *d)
 192{
 193        struct comedi_device *dev = d;
 194        struct comedi_subdevice *s = dev->read_subdev;
 195        struct comedi_cmd *cmd = &s->async->cmd;
 196        unsigned int data;
 197
 198        if (!dev->attached) {
 199                dev_err(dev->class_dev, "spurious interrupt\n");
 200                return IRQ_HANDLED;
 201        }
 202
 203        data = pcl711_ai_get_sample(dev, s);
 204
 205        outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
 206
 207        comedi_buf_write_samples(s, &data, 1);
 208
 209        if (cmd->stop_src == TRIG_COUNT &&
 210            s->async->scans_done >= cmd->stop_arg)
 211                s->async->events |= COMEDI_CB_EOA;
 212
 213        comedi_handle_events(dev, s);
 214
 215        return IRQ_HANDLED;
 216}
 217
 218static void pcl711_set_changain(struct comedi_device *dev,
 219                                struct comedi_subdevice *s,
 220                                unsigned int chanspec)
 221{
 222        unsigned int chan = CR_CHAN(chanspec);
 223        unsigned int range = CR_RANGE(chanspec);
 224        unsigned int aref = CR_AREF(chanspec);
 225        unsigned int mux = 0;
 226
 227        outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG);
 228
 229        if (s->n_chan > 8) {
 230                /* Select the correct MPC508A chip */
 231                if (aref == AREF_DIFF) {
 232                        chan &= 0x7;
 233                        mux |= PCL711_MUX_DIFF;
 234                } else {
 235                        if (chan < 8)
 236                                mux |= PCL711_MUX_CS0;
 237                        else
 238                                mux |= PCL711_MUX_CS1;
 239                }
 240        }
 241        outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
 242}
 243
 244static int pcl711_ai_eoc(struct comedi_device *dev,
 245                         struct comedi_subdevice *s,
 246                         struct comedi_insn *insn,
 247                         unsigned long context)
 248{
 249        unsigned int status;
 250
 251        status = inb(dev->iobase + PCL711_AI_MSB_REG);
 252        if ((status & PCL711_AI_MSB_DRDY) == 0)
 253                return 0;
 254        return -EBUSY;
 255}
 256
 257static int pcl711_ai_insn_read(struct comedi_device *dev,
 258                               struct comedi_subdevice *s,
 259                               struct comedi_insn *insn,
 260                               unsigned int *data)
 261{
 262        int ret;
 263        int i;
 264
 265        pcl711_set_changain(dev, s, insn->chanspec);
 266
 267        pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
 268
 269        for (i = 0; i < insn->n; i++) {
 270                outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
 271
 272                ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0);
 273                if (ret)
 274                        return ret;
 275
 276                data[i] = pcl711_ai_get_sample(dev, s);
 277        }
 278
 279        return insn->n;
 280}
 281
 282static int pcl711_ai_cmdtest(struct comedi_device *dev,
 283                             struct comedi_subdevice *s, struct comedi_cmd *cmd)
 284{
 285        int err = 0;
 286
 287        /* Step 1 : check if triggers are trivially valid */
 288
 289        err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
 290        err |= comedi_check_trigger_src(&cmd->scan_begin_src,
 291                                        TRIG_TIMER | TRIG_EXT);
 292        err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
 293        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 294        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
 295
 296        if (err)
 297                return 1;
 298
 299        /* Step 2a : make sure trigger sources are unique */
 300
 301        err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
 302        err |= comedi_check_trigger_is_unique(cmd->stop_src);
 303
 304        /* Step 2b : and mutually compatible */
 305
 306        if (err)
 307                return 2;
 308
 309        /* Step 3: check if arguments are trivially valid */
 310
 311        err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
 312
 313        if (cmd->scan_begin_src == TRIG_EXT) {
 314                err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
 315        } else {
 316#define MAX_SPEED 1000
 317                err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
 318                                                    MAX_SPEED);
 319        }
 320
 321        err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
 322        err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
 323                                           cmd->chanlist_len);
 324
 325        if (cmd->stop_src == TRIG_COUNT)
 326                err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
 327        else    /* TRIG_NONE */
 328                err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
 329
 330        if (err)
 331                return 3;
 332
 333        /* step 4 */
 334
 335        if (cmd->scan_begin_src == TRIG_TIMER) {
 336                unsigned int arg = cmd->scan_begin_arg;
 337
 338                comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
 339                err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
 340        }
 341
 342        if (err)
 343                return 4;
 344
 345        return 0;
 346}
 347
 348static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
 349{
 350        struct comedi_cmd *cmd = &s->async->cmd;
 351
 352        pcl711_set_changain(dev, s, cmd->chanlist[0]);
 353
 354        if (cmd->scan_begin_src == TRIG_TIMER) {
 355                comedi_8254_update_divisors(dev->pacer);
 356                comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
 357                outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
 358                pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
 359        } else {
 360                pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
 361        }
 362
 363        return 0;
 364}
 365
 366static void pcl711_ao_write(struct comedi_device *dev,
 367                            unsigned int chan, unsigned int val)
 368{
 369        outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
 370        outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
 371}
 372
 373static int pcl711_ao_insn_write(struct comedi_device *dev,
 374                                struct comedi_subdevice *s,
 375                                struct comedi_insn *insn,
 376                                unsigned int *data)
 377{
 378        unsigned int chan = CR_CHAN(insn->chanspec);
 379        unsigned int val = s->readback[chan];
 380        int i;
 381
 382        for (i = 0; i < insn->n; i++) {
 383                val = data[i];
 384                pcl711_ao_write(dev, chan, val);
 385        }
 386        s->readback[chan] = val;
 387
 388        return insn->n;
 389}
 390
 391static int pcl711_di_insn_bits(struct comedi_device *dev,
 392                               struct comedi_subdevice *s,
 393                               struct comedi_insn *insn,
 394                               unsigned int *data)
 395{
 396        unsigned int val;
 397
 398        val = inb(dev->iobase + PCL711_DI_LSB_REG);
 399        val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8);
 400
 401        data[1] = val;
 402
 403        return insn->n;
 404}
 405
 406static int pcl711_do_insn_bits(struct comedi_device *dev,
 407                               struct comedi_subdevice *s,
 408                               struct comedi_insn *insn,
 409                               unsigned int *data)
 410{
 411        unsigned int mask;
 412
 413        mask = comedi_dio_update_state(s, data);
 414        if (mask) {
 415                if (mask & 0x00ff)
 416                        outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG);
 417                if (mask & 0xff00)
 418                        outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG);
 419        }
 420
 421        data[1] = s->state;
 422
 423        return insn->n;
 424}
 425
 426static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 427{
 428        const struct pcl711_board *board = dev->board_ptr;
 429        struct comedi_subdevice *s;
 430        int ret;
 431
 432        ret = comedi_request_region(dev, it->options[0], 0x10);
 433        if (ret)
 434                return ret;
 435
 436        if (it->options[1] && it->options[1] <= board->maxirq) {
 437                ret = request_irq(it->options[1], pcl711_interrupt, 0,
 438                                  dev->board_name, dev);
 439                if (ret == 0)
 440                        dev->irq = it->options[1];
 441        }
 442
 443        dev->pacer = comedi_8254_init(dev->iobase + PCL711_TIMER_BASE,
 444                                      I8254_OSC_BASE_2MHZ, I8254_IO8, 0);
 445        if (!dev->pacer)
 446                return -ENOMEM;
 447
 448        ret = comedi_alloc_subdevices(dev, 4);
 449        if (ret)
 450                return ret;
 451
 452        /* Analog Input subdevice */
 453        s = &dev->subdevices[0];
 454        s->type         = COMEDI_SUBD_AI;
 455        s->subdev_flags = SDF_READABLE | SDF_GROUND;
 456        if (board->n_aichan > 8)
 457                s->subdev_flags |= SDF_DIFF;
 458        s->n_chan       = board->n_aichan;
 459        s->maxdata      = 0xfff;
 460        s->range_table  = board->ai_range_type;
 461        s->insn_read    = pcl711_ai_insn_read;
 462        if (dev->irq) {
 463                dev->read_subdev = s;
 464                s->subdev_flags |= SDF_CMD_READ;
 465                s->len_chanlist = 1;
 466                s->do_cmdtest   = pcl711_ai_cmdtest;
 467                s->do_cmd       = pcl711_ai_cmd;
 468                s->cancel       = pcl711_ai_cancel;
 469        }
 470
 471        /* Analog Output subdevice */
 472        s = &dev->subdevices[1];
 473        s->type         = COMEDI_SUBD_AO;
 474        s->subdev_flags = SDF_WRITABLE;
 475        s->n_chan       = board->n_aochan;
 476        s->maxdata      = 0xfff;
 477        s->range_table  = &range_bipolar5;
 478        s->insn_write   = pcl711_ao_insn_write;
 479
 480        ret = comedi_alloc_subdev_readback(s);
 481        if (ret)
 482                return ret;
 483
 484        /* Digital Input subdevice */
 485        s = &dev->subdevices[2];
 486        s->type         = COMEDI_SUBD_DI;
 487        s->subdev_flags = SDF_READABLE;
 488        s->n_chan       = 16;
 489        s->maxdata      = 1;
 490        s->range_table  = &range_digital;
 491        s->insn_bits    = pcl711_di_insn_bits;
 492
 493        /* Digital Output subdevice */
 494        s = &dev->subdevices[3];
 495        s->type         = COMEDI_SUBD_DO;
 496        s->subdev_flags = SDF_WRITABLE;
 497        s->n_chan       = 16;
 498        s->maxdata      = 1;
 499        s->range_table  = &range_digital;
 500        s->insn_bits    = pcl711_do_insn_bits;
 501
 502        /* clear DAC */
 503        pcl711_ao_write(dev, 0, 0x0);
 504        pcl711_ao_write(dev, 1, 0x0);
 505
 506        return 0;
 507}
 508
 509static struct comedi_driver pcl711_driver = {
 510        .driver_name    = "pcl711",
 511        .module         = THIS_MODULE,
 512        .attach         = pcl711_attach,
 513        .detach         = comedi_legacy_detach,
 514        .board_name     = &boardtypes[0].name,
 515        .num_names      = ARRAY_SIZE(boardtypes),
 516        .offset         = sizeof(struct pcl711_board),
 517};
 518module_comedi_driver(pcl711_driver);
 519
 520MODULE_AUTHOR("Comedi http://www.comedi.org");
 521MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards");
 522MODULE_LICENSE("GPL");
 523