linux/drivers/staging/comedi/drivers/das16m1.c
<<
>>
Prefs
   1/*
   2    comedi/drivers/das16m1.c
   3    CIO-DAS16/M1 driver
   4    Author: Frank Mori Hess, based on code from the das16
   5      driver.
   6    Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net>
   7
   8    COMEDI - Linux Control and Measurement Device Interface
   9    Copyright (C) 2000 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/*
  22Driver: das16m1
  23Description: CIO-DAS16/M1
  24Author: Frank Mori Hess <fmhess@users.sourceforge.net>
  25Devices: [Measurement Computing] CIO-DAS16/M1 (das16m1)
  26Status: works
  27
  28This driver supports a single board - the CIO-DAS16/M1.
  29As far as I know, there are no other boards that have
  30the same register layout.  Even the CIO-DAS16/M1/16 is
  31significantly different.
  32
  33I was _barely_ able to reach the full 1 MHz capability
  34of this board, using a hard real-time interrupt
  35(set the TRIG_RT flag in your struct comedi_cmd and use
  36rtlinux or RTAI).  The board can't do dma, so the bottleneck is
  37pulling the data across the ISA bus.  I timed the interrupt
  38handler, and it took my computer ~470 microseconds to pull 512
  39samples from the board.  So at 1 Mhz sampling rate,
  40expect your CPU to be spending almost all of its
  41time in the interrupt handler.
  42
  43This board has some unusual restrictions for its channel/gain list.  If the
  44list has 2 or more channels in it, then two conditions must be satisfied:
  45(1) - even/odd channels must appear at even/odd indices in the list
  46(2) - the list must have an even number of entries.
  47
  48Options:
  49        [0] - base io address
  50        [1] - irq (optional, but you probably want it)
  51
  52irq can be omitted, although the cmd interface will not work without it.
  53*/
  54
  55#include <linux/module.h>
  56#include <linux/slab.h>
  57#include <linux/interrupt.h>
  58#include "../comedidev.h"
  59
  60#include "8255.h"
  61#include "comedi_8254.h"
  62
  63#define DAS16M1_SIZE2 8
  64
  65#define FIFO_SIZE 1024          /*  1024 sample fifo */
  66
  67/*
  68    CIO-DAS16_M1.pdf
  69
  70    "cio-das16/m1"
  71
  72  0             a/d bits 0-3, mux               start 12 bit
  73  1             a/d bits 4-11           unused
  74  2             status          control
  75  3             di 4 bit                do 4 bit
  76  4             unused                  clear interrupt
  77  5             interrupt, pacer
  78  6             channel/gain queue address
  79  7             channel/gain queue data
  80  89ab          8254
  81  cdef          8254
  82  400           8255
  83  404-407       8254
  84
  85*/
  86
  87#define DAS16M1_AI             0        /*  16-bit wide register */
  88#define   AI_CHAN(x)             ((x) & 0xf)
  89#define DAS16M1_CS             2
  90#define   EXT_TRIG_BIT           0x1
  91#define   OVRUN                  0x20
  92#define   IRQDATA                0x80
  93#define DAS16M1_DIO            3
  94#define DAS16M1_CLEAR_INTR     4
  95#define DAS16M1_INTR_CONTROL   5
  96#define   EXT_PACER              0x2
  97#define   INT_PACER              0x3
  98#define   PACER_MASK             0x3
  99#define   INTE                   0x80
 100#define DAS16M1_QUEUE_ADDR     6
 101#define DAS16M1_QUEUE_DATA     7
 102#define   Q_CHAN(x)              ((x) & 0x7)
 103#define   Q_RANGE(x)             (((x) & 0xf) << 4)
 104#define   UNIPOLAR               0x40
 105#define DAS16M1_8254_FIRST             0x8
 106#define DAS16M1_8254_SECOND            0xc
 107#define DAS16M1_82C55                  0x400
 108#define DAS16M1_8254_THIRD             0x404
 109
 110static const struct comedi_lrange range_das16m1 = {
 111        9, {
 112                BIP_RANGE(5),
 113                BIP_RANGE(2.5),
 114                BIP_RANGE(1.25),
 115                BIP_RANGE(0.625),
 116                UNI_RANGE(10),
 117                UNI_RANGE(5),
 118                UNI_RANGE(2.5),
 119                UNI_RANGE(1.25),
 120                BIP_RANGE(10)
 121        }
 122};
 123
 124struct das16m1_private_struct {
 125        struct comedi_8254 *counter;
 126        unsigned int control_state;
 127        unsigned int adc_count; /*  number of samples completed */
 128        /* initial value in lower half of hardware conversion counter,
 129         * needed to keep track of whether new count has been loaded into
 130         * counter yet (loaded by first sample conversion) */
 131        u16 initial_hw_count;
 132        unsigned short ai_buffer[FIFO_SIZE];
 133        unsigned long extra_iobase;
 134};
 135
 136static inline unsigned short munge_sample(unsigned short data)
 137{
 138        return (data >> 4) & 0xfff;
 139}
 140
 141static void munge_sample_array(unsigned short *array, unsigned int num_elements)
 142{
 143        unsigned int i;
 144
 145        for (i = 0; i < num_elements; i++)
 146                array[i] = munge_sample(array[i]);
 147}
 148
 149static int das16m1_ai_check_chanlist(struct comedi_device *dev,
 150                                     struct comedi_subdevice *s,
 151                                     struct comedi_cmd *cmd)
 152{
 153        int i;
 154
 155        if (cmd->chanlist_len == 1)
 156                return 0;
 157
 158        if ((cmd->chanlist_len % 2) != 0) {
 159                dev_dbg(dev->class_dev,
 160                        "chanlist must be of even length or length 1\n");
 161                return -EINVAL;
 162        }
 163
 164        for (i = 0; i < cmd->chanlist_len; i++) {
 165                unsigned int chan = CR_CHAN(cmd->chanlist[i]);
 166
 167                if ((i % 2) != (chan % 2)) {
 168                        dev_dbg(dev->class_dev,
 169                                "even/odd channels must go have even/odd chanlist indices\n");
 170                        return -EINVAL;
 171                }
 172        }
 173
 174        return 0;
 175}
 176
 177static int das16m1_cmd_test(struct comedi_device *dev,
 178                            struct comedi_subdevice *s, struct comedi_cmd *cmd)
 179{
 180        int err = 0;
 181
 182        /* Step 1 : check if triggers are trivially valid */
 183
 184        err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
 185        err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
 186        err |= comedi_check_trigger_src(&cmd->convert_src,
 187                                        TRIG_TIMER | TRIG_EXT);
 188        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 189        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
 190
 191        if (err)
 192                return 1;
 193
 194        /* Step 2a : make sure trigger sources are unique */
 195
 196        err |= comedi_check_trigger_is_unique(cmd->start_src);
 197        err |= comedi_check_trigger_is_unique(cmd->convert_src);
 198        err |= comedi_check_trigger_is_unique(cmd->stop_src);
 199
 200        /* Step 2b : and mutually compatible */
 201
 202        if (err)
 203                return 2;
 204
 205        /* Step 3: check if arguments are trivially valid */
 206
 207        err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
 208
 209        if (cmd->scan_begin_src == TRIG_FOLLOW) /* internal trigger */
 210                err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
 211
 212        if (cmd->convert_src == TRIG_TIMER)
 213                err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 1000);
 214
 215        err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
 216                                           cmd->chanlist_len);
 217
 218        if (cmd->stop_src == TRIG_COUNT)
 219                err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
 220        else    /* TRIG_NONE */
 221                err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
 222
 223        if (err)
 224                return 3;
 225
 226        /* step 4: fix up arguments */
 227
 228        if (cmd->convert_src == TRIG_TIMER) {
 229                unsigned int arg = cmd->convert_arg;
 230
 231                comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
 232                err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
 233        }
 234
 235        if (err)
 236                return 4;
 237
 238        /* Step 5: check channel list if it exists */
 239        if (cmd->chanlist && cmd->chanlist_len > 0)
 240                err |= das16m1_ai_check_chanlist(dev, s, cmd);
 241
 242        if (err)
 243                return 5;
 244
 245        return 0;
 246}
 247
 248static int das16m1_cmd_exec(struct comedi_device *dev,
 249                            struct comedi_subdevice *s)
 250{
 251        struct das16m1_private_struct *devpriv = dev->private;
 252        struct comedi_async *async = s->async;
 253        struct comedi_cmd *cmd = &async->cmd;
 254        unsigned int byte, i;
 255
 256        /* disable interrupts and internal pacer */
 257        devpriv->control_state &= ~INTE & ~PACER_MASK;
 258        outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
 259
 260        /*  set software count */
 261        devpriv->adc_count = 0;
 262
 263        /*
 264         * Initialize lower half of hardware counter, used to determine how
 265         * many samples are in fifo.  Value doesn't actually load into counter
 266         * until counter's next clock (the next a/d conversion).
 267         */
 268        comedi_8254_set_mode(devpriv->counter, 1, I8254_MODE2 | I8254_BINARY);
 269        comedi_8254_write(devpriv->counter, 1, 0);
 270
 271        /*
 272         * Remember current reading of counter so we know when counter has
 273         * actually been loaded.
 274         */
 275        devpriv->initial_hw_count = comedi_8254_read(devpriv->counter, 1);
 276
 277        /* setup channel/gain queue */
 278        for (i = 0; i < cmd->chanlist_len; i++) {
 279                outb(i, dev->iobase + DAS16M1_QUEUE_ADDR);
 280                byte =
 281                    Q_CHAN(CR_CHAN(cmd->chanlist[i])) |
 282                    Q_RANGE(CR_RANGE(cmd->chanlist[i]));
 283                outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
 284        }
 285
 286        /* enable interrupts and set internal pacer counter mode and counts */
 287        devpriv->control_state &= ~PACER_MASK;
 288        if (cmd->convert_src == TRIG_TIMER) {
 289                comedi_8254_update_divisors(dev->pacer);
 290                comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
 291                devpriv->control_state |= INT_PACER;
 292        } else {        /* TRIG_EXT */
 293                devpriv->control_state |= EXT_PACER;
 294        }
 295
 296        /*  set control & status register */
 297        byte = 0;
 298        /* if we are using external start trigger (also board dislikes having
 299         * both start and conversion triggers external simultaneously) */
 300        if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT)
 301                byte |= EXT_TRIG_BIT;
 302
 303        outb(byte, dev->iobase + DAS16M1_CS);
 304        /* clear interrupt bit */
 305        outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
 306
 307        devpriv->control_state |= INTE;
 308        outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
 309
 310        return 0;
 311}
 312
 313static int das16m1_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
 314{
 315        struct das16m1_private_struct *devpriv = dev->private;
 316
 317        devpriv->control_state &= ~INTE & ~PACER_MASK;
 318        outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
 319
 320        return 0;
 321}
 322
 323static int das16m1_ai_eoc(struct comedi_device *dev,
 324                          struct comedi_subdevice *s,
 325                          struct comedi_insn *insn,
 326                          unsigned long context)
 327{
 328        unsigned int status;
 329
 330        status = inb(dev->iobase + DAS16M1_CS);
 331        if (status & IRQDATA)
 332                return 0;
 333        return -EBUSY;
 334}
 335
 336static int das16m1_ai_rinsn(struct comedi_device *dev,
 337                            struct comedi_subdevice *s,
 338                            struct comedi_insn *insn, unsigned int *data)
 339{
 340        struct das16m1_private_struct *devpriv = dev->private;
 341        int ret;
 342        int n;
 343        int byte;
 344
 345        /* disable interrupts and internal pacer */
 346        devpriv->control_state &= ~INTE & ~PACER_MASK;
 347        outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
 348
 349        /* setup channel/gain queue */
 350        outb(0, dev->iobase + DAS16M1_QUEUE_ADDR);
 351        byte =
 352            Q_CHAN(CR_CHAN(insn->chanspec)) | Q_RANGE(CR_RANGE(insn->chanspec));
 353        outb(byte, dev->iobase + DAS16M1_QUEUE_DATA);
 354
 355        for (n = 0; n < insn->n; n++) {
 356                /* clear IRQDATA bit */
 357                outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
 358                /* trigger conversion */
 359                outb(0, dev->iobase);
 360
 361                ret = comedi_timeout(dev, s, insn, das16m1_ai_eoc, 0);
 362                if (ret)
 363                        return ret;
 364
 365                data[n] = munge_sample(inw(dev->iobase));
 366        }
 367
 368        return n;
 369}
 370
 371static int das16m1_di_rbits(struct comedi_device *dev,
 372                            struct comedi_subdevice *s,
 373                            struct comedi_insn *insn, unsigned int *data)
 374{
 375        unsigned int bits;
 376
 377        bits = inb(dev->iobase + DAS16M1_DIO) & 0xf;
 378        data[1] = bits;
 379        data[0] = 0;
 380
 381        return insn->n;
 382}
 383
 384static int das16m1_do_wbits(struct comedi_device *dev,
 385                            struct comedi_subdevice *s,
 386                            struct comedi_insn *insn,
 387                            unsigned int *data)
 388{
 389        if (comedi_dio_update_state(s, data))
 390                outb(s->state, dev->iobase + DAS16M1_DIO);
 391
 392        data[1] = s->state;
 393
 394        return insn->n;
 395}
 396
 397static void das16m1_handler(struct comedi_device *dev, unsigned int status)
 398{
 399        struct das16m1_private_struct *devpriv = dev->private;
 400        struct comedi_subdevice *s;
 401        struct comedi_async *async;
 402        struct comedi_cmd *cmd;
 403        u16 num_samples;
 404        u16 hw_counter;
 405
 406        s = dev->read_subdev;
 407        async = s->async;
 408        cmd = &async->cmd;
 409
 410        /* figure out how many samples are in fifo */
 411        hw_counter = comedi_8254_read(devpriv->counter, 1);
 412        /* make sure hardware counter reading is not bogus due to initial value
 413         * not having been loaded yet */
 414        if (devpriv->adc_count == 0 &&
 415            hw_counter == devpriv->initial_hw_count) {
 416                num_samples = 0;
 417        } else {
 418                /* The calculation of num_samples looks odd, but it uses the
 419                 * following facts. 16 bit hardware counter is initialized with
 420                 * value of zero (which really means 0x1000).  The counter
 421                 * decrements by one on each conversion (when the counter
 422                 * decrements from zero it goes to 0xffff).  num_samples is a
 423                 * 16 bit variable, so it will roll over in a similar fashion
 424                 * to the hardware counter.  Work it out, and this is what you
 425                 * get. */
 426                num_samples = -hw_counter - devpriv->adc_count;
 427        }
 428        /*  check if we only need some of the points */
 429        if (cmd->stop_src == TRIG_COUNT) {
 430                if (num_samples > cmd->stop_arg * cmd->chanlist_len)
 431                        num_samples = cmd->stop_arg * cmd->chanlist_len;
 432        }
 433        /*  make sure we dont try to get too many points if fifo has overrun */
 434        if (num_samples > FIFO_SIZE)
 435                num_samples = FIFO_SIZE;
 436        insw(dev->iobase, devpriv->ai_buffer, num_samples);
 437        munge_sample_array(devpriv->ai_buffer, num_samples);
 438        comedi_buf_write_samples(s, devpriv->ai_buffer, num_samples);
 439        devpriv->adc_count += num_samples;
 440
 441        if (cmd->stop_src == TRIG_COUNT) {
 442                if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) {
 443                        /* end of acquisition */
 444                        async->events |= COMEDI_CB_EOA;
 445                }
 446        }
 447
 448        /* this probably won't catch overruns since the card doesn't generate
 449         * overrun interrupts, but we might as well try */
 450        if (status & OVRUN) {
 451                async->events |= COMEDI_CB_ERROR;
 452                dev_err(dev->class_dev, "fifo overflow\n");
 453        }
 454
 455        comedi_handle_events(dev, s);
 456}
 457
 458static int das16m1_poll(struct comedi_device *dev, struct comedi_subdevice *s)
 459{
 460        unsigned long flags;
 461        unsigned int status;
 462
 463        /*  prevent race with interrupt handler */
 464        spin_lock_irqsave(&dev->spinlock, flags);
 465        status = inb(dev->iobase + DAS16M1_CS);
 466        das16m1_handler(dev, status);
 467        spin_unlock_irqrestore(&dev->spinlock, flags);
 468
 469        return comedi_buf_n_bytes_ready(s);
 470}
 471
 472static irqreturn_t das16m1_interrupt(int irq, void *d)
 473{
 474        int status;
 475        struct comedi_device *dev = d;
 476
 477        if (!dev->attached) {
 478                dev_err(dev->class_dev, "premature interrupt\n");
 479                return IRQ_HANDLED;
 480        }
 481        /*  prevent race with comedi_poll() */
 482        spin_lock(&dev->spinlock);
 483
 484        status = inb(dev->iobase + DAS16M1_CS);
 485
 486        if ((status & (IRQDATA | OVRUN)) == 0) {
 487                dev_err(dev->class_dev, "spurious interrupt\n");
 488                spin_unlock(&dev->spinlock);
 489                return IRQ_NONE;
 490        }
 491
 492        das16m1_handler(dev, status);
 493
 494        /* clear interrupt */
 495        outb(0, dev->iobase + DAS16M1_CLEAR_INTR);
 496
 497        spin_unlock(&dev->spinlock);
 498        return IRQ_HANDLED;
 499}
 500
 501static int das16m1_irq_bits(unsigned int irq)
 502{
 503        switch (irq) {
 504        case 10:
 505                return 0x0;
 506        case 11:
 507                return 0x1;
 508        case 12:
 509                return 0x2;
 510        case 15:
 511                return 0x3;
 512        case 2:
 513                return 0x4;
 514        case 3:
 515                return 0x5;
 516        case 5:
 517                return 0x6;
 518        case 7:
 519                return 0x7;
 520        default:
 521                return 0x0;
 522        }
 523}
 524
 525/*
 526 * Options list:
 527 *   0  I/O base
 528 *   1  IRQ
 529 */
 530static int das16m1_attach(struct comedi_device *dev,
 531                          struct comedi_devconfig *it)
 532{
 533        struct das16m1_private_struct *devpriv;
 534        struct comedi_subdevice *s;
 535        int ret;
 536
 537        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 538        if (!devpriv)
 539                return -ENOMEM;
 540
 541        ret = comedi_request_region(dev, it->options[0], 0x10);
 542        if (ret)
 543                return ret;
 544        /* Request an additional region for the 8255 */
 545        ret = __comedi_request_region(dev, dev->iobase + DAS16M1_82C55,
 546                                      DAS16M1_SIZE2);
 547        if (ret)
 548                return ret;
 549        devpriv->extra_iobase = dev->iobase + DAS16M1_82C55;
 550
 551        /* only irqs 2, 3, 4, 5, 6, 7, 10, 11, 12, 14, and 15 are valid */
 552        if ((1 << it->options[1]) & 0xdcfc) {
 553                ret = request_irq(it->options[1], das16m1_interrupt, 0,
 554                                  dev->board_name, dev);
 555                if (ret == 0)
 556                        dev->irq = it->options[1];
 557        }
 558
 559        dev->pacer = comedi_8254_init(dev->iobase + DAS16M1_8254_SECOND,
 560                                      I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
 561        if (!dev->pacer)
 562                return -ENOMEM;
 563
 564        devpriv->counter = comedi_8254_init(dev->iobase + DAS16M1_8254_FIRST,
 565                                            0, I8254_IO8, 0);
 566        if (!devpriv->counter)
 567                return -ENOMEM;
 568
 569        ret = comedi_alloc_subdevices(dev, 4);
 570        if (ret)
 571                return ret;
 572
 573        s = &dev->subdevices[0];
 574        /* ai */
 575        s->type = COMEDI_SUBD_AI;
 576        s->subdev_flags = SDF_READABLE | SDF_DIFF;
 577        s->n_chan = 8;
 578        s->maxdata = (1 << 12) - 1;
 579        s->range_table = &range_das16m1;
 580        s->insn_read = das16m1_ai_rinsn;
 581        if (dev->irq) {
 582                dev->read_subdev = s;
 583                s->subdev_flags |= SDF_CMD_READ;
 584                s->len_chanlist = 256;
 585                s->do_cmdtest = das16m1_cmd_test;
 586                s->do_cmd = das16m1_cmd_exec;
 587                s->cancel = das16m1_cancel;
 588                s->poll = das16m1_poll;
 589        }
 590
 591        s = &dev->subdevices[1];
 592        /* di */
 593        s->type = COMEDI_SUBD_DI;
 594        s->subdev_flags = SDF_READABLE;
 595        s->n_chan = 4;
 596        s->maxdata = 1;
 597        s->range_table = &range_digital;
 598        s->insn_bits = das16m1_di_rbits;
 599
 600        s = &dev->subdevices[2];
 601        /* do */
 602        s->type = COMEDI_SUBD_DO;
 603        s->subdev_flags = SDF_WRITABLE;
 604        s->n_chan = 4;
 605        s->maxdata = 1;
 606        s->range_table = &range_digital;
 607        s->insn_bits = das16m1_do_wbits;
 608
 609        s = &dev->subdevices[3];
 610        /* 8255 */
 611        ret = subdev_8255_init(dev, s, NULL, DAS16M1_82C55);
 612        if (ret)
 613                return ret;
 614
 615        /*  initialize digital output lines */
 616        outb(0, dev->iobase + DAS16M1_DIO);
 617
 618        /* set the interrupt level */
 619        devpriv->control_state = das16m1_irq_bits(dev->irq) << 4;
 620        outb(devpriv->control_state, dev->iobase + DAS16M1_INTR_CONTROL);
 621
 622        return 0;
 623}
 624
 625static void das16m1_detach(struct comedi_device *dev)
 626{
 627        struct das16m1_private_struct *devpriv = dev->private;
 628
 629        if (devpriv) {
 630                if (devpriv->extra_iobase)
 631                        release_region(devpriv->extra_iobase, DAS16M1_SIZE2);
 632                kfree(devpriv->counter);
 633        }
 634        comedi_legacy_detach(dev);
 635}
 636
 637static struct comedi_driver das16m1_driver = {
 638        .driver_name    = "das16m1",
 639        .module         = THIS_MODULE,
 640        .attach         = das16m1_attach,
 641        .detach         = das16m1_detach,
 642};
 643module_comedi_driver(das16m1_driver);
 644
 645MODULE_AUTHOR("Comedi http://www.comedi.org");
 646MODULE_DESCRIPTION("Comedi low-level driver");
 647MODULE_LICENSE("GPL");
 648