linux/drivers/comedi/drivers/dt2814.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * comedi/drivers/dt2814.c
   4 * Hardware driver for Data Translation DT2814
   5 *
   6 * COMEDI - Linux Control and Measurement Device Interface
   7 * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
   8 */
   9/*
  10 * Driver: dt2814
  11 * Description: Data Translation DT2814
  12 * Author: ds
  13 * Status: complete
  14 * Devices: [Data Translation] DT2814 (dt2814)
  15 *
  16 * Configuration options:
  17 * [0] - I/O port base address
  18 * [1] - IRQ
  19 *
  20 * This card has 16 analog inputs multiplexed onto a 12 bit ADC.  There
  21 * is a minimally useful onboard clock.  The base frequency for the
  22 * clock is selected by jumpers, and the clock divider can be selected
  23 * via programmed I/O.  Unfortunately, the clock divider can only be
  24 * a power of 10, from 1 to 10^7, of which only 3 or 4 are useful.  In
  25 * addition, the clock does not seem to be very accurate.
  26 */
  27
  28#include <linux/module.h>
  29#include <linux/interrupt.h>
  30#include "../comedidev.h"
  31
  32#include <linux/delay.h>
  33
  34#define DT2814_CSR 0
  35#define DT2814_DATA 1
  36
  37/*
  38 * flags
  39 */
  40
  41#define DT2814_FINISH 0x80
  42#define DT2814_ERR 0x40
  43#define DT2814_BUSY 0x20
  44#define DT2814_ENB 0x10
  45#define DT2814_CHANMASK 0x0f
  46
  47#define DT2814_TIMEOUT 10
  48#define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */
  49
  50static int dt2814_ai_notbusy(struct comedi_device *dev,
  51                             struct comedi_subdevice *s,
  52                             struct comedi_insn *insn,
  53                             unsigned long context)
  54{
  55        unsigned int status;
  56
  57        status = inb(dev->iobase + DT2814_CSR);
  58        if (context)
  59                *(unsigned int *)context = status;
  60        if (status & DT2814_BUSY)
  61                return -EBUSY;
  62        return 0;
  63}
  64
  65static int dt2814_ai_clear(struct comedi_device *dev)
  66{
  67        unsigned int status = 0;
  68        int ret;
  69
  70        /* Wait until not busy and get status register value. */
  71        ret = comedi_timeout(dev, NULL, NULL, dt2814_ai_notbusy,
  72                             (unsigned long)&status);
  73        if (ret)
  74                return ret;
  75
  76        if (status & (DT2814_FINISH | DT2814_ERR)) {
  77                /*
  78                 * There unread data, or the error flag is set.
  79                 * Read the data register twice to clear the condition.
  80                 */
  81                inb(dev->iobase + DT2814_DATA);
  82                inb(dev->iobase + DT2814_DATA);
  83        }
  84        return 0;
  85}
  86
  87static int dt2814_ai_eoc(struct comedi_device *dev,
  88                         struct comedi_subdevice *s,
  89                         struct comedi_insn *insn,
  90                         unsigned long context)
  91{
  92        unsigned int status;
  93
  94        status = inb(dev->iobase + DT2814_CSR);
  95        if (status & DT2814_FINISH)
  96                return 0;
  97        return -EBUSY;
  98}
  99
 100static int dt2814_ai_insn_read(struct comedi_device *dev,
 101                               struct comedi_subdevice *s,
 102                               struct comedi_insn *insn, unsigned int *data)
 103{
 104        int n, hi, lo;
 105        int chan;
 106        int ret;
 107
 108        dt2814_ai_clear(dev);   /* clear stale data or error */
 109        for (n = 0; n < insn->n; n++) {
 110                chan = CR_CHAN(insn->chanspec);
 111
 112                outb(chan, dev->iobase + DT2814_CSR);
 113
 114                ret = comedi_timeout(dev, s, insn, dt2814_ai_eoc, 0);
 115                if (ret)
 116                        return ret;
 117
 118                hi = inb(dev->iobase + DT2814_DATA);
 119                lo = inb(dev->iobase + DT2814_DATA);
 120
 121                data[n] = (hi << 4) | (lo >> 4);
 122        }
 123
 124        return n;
 125}
 126
 127static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags)
 128{
 129        int i;
 130        unsigned int f;
 131
 132        /* XXX ignores flags */
 133
 134        f = 10000;              /* ns */
 135        for (i = 0; i < 8; i++) {
 136                if ((2 * (*ns)) < (f * 11))
 137                        break;
 138                f *= 10;
 139        }
 140
 141        *ns = f;
 142
 143        return i;
 144}
 145
 146static int dt2814_ai_cmdtest(struct comedi_device *dev,
 147                             struct comedi_subdevice *s, struct comedi_cmd *cmd)
 148{
 149        int err = 0;
 150        unsigned int arg;
 151
 152        /* Step 1 : check if triggers are trivially valid */
 153
 154        err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
 155        err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
 156        err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
 157        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 158        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
 159
 160        if (err)
 161                return 1;
 162
 163        /* Step 2a : make sure trigger sources are unique */
 164
 165        err |= comedi_check_trigger_is_unique(cmd->stop_src);
 166
 167        /* Step 2b : and mutually compatible */
 168
 169        if (err)
 170                return 2;
 171
 172        /* Step 3: check if arguments are trivially valid */
 173
 174        err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
 175
 176        err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000);
 177        err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
 178                                            DT2814_MAX_SPEED);
 179
 180        err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
 181                                           cmd->chanlist_len);
 182
 183        if (cmd->stop_src == TRIG_COUNT)
 184                err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 2);
 185        else    /* TRIG_NONE */
 186                err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
 187
 188        if (err)
 189                return 3;
 190
 191        /* step 4: fix up any arguments */
 192
 193        arg = cmd->scan_begin_arg;
 194        dt2814_ns_to_timer(&arg, cmd->flags);
 195        err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
 196
 197        if (err)
 198                return 4;
 199
 200        return 0;
 201}
 202
 203static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
 204{
 205        struct comedi_cmd *cmd = &s->async->cmd;
 206        int chan;
 207        int trigvar;
 208
 209        dt2814_ai_clear(dev);   /* clear stale data or error */
 210        trigvar = dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags);
 211
 212        chan = CR_CHAN(cmd->chanlist[0]);
 213
 214        outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR);
 215
 216        return 0;
 217}
 218
 219static int dt2814_ai_cancel(struct comedi_device *dev,
 220                            struct comedi_subdevice *s)
 221{
 222        unsigned int status;
 223        unsigned long flags;
 224
 225        spin_lock_irqsave(&dev->spinlock, flags);
 226        status = inb(dev->iobase + DT2814_CSR);
 227        if (status & DT2814_ENB) {
 228                /*
 229                 * Clear the timed trigger enable bit.
 230                 *
 231                 * Note: turning off timed mode triggers another
 232                 * sample.  This will be mopped up by the calls to
 233                 * dt2814_ai_clear().
 234                 */
 235                outb(status & DT2814_CHANMASK, dev->iobase + DT2814_CSR);
 236        }
 237        spin_unlock_irqrestore(&dev->spinlock, flags);
 238        return 0;
 239}
 240
 241static irqreturn_t dt2814_interrupt(int irq, void *d)
 242{
 243        struct comedi_device *dev = d;
 244        struct comedi_subdevice *s = dev->read_subdev;
 245        struct comedi_async *async;
 246        unsigned int lo, hi;
 247        unsigned short data;
 248        unsigned int status;
 249
 250        if (!dev->attached) {
 251                dev_err(dev->class_dev, "spurious interrupt\n");
 252                return IRQ_HANDLED;
 253        }
 254
 255        async = s->async;
 256
 257        spin_lock(&dev->spinlock);
 258
 259        status = inb(dev->iobase + DT2814_CSR);
 260        if (!(status & DT2814_ENB)) {
 261                /* Timed acquisition not enabled.  Nothing to do. */
 262                spin_unlock(&dev->spinlock);
 263                return IRQ_HANDLED;
 264        }
 265
 266        if (!(status & (DT2814_FINISH | DT2814_ERR))) {
 267                /* Spurious interrupt? */
 268                spin_unlock(&dev->spinlock);
 269                return IRQ_HANDLED;
 270        }
 271
 272        /* Read data or clear error. */
 273        hi = inb(dev->iobase + DT2814_DATA);
 274        lo = inb(dev->iobase + DT2814_DATA);
 275
 276        data = (hi << 4) | (lo >> 4);
 277
 278        if (status & DT2814_ERR) {
 279                async->events |= COMEDI_CB_ERROR;
 280        } else {
 281                comedi_buf_write_samples(s, &data, 1);
 282                if (async->cmd.stop_src == TRIG_COUNT &&
 283                    async->scans_done >=  async->cmd.stop_arg) {
 284                        async->events |= COMEDI_CB_EOA;
 285                }
 286        }
 287        if (async->events & COMEDI_CB_CANCEL_MASK) {
 288                /*
 289                 * Disable timed mode.
 290                 *
 291                 * Note: turning off timed mode triggers another
 292                 * sample.  This will be mopped up by the calls to
 293                 * dt2814_ai_clear().
 294                 */
 295                outb(status & DT2814_CHANMASK, dev->iobase + DT2814_CSR);
 296        }
 297
 298        spin_unlock(&dev->spinlock);
 299
 300        comedi_handle_events(dev, s);
 301        return IRQ_HANDLED;
 302}
 303
 304static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 305{
 306        struct comedi_subdevice *s;
 307        int ret;
 308
 309        ret = comedi_request_region(dev, it->options[0], 0x2);
 310        if (ret)
 311                return ret;
 312
 313        outb(0, dev->iobase + DT2814_CSR);
 314        if (dt2814_ai_clear(dev)) {
 315                dev_err(dev->class_dev, "reset error (fatal)\n");
 316                return -EIO;
 317        }
 318
 319        if (it->options[1]) {
 320                ret = request_irq(it->options[1], dt2814_interrupt, 0,
 321                                  dev->board_name, dev);
 322                if (ret == 0)
 323                        dev->irq = it->options[1];
 324        }
 325
 326        ret = comedi_alloc_subdevices(dev, 1);
 327        if (ret)
 328                return ret;
 329
 330        s = &dev->subdevices[0];
 331        s->type = COMEDI_SUBD_AI;
 332        s->subdev_flags = SDF_READABLE | SDF_GROUND;
 333        s->n_chan = 16;         /* XXX */
 334        s->insn_read = dt2814_ai_insn_read;
 335        s->maxdata = 0xfff;
 336        s->range_table = &range_unknown;        /* XXX */
 337        if (dev->irq) {
 338                dev->read_subdev = s;
 339                s->subdev_flags |= SDF_CMD_READ;
 340                s->len_chanlist = 1;
 341                s->do_cmd = dt2814_ai_cmd;
 342                s->do_cmdtest = dt2814_ai_cmdtest;
 343                s->cancel = dt2814_ai_cancel;
 344        }
 345
 346        return 0;
 347}
 348
 349static void dt2814_detach(struct comedi_device *dev)
 350{
 351        if (dev->irq) {
 352                /*
 353                 * An extra conversion triggered on termination of an
 354                 * asynchronous command may still be in progress.  Wait for
 355                 * it to finish and clear the data or error status.
 356                 */
 357                dt2814_ai_clear(dev);
 358        }
 359        comedi_legacy_detach(dev);
 360}
 361
 362static struct comedi_driver dt2814_driver = {
 363        .driver_name    = "dt2814",
 364        .module         = THIS_MODULE,
 365        .attach         = dt2814_attach,
 366        .detach         = dt2814_detach,
 367};
 368module_comedi_driver(dt2814_driver);
 369
 370MODULE_AUTHOR("Comedi https://www.comedi.org");
 371MODULE_DESCRIPTION("Comedi low-level driver");
 372MODULE_LICENSE("GPL");
 373