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