linux/drivers/staging/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
  47struct dt2814_private {
  48        int ntrig;
  49        int curadchan;
  50};
  51
  52#define DT2814_TIMEOUT 10
  53#define DT2814_MAX_SPEED 100000 /* Arbitrary 10 khz limit */
  54
  55static int dt2814_ai_eoc(struct comedi_device *dev,
  56                         struct comedi_subdevice *s,
  57                         struct comedi_insn *insn,
  58                         unsigned long context)
  59{
  60        unsigned int status;
  61
  62        status = inb(dev->iobase + DT2814_CSR);
  63        if (status & DT2814_FINISH)
  64                return 0;
  65        return -EBUSY;
  66}
  67
  68static int dt2814_ai_insn_read(struct comedi_device *dev,
  69                               struct comedi_subdevice *s,
  70                               struct comedi_insn *insn, unsigned int *data)
  71{
  72        int n, hi, lo;
  73        int chan;
  74        int ret;
  75
  76        for (n = 0; n < insn->n; n++) {
  77                chan = CR_CHAN(insn->chanspec);
  78
  79                outb(chan, dev->iobase + DT2814_CSR);
  80
  81                ret = comedi_timeout(dev, s, insn, dt2814_ai_eoc, 0);
  82                if (ret)
  83                        return ret;
  84
  85                hi = inb(dev->iobase + DT2814_DATA);
  86                lo = inb(dev->iobase + DT2814_DATA);
  87
  88                data[n] = (hi << 4) | (lo >> 4);
  89        }
  90
  91        return n;
  92}
  93
  94static int dt2814_ns_to_timer(unsigned int *ns, unsigned int flags)
  95{
  96        int i;
  97        unsigned int f;
  98
  99        /* XXX ignores flags */
 100
 101        f = 10000;              /* ns */
 102        for (i = 0; i < 8; i++) {
 103                if ((2 * (*ns)) < (f * 11))
 104                        break;
 105                f *= 10;
 106        }
 107
 108        *ns = f;
 109
 110        return i;
 111}
 112
 113static int dt2814_ai_cmdtest(struct comedi_device *dev,
 114                             struct comedi_subdevice *s, struct comedi_cmd *cmd)
 115{
 116        int err = 0;
 117        unsigned int arg;
 118
 119        /* Step 1 : check if triggers are trivially valid */
 120
 121        err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
 122        err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_TIMER);
 123        err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
 124        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 125        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
 126
 127        if (err)
 128                return 1;
 129
 130        /* Step 2a : make sure trigger sources are unique */
 131
 132        err |= comedi_check_trigger_is_unique(cmd->stop_src);
 133
 134        /* Step 2b : and mutually compatible */
 135
 136        if (err)
 137                return 2;
 138
 139        /* Step 3: check if arguments are trivially valid */
 140
 141        err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
 142
 143        err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg, 1000000000);
 144        err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
 145                                            DT2814_MAX_SPEED);
 146
 147        err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
 148                                           cmd->chanlist_len);
 149
 150        if (cmd->stop_src == TRIG_COUNT)
 151                err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 2);
 152        else    /* TRIG_NONE */
 153                err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
 154
 155        if (err)
 156                return 3;
 157
 158        /* step 4: fix up any arguments */
 159
 160        arg = cmd->scan_begin_arg;
 161        dt2814_ns_to_timer(&arg, cmd->flags);
 162        err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
 163
 164        if (err)
 165                return 4;
 166
 167        return 0;
 168}
 169
 170static int dt2814_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
 171{
 172        struct dt2814_private *devpriv = dev->private;
 173        struct comedi_cmd *cmd = &s->async->cmd;
 174        int chan;
 175        int trigvar;
 176
 177        trigvar = dt2814_ns_to_timer(&cmd->scan_begin_arg, cmd->flags);
 178
 179        chan = CR_CHAN(cmd->chanlist[0]);
 180
 181        devpriv->ntrig = cmd->stop_arg;
 182        outb(chan | DT2814_ENB | (trigvar << 5), dev->iobase + DT2814_CSR);
 183
 184        return 0;
 185}
 186
 187static irqreturn_t dt2814_interrupt(int irq, void *d)
 188{
 189        int lo, hi;
 190        struct comedi_device *dev = d;
 191        struct dt2814_private *devpriv = dev->private;
 192        struct comedi_subdevice *s = dev->read_subdev;
 193        int data;
 194
 195        if (!dev->attached) {
 196                dev_err(dev->class_dev, "spurious interrupt\n");
 197                return IRQ_HANDLED;
 198        }
 199
 200        hi = inb(dev->iobase + DT2814_DATA);
 201        lo = inb(dev->iobase + DT2814_DATA);
 202
 203        data = (hi << 4) | (lo >> 4);
 204
 205        if (!(--devpriv->ntrig)) {
 206                int i;
 207
 208                outb(0, dev->iobase + DT2814_CSR);
 209                /*
 210                 * note: turning off timed mode triggers another
 211                 * sample.
 212                 */
 213
 214                for (i = 0; i < DT2814_TIMEOUT; i++) {
 215                        if (inb(dev->iobase + DT2814_CSR) & DT2814_FINISH)
 216                                break;
 217                }
 218                inb(dev->iobase + DT2814_DATA);
 219                inb(dev->iobase + DT2814_DATA);
 220
 221                s->async->events |= COMEDI_CB_EOA;
 222        }
 223        comedi_handle_events(dev, s);
 224        return IRQ_HANDLED;
 225}
 226
 227static int dt2814_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 228{
 229        struct dt2814_private *devpriv;
 230        struct comedi_subdevice *s;
 231        int ret;
 232        int i;
 233
 234        ret = comedi_request_region(dev, it->options[0], 0x2);
 235        if (ret)
 236                return ret;
 237
 238        outb(0, dev->iobase + DT2814_CSR);
 239        usleep_range(100, 200);
 240        if (inb(dev->iobase + DT2814_CSR) & DT2814_ERR) {
 241                dev_err(dev->class_dev, "reset error (fatal)\n");
 242                return -EIO;
 243        }
 244        i = inb(dev->iobase + DT2814_DATA);
 245        i = inb(dev->iobase + DT2814_DATA);
 246
 247        if (it->options[1]) {
 248                ret = request_irq(it->options[1], dt2814_interrupt, 0,
 249                                  dev->board_name, dev);
 250                if (ret == 0)
 251                        dev->irq = it->options[1];
 252        }
 253
 254        ret = comedi_alloc_subdevices(dev, 1);
 255        if (ret)
 256                return ret;
 257
 258        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 259        if (!devpriv)
 260                return -ENOMEM;
 261
 262        s = &dev->subdevices[0];
 263        s->type = COMEDI_SUBD_AI;
 264        s->subdev_flags = SDF_READABLE | SDF_GROUND;
 265        s->n_chan = 16;         /* XXX */
 266        s->insn_read = dt2814_ai_insn_read;
 267        s->maxdata = 0xfff;
 268        s->range_table = &range_unknown;        /* XXX */
 269        if (dev->irq) {
 270                dev->read_subdev = s;
 271                s->subdev_flags |= SDF_CMD_READ;
 272                s->len_chanlist = 1;
 273                s->do_cmd = dt2814_ai_cmd;
 274                s->do_cmdtest = dt2814_ai_cmdtest;
 275        }
 276
 277        return 0;
 278}
 279
 280static struct comedi_driver dt2814_driver = {
 281        .driver_name    = "dt2814",
 282        .module         = THIS_MODULE,
 283        .attach         = dt2814_attach,
 284        .detach         = comedi_legacy_detach,
 285};
 286module_comedi_driver(dt2814_driver);
 287
 288MODULE_AUTHOR("Comedi http://www.comedi.org");
 289MODULE_DESCRIPTION("Comedi low-level driver");
 290MODULE_LICENSE("GPL");
 291