root/drivers/staging/comedi/drivers/das16m1.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. das16m1_ai_set_queue
  2. das16m1_ai_munge
  3. das16m1_ai_check_chanlist
  4. das16m1_ai_cmdtest
  5. das16m1_ai_cmd
  6. das16m1_ai_cancel
  7. das16m1_ai_eoc
  8. das16m1_ai_insn_read
  9. das16m1_di_insn_bits
  10. das16m1_do_insn_bits
  11. das16m1_handler
  12. das16m1_ai_poll
  13. das16m1_interrupt
  14. das16m1_irq_bits
  15. das16m1_attach
  16. das16m1_detach

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

/* [<][>][^][v][top][bottom][index][help] */