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