Merge tag 'metag-for-v3.13' of git://git.kernel.org/pub/scm/linux/kernel/git/jhogan...
[cascardo/linux.git] / drivers / staging / comedi / drivers / pcl711.c
index 3a2e5f1..f0fc123 100644 (file)
@@ -1,63 +1,47 @@
 /*
  comedi/drivers/pcl711.c
-   hardware driver for PC-LabCard PCL-711 and AdSys ACL-8112
-   and compatibles
-
-   COMEDI - Linux Control and Measurement Device Interface
-   Copyright (C) 1998 David A. Schleef <ds@schleef.org>
-   Janne Jalkanen <jalkanen@cs.hut.fi>
  Eric Bunn <ebu@cs.hut.fi>
-
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
-
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
pcl711.c
+ * Comedi driver for PC-LabCard PCL-711 and AdSys ACL-8112 and compatibles
+ * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *                   Janne Jalkanen <jalkanen@cs.hut.fi>
+ *                   Eric Bunn <ebu@cs.hut.fi>
+ *
+ * COMEDI - Linux Control and Measurement Device Interface
* Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+ *
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
  */
-/*
-Driver: pcl711
-Description: Advantech PCL-711 and 711b, ADLink ACL-8112
-Author: ds, Janne Jalkanen <jalkanen@cs.hut.fi>, Eric Bunn <ebu@cs.hut.fi>
-Status: mostly complete
-Devices: [Advantech] PCL-711 (pcl711), PCL-711B (pcl711b),
-  [AdLink] ACL-8112HG (acl8112hg), ACL-8112DG (acl8112dg)
-
-Since these boards do not have DMA or FIFOs, only immediate mode is
-supported.
-
-*/
 
 /*
-   Dave Andruczyk <dave@tech.buffalostate.edu> also wrote a
-   driver for the PCL-711.  I used a few ideas from his driver
-   here.  His driver also has more comments, if you are
-   interested in understanding how this driver works.
-   http://tech.buffalostate.edu/~dave/driver/
-
-   The ACL-8112 driver was hacked from the sources of the PCL-711
-   driver (the 744 chip used on the 8112 is almost the same as
-   the 711b chip, but it has more I/O channels) by
-   Janne Jalkanen (jalkanen@cs.hut.fi) and
-   Erik Bunn (ebu@cs.hut.fi).  Remerged with the PCL-711 driver
-   by ds.
-
-   [acl-8112]
-   This driver supports both TRIGNOW and TRIGCLK,
-   but does not yet support DMA transfers.  It also supports
-   both high (HG) and low (DG) versions of the card, though
-   the HG version has been untested.
-
+ * Driver: pcl711
+ * Description: Advantech PCL-711 and 711b, ADLink ACL-8112
+ * Devices: (Advantech) PCL-711 [pcl711]
+ *         (Advantech) PCL-711B [pcl711b]
+ *         (AdLink) ACL-8112HG [acl8112hg]
+ *         (AdLink) ACL-8112DG [acl8112dg]
+ * Author: David A. Schleef <ds@schleef.org>
+ *        Janne Jalkanen <jalkanen@cs.hut.fi>
+ *        Eric Bunn <ebu@cs.hut.fi>
+ * Updated:
+ * Status: mostly complete
+ *
+ * Configuration Options:
+ *   [0] - I/O port base
+ *   [1] - IRQ, optional
  */
 
 #include <linux/module.h>
+#include <linux/delay.h>
 #include <linux/interrupt.h>
-#include "../comedidev.h"
 
-#include <linux/delay.h>
+#include "../comedidev.h"
 
 #include "comedi_fc.h"
 #include "8253.h"
@@ -65,23 +49,35 @@ supported.
 /*
  * I/O port register map
  */
-#define PCL711_CTR0            0x00
-#define PCL711_CTR1            0x01
-#define PCL711_CTR2            0x02
-#define PCL711_CTRCTL          0x03
-#define PCL711_AD_LO           0x04
-#define PCL711_AD_HI           0x05
+#define PCL711_TIMER_BASE      0x00
+#define PCL711_AI_LSB_REG      0x04
+#define PCL711_AI_MSB_REG      0x05
+#define PCL711_AI_MSB_DRDY     (1 << 4)
 #define PCL711_AO_LSB_REG(x)   (0x04 + ((x) * 2))
 #define PCL711_AO_MSB_REG(x)   (0x05 + ((x) * 2))
-#define PCL711_DI_LO           0x06
-#define PCL711_DI_HI           0x07
-#define PCL711_CLRINTR         0x08
-#define PCL711_GAIN            0x09
-#define PCL711_MUX             0x0a
-#define PCL711_MODE            0x0b
-#define PCL711_SOFTTRIG                0x0c
-#define PCL711_DO_LO           0x0d
-#define PCL711_DO_HI           0x0e
+#define PCL711_DI_LSB_REG      0x06
+#define PCL711_DI_MSB_REG      0x07
+#define PCL711_INT_STAT_REG    0x08
+#define PCL711_INT_STAT_CLR    (0 << 0)  /* any value will work */
+#define PCL711_AI_GAIN_REG     0x09
+#define PCL711_AI_GAIN(x)      (((x) & 0xf) << 0)
+#define PCL711_MUX_REG         0x0a
+#define PCL711_MUX_CHAN(x)     (((x) & 0xf) << 0)
+#define PCL711_MUX_CS0         (1 << 4)
+#define PCL711_MUX_CS1         (1 << 5)
+#define PCL711_MUX_DIFF                (PCL711_MUX_CS0 | PCL711_MUX_CS1)
+#define PCL711_MODE_REG                0x0b
+#define PCL711_MODE_DEFAULT    (0 << 0)
+#define PCL711_MODE_SOFTTRIG   (1 << 0)
+#define PCL711_MODE_EXT                (2 << 0)
+#define PCL711_MODE_EXT_IRQ    (3 << 0)
+#define PCL711_MODE_PACER      (4 << 0)
+#define PCL711_MODE_PACER_IRQ  (6 << 0)
+#define PCL711_MODE_IRQ(x)     (((x) & 0x7) << 4)
+#define PCL711_SOFTTRIG_REG    0x0c
+#define PCL711_SOFTTRIG                (0 << 0)  /* any value will work */
+#define PCL711_DO_LSB_REG      0x0d
+#define PCL711_DO_MSB_REG      0x0e
 
 static const struct comedi_lrange range_pcl711b_ai = {
        5, {
@@ -124,19 +120,8 @@ static const struct comedi_lrange range_acl8112dg_ai = {
        }
 };
 
-/*
- * flags
- */
-
-#define PCL711_TIMEOUT 100
-#define PCL711_DRDY 0x10
-
-static const int i8253_osc_base = 500; /* 2 Mhz */
-
 struct pcl711_board {
        const char *name;
-       unsigned int is_pcl711b:1;
-       unsigned int is_8112:1;
        int n_aichan;
        int n_aochan;
        int maxirq;
@@ -151,21 +136,18 @@ static const struct pcl711_board boardtypes[] = {
                .ai_range_type  = &range_bipolar5,
        }, {
                .name           = "pcl711b",
-               .is_pcl711b     = 1,
                .n_aichan       = 8,
                .n_aochan       = 1,
                .maxirq         = 7,
                .ai_range_type  = &range_pcl711b_ai,
        }, {
                .name           = "acl8112hg",
-               .is_8112        = 1,
                .n_aichan       = 16,
                .n_aochan       = 2,
                .maxirq         = 15,
                .ai_range_type  = &range_acl8112hg_ai,
        }, {
                .name           = "acl8112dg",
-               .is_8112        = 1,
                .n_aichan       = 16,
                .n_aochan       = 2,
                .maxirq         = 15,
@@ -174,114 +156,140 @@ static const struct pcl711_board boardtypes[] = {
 };
 
 struct pcl711_private {
-
-       int board;
-       int adchan;
-       int ntrig;
-       int aip[8];
-       int mode;
+       unsigned int ntrig;
        unsigned int ao_readback[2];
        unsigned int divisor1;
        unsigned int divisor2;
 };
 
+static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
+{
+       /*
+        * The pcl711b board uses bits in the mode register to select the
+        * interrupt. The other boards supported by this driver all use
+        * jumpers on the board.
+        *
+        * Enables the interrupt when needed on the pcl711b board. These
+        * bits do nothing on the other boards.
+        */
+       if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
+               mode |= PCL711_MODE_IRQ(dev->irq);
+
+       outb(mode, dev->iobase + PCL711_MODE_REG);
+}
+
+static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
+                                        struct comedi_subdevice *s)
+{
+       unsigned int val;
+
+       val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
+       val |= inb(dev->iobase + PCL711_AI_LSB_REG);
+
+       return val & s->maxdata;
+}
+
+static int pcl711_ai_cancel(struct comedi_device *dev,
+                           struct comedi_subdevice *s)
+{
+       outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
+       pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
+       return 0;
+}
+
 static irqreturn_t pcl711_interrupt(int irq, void *d)
 {
-       int lo, hi;
-       int data;
        struct comedi_device *dev = d;
-       const struct pcl711_board *board = comedi_board(dev);
        struct pcl711_private *devpriv = dev->private;
-       struct comedi_subdevice *s = &dev->subdevices[0];
+       struct comedi_subdevice *s = dev->read_subdev;
+       unsigned int data;
 
        if (!dev->attached) {
                comedi_error(dev, "spurious interrupt");
                return IRQ_HANDLED;
        }
 
-       hi = inb(dev->iobase + PCL711_AD_HI);
-       lo = inb(dev->iobase + PCL711_AD_LO);
-       outb(0, dev->iobase + PCL711_CLRINTR);
+       data = pcl711_ai_get_sample(dev, s);
 
-       data = (hi << 8) | lo;
+       outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
 
-       /* FIXME! Nothing else sets ntrig! */
-       if (!(--devpriv->ntrig)) {
-               if (board->is_8112)
-                       outb(1, dev->iobase + PCL711_MODE);
-               else
-                       outb(0, dev->iobase + PCL711_MODE);
-
-               s->async->events |= COMEDI_CB_EOA;
+       if (comedi_buf_put(s->async, data) == 0) {
+               s->async->events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR;
+       } else {
+               s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
+               if (s->async->cmd.stop_src == TRIG_COUNT &&
+                   !(--devpriv->ntrig)) {
+                       pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
+                       s->async->events |= COMEDI_CB_EOA;
+               }
        }
        comedi_event(dev, s);
        return IRQ_HANDLED;
 }
 
-static void pcl711_set_changain(struct comedi_device *dev, int chan)
+static void pcl711_set_changain(struct comedi_device *dev,
+                               struct comedi_subdevice *s,
+                               unsigned int chanspec)
 {
-       const struct pcl711_board *board = comedi_board(dev);
-       int chan_register;
-
-       outb(CR_RANGE(chan), dev->iobase + PCL711_GAIN);
-
-       chan_register = CR_CHAN(chan);
-
-       if (board->is_8112) {
+       unsigned int chan = CR_CHAN(chanspec);
+       unsigned int range = CR_RANGE(chanspec);
+       unsigned int aref = CR_AREF(chanspec);
+       unsigned int mux = 0;
+
+       outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG);
+
+       if (s->n_chan > 8) {
+               /* Select the correct MPC508A chip */
+               if (aref == AREF_DIFF) {
+                       chan &= 0x7;
+                       mux |= PCL711_MUX_DIFF;
+               } else {
+                       if (chan < 8)
+                               mux |= PCL711_MUX_CS0;
+                       else
+                               mux |= PCL711_MUX_CS1;
+               }
+       }
+       outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
+}
 
-               /*
-                *  Set the correct channel.  The two channel banks are switched
-                *  using the mask value.
-                *  NB: To use differential channels, you should use
-                *  mask = 0x30, but I haven't written the support for this
-                *  yet. /JJ
-                */
+static int pcl711_ai_wait_for_eoc(struct comedi_device *dev,
+                                 unsigned int timeout)
+{
+       unsigned int msb;
 
-               if (chan_register >= 8)
-                       chan_register = 0x20 | (chan_register & 0x7);
-               else
-                       chan_register |= 0x10;
-       } else {
-               outb(chan_register, dev->iobase + PCL711_MUX);
+       while (timeout--) {
+               msb = inb(dev->iobase + PCL711_AI_MSB_REG);
+               if ((msb & PCL711_AI_MSB_DRDY) == 0)
+                       return 0;
+               udelay(1);
        }
+       return -ETIME;
 }
 
-static int pcl711_ai_insn(struct comedi_device *dev, struct comedi_subdevice *s,
-                         struct comedi_insn *insn, unsigned int *data)
+static int pcl711_ai_insn_read(struct comedi_device *dev,
+                              struct comedi_subdevice *s,
+                              struct comedi_insn *insn,
+                              unsigned int *data)
 {
-       const struct pcl711_board *board = comedi_board(dev);
-       int i, n;
-       int hi, lo;
-
-       pcl711_set_changain(dev, insn->chanspec);
-
-       for (n = 0; n < insn->n; n++) {
-               /*
-                *  Write the correct mode (software polling) and start polling
-                *  by writing to the trigger register
-                */
-               outb(1, dev->iobase + PCL711_MODE);
-
-               if (!board->is_8112)
-                       outb(0, dev->iobase + PCL711_SOFTTRIG);
-
-               i = PCL711_TIMEOUT;
-               while (--i) {
-                       hi = inb(dev->iobase + PCL711_AD_HI);
-                       if (!(hi & PCL711_DRDY))
-                               goto ok;
-                       udelay(1);
-               }
-               printk(KERN_ERR "comedi%d: pcl711: A/D timeout\n", dev->minor);
-               return -ETIME;
+       int ret;
+       int i;
+
+       pcl711_set_changain(dev, s, insn->chanspec);
+
+       pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
 
-ok:
-               lo = inb(dev->iobase + PCL711_AD_LO);
+       for (i = 0; i < insn->n; i++) {
+               outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
+
+               ret = pcl711_ai_wait_for_eoc(dev, 100);
+               if (ret)
+                       return ret;
 
-               data[n] = ((hi & 0xf) << 8) | lo;
+               data[i] = pcl711_ai_get_sample(dev, s);
        }
 
-       return n;
+       return insn->n;
 }
 
 static int pcl711_ai_cmdtest(struct comedi_device *dev,
@@ -321,7 +329,6 @@ static int pcl711_ai_cmdtest(struct comedi_device *dev,
                err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
        } else {
 #define MAX_SPEED 1000
-#define TIMER_BASE 100
                err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
                                                 MAX_SPEED);
        }
@@ -342,11 +349,11 @@ static int pcl711_ai_cmdtest(struct comedi_device *dev,
 
        if (cmd->scan_begin_src == TRIG_TIMER) {
                tmp = cmd->scan_begin_arg;
-               i8253_cascade_ns_to_timer_2div(TIMER_BASE,
-                                              &devpriv->divisor1,
-                                              &devpriv->divisor2,
-                                              &cmd->scan_begin_arg,
-                                              cmd->flags & TRIG_ROUND_MASK);
+               i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ,
+                                         &devpriv->divisor1,
+                                         &devpriv->divisor2,
+                                         &cmd->scan_begin_arg,
+                                         cmd->flags);
                if (tmp != cmd->scan_begin_arg)
                        err++;
        }
@@ -360,44 +367,31 @@ static int pcl711_ai_cmdtest(struct comedi_device *dev,
 static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
 {
        struct pcl711_private *devpriv = dev->private;
-       int timer1, timer2;
        struct comedi_cmd *cmd = &s->async->cmd;
 
-       pcl711_set_changain(dev, cmd->chanlist[0]);
+       pcl711_set_changain(dev, s, cmd->chanlist[0]);
+
+       if (cmd->stop_src == TRIG_COUNT) {
+               if (cmd->stop_arg == 0) {
+                       /* an empty acquisition */
+                       s->async->events |= COMEDI_CB_EOA;
+                       comedi_event(dev, s);
+                       return 0;
+               }
+               devpriv->ntrig = cmd->stop_arg;
+       }
 
        if (cmd->scan_begin_src == TRIG_TIMER) {
-               /*
-                *  Set timers
-                *      timer chip is an 8253, with timers 1 and 2
-                *      cascaded
-                *  0x74 = Select Counter 1 | LSB/MSB | Mode=2 | Binary
-                *        Mode 2 = Rate generator
-                *
-                *  0xb4 = Select Counter 2 | LSB/MSB | Mode=2 | Binary
-                */
-
-               timer1 = timer2 = 0;
-               i8253_cascade_ns_to_timer(i8253_osc_base, &timer1, &timer2,
-                                         &cmd->scan_begin_arg,
-                                         TRIG_ROUND_NEAREST);
-
-               outb(0x74, dev->iobase + PCL711_CTRCTL);
-               outb(timer1 & 0xff, dev->iobase + PCL711_CTR1);
-               outb((timer1 >> 8) & 0xff, dev->iobase + PCL711_CTR1);
-               outb(0xb4, dev->iobase + PCL711_CTRCTL);
-               outb(timer2 & 0xff, dev->iobase + PCL711_CTR2);
-               outb((timer2 >> 8) & 0xff, dev->iobase + PCL711_CTR2);
-
-               /* clear pending interrupts (just in case) */
-               outb(0, dev->iobase + PCL711_CLRINTR);
-
-               /*
-                *  Set mode to IRQ transfer
-                */
-               outb(devpriv->mode | 6, dev->iobase + PCL711_MODE);
+               i8254_load(dev->iobase + PCL711_TIMER_BASE, 0,
+                          1, devpriv->divisor1, I8254_MODE2 | I8254_BINARY);
+               i8254_load(dev->iobase + PCL711_TIMER_BASE, 0,
+                          2, devpriv->divisor2, I8254_MODE2 | I8254_BINARY);
+
+               outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
+
+               pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
        } else {
-               /* external trigger */
-               outb(devpriv->mode | 3, dev->iobase + PCL711_MODE);
+               pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
        }
 
        return 0;
@@ -444,13 +438,17 @@ static int pcl711_ao_insn_read(struct comedi_device *dev,
        return insn->n;
 }
 
-/* Digital port read - Untested on 8112 */
 static int pcl711_di_insn_bits(struct comedi_device *dev,
                               struct comedi_subdevice *s,
-                              struct comedi_insn *insn, unsigned int *data)
+                              struct comedi_insn *insn,
+                              unsigned int *data)
 {
-       data[1] = inb(dev->iobase + PCL711_DI_LO) |
-           (inb(dev->iobase + PCL711_DI_HI) << 8);
+       unsigned int val;
+
+       val = inb(dev->iobase + PCL711_DI_LSB_REG);
+       val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8);
+
+       data[1] = val;
 
        return insn->n;
 }
@@ -465,9 +463,9 @@ static int pcl711_do_insn_bits(struct comedi_device *dev,
        mask = comedi_dio_update_state(s, data);
        if (mask) {
                if (mask & 0x00ff)
-                       outb(s->state & 0xff, dev->iobase + PCL711_DO_LO);
+                       outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG);
                if (mask & 0xff00)
-                       outb((s->state >> 8), dev->iobase + PCL711_DO_HI);
+                       outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG);
        }
 
        data[1] = s->state;
@@ -493,16 +491,8 @@ static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
        if (it->options[1] && it->options[1] <= board->maxirq) {
                ret = request_irq(it->options[1], pcl711_interrupt, 0,
                                  dev->board_name, dev);
-               if (ret == 0) {
+               if (ret == 0)
                        dev->irq = it->options[1];
-
-                       /*
-                        * The PCL711b needs the irq number in the
-                        * mode register.
-                        */
-                       if (board->is_pcl711b)
-                               devpriv->mode = (dev->irq << 4);
-               }
        }
 
        ret = comedi_alloc_subdevices(dev, 4);
@@ -513,16 +503,19 @@ static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
        s = &dev->subdevices[0];
        s->type         = COMEDI_SUBD_AI;
        s->subdev_flags = SDF_READABLE | SDF_GROUND;
+       if (board->n_aichan > 8)
+               s->subdev_flags |= SDF_DIFF;
        s->n_chan       = board->n_aichan;
        s->maxdata      = 0xfff;
        s->range_table  = board->ai_range_type;
-       s->insn_read    = pcl711_ai_insn;
+       s->insn_read    = pcl711_ai_insn_read;
        if (dev->irq) {
                dev->read_subdev = s;
                s->subdev_flags |= SDF_CMD_READ;
                s->len_chanlist = 1;
                s->do_cmdtest   = pcl711_ai_cmdtest;
                s->do_cmd       = pcl711_ai_cmd;
+               s->cancel       = pcl711_ai_cancel;
        }
 
        /* Analog Output subdevice */
@@ -572,5 +565,5 @@ static struct comedi_driver pcl711_driver = {
 module_comedi_driver(pcl711_driver);
 
 MODULE_AUTHOR("Comedi http://www.comedi.org");
-MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards");
 MODULE_LICENSE("GPL");