* Author: Ian Abbott <abbotti@mev.co.uk>
  * Devices: [Amplicon] PCI224 (amplc_pci224 or pci224),
  *   PCI234 (amplc_pci224 or pci234)
- * Updated: Wed, 22 Oct 2008 12:25:08 +0100
+ * Updated: Wed, 30 Jul 2014 18:08:43 +0000
  * Status: works, but see caveats
  *
  * Supports:
  *     There is only one external trigger source so only one of start_src,
  *     scan_begin_src or stop_src may use TRIG_EXT.
  *
- * Configuration options - PCI224:
+ * 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.
- *   [2] - Select available ranges according to jumper LK1.  All channels
- *         are set to the same range:
- *         0=Jumper position 1-2 (factory default), 4 software-selectable
- *           internal voltage references, giving 4 bipolar and 4 unipolar
- *           ranges:
- *             [-10V,+10V], [-5V,+5V], [-2.5V,+2.5V], [-1.25V,+1.25V],
- *             [0,+10V], [0,+5V], [0,+2.5V], [0,1.25V].
- *         1=Jumper position 2-3, 1 external voltage reference, giving
- *           1 bipolar and 1 unipolar range:
- *             [-Vext,+Vext], [0,+Vext].
- *
- * Configuration options - PCI234:
- *   [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.
- *   [2] - Select internal or external voltage reference according to
- *         jumper LK1.  This affects all channels:
- *         0=Jumper position 1-2 (factory default), Vref=5V internal.
- *         1=Jumper position 2-3, Vref=Vext external.
- *   [3] - Select channel 0 range according to jumper LK2:
- *         0=Jumper position 2-3 (factory default), range [-2*Vref,+2*Vref]
- *           (10V bipolar when options[2]=0).
- *         1=Jumper position 1-2, range [-Vref,+Vref]
- *           (5V bipolar when options[2]=0).
- *   [4] - Select channel 1 range according to jumper LK3: cf. options[3].
- *   [5] - Select channel 2 range according to jumper LK4: cf. options[3].
- *   [6] - Select channel 3 range according to jumper LK5: cf. options[3].
  *
  * Passing a zero for an option is the same as leaving it unspecified.
  *
+ * Output range selection - PCI224:
+ *
+ *   Output ranges on PCI224 are partly software-selectable and partly
+ *   hardware-selectable according to jumper LK1.  All channels are set
+ *   to the same range:
+ *
+ *   - LK1 position 1-2 (factory default) corresponds to the following
+ *     comedi ranges:
+ *
+ *       0: [-10V,+10V]; 1: [-5V,+5V]; 2: [-2.5V,+2.5V], 3: [-1.25V,+1.25V],
+ *       4: [0,+10V],    5: [0,+5V],   6: [0,+2.5V],     7: [0,+1.25V]
+ *
+ *   - LK1 position 2-3 corresponds to the following Comedi ranges, using
+ *     an external voltage reference:
+ *
+ *       0: [-Vext,+Vext],
+ *       1: [0,+Vext]
+ *
+ * Output range selection - PCI234:
+ *
+ *   Output ranges on PCI234 are hardware-selectable according to jumper
+ *   LK1 which affects all channels, and jumpers LK2, LK3, LK4 and LK5
+ *   which affect channels 0, 1, 2 and 3 individually.  LK1 chooses between
+ *   an internal 5V reference and an external voltage reference (Vext).
+ *   LK2/3/4/5 choose (per channel) to double the reference or not according
+ *   to the following table:
+ *
+ *     LK1 position   LK2/3/4/5 pos  Comedi range
+ *     -------------  -------------  --------------
+ *     2-3 (factory)  1-2 (factory)  0: [-10V,+10V]
+ *     2-3 (factory)  2-3            1: [-5V,+5V]
+ *     1-2            1-2 (factory)  2: [-2*Vext,+2*Vext]
+ *     1-2            2-3            3: [-Vext,+Vext]
+ *
  * Caveats:
  *
  *   1) All channels on the PCI224 share the same range.  Any change to the
  * Range tables.
  */
 
-/* The software selectable internal ranges for PCI224 (option[2] == 0). */
-static const struct comedi_lrange range_pci224_internal = {
-       8, {
+/*
+ * The ranges for PCI224.
+ *
+ * These are partly hardware-selectable by jumper LK1 and partly
+ * software-selectable.
+ *
+ * All channels share the same hardware range.
+ */
+static const struct comedi_lrange range_pci224 = {
+       10, {
+               /* jumper LK1 in position 1-2 (factory default) */
                BIP_RANGE(10),
                BIP_RANGE(5),
                BIP_RANGE(2.5),
                UNI_RANGE(10),
                UNI_RANGE(5),
                UNI_RANGE(2.5),
-               UNI_RANGE(1.25)
+               UNI_RANGE(1.25),
+               /* jumper LK1 in position 2-3 */
+               RANGE_ext(-1, 1),       /* bipolar [-Vext,+Vext] */
+               RANGE_ext(0, 1),        /* unipolar [0,+Vext] */
        }
 };
 
-static const unsigned short hwrange_pci224_internal[8] = {
+static const unsigned short hwrange_pci224[10] = {
+       /* jumper LK1 in position 1-2 (factory default) */
        PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_10,
        PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_5,
        PCI224_DACCON_POLAR_BI | PCI224_DACCON_VREF_2_5,
        PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_5,
        PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_2_5,
        PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_1_25,
-};
-
-/* The software selectable external ranges for PCI224 (option[2] == 1). */
-static const struct comedi_lrange range_pci224_external = {
-       2, {
-               RANGE_ext(-1, 1),       /* bipolar [-Vref,+Vref] */
-               RANGE_ext(0, 1)         /* unipolar [0,+Vref] */
-       }
-};
-
-static const unsigned short hwrange_pci224_external[2] = {
+       /* jumper LK1 in position 2-3 */
        PCI224_DACCON_POLAR_BI,
        PCI224_DACCON_POLAR_UNI,
 };
 
-/*
- * The hardware selectable Vref*2 external range for PCI234
- * (option[2] == 1, option[3+n] == 0).
- */
-static const struct comedi_lrange range_pci234_ext2 = {
-       1, {
-               RANGE_ext(-2, 2)
-       }
+/* Used to check all channels set to the same range on PCI224. */
+static const unsigned char range_check_pci224[10] = {
+       0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
 };
 
 /*
- * The hardware selectable Vref external range for PCI234
- * (option[2] == 1, option[3+n] == 1).
+ * The ranges for PCI234.
+ *
+ * These are all hardware-selectable by jumper LK1 affecting all channels,
+ * and jumpers LK2, LK3, LK4 and LK5 affecting channels 0, 1, 2 and 3
+ * individually.
  */
-static const struct comedi_lrange range_pci234_ext = {
-       1, {
-               RANGE_ext(-1, 1)
+static const struct comedi_lrange range_pci234 = {
+       4, {
+               /* LK1: 1-2 (fact def), LK2/3/4/5: 2-3 (fac def) */
+               BIP_RANGE(10),
+               /* LK1: 1-2 (fact def), LK2/3/4/5: 1-2 */
+               BIP_RANGE(5),
+               /* LK1: 2-3, LK2/3/4/5: 2-3 (fac def) */
+               RANGE_ext(-2, 2),       /* bipolar [-2*Vext,+2*Vext] */
+               /* LK1: 2-3, LK2/3/4/5: 1-2 */
+               RANGE_ext(-1, 1),       /* bipolar [-Vext,+Vext] */
        }
 };
 
-/* This serves for all the PCI234 ranges. */
-static const unsigned short hwrange_pci234[1] = {
-       PCI224_DACCON_POLAR_BI, /* bipolar - hardware ignores it! */
+/* N.B. PCI234 ignores the polarity bit, but software uses it. */
+static const unsigned short hwrange_pci234[4] = {
+       PCI224_DACCON_POLAR_BI,
+       PCI224_DACCON_POLAR_BI,
+       PCI224_DACCON_POLAR_BI,
+       PCI224_DACCON_POLAR_BI,
+};
+
+/* Used to check all channels use same LK1 setting on PCI234. */
+static const unsigned char range_check_pci234[4] = {
+       0, 0, 1, 1,
 };
 
 /*
        enum pci224_model model;
        unsigned int ao_chans;
        unsigned int ao_bits;
+       const struct comedi_lrange *ao_range;
+       const unsigned short *ao_hwrange;
+       const unsigned char *ao_range_check;
 };
 
 static const struct pci224_board pci224_boards[] = {
                .model          = pci224_model,
                .ao_chans       = 16,
                .ao_bits        = 12,
+               .ao_range       = &range_pci224,
+               .ao_hwrange     = &hwrange_pci224[0],
+               .ao_range_check = &range_check_pci224[0],
        },
        {
                .name           = "pci234",
                .model          = pci234_model,
                .ao_chans       = 4,
                .ao_bits        = 16,
+               .ao_range       = &range_pci234,
+               .ao_hwrange     = &hwrange_pci234[0],
+               .ao_range_check = &range_check_pci234[0],
        },
        {
                .name           = "amplc_pci224",
 };
 
 struct pci224_private {
-       const unsigned short *hwrange;
        unsigned long iobase1;
        unsigned long state;
        spinlock_t ao_spinlock; /* spinlock for AO command handling */
        /* Enable the channel. */
        outw(1 << chan, dev->iobase + PCI224_DACCEN);
        /* Set range and reset FIFO. */
-       devpriv->daccon = COMBINE(devpriv->daccon, devpriv->hwrange[range],
+       devpriv->daccon = COMBINE(devpriv->daccon, thisboard->ao_hwrange[range],
                                  PCI224_DACCON_POLAR_MASK |
                                  PCI224_DACCON_VREF_MASK);
        outw(devpriv->daccon | PCI224_DACCON_FIFORESET,
                                    struct comedi_subdevice *s,
                                    struct comedi_cmd *cmd)
 {
-       unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
+       const struct pci224_board *thisboard = comedi_board(dev);
+       unsigned int range_check_0;
        unsigned int chan_mask = 0;
        int i;
 
+       range_check_0 = thisboard->ao_range_check[CR_RANGE(cmd->chanlist[0])];
        for (i = 0; i < cmd->chanlist_len; i++) {
                unsigned int chan = CR_CHAN(cmd->chanlist[i]);
-               unsigned int range = CR_RANGE(cmd->chanlist[i]);
 
                if (chan_mask & (1 << chan)) {
                        dev_dbg(dev->class_dev,
                }
                chan_mask |= 1 << chan;
 
-               if (range != range0) {
+               if (thisboard->ao_range_check[CR_RANGE(cmd->chanlist[i])] !=
+                   range_check_0) {
                        dev_dbg(dev->class_dev,
-                               "%s: entries in chanlist must all have the same range index\n",
+                               "%s: entries in chanlist have incompatible ranges\n",
                                __func__);
                        return -EINVAL;
                }
 
 static int pci224_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
 {
+       const struct pci224_board *thisboard = comedi_board(dev);
        struct pci224_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        int range;
         */
        devpriv->daccon =
            COMBINE(devpriv->daccon,
-                   devpriv->hwrange[range] | PCI224_DACCON_TRIG_NONE |
+                   thisboard->ao_hwrange[range] | PCI224_DACCON_TRIG_NONE |
                    PCI224_DACCON_FIFOINTR_NHALF,
                    PCI224_DACCON_POLAR_MASK | PCI224_DACCON_VREF_MASK |
                    PCI224_DACCON_TRIG_MASK | PCI224_DACCON_FIFOINTR_MASK);
                void *data, unsigned int num_bytes, unsigned int chan_index)
 {
        const struct pci224_board *thisboard = comedi_board(dev);
-       struct pci224_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        unsigned short *array = data;
        unsigned int length = num_bytes / sizeof(*array);
        /* The hardware expects 16-bit numbers. */
        shift = 16 - thisboard->ao_bits;
        /* Channels will be all bipolar or all unipolar. */
-       if ((devpriv->hwrange[CR_RANGE(cmd->chanlist[0])] &
+       if ((thisboard->ao_hwrange[CR_RANGE(cmd->chanlist[0])] &
             PCI224_DACCON_POLAR_MASK) == PCI224_DACCON_POLAR_UNI) {
                /* Unipolar */
                offset = 0;
  * Common part of attach and auto_attach.
  */
 static int pci224_attach_common(struct comedi_device *dev,
-                               struct pci_dev *pci_dev, int *options)
+                               struct pci_dev *pci_dev)
 {
        const struct pci224_board *thisboard = comedi_board(dev);
        struct pci224_private *devpriv = dev->private;
        struct comedi_subdevice *s;
        unsigned int irq;
-       unsigned n;
        int ret;
 
        comedi_set_hw_dev(dev, &pci_dev->dev);
        s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
        s->n_chan = thisboard->ao_chans;
        s->maxdata = (1 << thisboard->ao_bits) - 1;
+       s->range_table = thisboard->ao_range;
        s->insn_write = pci224_ao_insn_write;
        s->insn_read = pci224_ao_insn_read;
        s->len_chanlist = s->n_chan;
        s->cancel = pci224_ao_cancel;
        s->munge = pci224_ao_munge;
 
-       /* Sort out channel range options. */
-       if (thisboard->model == pci234_model) {
-               /* PCI234 range options. */
-               const struct comedi_lrange **range_table_list;
-
-               range_table_list =
-                   kmalloc(sizeof(struct comedi_lrange *) * s->n_chan,
-                           GFP_KERNEL);
-               if (!range_table_list)
-                       return -ENOMEM;
-               s->range_table_list = range_table_list;
-
-               if (options) {
-                       for (n = 2; n < 3 + s->n_chan; n++) {
-                               if (options[n] < 0 || options[n] > 1) {
-                                       dev_warn(dev->class_dev,
-                                                "warning! bad options[%u]=%d\n",
-                                                n, options[n]);
-                               }
-                       }
-               }
-               for (n = 0; n < s->n_chan; n++) {
-                       if (n < COMEDI_NDEVCONFOPTS - 3 && options &&
-                           options[3 + n] == 1) {
-                               if (options[2] == 1)
-                                       range_table_list[n] = &range_pci234_ext;
-                               else
-                                       range_table_list[n] = &range_bipolar5;
-
-                       } else {
-                               if (options && options[2] == 1) {
-                                       range_table_list[n] =
-                                           &range_pci234_ext2;
-                               } else {
-                                       range_table_list[n] = &range_bipolar10;
-                               }
-                       }
-               }
-               devpriv->hwrange = hwrange_pci234;
-       } else {
-               /* PCI224 range options. */
-               if (options && options[2] == 1) {
-                       s->range_table = &range_pci224_external;
-                       devpriv->hwrange = hwrange_pci224_external;
-               } else {
-                       if (options && options[2] != 0) {
-                               dev_warn(dev->class_dev,
-                                        "warning! bad options[2]=%d\n",
-                                        options[2]);
-                       }
-                       s->range_table = &range_pci224_internal;
-                       devpriv->hwrange = hwrange_pci224_internal;
-               }
-       }
-
        dev->board_name = thisboard->name;
 
        if (irq) {
        if (!pci_dev)
                return -EIO;
 
-       return pci224_attach_common(dev, pci_dev, it->options);
+       return pci224_attach_common(dev, pci_dev);
 }
 
 static int
         * has been removed.
         */
        pci_dev_get(pci_dev);
-       return pci224_attach_common(dev, pci_dev, NULL);
+       return pci224_attach_common(dev, pci_dev);
 }
 
 static void pci224_detach(struct comedi_device *dev)
 
        if (dev->irq)
                free_irq(dev->irq, dev);
-       if (dev->subdevices) {
-               struct comedi_subdevice *s;
-
-               s = &dev->subdevices[0];
-               /* AO subdevice */
-               kfree(s->range_table_list);
-       }
        if (devpriv) {
                kfree(devpriv->ao_readback);
                kfree(devpriv->ao_scan_vals);