Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / staging / comedi / drivers / adv_pci1710.c
diff --git a/kernel/drivers/staging/comedi/drivers/adv_pci1710.c b/kernel/drivers/staging/comedi/drivers/adv_pci1710.c
new file mode 100644 (file)
index 0000000..0c6aa96
--- /dev/null
@@ -0,0 +1,1100 @@
+/*
+ * comedi/drivers/adv_pci1710.c
+ *
+ * Author: Michal Dobes <dobes@tesnet.cz>
+ *
+ * Thanks to ZhenGang Shang <ZhenGang.Shang@Advantech.com.cn>
+ * for testing and information.
+ *
+ *  hardware driver for Advantech cards:
+ *   card:   PCI-1710, PCI-1710HG, PCI-1711, PCI-1713, PCI-1720, PCI-1731
+ *   driver: pci1710,  pci1710hg,  pci1711,  pci1713,  pci1720,  pci1731
+ *
+ * Options:
+ *  [0] - PCI bus number - if bus number and slot number are 0,
+ *                         then driver search for first unused card
+ *  [1] - PCI slot number
+ *
+*/
+/*
+Driver: adv_pci1710
+Description: Advantech PCI-1710, PCI-1710HG, PCI-1711, PCI-1713,
+            Advantech PCI-1720, PCI-1731
+Author: Michal Dobes <dobes@tesnet.cz>
+Devices: [Advantech] PCI-1710 (adv_pci1710), PCI-1710HG (pci1710hg),
+  PCI-1711 (adv_pci1710), PCI-1713, PCI-1720,
+  PCI-1731
+Status: works
+
+This driver supports AI, AO, DI and DO subdevices.
+AI subdevice supports cmd and insn interface,
+other subdevices support only insn interface.
+
+The PCI-1710 and PCI-1710HG have the same PCI device ID, so the
+driver cannot distinguish between them, as would be normal for a
+PCI driver.
+
+Configuration options:
+  [0] - PCI bus of device (optional)
+  [1] - PCI slot of device (optional)
+       If bus/slot is not specified, the first available PCI
+       device will be used.
+*/
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+
+#include "../comedi_pci.h"
+
+#include "comedi_8254.h"
+#include "amcc_s5933.h"
+
+#define PCI171x_AD_DATA         0      /* R:   A/D data */
+#define PCI171x_SOFTTRG         0      /* W:   soft trigger for A/D */
+#define PCI171x_RANGE   2      /* W:   A/D gain/range register */
+#define PCI171x_MUX     4      /* W:   A/D multiplexor control */
+#define PCI171x_STATUS  6      /* R:   status register */
+#define PCI171x_CONTROL         6      /* W:   control register */
+#define PCI171x_CLRINT  8      /* W:   clear interrupts request */
+#define PCI171x_CLRFIFO         9      /* W:   clear FIFO */
+#define PCI171x_DA1    10      /* W:   D/A register */
+#define PCI171x_DA2    12      /* W:   D/A register */
+#define PCI171x_DAREF  14      /* W:   D/A reference control */
+#define PCI171x_DI     16      /* R:   digi inputs */
+#define PCI171x_DO     16      /* R:   digi inputs */
+
+#define PCI171X_TIMER_BASE     0x18
+
+/* upper bits from status register (PCI171x_STATUS) (lower is same with control
+ * reg) */
+#define        Status_FE       0x0100  /* 1=FIFO is empty */
+#define Status_FH      0x0200  /* 1=FIFO is half full */
+#define Status_FF      0x0400  /* 1=FIFO is full, fatal error */
+#define Status_IRQ     0x0800  /* 1=IRQ occurred */
+/* bits from control register (PCI171x_CONTROL) */
+#define Control_CNT0   0x0040  /* 1=CNT0 have external source,
+                                * 0=have internal 100kHz source */
+#define Control_ONEFH  0x0020  /* 1=IRQ on FIFO is half full, 0=every sample */
+#define Control_IRQEN  0x0010  /* 1=enable IRQ */
+#define Control_GATE   0x0008  /* 1=enable external trigger GATE (8254?) */
+#define Control_EXT    0x0004  /* 1=external trigger source */
+#define Control_PACER  0x0002  /* 1=enable internal 8254 trigger source */
+#define Control_SW     0x0001  /* 1=enable software trigger source */
+
+#define PCI1720_DA0     0      /* W:   D/A register 0 */
+#define PCI1720_DA1     2      /* W:   D/A register 1 */
+#define PCI1720_DA2     4      /* W:   D/A register 2 */
+#define PCI1720_DA3     6      /* W:   D/A register 3 */
+#define PCI1720_RANGE   8      /* R/W: D/A range register */
+#define PCI1720_SYNCOUT         9      /* W:   D/A synchronized output register */
+#define PCI1720_SYNCONT        15      /* R/W: D/A synchronized control */
+
+/* D/A synchronized control (PCI1720_SYNCONT) */
+#define Syncont_SC0     1      /* set synchronous output mode */
+
+static const struct comedi_lrange range_pci1710_3 = {
+       9, {
+               BIP_RANGE(5),
+               BIP_RANGE(2.5),
+               BIP_RANGE(1.25),
+               BIP_RANGE(0.625),
+               BIP_RANGE(10),
+               UNI_RANGE(10),
+               UNI_RANGE(5),
+               UNI_RANGE(2.5),
+               UNI_RANGE(1.25)
+       }
+};
+
+static const char range_codes_pci1710_3[] = { 0x00, 0x01, 0x02, 0x03, 0x04,
+                                             0x10, 0x11, 0x12, 0x13 };
+
+static const struct comedi_lrange range_pci1710hg = {
+       12, {
+               BIP_RANGE(5),
+               BIP_RANGE(0.5),
+               BIP_RANGE(0.05),
+               BIP_RANGE(0.005),
+               BIP_RANGE(10),
+               BIP_RANGE(1),
+               BIP_RANGE(0.1),
+               BIP_RANGE(0.01),
+               UNI_RANGE(10),
+               UNI_RANGE(1),
+               UNI_RANGE(0.1),
+               UNI_RANGE(0.01)
+       }
+};
+
+static const char range_codes_pci1710hg[] = { 0x00, 0x01, 0x02, 0x03, 0x04,
+                                             0x05, 0x06, 0x07, 0x10, 0x11,
+                                             0x12, 0x13 };
+
+static const struct comedi_lrange range_pci17x1 = {
+       5, {
+               BIP_RANGE(10),
+               BIP_RANGE(5),
+               BIP_RANGE(2.5),
+               BIP_RANGE(1.25),
+               BIP_RANGE(0.625)
+       }
+};
+
+static const char range_codes_pci17x1[] = { 0x00, 0x01, 0x02, 0x03, 0x04 };
+
+static const struct comedi_lrange pci1720_ao_range = {
+       4, {
+               UNI_RANGE(5),
+               UNI_RANGE(10),
+               BIP_RANGE(5),
+               BIP_RANGE(10)
+       }
+};
+
+static const struct comedi_lrange pci171x_ao_range = {
+       2, {
+               UNI_RANGE(5),
+               UNI_RANGE(10)
+       }
+};
+
+enum pci1710_boardid {
+       BOARD_PCI1710,
+       BOARD_PCI1710HG,
+       BOARD_PCI1711,
+       BOARD_PCI1713,
+       BOARD_PCI1720,
+       BOARD_PCI1731,
+};
+
+struct boardtype {
+       const char *name;       /*  board name */
+       int n_aichan;           /*  num of A/D chans */
+       const struct comedi_lrange *rangelist_ai;       /*  rangelist for A/D */
+       const char *rangecode_ai;       /*  range codes for programming */
+       unsigned int is_pci1713:1;
+       unsigned int is_pci1720:1;
+       unsigned int has_irq:1;
+       unsigned int has_large_fifo:1;  /* 4K or 1K FIFO */
+       unsigned int has_diff_ai:1;
+       unsigned int has_ao:1;
+       unsigned int has_di_do:1;
+       unsigned int has_counter:1;
+};
+
+static const struct boardtype boardtypes[] = {
+       [BOARD_PCI1710] = {
+               .name           = "pci1710",
+               .n_aichan       = 16,
+               .rangelist_ai   = &range_pci1710_3,
+               .rangecode_ai   = range_codes_pci1710_3,
+               .has_irq        = 1,
+               .has_large_fifo = 1,
+               .has_diff_ai    = 1,
+               .has_ao         = 1,
+               .has_di_do      = 1,
+               .has_counter    = 1,
+       },
+       [BOARD_PCI1710HG] = {
+               .name           = "pci1710hg",
+               .n_aichan       = 16,
+               .rangelist_ai   = &range_pci1710hg,
+               .rangecode_ai   = range_codes_pci1710hg,
+               .has_irq        = 1,
+               .has_large_fifo = 1,
+               .has_diff_ai    = 1,
+               .has_ao         = 1,
+               .has_di_do      = 1,
+               .has_counter    = 1,
+       },
+       [BOARD_PCI1711] = {
+               .name           = "pci1711",
+               .n_aichan       = 16,
+               .rangelist_ai   = &range_pci17x1,
+               .rangecode_ai   = range_codes_pci17x1,
+               .has_irq        = 1,
+               .has_ao         = 1,
+               .has_di_do      = 1,
+               .has_counter    = 1,
+       },
+       [BOARD_PCI1713] = {
+               .name           = "pci1713",
+               .n_aichan       = 32,
+               .rangelist_ai   = &range_pci1710_3,
+               .rangecode_ai   = range_codes_pci1710_3,
+               .is_pci1713     = 1,
+               .has_irq        = 1,
+               .has_large_fifo = 1,
+               .has_diff_ai    = 1,
+       },
+       [BOARD_PCI1720] = {
+               .name           = "pci1720",
+               .is_pci1720     = 1,
+               .has_ao         = 1,
+       },
+       [BOARD_PCI1731] = {
+               .name           = "pci1731",
+               .n_aichan       = 16,
+               .rangelist_ai   = &range_pci17x1,
+               .rangecode_ai   = range_codes_pci17x1,
+               .has_irq        = 1,
+               .has_di_do      = 1,
+       },
+};
+
+struct pci1710_private {
+       unsigned int max_samples;
+       unsigned int CntrlReg;  /*  Control register */
+       unsigned char ai_et;
+       unsigned int ai_et_CntrlReg;
+       unsigned int ai_et_MuxVal;
+       unsigned int act_chanlist[32];  /*  list of scanned channel */
+       unsigned char saved_seglen;     /* len of the non-repeating chanlist */
+       unsigned char da_ranges;        /*  copy of D/A outpit range register */
+};
+
+static int pci171x_ai_check_chanlist(struct comedi_device *dev,
+                                    struct comedi_subdevice *s,
+                                    struct comedi_cmd *cmd)
+{
+       struct pci1710_private *devpriv = dev->private;
+       unsigned int chan0 = CR_CHAN(cmd->chanlist[0]);
+       unsigned int last_aref = CR_AREF(cmd->chanlist[0]);
+       unsigned int next_chan = (chan0 + 1) % s->n_chan;
+       unsigned int chansegment[32];
+       unsigned int seglen;
+       int i;
+
+       if (cmd->chanlist_len == 1) {
+               devpriv->saved_seglen = cmd->chanlist_len;
+               return 0;
+       }
+
+       /* first channel is always ok */
+       chansegment[0] = cmd->chanlist[0];
+
+       for (i = 1; i < cmd->chanlist_len; i++) {
+               unsigned int chan = CR_CHAN(cmd->chanlist[i]);
+               unsigned int aref = CR_AREF(cmd->chanlist[i]);
+
+               if (cmd->chanlist[0] == cmd->chanlist[i])
+                       break;  /*  we detected a loop, stop */
+
+               if (aref == AREF_DIFF && (chan & 1)) {
+                       dev_err(dev->class_dev,
+                               "Odd channel cannot be differential input!\n");
+                       return -EINVAL;
+               }
+
+               if (last_aref == AREF_DIFF)
+                       next_chan = (next_chan + 1) % s->n_chan;
+               if (chan != next_chan) {
+                       dev_err(dev->class_dev,
+                               "channel list must be continuous! chanlist[%i]=%d but must be %d or %d!\n",
+                               i, chan, next_chan, chan0);
+                       return -EINVAL;
+               }
+
+               /* next correct channel in list */
+               chansegment[i] = cmd->chanlist[i];
+               last_aref = aref;
+       }
+       seglen = i;
+
+       for (i = 0; i < cmd->chanlist_len; i++) {
+               if (cmd->chanlist[i] != chansegment[i % seglen]) {
+                       dev_err(dev->class_dev,
+                               "bad channel, reference or range number! chanlist[%i]=%d,%d,%d and not %d,%d,%d!\n",
+                               i, CR_CHAN(chansegment[i]),
+                               CR_RANGE(chansegment[i]),
+                               CR_AREF(chansegment[i]),
+                               CR_CHAN(cmd->chanlist[i % seglen]),
+                               CR_RANGE(cmd->chanlist[i % seglen]),
+                               CR_AREF(chansegment[i % seglen]));
+                       return -EINVAL;
+               }
+       }
+       devpriv->saved_seglen = seglen;
+
+       return 0;
+}
+
+static void pci171x_ai_setup_chanlist(struct comedi_device *dev,
+                                     struct comedi_subdevice *s,
+                                     unsigned int *chanlist,
+                                     unsigned int n_chan,
+                                     unsigned int seglen)
+{
+       const struct boardtype *board = dev->board_ptr;
+       struct pci1710_private *devpriv = dev->private;
+       unsigned int first_chan = CR_CHAN(chanlist[0]);
+       unsigned int last_chan = CR_CHAN(chanlist[seglen - 1]);
+       unsigned int i;
+
+       for (i = 0; i < seglen; i++) {  /*  store range list to card */
+               unsigned int chan = CR_CHAN(chanlist[i]);
+               unsigned int range = CR_RANGE(chanlist[i]);
+               unsigned int aref = CR_AREF(chanlist[i]);
+               unsigned int rangeval;
+
+               rangeval = board->rangecode_ai[range];
+               if (aref == AREF_DIFF)
+                       rangeval |= 0x0020;
+
+               /* select channel and set range */
+               outw(chan | (chan << 8), dev->iobase + PCI171x_MUX);
+               outw(rangeval, dev->iobase + PCI171x_RANGE);
+
+               devpriv->act_chanlist[i] = chan;
+       }
+       for ( ; i < n_chan; i++)        /* store remainder of channel list */
+               devpriv->act_chanlist[i] = CR_CHAN(chanlist[i]);
+
+       /* select channel interval to scan */
+       devpriv->ai_et_MuxVal = first_chan | (last_chan << 8);
+       outw(devpriv->ai_et_MuxVal, dev->iobase + PCI171x_MUX);
+}
+
+static int pci171x_ai_eoc(struct comedi_device *dev,
+                         struct comedi_subdevice *s,
+                         struct comedi_insn *insn,
+                         unsigned long context)
+{
+       unsigned int status;
+
+       status = inw(dev->iobase + PCI171x_STATUS);
+       if ((status & Status_FE) == 0)
+               return 0;
+       return -EBUSY;
+}
+
+static int pci171x_ai_read_sample(struct comedi_device *dev,
+                                 struct comedi_subdevice *s,
+                                 unsigned int cur_chan,
+                                 unsigned int *val)
+{
+       const struct boardtype *board = dev->board_ptr;
+       struct pci1710_private *devpriv = dev->private;
+       unsigned int sample;
+       unsigned int chan;
+
+       sample = inw(dev->iobase + PCI171x_AD_DATA);
+       if (!board->is_pci1713) {
+               /*
+                * The upper 4 bits of the 16-bit sample are the channel number
+                * that the sample was acquired from. Verify that this channel
+                * number matches the expected channel number.
+                */
+               chan = sample >> 12;
+               if (chan != devpriv->act_chanlist[cur_chan]) {
+                       dev_err(dev->class_dev,
+                               "A/D data droput: received from channel %d, expected %d\n",
+                               chan, devpriv->act_chanlist[cur_chan]);
+                       return -ENODATA;
+               }
+       }
+       *val = sample & s->maxdata;
+       return 0;
+}
+
+static int pci171x_ai_insn_read(struct comedi_device *dev,
+                               struct comedi_subdevice *s,
+                               struct comedi_insn *insn,
+                               unsigned int *data)
+{
+       struct pci1710_private *devpriv = dev->private;
+       int ret = 0;
+       int i;
+
+       devpriv->CntrlReg &= Control_CNT0;
+       devpriv->CntrlReg |= Control_SW;        /*  set software trigger */
+       outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+       outb(0, dev->iobase + PCI171x_CLRFIFO);
+       outb(0, dev->iobase + PCI171x_CLRINT);
+
+       pci171x_ai_setup_chanlist(dev, s, &insn->chanspec, 1, 1);
+
+       for (i = 0; i < insn->n; i++) {
+               unsigned int val;
+
+               outw(0, dev->iobase + PCI171x_SOFTTRG); /* start conversion */
+
+               ret = comedi_timeout(dev, s, insn, pci171x_ai_eoc, 0);
+               if (ret)
+                       break;
+
+               ret = pci171x_ai_read_sample(dev, s, 0, &val);
+               if (ret)
+                       break;
+
+               data[i] = val;
+       }
+
+       outb(0, dev->iobase + PCI171x_CLRFIFO);
+       outb(0, dev->iobase + PCI171x_CLRINT);
+
+       return ret ? ret : insn->n;
+}
+
+static int pci171x_ao_insn_write(struct comedi_device *dev,
+                                struct comedi_subdevice *s,
+                                struct comedi_insn *insn,
+                                unsigned int *data)
+{
+       struct pci1710_private *devpriv = dev->private;
+       unsigned int chan = CR_CHAN(insn->chanspec);
+       unsigned int range = CR_RANGE(insn->chanspec);
+       unsigned int reg = chan ? PCI171x_DA2 : PCI171x_DA1;
+       unsigned int val = s->readback[chan];
+       int i;
+
+       devpriv->da_ranges &= ~(1 << (chan << 1));
+       devpriv->da_ranges |= (range << (chan << 1));
+       outw(devpriv->da_ranges, dev->iobase + PCI171x_DAREF);
+
+       for (i = 0; i < insn->n; i++) {
+               val = data[i];
+               outw(val, dev->iobase + reg);
+       }
+
+       s->readback[chan] = val;
+
+       return insn->n;
+}
+
+static int pci171x_di_insn_bits(struct comedi_device *dev,
+                               struct comedi_subdevice *s,
+                               struct comedi_insn *insn,
+                               unsigned int *data)
+{
+       data[1] = inw(dev->iobase + PCI171x_DI);
+
+       return insn->n;
+}
+
+static int pci171x_do_insn_bits(struct comedi_device *dev,
+                               struct comedi_subdevice *s,
+                               struct comedi_insn *insn,
+                               unsigned int *data)
+{
+       if (comedi_dio_update_state(s, data))
+               outw(s->state, dev->iobase + PCI171x_DO);
+
+       data[1] = s->state;
+
+       return insn->n;
+}
+
+static int pci1720_ao_insn_write(struct comedi_device *dev,
+                                struct comedi_subdevice *s,
+                                struct comedi_insn *insn,
+                                unsigned int *data)
+{
+       struct pci1710_private *devpriv = dev->private;
+       unsigned int chan = CR_CHAN(insn->chanspec);
+       unsigned int range = CR_RANGE(insn->chanspec);
+       unsigned int val;
+       int i;
+
+       val = devpriv->da_ranges & (~(0x03 << (chan << 1)));
+       val |= (range << (chan << 1));
+       if (val != devpriv->da_ranges) {
+               outb(val, dev->iobase + PCI1720_RANGE);
+               devpriv->da_ranges = val;
+       }
+
+       val = s->readback[chan];
+       for (i = 0; i < insn->n; i++) {
+               val = data[i];
+               outw(val, dev->iobase + PCI1720_DA0 + (chan << 1));
+               outb(0, dev->iobase + PCI1720_SYNCOUT); /* update outputs */
+       }
+
+       s->readback[chan] = val;
+
+       return insn->n;
+}
+
+static int pci171x_ai_cancel(struct comedi_device *dev,
+                            struct comedi_subdevice *s)
+{
+       struct pci1710_private *devpriv = dev->private;
+
+       devpriv->CntrlReg &= Control_CNT0;
+       devpriv->CntrlReg |= Control_SW;
+       /* reset any operations */
+       outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+       comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
+       outb(0, dev->iobase + PCI171x_CLRFIFO);
+       outb(0, dev->iobase + PCI171x_CLRINT);
+
+       return 0;
+}
+
+static void pci1710_handle_every_sample(struct comedi_device *dev,
+                                       struct comedi_subdevice *s)
+{
+       struct comedi_cmd *cmd = &s->async->cmd;
+       unsigned int status;
+       unsigned int val;
+       int ret;
+
+       status = inw(dev->iobase + PCI171x_STATUS);
+       if (status & Status_FE) {
+               dev_dbg(dev->class_dev, "A/D FIFO empty (%4x)\n", status);
+               s->async->events |= COMEDI_CB_ERROR;
+               return;
+       }
+       if (status & Status_FF) {
+               dev_dbg(dev->class_dev,
+                       "A/D FIFO Full status (Fatal Error!) (%4x)\n", status);
+               s->async->events |= COMEDI_CB_ERROR;
+               return;
+       }
+
+       outb(0, dev->iobase + PCI171x_CLRINT);  /*  clear our INT request */
+
+       for (; !(inw(dev->iobase + PCI171x_STATUS) & Status_FE);) {
+               ret = pci171x_ai_read_sample(dev, s, s->async->cur_chan, &val);
+               if (ret) {
+                       s->async->events |= COMEDI_CB_ERROR;
+                       break;
+               }
+
+               comedi_buf_write_samples(s, &val, 1);
+
+               if (cmd->stop_src == TRIG_COUNT &&
+                   s->async->scans_done >= cmd->stop_arg) {
+                       s->async->events |= COMEDI_CB_EOA;
+                       break;
+               }
+       }
+
+       outb(0, dev->iobase + PCI171x_CLRINT);  /*  clear our INT request */
+}
+
+static void pci1710_handle_fifo(struct comedi_device *dev,
+                               struct comedi_subdevice *s)
+{
+       struct pci1710_private *devpriv = dev->private;
+       struct comedi_async *async = s->async;
+       struct comedi_cmd *cmd = &async->cmd;
+       unsigned int status;
+       int i;
+
+       status = inw(dev->iobase + PCI171x_STATUS);
+       if (!(status & Status_FH)) {
+               dev_dbg(dev->class_dev, "A/D FIFO not half full!\n");
+               async->events |= COMEDI_CB_ERROR;
+               return;
+       }
+       if (status & Status_FF) {
+               dev_dbg(dev->class_dev,
+                       "A/D FIFO Full status (Fatal Error!)\n");
+               async->events |= COMEDI_CB_ERROR;
+               return;
+       }
+
+       for (i = 0; i < devpriv->max_samples; i++) {
+               unsigned int val;
+               int ret;
+
+               ret = pci171x_ai_read_sample(dev, s, s->async->cur_chan, &val);
+               if (ret) {
+                       s->async->events |= COMEDI_CB_ERROR;
+                       break;
+               }
+
+               if (!comedi_buf_write_samples(s, &val, 1))
+                       break;
+
+               if (cmd->stop_src == TRIG_COUNT &&
+                   async->scans_done >= cmd->stop_arg) {
+                       async->events |= COMEDI_CB_EOA;
+                       break;
+               }
+       }
+
+       outb(0, dev->iobase + PCI171x_CLRINT);  /*  clear our INT request */
+}
+
+static irqreturn_t interrupt_service_pci1710(int irq, void *d)
+{
+       struct comedi_device *dev = d;
+       struct pci1710_private *devpriv = dev->private;
+       struct comedi_subdevice *s;
+       struct comedi_cmd *cmd;
+
+       if (!dev->attached)     /*  is device attached? */
+               return IRQ_NONE;        /*  no, exit */
+
+       s = dev->read_subdev;
+       cmd = &s->async->cmd;
+
+       /*  is this interrupt from our board? */
+       if (!(inw(dev->iobase + PCI171x_STATUS) & Status_IRQ))
+               return IRQ_NONE;        /*  no, exit */
+
+       if (devpriv->ai_et) {   /*  Switch from initial TRIG_EXT to TRIG_xxx. */
+               devpriv->ai_et = 0;
+               devpriv->CntrlReg &= Control_CNT0;
+               devpriv->CntrlReg |= Control_SW; /* set software trigger */
+               outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+               devpriv->CntrlReg = devpriv->ai_et_CntrlReg;
+               outb(0, dev->iobase + PCI171x_CLRFIFO);
+               outb(0, dev->iobase + PCI171x_CLRINT);
+               outw(devpriv->ai_et_MuxVal, dev->iobase + PCI171x_MUX);
+               outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+               comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+               return IRQ_HANDLED;
+       }
+
+       if (cmd->flags & CMDF_WAKE_EOS)
+               pci1710_handle_every_sample(dev, s);
+       else
+               pci1710_handle_fifo(dev, s);
+
+       comedi_handle_events(dev, s);
+
+       return IRQ_HANDLED;
+}
+
+static int pci171x_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
+{
+       struct pci1710_private *devpriv = dev->private;
+       struct comedi_cmd *cmd = &s->async->cmd;
+
+       pci171x_ai_setup_chanlist(dev, s, cmd->chanlist, cmd->chanlist_len,
+                                 devpriv->saved_seglen);
+
+       outb(0, dev->iobase + PCI171x_CLRFIFO);
+       outb(0, dev->iobase + PCI171x_CLRINT);
+
+       devpriv->CntrlReg &= Control_CNT0;
+       if ((cmd->flags & CMDF_WAKE_EOS) == 0)
+               devpriv->CntrlReg |= Control_ONEFH;
+
+       if (cmd->convert_src == TRIG_TIMER) {
+               comedi_8254_update_divisors(dev->pacer);
+
+               devpriv->CntrlReg |= Control_PACER | Control_IRQEN;
+               if (cmd->start_src == TRIG_EXT) {
+                       devpriv->ai_et_CntrlReg = devpriv->CntrlReg;
+                       devpriv->CntrlReg &=
+                           ~(Control_PACER | Control_ONEFH | Control_GATE);
+                       devpriv->CntrlReg |= Control_EXT;
+                       devpriv->ai_et = 1;
+               } else {        /* TRIG_NOW */
+                       devpriv->ai_et = 0;
+               }
+               outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+
+               if (cmd->start_src == TRIG_NOW)
+                       comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
+       } else {        /* TRIG_EXT */
+               devpriv->CntrlReg |= Control_EXT | Control_IRQEN;
+               outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+       }
+
+       return 0;
+}
+
+static int pci171x_ai_cmdtest(struct comedi_device *dev,
+                             struct comedi_subdevice *s,
+                             struct comedi_cmd *cmd)
+{
+       int err = 0;
+
+       /* Step 1 : check if triggers are trivially valid */
+
+       err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
+       err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_FOLLOW);
+       err |= comedi_check_trigger_src(&cmd->convert_src,
+                                       TRIG_TIMER | TRIG_EXT);
+       err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
+       err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
+
+       if (err)
+               return 1;
+
+       /* step 2a: make sure trigger sources are unique */
+
+       err |= comedi_check_trigger_is_unique(cmd->start_src);
+       err |= comedi_check_trigger_is_unique(cmd->convert_src);
+       err |= comedi_check_trigger_is_unique(cmd->stop_src);
+
+       /* step 2b: and mutually compatible */
+
+       if (err)
+               return 2;
+
+       /* Step 3: check if arguments are trivially valid */
+
+       err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
+       err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
+
+       if (cmd->convert_src == TRIG_TIMER)
+               err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);
+       else    /* TRIG_FOLLOW */
+               err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
+
+       err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
+                                          cmd->chanlist_len);
+
+       if (cmd->stop_src == TRIG_COUNT)
+               err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
+       else    /* TRIG_NONE */
+               err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
+
+       if (err)
+               return 3;
+
+       /* step 4: fix up any arguments */
+
+       if (cmd->convert_src == TRIG_TIMER) {
+               unsigned int arg = cmd->convert_arg;
+
+               comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
+               err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);
+       }
+
+       if (err)
+               return 4;
+
+       /* Step 5: check channel list */
+
+       err |= pci171x_ai_check_chanlist(dev, s, cmd);
+
+       if (err)
+               return 5;
+
+       return 0;
+}
+
+static int pci171x_insn_counter_config(struct comedi_device *dev,
+                                      struct comedi_subdevice *s,
+                                      struct comedi_insn *insn,
+                                      unsigned int *data)
+{
+       struct pci1710_private *devpriv = dev->private;
+
+       switch (data[0]) {
+       case INSN_CONFIG_SET_CLOCK_SRC:
+               switch (data[1]) {
+               case 0: /* internal */
+                       devpriv->ai_et_CntrlReg &= ~Control_CNT0;
+                       break;
+               case 1: /* external */
+                       devpriv->ai_et_CntrlReg |= Control_CNT0;
+                       break;
+               default:
+                       return -EINVAL;
+               }
+               outw(devpriv->ai_et_CntrlReg, dev->iobase + PCI171x_CONTROL);
+               break;
+       case INSN_CONFIG_GET_CLOCK_SRC:
+               if (devpriv->ai_et_CntrlReg & Control_CNT0) {
+                       data[1] = 1;
+                       data[2] = 0;
+               } else {
+                       data[1] = 0;
+                       data[2] = I8254_OSC_BASE_10MHZ;
+               }
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       return insn->n;
+}
+
+static int pci171x_reset(struct comedi_device *dev)
+{
+       const struct boardtype *board = dev->board_ptr;
+       struct pci1710_private *devpriv = dev->private;
+
+       /* Software trigger, CNT0=external */
+       devpriv->CntrlReg = Control_SW | Control_CNT0;
+       /* reset any operations */
+       outw(devpriv->CntrlReg, dev->iobase + PCI171x_CONTROL);
+       outb(0, dev->iobase + PCI171x_CLRFIFO); /*  clear FIFO */
+       outb(0, dev->iobase + PCI171x_CLRINT);  /*  clear INT request */
+       devpriv->da_ranges = 0;
+       if (board->has_ao) {
+               /* set DACs to 0..5V */
+               outb(devpriv->da_ranges, dev->iobase + PCI171x_DAREF);
+               outw(0, dev->iobase + PCI171x_DA1); /* set DA outputs to 0V */
+               outw(0, dev->iobase + PCI171x_DA2);
+       }
+       outw(0, dev->iobase + PCI171x_DO);      /*  digital outputs to 0 */
+       outb(0, dev->iobase + PCI171x_CLRFIFO); /*  clear FIFO */
+       outb(0, dev->iobase + PCI171x_CLRINT);  /*  clear INT request */
+
+       return 0;
+}
+
+static int pci1720_reset(struct comedi_device *dev)
+{
+       struct pci1710_private *devpriv = dev->private;
+       /* set synchronous output mode */
+       outb(Syncont_SC0, dev->iobase + PCI1720_SYNCONT);
+       devpriv->da_ranges = 0xAA;
+       /* set all ranges to +/-5V */
+       outb(devpriv->da_ranges, dev->iobase + PCI1720_RANGE);
+       outw(0x0800, dev->iobase + PCI1720_DA0);        /*  set outputs to 0V */
+       outw(0x0800, dev->iobase + PCI1720_DA1);
+       outw(0x0800, dev->iobase + PCI1720_DA2);
+       outw(0x0800, dev->iobase + PCI1720_DA3);
+       outb(0, dev->iobase + PCI1720_SYNCOUT); /*  update outputs */
+
+       return 0;
+}
+
+static int pci1710_reset(struct comedi_device *dev)
+{
+       const struct boardtype *board = dev->board_ptr;
+
+       if (board->is_pci1720)
+               return pci1720_reset(dev);
+
+       return pci171x_reset(dev);
+}
+
+static int pci1710_auto_attach(struct comedi_device *dev,
+                              unsigned long context)
+{
+       struct pci_dev *pcidev = comedi_to_pci_dev(dev);
+       const struct boardtype *board = NULL;
+       struct pci1710_private *devpriv;
+       struct comedi_subdevice *s;
+       int ret, subdev, n_subdevices;
+
+       if (context < ARRAY_SIZE(boardtypes))
+               board = &boardtypes[context];
+       if (!board)
+               return -ENODEV;
+       dev->board_ptr = board;
+       dev->board_name = board->name;
+
+       devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
+       if (!devpriv)
+               return -ENOMEM;
+
+       ret = comedi_pci_enable(dev);
+       if (ret)
+               return ret;
+       dev->iobase = pci_resource_start(pcidev, 2);
+
+       dev->pacer = comedi_8254_init(dev->iobase + PCI171X_TIMER_BASE,
+                                     I8254_OSC_BASE_10MHZ, I8254_IO16, 0);
+       if (!dev->pacer)
+               return -ENOMEM;
+
+       n_subdevices = 0;
+       if (board->n_aichan)
+               n_subdevices++;
+       if (board->has_ao)
+               n_subdevices++;
+       if (board->has_di_do)
+               n_subdevices += 2;
+       if (board->has_counter)
+               n_subdevices++;
+
+       ret = comedi_alloc_subdevices(dev, n_subdevices);
+       if (ret)
+               return ret;
+
+       pci1710_reset(dev);
+
+       if (board->has_irq && pcidev->irq) {
+               ret = request_irq(pcidev->irq, interrupt_service_pci1710,
+                                 IRQF_SHARED, dev->board_name, dev);
+               if (ret == 0)
+                       dev->irq = pcidev->irq;
+       }
+
+       subdev = 0;
+
+       if (board->n_aichan) {
+               s = &dev->subdevices[subdev];
+               s->type         = COMEDI_SUBD_AI;
+               s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND;
+               if (board->has_diff_ai)
+                       s->subdev_flags |= SDF_DIFF;
+               s->n_chan       = board->n_aichan;
+               s->maxdata      = 0x0fff;
+               s->range_table  = board->rangelist_ai;
+               s->insn_read    = pci171x_ai_insn_read;
+               if (dev->irq) {
+                       dev->read_subdev = s;
+                       s->subdev_flags |= SDF_CMD_READ;
+                       s->len_chanlist = s->n_chan;
+                       s->do_cmdtest   = pci171x_ai_cmdtest;
+                       s->do_cmd       = pci171x_ai_cmd;
+                       s->cancel       = pci171x_ai_cancel;
+               }
+               subdev++;
+       }
+
+       if (board->has_ao) {
+               s = &dev->subdevices[subdev];
+               s->type         = COMEDI_SUBD_AO;
+               s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
+               s->maxdata      = 0x0fff;
+               if (board->is_pci1720) {
+                       s->n_chan       = 4;
+                       s->range_table  = &pci1720_ao_range;
+                       s->insn_write   = pci1720_ao_insn_write;
+               } else {
+                       s->n_chan       = 2;
+                       s->range_table  = &pci171x_ao_range;
+                       s->insn_write   = pci171x_ao_insn_write;
+               }
+
+               ret = comedi_alloc_subdev_readback(s);
+               if (ret)
+                       return ret;
+
+               /* initialize the readback values to match the board reset */
+               if (board->is_pci1720) {
+                       int i;
+
+                       for (i = 0; i < s->n_chan; i++)
+                               s->readback[i] = 0x0800;
+               }
+
+               subdev++;
+       }
+
+       if (board->has_di_do) {
+               s = &dev->subdevices[subdev];
+               s->type         = COMEDI_SUBD_DI;
+               s->subdev_flags = SDF_READABLE;
+               s->n_chan       = 16;
+               s->maxdata      = 1;
+               s->range_table  = &range_digital;
+               s->insn_bits    = pci171x_di_insn_bits;
+               subdev++;
+
+               s = &dev->subdevices[subdev];
+               s->type         = COMEDI_SUBD_DO;
+               s->subdev_flags = SDF_WRITABLE;
+               s->n_chan       = 16;
+               s->maxdata      = 1;
+               s->range_table  = &range_digital;
+               s->insn_bits    = pci171x_do_insn_bits;
+               subdev++;
+       }
+
+       /* Counter subdevice (8254) */
+       if (board->has_counter) {
+               s = &dev->subdevices[subdev];
+               comedi_8254_subdevice_init(s, dev->pacer);
+
+               dev->pacer->insn_config = pci171x_insn_counter_config;
+
+               /* counters 1 and 2 are used internally for the pacer */
+               comedi_8254_set_busy(dev->pacer, 1, true);
+               comedi_8254_set_busy(dev->pacer, 2, true);
+
+               subdev++;
+       }
+
+       /* max_samples is half the FIFO size (2 bytes/sample) */
+       devpriv->max_samples = (board->has_large_fifo) ? 2048 : 512;
+
+       return 0;
+}
+
+static void pci1710_detach(struct comedi_device *dev)
+{
+       if (dev->iobase)
+               pci1710_reset(dev);
+       comedi_pci_detach(dev);
+}
+
+static struct comedi_driver adv_pci1710_driver = {
+       .driver_name    = "adv_pci1710",
+       .module         = THIS_MODULE,
+       .auto_attach    = pci1710_auto_attach,
+       .detach         = pci1710_detach,
+};
+
+static int adv_pci1710_pci_probe(struct pci_dev *dev,
+                                const struct pci_device_id *id)
+{
+       return comedi_pci_auto_config(dev, &adv_pci1710_driver,
+                                     id->driver_data);
+}
+
+static const struct pci_device_id adv_pci1710_pci_table[] = {
+       {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_PLX, PCI_DEVICE_ID_PLX_9050),
+               .driver_data = BOARD_PCI1710,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0x0000),
+               .driver_data = BOARD_PCI1710,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0xb100),
+               .driver_data = BOARD_PCI1710,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0xb200),
+               .driver_data = BOARD_PCI1710,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0xc100),
+               .driver_data = BOARD_PCI1710,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0xc200),
+               .driver_data = BOARD_PCI1710,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd100),
+               .driver_data = BOARD_PCI1710,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0x0002),
+               .driver_data = BOARD_PCI1710HG,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0xb102),
+               .driver_data = BOARD_PCI1710HG,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0xb202),
+               .driver_data = BOARD_PCI1710HG,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0xc102),
+               .driver_data = BOARD_PCI1710HG,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710,
+                              PCI_VENDOR_ID_ADVANTECH, 0xc202),
+               .driver_data = BOARD_PCI1710HG,
+       }, {
+               PCI_DEVICE_SUB(PCI_VENDOR_ID_ADVANTECH, 0x1710, 0x1000, 0xd102),
+               .driver_data = BOARD_PCI1710HG,
+       },
+       { PCI_VDEVICE(ADVANTECH, 0x1711), BOARD_PCI1711 },
+       { PCI_VDEVICE(ADVANTECH, 0x1713), BOARD_PCI1713 },
+       { PCI_VDEVICE(ADVANTECH, 0x1720), BOARD_PCI1720 },
+       { PCI_VDEVICE(ADVANTECH, 0x1731), BOARD_PCI1731 },
+       { 0 }
+};
+MODULE_DEVICE_TABLE(pci, adv_pci1710_pci_table);
+
+static struct pci_driver adv_pci1710_pci_driver = {
+       .name           = "adv_pci1710",
+       .id_table       = adv_pci1710_pci_table,
+       .probe          = adv_pci1710_pci_probe,
+       .remove         = comedi_pci_auto_unconfig,
+};
+module_comedi_pci_driver(adv_pci1710_driver, adv_pci1710_pci_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi: Advantech PCI-1710 Series Multifunction DAS Cards");
+MODULE_LICENSE("GPL");