linux/drivers/staging/comedi/drivers/das16m1.c
<<
>>
Prefs
   1/*
   2 * Comedi driver for CIO-DAS16/M1
   3 * Author: Frank Mori Hess, based on code from the das16 driver.
   4 * Copyright (C) 2001 Frank Mori Hess <fmhess@users.sourceforge.net>
   5 *
   6 * COMEDI - Linux Control and Measurement Device Interface
   7 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
   8 *
   9 * This program is free software; you can redistribute it and/or modify
  10 * it under the terms of the GNU General Public License as published by
  11 * the Free Software Foundation; either version 2 of the License, or
  12 * (at your option) any later version.
  13 *
  14 * This program is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17 * GNU General Public License for more details.
  18 */
  19
  20/*
  21 * Driver: das16m1
  22 * Description: CIO-DAS16/M1
  23 * Author: Frank Mori Hess <fmhess@users.sourceforge.net>
  24 * Devices: [Measurement Computing] CIO-DAS16/M1 (das16m1)
  25 * Status: works
  26 *
  27 * This driver supports a single board - the CIO-DAS16/M1. As far as I know,
  28 * there are no other boards that have the same register layout. Even the
  29 * CIO-DAS16/M1/16 is significantly different.
  30 *
  31 * I was _barely_ able to reach the full 1 MHz capability of this board, using
  32 * a hard real-time interrupt (set the TRIG_RT flag in your struct comedi_cmd
  33 * and use rtlinux or RTAI). The board can't do dma, so the bottleneck is
  34 * pulling the data across the ISA bus. I timed the interrupt handler, and it
  35 * took my computer ~470 microseconds to pull 512 samples from the board. So
  36 * at 1 Mhz sampling rate, expect your CPU to be spending almost all of its
  37 * time in the interrupt handler.
  38 *
  39 * This board has some unusual restrictions for its channel/gain list.  If the
  40 * list has 2 or more channels in it, then two conditions must be satisfied:
  41 * (1) - even/odd channels must appear at even/odd indices in the list
  42 * (2) - the list must have an even number of entries.
  43 *
  44 * Configuration options:
  45 *   [0] - base io address
  46 *   [1] - irq (optional, but you probably want it)
  47 *
  48 * irq can be omitted, although the cmd interface will not work without it.
  49 */
  50
  51#include <linux/module.h>
  52#include <linux/slab.h>
  53#include <linux/interrupt.h>
  54#include "../comedidev.h"
  55
  56#include "8255.h"
  57#include "comedi_8254.h"
  58
  59/*
  60 * Register map (dev->iobase)
  61 */
  62#define DAS16M1_AI_REG                  0x00    /* 16-bit register */
  63#define DAS16M1_AI_TO_CHAN(x)           (((x) >> 0) & 0xf)
  64#define DAS16M1_AI_TO_SAMPLE(x)         (((x) >> 4) & 0xfff)
  65#define DAS16M1_CS_REG                  0x02
  66#define DAS16M1_CS_EXT_TRIG             BIT(0)
  67#define DAS16M1_CS_OVRUN                BIT(5)
  68#define DAS16M1_CS_IRQDATA              BIT(7)
  69#define DAS16M1_DI_REG                  0x03
  70#define DAS16M1_DO_REG                  0x03
  71#define DAS16M1_CLR_INTR_REG            0x04
  72#define DAS16M1_INTR_CTRL_REG           0x05
  73#define DAS16M1_INTR_CTRL_PACER(x)      (((x) & 0x3) << 0)
  74#define DAS16M1_INTR_CTRL_PACER_EXT     DAS16M1_INTR_CTRL_PACER(2)
  75#define DAS16M1_INTR_CTRL_PACER_INT     DAS16M1_INTR_CTRL_PACER(3)
  76#define DAS16M1_INTR_CTRL_PACER_MASK    DAS16M1_INTR_CTRL_PACER(3)
  77#define DAS16M1_INTR_CTRL_IRQ(x)        (((x) & 0x7) << 4)
  78#define DAS16M1_INTR_CTRL_INTE          BIT(7)
  79#define DAS16M1_Q_ADDR_REG              0x06
  80#define DAS16M1_Q_REG                   0x07
  81#define DAS16M1_Q_CHAN(x)              (((x) & 0x7) << 0)
  82#define DAS16M1_Q_RANGE(x)             (((x) & 0xf) << 4)
  83#define DAS16M1_8254_IOBASE1            0x08
  84#define DAS16M1_8254_IOBASE2            0x0c
  85#define DAS16M1_8255_IOBASE             0x400
  86#define DAS16M1_8254_IOBASE3            0x404
  87
  88#define DAS16M1_SIZE2                   0x08
  89
  90#define DAS16M1_AI_FIFO_SZ              1024    /* # samples */
  91
  92static const struct comedi_lrange range_das16m1 = {
  93        9, {
  94                BIP_RANGE(5),
  95                BIP_RANGE(2.5),
  96                BIP_RANGE(1.25),
  97                BIP_RANGE(0.625),
  98                UNI_RANGE(10),
  99                UNI_RANGE(5),
 100                UNI_RANGE(2.5),
 101                UNI_RANGE(1.25),
 102                BIP_RANGE(10)
 103        }
 104};
 105
 106struct das16m1_private {
 107        struct comedi_8254 *counter;
 108        unsigned int intr_ctrl;
 109        unsigned int adc_count;
 110        u16 initial_hw_count;
 111        unsigned short ai_buffer[DAS16M1_AI_FIFO_SZ];
 112        unsigned long extra_iobase;
 113};
 114
 115static void das16m1_ai_set_queue(struct comedi_device *dev,
 116                                 unsigned int *chanspec, unsigned int len)
 117{
 118        unsigned int i;
 119
 120        for (i = 0; i < len; i++) {
 121                unsigned int chan = CR_CHAN(chanspec[i]);
 122                unsigned int range = CR_RANGE(chanspec[i]);
 123
 124                outb(i, dev->iobase + DAS16M1_Q_ADDR_REG);
 125                outb(DAS16M1_Q_CHAN(chan) | DAS16M1_Q_RANGE(range),
 126                     dev->iobase + DAS16M1_Q_REG);
 127        }
 128}
 129
 130static void das16m1_ai_munge(struct comedi_device *dev,
 131                             struct comedi_subdevice *s,
 132                             void *data, unsigned int num_bytes,
 133                             unsigned int start_chan_index)
 134{
 135        unsigned short *array = data;
 136        unsigned int nsamples = comedi_bytes_to_samples(s, num_bytes);
 137        unsigned int i;
 138
 139        /*
 140         * The fifo values have the channel number in the lower 4-bits and
 141         * the sample in the upper 12-bits. This just shifts the values
 142         * to remove the channel numbers.
 143         */
 144        for (i = 0; i < nsamples; i++)
 145                array[i] = DAS16M1_AI_TO_SAMPLE(array[i]);
 146}
 147
 148static int das16m1_ai_check_chanlist(struct comedi_device *dev,
 149                                     struct comedi_subdevice *s,
 150                                     struct comedi_cmd *cmd)
 151{
 152        int i;
 153
 154        if (cmd->chanlist_len == 1)
 155                return 0;
 156
 157        if ((cmd->chanlist_len % 2) != 0) {
 158                dev_dbg(dev->class_dev,
 159                        "chanlist must be of even length or length 1\n");
 160                return -EINVAL;
 161        }
 162
 163        for (i = 0; i < cmd->chanlist_len; i++) {
 164                unsigned int chan = CR_CHAN(cmd->chanlist[i]);
 165
 166                if ((i % 2) != (chan % 2)) {
 167                        dev_dbg(dev->class_dev,
 168                                "even/odd channels must go have even/odd chanlist indices\n");
 169                        return -EINVAL;
 170                }
 171        }
 172
 173        return 0;
 174}
 175
 176static int das16m1_ai_cmdtest(struct comedi_device *dev,
 177                              struct comedi_subdevice *s,
 178                              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_ai_cmd(struct comedi_device *dev,
 249                          struct comedi_subdevice *s)
 250{
 251        struct das16m1_private *devpriv = dev->private;
 252        struct comedi_async *async = s->async;
 253        struct comedi_cmd *cmd = &async->cmd;
 254        unsigned int byte;
 255
 256        /*  set software count */
 257        devpriv->adc_count = 0;
 258
 259        /*
 260         * Initialize lower half of hardware counter, used to determine how
 261         * many samples are in fifo.  Value doesn't actually load into counter
 262         * until counter's next clock (the next a/d conversion).
 263         */
 264        comedi_8254_set_mode(devpriv->counter, 1, I8254_MODE2 | I8254_BINARY);
 265        comedi_8254_write(devpriv->counter, 1, 0);
 266
 267        /*
 268         * Remember current reading of counter so we know when counter has
 269         * actually been loaded.
 270         */
 271        devpriv->initial_hw_count = comedi_8254_read(devpriv->counter, 1);
 272
 273        das16m1_ai_set_queue(dev, cmd->chanlist, cmd->chanlist_len);
 274
 275        /* enable interrupts and set internal pacer counter mode and counts */
 276        devpriv->intr_ctrl &= ~DAS16M1_INTR_CTRL_PACER_MASK;
 277        if (cmd->convert_src == TRIG_TIMER) {
 278                comedi_8254_update_divisors(dev->pacer);
 279                comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
 280                devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_PACER_INT;
 281        } else {        /* TRIG_EXT */
 282                devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_PACER_EXT;
 283        }
 284
 285        /*  set control & status register */
 286        byte = 0;
 287        /*
 288         * If we are using external start trigger (also board dislikes having
 289         * both start and conversion triggers external simultaneously).
 290         */
 291        if (cmd->start_src == TRIG_EXT && cmd->convert_src != TRIG_EXT)
 292                byte |= DAS16M1_CS_EXT_TRIG;
 293
 294        outb(byte, dev->iobase + DAS16M1_CS_REG);
 295
 296        /* clear interrupt */
 297        outb(0, dev->iobase + DAS16M1_CLR_INTR_REG);
 298
 299        devpriv->intr_ctrl |= DAS16M1_INTR_CTRL_INTE;
 300        outb(devpriv->intr_ctrl, dev->iobase + DAS16M1_INTR_CTRL_REG);
 301
 302        return 0;
 303}
 304
 305static int das16m1_ai_cancel(struct comedi_device *dev,
 306                             struct comedi_subdevice *s)
 307{
 308        struct das16m1_private *devpriv = dev->private;
 309
 310        /* disable interrupts and pacer */
 311        devpriv->intr_ctrl &= ~(DAS16M1_INTR_CTRL_INTE |
 312                                DAS16M1_INTR_CTRL_PACER_MASK);
 313        outb(devpriv->intr_ctrl, dev->iobase + DAS16M1_INTR_CTRL_REG);
 314
 315        return 0;
 316}
 317
 318static int das16m1_ai_eoc(struct comedi_device *dev,
 319                          struct comedi_subdevice *s,
 320                          struct comedi_insn *insn,
 321                          unsigned long context)
 322{
 323        unsigned int status;
 324
 325        status = inb(dev->iobase + DAS16M1_CS_REG);
 326        if (status & DAS16M1_CS_IRQDATA)
 327                return 0;
 328        return -EBUSY;
 329}
 330
 331static int das16m1_ai_insn_read(struct comedi_device *dev,
 332                                struct comedi_subdevice *s,
 333                                struct comedi_insn *insn,
 334                                unsigned int *data)
 335{
 336        int ret;
 337        int i;
 338
 339        das16m1_ai_set_queue(dev, &insn->chanspec, 1);
 340
 341        for (i = 0; i < insn->n; i++) {
 342                unsigned short val;
 343
 344                /* clear interrupt */
 345                outb(0, dev->iobase + DAS16M1_CLR_INTR_REG);
 346                /* trigger conversion */
 347                outb(0, dev->iobase + DAS16M1_AI_REG);
 348
 349                ret = comedi_timeout(dev, s, insn, das16m1_ai_eoc, 0);
 350                if (ret)
 351                        return ret;
 352
 353                val = inw(dev->iobase + DAS16M1_AI_REG);
 354                data[i] = DAS16M1_AI_TO_SAMPLE(val);
 355        }
 356
 357        return insn->n;
 358}
 359
 360static int das16m1_di_insn_bits(struct comedi_device *dev,
 361                                struct comedi_subdevice *s,
 362                                struct comedi_insn *insn,
 363                                unsigned int *data)
 364{
 365        data[1] = inb(dev->iobase + DAS16M1_DI_REG) & 0xf;
 366
 367        return insn->n;
 368}
 369
 370static int das16m1_do_insn_bits(struct comedi_device *dev,
 371                                struct comedi_subdevice *s,
 372                                struct comedi_insn *insn,
 373                                unsigned int *data)
 374{
 375        if (comedi_dio_update_state(s, data))
 376                outb(s->state, dev->iobase + DAS16M1_DO_REG);
 377
 378        data[1] = s->state;
 379
 380        return insn->n;
 381}
 382
 383static void das16m1_handler(struct comedi_device *dev, unsigned int status)
 384{
 385        struct das16m1_private *devpriv = dev->private;
 386        struct comedi_subdevice *s = dev->read_subdev;
 387        struct comedi_async *async = s->async;
 388        struct comedi_cmd *cmd = &async->cmd;
 389        u16 num_samples;
 390        u16 hw_counter;
 391
 392        /* figure out how many samples are in fifo */
 393        hw_counter = comedi_8254_read(devpriv->counter, 1);
 394        /*
 395         * Make sure hardware counter reading is not bogus due to initial
 396         * value not having been loaded yet.
 397         */
 398        if (devpriv->adc_count == 0 &&
 399            hw_counter == devpriv->initial_hw_count) {
 400                num_samples = 0;
 401        } else {
 402                /*
 403                 * The calculation of num_samples looks odd, but it uses the
 404                 * following facts. 16 bit hardware counter is initialized with
 405                 * value of zero (which really means 0x1000).  The counter
 406                 * decrements by one on each conversion (when the counter
 407                 * decrements from zero it goes to 0xffff).  num_samples is a
 408                 * 16 bit variable, so it will roll over in a similar fashion
 409                 * to the hardware counter.  Work it out, and this is what you
 410                 * get.
 411                 */
 412                num_samples = -hw_counter - devpriv->adc_count;
 413        }
 414        /*  check if we only need some of the points */
 415        if (cmd->stop_src == TRIG_COUNT) {
 416                if (num_samples > cmd->stop_arg * cmd->chanlist_len)
 417                        num_samples = cmd->stop_arg * cmd->chanlist_len;
 418        }
 419        /*  make sure we dont try to get too many points if fifo has overrun */
 420        if (num_samples > DAS16M1_AI_FIFO_SZ)
 421                num_samples = DAS16M1_AI_FIFO_SZ;
 422        insw(dev->iobase, devpriv->ai_buffer, num_samples);
 423        comedi_buf_write_samples(s, devpriv->ai_buffer, num_samples);
 424        devpriv->adc_count += num_samples;
 425
 426        if (cmd->stop_src == TRIG_COUNT) {
 427                if (devpriv->adc_count >= cmd->stop_arg * cmd->chanlist_len) {
 428                        /* end of acquisition */
 429                        async->events |= COMEDI_CB_EOA;
 430                }
 431        }
 432
 433        /*
 434         * This probably won't catch overruns since the card doesn't generate
 435         * overrun interrupts, but we might as well try.
 436         */
 437        if (status & DAS16M1_CS_OVRUN) {
 438                async->events |= COMEDI_CB_ERROR;
 439                dev_err(dev->class_dev, "fifo overflow\n");
 440        }
 441
 442        comedi_handle_events(dev, s);
 443}
 444
 445static int das16m1_ai_poll(struct comedi_device *dev,
 446                           struct comedi_subdevice *s)
 447{
 448        unsigned long flags;
 449        unsigned int status;
 450
 451        /*  prevent race with interrupt handler */
 452        spin_lock_irqsave(&dev->spinlock, flags);
 453        status = inb(dev->iobase + DAS16M1_CS_REG);
 454        das16m1_handler(dev, status);
 455        spin_unlock_irqrestore(&dev->spinlock, flags);
 456
 457        return comedi_buf_n_bytes_ready(s);
 458}
 459
 460static irqreturn_t das16m1_interrupt(int irq, void *d)
 461{
 462        int status;
 463        struct comedi_device *dev = d;
 464
 465        if (!dev->attached) {
 466                dev_err(dev->class_dev, "premature interrupt\n");
 467                return IRQ_HANDLED;
 468        }
 469        /*  prevent race with comedi_poll() */
 470        spin_lock(&dev->spinlock);
 471
 472        status = inb(dev->iobase + DAS16M1_CS_REG);
 473
 474        if ((status & (DAS16M1_CS_IRQDATA | DAS16M1_CS_OVRUN)) == 0) {
 475                dev_err(dev->class_dev, "spurious interrupt\n");
 476                spin_unlock(&dev->spinlock);
 477                return IRQ_NONE;
 478        }
 479
 480        das16m1_handler(dev, status);
 481
 482        /* clear interrupt */
 483        outb(0, dev->iobase + DAS16M1_CLR_INTR_REG);
 484
 485        spin_unlock(&dev->spinlock);
 486        return IRQ_HANDLED;
 487}
 488
 489static int das16m1_irq_bits(unsigned int irq)
 490{
 491        switch (irq) {
 492        case 10:
 493                return 0x0;
 494        case 11:
 495                return 0x1;
 496        case 12:
 497                return 0x2;
 498        case 15:
 499                return 0x3;
 500        case 2:
 501                return 0x4;
 502        case 3:
 503                return 0x5;
 504        case 5:
 505                return 0x6;
 506        case 7:
 507                return 0x7;
 508        default:
 509                return 0x0;
 510        }
 511}
 512
 513static int das16m1_attach(struct comedi_device *dev,
 514                          struct comedi_devconfig *it)
 515{
 516        struct das16m1_private *devpriv;
 517        struct comedi_subdevice *s;
 518        int ret;
 519
 520        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 521        if (!devpriv)
 522                return -ENOMEM;
 523
 524        ret = comedi_request_region(dev, it->options[0], 0x10);
 525        if (ret)
 526                return ret;
 527        /* Request an additional region for the 8255 and 3rd 8254 */
 528        ret = __comedi_request_region(dev, dev->iobase + DAS16M1_8255_IOBASE,
 529                                      DAS16M1_SIZE2);
 530        if (ret)
 531                return ret;
 532        devpriv->extra_iobase = dev->iobase + DAS16M1_8255_IOBASE;
 533
 534        /* only irqs 2, 3, 4, 5, 6, 7, 10, 11, 12, 14, and 15 are valid */
 535        if ((1 << it->options[1]) & 0xdcfc) {
 536                ret = request_irq(it->options[1], das16m1_interrupt, 0,
 537                                  dev->board_name, dev);
 538                if (ret == 0)
 539                        dev->irq = it->options[1];
 540        }
 541
 542        dev->pacer = comedi_8254_init(dev->iobase + DAS16M1_8254_IOBASE2,
 543                                      I8254_OSC_BASE_10MHZ, I8254_IO8, 0);
 544        if (!dev->pacer)
 545                return -ENOMEM;
 546
 547        devpriv->counter = comedi_8254_init(dev->iobase + DAS16M1_8254_IOBASE1,
 548                                            0, I8254_IO8, 0);
 549        if (!devpriv->counter)
 550                return -ENOMEM;
 551
 552        ret = comedi_alloc_subdevices(dev, 4);
 553        if (ret)
 554                return ret;
 555
 556        /* Analog Input subdevice */
 557        s = &dev->subdevices[0];
 558        s->type         = COMEDI_SUBD_AI;
 559        s->subdev_flags = SDF_READABLE | SDF_DIFF;
 560        s->n_chan       = 8;
 561        s->maxdata      = 0x0fff;
 562        s->range_table  = &range_das16m1;
 563        s->insn_read    = das16m1_ai_insn_read;
 564        if (dev->irq) {
 565                dev->read_subdev = s;
 566                s->subdev_flags |= SDF_CMD_READ;
 567                s->len_chanlist = 256;
 568                s->do_cmdtest   = das16m1_ai_cmdtest;
 569                s->do_cmd       = das16m1_ai_cmd;
 570                s->cancel       = das16m1_ai_cancel;
 571                s->poll         = das16m1_ai_poll;
 572                s->munge        = das16m1_ai_munge;
 573        }
 574
 575        /* Digital Input subdevice */
 576        s = &dev->subdevices[1];
 577        s->type         = COMEDI_SUBD_DI;
 578        s->subdev_flags = SDF_READABLE;
 579        s->n_chan       = 4;
 580        s->maxdata      = 1;
 581        s->range_table  = &range_digital;
 582        s->insn_bits    = das16m1_di_insn_bits;
 583
 584        /* Digital Output subdevice */
 585        s = &dev->subdevices[2];
 586        s->type         = COMEDI_SUBD_DO;
 587        s->subdev_flags = SDF_WRITABLE;
 588        s->n_chan       = 4;
 589        s->maxdata      = 1;
 590        s->range_table  = &range_digital;
 591        s->insn_bits    = das16m1_do_insn_bits;
 592
 593        /* Digital I/O subdevice (8255) */
 594        s = &dev->subdevices[3];
 595        ret = subdev_8255_init(dev, s, NULL, DAS16M1_8255_IOBASE);
 596        if (ret)
 597                return ret;
 598
 599        /*  initialize digital output lines */
 600        outb(0, dev->iobase + DAS16M1_DO_REG);
 601
 602        /* set the interrupt level */
 603        devpriv->intr_ctrl = DAS16M1_INTR_CTRL_IRQ(das16m1_irq_bits(dev->irq));
 604        outb(devpriv->intr_ctrl, dev->iobase + DAS16M1_INTR_CTRL_REG);
 605
 606        return 0;
 607}
 608
 609static void das16m1_detach(struct comedi_device *dev)
 610{
 611        struct das16m1_private *devpriv = dev->private;
 612
 613        if (devpriv) {
 614                if (devpriv->extra_iobase)
 615                        release_region(devpriv->extra_iobase, DAS16M1_SIZE2);
 616                kfree(devpriv->counter);
 617        }
 618        comedi_legacy_detach(dev);
 619}
 620
 621static struct comedi_driver das16m1_driver = {
 622        .driver_name    = "das16m1",
 623        .module         = THIS_MODULE,
 624        .attach         = das16m1_attach,
 625        .detach         = das16m1_detach,
 626};
 627module_comedi_driver(das16m1_driver);
 628
 629MODULE_AUTHOR("Comedi http://www.comedi.org");
 630MODULE_DESCRIPTION("Comedi driver for CIO-DAS16/M1 ISA cards");
 631MODULE_LICENSE("GPL");
 632