aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/comedi/drivers/amplc_pci224.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/comedi/drivers/amplc_pci224.c')
-rw-r--r--drivers/staging/comedi/drivers/amplc_pci224.c1545
1 files changed, 1545 insertions, 0 deletions
diff --git a/drivers/staging/comedi/drivers/amplc_pci224.c b/drivers/staging/comedi/drivers/amplc_pci224.c
new file mode 100644
index 000000000000..770b96648932
--- /dev/null
+++ b/drivers/staging/comedi/drivers/amplc_pci224.c
@@ -0,0 +1,1545 @@
+/*
+ comedi/drivers/amplc_pci224.c
+ Driver for Amplicon PCI224 and PCI234 AO boards.
+
+ Copyright (C) 2005 MEV Ltd. <http://www.mev.co.uk/>
+
+ COMEDI - Linux Control and Measurement Device Interface
+ Copyright (C) 1998,2000 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.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+/*
+Driver: amplc_pci224
+Description: Amplicon PCI224, PCI234
+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
+Status: works, but see caveats
+
+Supports:
+
+ - ao_insn read/write
+ - ao_do_cmd mode with the following sources:
+
+ - start_src TRIG_INT TRIG_EXT
+ - scan_begin_src TRIG_TIMER TRIG_EXT
+ - convert_src TRIG_NOW
+ - scan_end_src TRIG_COUNT
+ - stop_src TRIG_COUNT TRIG_EXT TRIG_NONE
+
+ The channel list must contain at least one channel with no repeated
+ channels. The scan end count must equal the number of channels in
+ the channel list.
+
+ 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:
+ [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.
+
+Caveats:
+
+ 1) All channels on the PCI224 share the same range. Any change to the
+ range as a result of insn_write or a streaming command will affect
+ the output voltages of all channels, including those not specified
+ by the instruction or command.
+
+ 2) For the analog output command, the first scan may be triggered
+ falsely at the start of acquisition. This occurs when the DAC scan
+ trigger source is switched from 'none' to 'timer' (scan_begin_src =
+ TRIG_TIMER) or 'external' (scan_begin_src == TRIG_EXT) at the start
+ of acquisition and the trigger source is at logic level 1 at the
+ time of the switch. This is very likely for TRIG_TIMER. For
+ TRIG_EXT, it depends on the state of the external line and whether
+ the CR_INVERT flag has been set. The remaining scans are triggered
+ correctly.
+*/
+
+#include "../comedidev.h"
+
+#include "comedi_pci.h"
+
+#include "comedi_fc.h"
+#include "8253.h"
+
+#define DRIVER_NAME "amplc_pci224"
+
+/*
+ * PCI IDs.
+ */
+/* #define PCI_VENDOR_ID_AMPLICON 0x14dc */
+#define PCI_DEVICE_ID_AMPLICON_PCI224 0x0007
+#define PCI_DEVICE_ID_AMPLICON_PCI234 0x0008
+#define PCI_DEVICE_ID_INVALID 0xffff
+
+/*
+ * PCI224/234 i/o space 1 (PCIBAR2) registers.
+ */
+#define PCI224_IO1_SIZE 0x20 /* Size of i/o space 1 (8-bit registers) */
+#define PCI224_Z2_CT0 0x14 /* 82C54 counter/timer 0 */
+#define PCI224_Z2_CT1 0x15 /* 82C54 counter/timer 1 */
+#define PCI224_Z2_CT2 0x16 /* 82C54 counter/timer 2 */
+#define PCI224_Z2_CTC 0x17 /* 82C54 counter/timer control word */
+#define PCI224_ZCLK_SCE 0x1A /* Group Z Clock Configuration Register */
+#define PCI224_ZGAT_SCE 0x1D /* Group Z Gate Configuration Register */
+#define PCI224_INT_SCE 0x1E /* ISR Interrupt source mask register */
+ /* /Interrupt status */
+
+/*
+ * PCI224/234 i/o space 2 (PCIBAR3) 16-bit registers.
+ */
+#define PCI224_IO2_SIZE 0x10 /* Size of i/o space 2 (16-bit registers). */
+#define PCI224_DACDATA 0x00 /* (w-o) DAC FIFO data. */
+#define PCI224_SOFTTRIG 0x00 /* (r-o) DAC software scan trigger. */
+#define PCI224_DACCON 0x02 /* (r/w) DAC status/configuration. */
+#define PCI224_FIFOSIZ 0x04 /* (w-o) FIFO size for wraparound mode. */
+#define PCI224_DACCEN 0x06 /* (w-o) DAC channel enable register. */
+
+/*
+ * DACCON values.
+ */
+/* (r/w) Scan trigger. */
+#define PCI224_DACCON_TRIG_MASK (7 << 0)
+#define PCI224_DACCON_TRIG_NONE (0 << 0) /* none */
+#define PCI224_DACCON_TRIG_SW (1 << 0) /* software trig */
+#define PCI224_DACCON_TRIG_EXTP (2 << 0) /* ext +ve edge */
+#define PCI224_DACCON_TRIG_EXTN (3 << 0) /* ext -ve edge */
+#define PCI224_DACCON_TRIG_Z2CT0 (4 << 0) /* Z2 CT0 out */
+#define PCI224_DACCON_TRIG_Z2CT1 (5 << 0) /* Z2 CT1 out */
+#define PCI224_DACCON_TRIG_Z2CT2 (6 << 0) /* Z2 CT2 out */
+/* (r/w) Polarity (PCI224 only, PCI234 always bipolar!). */
+#define PCI224_DACCON_POLAR_MASK (1 << 3)
+#define PCI224_DACCON_POLAR_UNI (0 << 3) /* range [0,Vref] */
+#define PCI224_DACCON_POLAR_BI (1 << 3) /* range [-Vref,Vref] */
+/* (r/w) Internal Vref (PCI224 only, when LK1 in position 1-2). */
+#define PCI224_DACCON_VREF_MASK (3 << 4)
+#define PCI224_DACCON_VREF_1_25 (0 << 4) /* Vref = 1.25V */
+#define PCI224_DACCON_VREF_2_5 (1 << 4) /* Vref = 2.5V */
+#define PCI224_DACCON_VREF_5 (2 << 4) /* Vref = 5V */
+#define PCI224_DACCON_VREF_10 (3 << 4) /* Vref = 10V */
+/* (r/w) Wraparound mode enable (to play back stored waveform). */
+#define PCI224_DACCON_FIFOWRAP (1 << 7)
+/* (r/w) FIFO enable. It MUST be set! */
+#define PCI224_DACCON_FIFOENAB (1 << 8)
+/* (r/w) FIFO interrupt trigger level (most values are not very useful). */
+#define PCI224_DACCON_FIFOINTR_MASK (7 << 9)
+#define PCI224_DACCON_FIFOINTR_EMPTY (0 << 9) /* when empty */
+#define PCI224_DACCON_FIFOINTR_NEMPTY (1 << 9) /* when not empty */
+#define PCI224_DACCON_FIFOINTR_NHALF (2 << 9) /* when not half full */
+#define PCI224_DACCON_FIFOINTR_HALF (3 << 9) /* when half full */
+#define PCI224_DACCON_FIFOINTR_NFULL (4 << 9) /* when not full */
+#define PCI224_DACCON_FIFOINTR_FULL (5 << 9) /* when full */
+/* (r-o) FIFO fill level. */
+#define PCI224_DACCON_FIFOFL_MASK (7 << 12)
+#define PCI224_DACCON_FIFOFL_EMPTY (1 << 12) /* 0 */
+#define PCI224_DACCON_FIFOFL_ONETOHALF (0 << 12) /* [1,2048] */
+#define PCI224_DACCON_FIFOFL_HALFTOFULL (4 << 12) /* [2049,4095] */
+#define PCI224_DACCON_FIFOFL_FULL (6 << 12) /* 4096 */
+/* (r-o) DAC busy flag. */
+#define PCI224_DACCON_BUSY (1 << 15)
+/* (w-o) FIFO reset. */
+#define PCI224_DACCON_FIFORESET (1 << 12)
+/* (w-o) Global reset (not sure what it does). */
+#define PCI224_DACCON_GLOBALRESET (1 << 13)
+
+/*
+ * DAC FIFO size.
+ */
+#define PCI224_FIFO_SIZE 4096
+
+/*
+ * DAC FIFO guaranteed minimum room available, depending on reported fill level.
+ * The maximum room available depends on the reported fill level and how much
+ * has been written!
+ */
+#define PCI224_FIFO_ROOM_EMPTY PCI224_FIFO_SIZE
+#define PCI224_FIFO_ROOM_ONETOHALF (PCI224_FIFO_SIZE / 2)
+#define PCI224_FIFO_ROOM_HALFTOFULL 1
+#define PCI224_FIFO_ROOM_FULL 0
+
+/*
+ * Counter/timer clock input configuration sources.
+ */
+#define CLK_CLK 0 /* reserved (channel-specific clock) */
+#define CLK_10MHZ 1 /* internal 10 MHz clock */
+#define CLK_1MHZ 2 /* internal 1 MHz clock */
+#define CLK_100KHZ 3 /* internal 100 kHz clock */
+#define CLK_10KHZ 4 /* internal 10 kHz clock */
+#define CLK_1KHZ 5 /* internal 1 kHz clock */
+#define CLK_OUTNM1 6 /* output of channel-1 modulo total */
+#define CLK_EXT 7 /* external clock */
+/* Macro to construct clock input configuration register value. */
+#define CLK_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7))
+/* Timebases in ns. */
+#define TIMEBASE_10MHZ 100
+#define TIMEBASE_1MHZ 1000
+#define TIMEBASE_100KHZ 10000
+#define TIMEBASE_10KHZ 100000
+#define TIMEBASE_1KHZ 1000000
+
+/*
+ * Counter/timer gate input configuration sources.
+ */
+#define GAT_VCC 0 /* VCC (i.e. enabled) */
+#define GAT_GND 1 /* GND (i.e. disabled) */
+#define GAT_EXT 2 /* reserved (external gate input) */
+#define GAT_NOUTNM2 3 /* inverted output of channel-2 modulo total */
+/* Macro to construct gate input configuration register value. */
+#define GAT_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7))
+
+/*
+ * Summary of CLK_OUTNM1 and GAT_NOUTNM2 connections for PCI224 and PCI234:
+ *
+ * Channel's Channel's
+ * clock input gate input
+ * Channel CLK_OUTNM1 GAT_NOUTNM2
+ * ------- ---------- -----------
+ * Z2-CT0 Z2-CT2-OUT /Z2-CT1-OUT
+ * Z2-CT1 Z2-CT0-OUT /Z2-CT2-OUT
+ * Z2-CT2 Z2-CT1-OUT /Z2-CT0-OUT
+ */
+
+/*
+ * Interrupt enable/status bits
+ */
+#define PCI224_INTR_EXT 0x01 /* rising edge on external input */
+#define PCI224_INTR_DAC 0x04 /* DAC (FIFO) interrupt */
+#define PCI224_INTR_Z2CT1 0x20 /* rising edge on Z2-CT1 output */
+
+#define PCI224_INTR_EDGE_BITS (PCI224_INTR_EXT | PCI224_INTR_Z2CT1)
+#define PCI224_INTR_LEVEL_BITS PCI224_INTR_DACFIFO
+
+/*
+ * Handy macros.
+ */
+
+/* Combine old and new bits. */
+#define COMBINE(old, new, mask) (((old) & ~(mask)) | ((new) & (mask)))
+
+/* A generic null function pointer value. */
+#define NULLFUNC 0
+
+/* Current CPU. XXX should this be hard_smp_processor_id()? */
+#define THISCPU smp_processor_id()
+
+/* State bits for use with atomic bit operations. */
+#define AO_CMD_STARTED 0
+
+/*
+ * Range tables.
+ */
+
+/* The software selectable internal ranges for PCI224 (option[2] == 0). */
+static const struct comedi_lrange range_pci224_internal = {
+ 8,
+ {
+ BIP_RANGE(10),
+ BIP_RANGE(5),
+ BIP_RANGE(2.5),
+ BIP_RANGE(1.25),
+ UNI_RANGE(10),
+ UNI_RANGE(5),
+ UNI_RANGE(2.5),
+ UNI_RANGE(1.25),
+ }
+};
+
+static const unsigned short hwrange_pci224_internal[8] = {
+ 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_BI | PCI224_DACCON_VREF_1_25,
+ PCI224_DACCON_POLAR_UNI | PCI224_DACCON_VREF_10,
+ 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] = {
+ 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),
+ }
+};
+
+/* The hardware selectable Vref external range for PCI234
+ * (option[2] == 1, option[3+n] == 1). */
+static const struct comedi_lrange range_pci234_ext = {
+ 1,
+ {
+ RANGE_ext(-1, 1),
+ }
+};
+
+/* This serves for all the PCI234 ranges. */
+static const unsigned short hwrange_pci234[1] = {
+ PCI224_DACCON_POLAR_BI, /* bipolar - hardware ignores it! */
+};
+
+/*
+ * Board descriptions.
+ */
+
+enum pci224_model { any_model, pci224_model, pci234_model };
+
+struct pci224_board {
+ const char *name;
+ unsigned short devid;
+ enum pci224_model model;
+ unsigned int ao_chans;
+ unsigned int ao_bits;
+};
+
+static const struct pci224_board pci224_boards[] = {
+ {
+ name: "pci224",
+ devid: PCI_DEVICE_ID_AMPLICON_PCI224,
+ model: pci224_model,
+ ao_chans:16,
+ ao_bits: 12,
+ },
+ {
+ name: "pci234",
+ devid: PCI_DEVICE_ID_AMPLICON_PCI234,
+ model: pci234_model,
+ ao_chans:4,
+ ao_bits: 16,
+ },
+ {
+ name: DRIVER_NAME,
+ devid: PCI_DEVICE_ID_INVALID,
+ model: any_model, /* wildcard */
+ },
+};
+
+/*
+ * PCI driver table.
+ */
+
+static DEFINE_PCI_DEVICE_TABLE(pci224_pci_table) = {
+ {PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI224,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+ {PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_AMPLICON_PCI234,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
+ {0}
+};
+
+MODULE_DEVICE_TABLE(pci, pci224_pci_table);
+
+/*
+ * Useful for shorthand access to the particular board structure
+ */
+#define thisboard ((struct pci224_board *)dev->board_ptr)
+
+/* this structure is for data unique to this hardware driver. If
+ several hardware drivers keep similar information in this structure,
+ feel free to suggest moving the variable to the struct comedi_device struct. */
+struct pci224_private {
+ struct pci_dev *pci_dev; /* PCI device */
+ const unsigned short *hwrange;
+ unsigned long iobase1;
+ unsigned long state;
+ spinlock_t ao_spinlock;
+ unsigned int *ao_readback;
+ short *ao_scan_vals;
+ unsigned char *ao_scan_order;
+ int intr_cpuid;
+ short intr_running;
+ unsigned short daccon;
+ unsigned int cached_div1;
+ unsigned int cached_div2;
+ unsigned int ao_stop_count;
+ short ao_stop_continuous;
+ unsigned short ao_enab; /* max 16 channels so 'short' will do */
+ unsigned char intsce;
+};
+
+#define devpriv ((struct pci224_private *)dev->private)
+
+/*
+ * The struct comedi_driver structure tells the Comedi core module
+ * which functions to call to configure/deconfigure (attach/detach)
+ * the board, and also about the kernel module that contains
+ * the device code.
+ */
+static int pci224_attach(struct comedi_device * dev, struct comedi_devconfig * it);
+static int pci224_detach(struct comedi_device * dev);
+static struct comedi_driver driver_amplc_pci224 = {
+ driver_name:DRIVER_NAME,
+ module:THIS_MODULE,
+ attach:pci224_attach,
+ detach:pci224_detach,
+ board_name:&pci224_boards[0].name,
+ offset:sizeof(struct pci224_board),
+ num_names:sizeof(pci224_boards) / sizeof(struct pci224_board),
+};
+
+COMEDI_PCI_INITCLEANUP(driver_amplc_pci224, pci224_pci_table);
+
+/*
+ * Called from the 'insn_write' function to perform a single write.
+ */
+static void
+pci224_ao_set_data(struct comedi_device * dev, int chan, int range, unsigned int data)
+{
+ unsigned short mangled;
+
+ /* Store unmangled data for readback. */
+ devpriv->ao_readback[chan] = data;
+ /* Enable the channel. */
+ outw(1 << chan, dev->iobase + PCI224_DACCEN);
+ /* Set range and reset FIFO. */
+ devpriv->daccon = COMBINE(devpriv->daccon, devpriv->hwrange[range],
+ (PCI224_DACCON_POLAR_MASK | PCI224_DACCON_VREF_MASK));
+ outw(devpriv->daccon | PCI224_DACCON_FIFORESET,
+ dev->iobase + PCI224_DACCON);
+ /*
+ * Mangle the data. The hardware expects:
+ * - bipolar: 16-bit 2's complement
+ * - unipolar: 16-bit unsigned
+ */
+ mangled = (unsigned short)data << (16 - thisboard->ao_bits);
+ if ((devpriv->daccon & PCI224_DACCON_POLAR_MASK) ==
+ PCI224_DACCON_POLAR_BI) {
+ mangled ^= 0x8000;
+ }
+ /* Write mangled data to the FIFO. */
+ outw(mangled, dev->iobase + PCI224_DACDATA);
+ /* Trigger the conversion. */
+ inw(dev->iobase + PCI224_SOFTTRIG);
+}
+
+/*
+ * 'insn_write' function for AO subdevice.
+ */
+static int
+pci224_ao_insn_write(struct comedi_device * dev, struct comedi_subdevice * s,
+ struct comedi_insn * insn, unsigned int * data)
+{
+ int i;
+ int chan, range;
+
+ /* Unpack channel and range. */
+ chan = CR_CHAN(insn->chanspec);
+ range = CR_RANGE(insn->chanspec);
+
+ /* Writing a list of values to an AO channel is probably not
+ * very useful, but that's how the interface is defined. */
+ for (i = 0; i < insn->n; i++) {
+ pci224_ao_set_data(dev, chan, range, data[i]);
+ }
+ return i;
+}
+
+/*
+ * 'insn_read' function for AO subdevice.
+ *
+ * N.B. The value read will not be valid if the DAC channel has
+ * never been written successfully since the device was attached
+ * or since the channel has been used by an AO streaming write
+ * command.
+ */
+static int
+pci224_ao_insn_read(struct comedi_device * dev, struct comedi_subdevice * s,
+ struct comedi_insn * insn, unsigned int * data)
+{
+ int i;
+ int chan;
+
+ chan = CR_CHAN(insn->chanspec);
+
+ for (i = 0; i < insn->n; i++) {
+ data[i] = devpriv->ao_readback[chan];
+ }
+
+ return i;
+}
+
+/*
+ * Just a wrapper for the inline function 'i8253_cascade_ns_to_timer'.
+ */
+static void
+pci224_cascade_ns_to_timer(int osc_base, unsigned int *d1, unsigned int *d2,
+ unsigned int *nanosec, int round_mode)
+{
+ i8253_cascade_ns_to_timer(osc_base, d1, d2, nanosec, round_mode);
+}
+
+/*
+ * Kills a command running on the AO subdevice.
+ */
+static void pci224_ao_stop(struct comedi_device * dev, struct comedi_subdevice * s)
+{
+ unsigned long flags;
+
+ if (!test_and_clear_bit(AO_CMD_STARTED, &devpriv->state)) {
+ return;
+ }
+
+ comedi_spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ /* Kill the interrupts. */
+ devpriv->intsce = 0;
+ outb(0, devpriv->iobase1 + PCI224_INT_SCE);
+ /*
+ * Interrupt routine may or may not be running. We may or may not
+ * have been called from the interrupt routine (directly or
+ * indirectly via a comedi_events() callback routine). It's highly
+ * unlikely that we've been called from some other interrupt routine
+ * but who knows what strange things coders get up to!
+ *
+ * If the interrupt routine is currently running, wait for it to
+ * finish, unless we appear to have been called via the interrupt
+ * routine.
+ */
+ while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) {
+ comedi_spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ comedi_spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ }
+ comedi_spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ /* Reconfigure DAC for insn_write usage. */
+ outw(0, dev->iobase + PCI224_DACCEN); /* Disable channels. */
+ devpriv->daccon = COMBINE(devpriv->daccon,
+ PCI224_DACCON_TRIG_SW | PCI224_DACCON_FIFOINTR_EMPTY,
+ PCI224_DACCON_TRIG_MASK | PCI224_DACCON_FIFOINTR_MASK);
+ outw(devpriv->daccon | PCI224_DACCON_FIFORESET,
+ dev->iobase + PCI224_DACCON);
+}
+
+/*
+ * Handles start of acquisition for the AO subdevice.
+ */
+static void pci224_ao_start(struct comedi_device * dev, struct comedi_subdevice * s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned long flags;
+
+ set_bit(AO_CMD_STARTED, &devpriv->state);
+ if (!devpriv->ao_stop_continuous && devpriv->ao_stop_count == 0) {
+ /* An empty acquisition! */
+ pci224_ao_stop(dev, s);
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ } else {
+ /* Enable interrupts. */
+ comedi_spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ if (cmd->stop_src == TRIG_EXT) {
+ devpriv->intsce = PCI224_INTR_EXT | PCI224_INTR_DAC;
+ } else {
+ devpriv->intsce = PCI224_INTR_DAC;
+ }
+ outb(devpriv->intsce, devpriv->iobase1 + PCI224_INT_SCE);
+ comedi_spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ }
+}
+
+/*
+ * Handles interrupts from the DAC FIFO.
+ */
+static void pci224_ao_handle_fifo(struct comedi_device * dev, struct comedi_subdevice * s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ unsigned int num_scans;
+ unsigned int room;
+ unsigned short dacstat;
+ unsigned int i, n;
+ unsigned int bytes_per_scan;
+
+ if (cmd->chanlist_len) {
+ bytes_per_scan = cmd->chanlist_len * sizeof(short);
+ } else {
+ /* Shouldn't get here! */
+ bytes_per_scan = sizeof(short);
+ }
+ /* Determine number of scans available in buffer. */
+ num_scans = comedi_buf_read_n_available(s->async) / bytes_per_scan;
+ if (!devpriv->ao_stop_continuous) {
+ /* Fixed number of scans. */
+ if (num_scans > devpriv->ao_stop_count) {
+ num_scans = devpriv->ao_stop_count;
+ }
+ }
+
+ /* Determine how much room is in the FIFO (in samples). */
+ dacstat = inw(dev->iobase + PCI224_DACCON);
+ switch (dacstat & PCI224_DACCON_FIFOFL_MASK) {
+ case PCI224_DACCON_FIFOFL_EMPTY:
+ room = PCI224_FIFO_ROOM_EMPTY;
+ if (!devpriv->ao_stop_continuous
+ && devpriv->ao_stop_count == 0) {
+ /* FIFO empty at end of counted acquisition. */
+ pci224_ao_stop(dev, s);
+ s->async->events |= COMEDI_CB_EOA;
+ comedi_event(dev, s);
+ return;
+ }
+ break;
+ case PCI224_DACCON_FIFOFL_ONETOHALF:
+ room = PCI224_FIFO_ROOM_ONETOHALF;
+ break;
+ case PCI224_DACCON_FIFOFL_HALFTOFULL:
+ room = PCI224_FIFO_ROOM_HALFTOFULL;
+ break;
+ default:
+ room = PCI224_FIFO_ROOM_FULL;
+ break;
+ }
+ if (room >= PCI224_FIFO_ROOM_ONETOHALF) {
+ /* FIFO is less than half-full. */
+ if (num_scans == 0) {
+ /* Nothing left to put in the FIFO. */
+ pci224_ao_stop(dev, s);
+ s->async->events |= COMEDI_CB_OVERFLOW;
+ rt_printk(KERN_ERR "comedi%d: "
+ "AO buffer underrun\n", dev->minor);
+ }
+ }
+ /* Determine how many new scans can be put in the FIFO. */
+ if (cmd->chanlist_len) {
+ room /= cmd->chanlist_len;
+ }
+ /* Determine how many scans to process. */
+ if (num_scans > room) {
+ num_scans = room;
+ }
+ /* Process scans. */
+ for (n = 0; n < num_scans; n++) {
+ cfc_read_array_from_buffer(s, &devpriv->ao_scan_vals[0],
+ bytes_per_scan);
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ outw(devpriv->ao_scan_vals[devpriv->
+ ao_scan_order[i]],
+ dev->iobase + PCI224_DACDATA);
+ }
+ }
+ if (!devpriv->ao_stop_continuous) {
+ devpriv->ao_stop_count -= num_scans;
+ if (devpriv->ao_stop_count == 0) {
+ /*
+ * Change FIFO interrupt trigger level to wait
+ * until FIFO is empty.
+ */
+ devpriv->daccon = COMBINE(devpriv->daccon,
+ PCI224_DACCON_FIFOINTR_EMPTY,
+ PCI224_DACCON_FIFOINTR_MASK);
+ outw(devpriv->daccon,
+ dev->iobase + PCI224_DACCON);
+ }
+ }
+ if ((devpriv->daccon & PCI224_DACCON_TRIG_MASK) ==
+ PCI224_DACCON_TRIG_NONE) {
+ unsigned short trig;
+
+ /*
+ * This is the initial DAC FIFO interrupt at the
+ * start of the acquisition. The DAC's scan trigger
+ * has been set to 'none' up until now.
+ *
+ * Now that data has been written to the FIFO, the
+ * DAC's scan trigger source can be set to the
+ * correct value.
+ *
+ * BUG: The first scan will be triggered immediately
+ * if the scan trigger source is at logic level 1.
+ */
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ trig = PCI224_DACCON_TRIG_Z2CT0;
+ } else {
+ /* cmd->scan_begin_src == TRIG_EXT */
+ if (cmd->scan_begin_arg & CR_INVERT) {
+ trig = PCI224_DACCON_TRIG_EXTN;
+ } else {
+ trig = PCI224_DACCON_TRIG_EXTP;
+ }
+ }
+ devpriv->daccon = COMBINE(devpriv->daccon, trig,
+ PCI224_DACCON_TRIG_MASK);
+ outw(devpriv->daccon, dev->iobase + PCI224_DACCON);
+ }
+ if (s->async->events) {
+ comedi_event(dev, s);
+ }
+}
+
+/*
+ * Internal trigger function to start acquisition on AO subdevice.
+ */
+static int
+pci224_ao_inttrig_start(struct comedi_device * dev, struct comedi_subdevice * s,
+ unsigned int trignum)
+{
+ if (trignum != 0)
+ return -EINVAL;
+
+ s->async->inttrig = NULLFUNC;
+ pci224_ao_start(dev, s);
+
+ return 1;
+}
+
+#define MAX_SCAN_PERIOD 0xFFFFFFFFU
+#define MIN_SCAN_PERIOD 2500
+#define CONVERT_PERIOD 625
+
+/*
+ * 'do_cmdtest' function for AO subdevice.
+ */
+static int
+pci224_ao_cmdtest(struct comedi_device * dev, struct comedi_subdevice * s, struct comedi_cmd * cmd)
+{
+ int err = 0;
+ unsigned int tmp;
+
+ /* Step 1: make sure trigger sources are trivially valid. */
+
+ tmp = cmd->start_src;
+ cmd->start_src &= TRIG_INT | TRIG_EXT;
+ if (!cmd->start_src || tmp != cmd->start_src)
+ err++;
+
+ tmp = cmd->scan_begin_src;
+ cmd->scan_begin_src &= TRIG_EXT | TRIG_TIMER;
+ if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
+ err++;
+
+ tmp = cmd->convert_src;
+ cmd->convert_src &= TRIG_NOW;
+ if (!cmd->convert_src || tmp != cmd->convert_src)
+ err++;
+
+ tmp = cmd->scan_end_src;
+ cmd->scan_end_src &= TRIG_COUNT;
+ if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
+ err++;
+
+ tmp = cmd->stop_src;
+ cmd->stop_src &= TRIG_COUNT | TRIG_EXT | TRIG_NONE;
+ if (!cmd->stop_src || tmp != cmd->stop_src)
+ err++;
+
+ if (err)
+ return 1;
+
+ /* Step 2: make sure trigger sources are unique and mutually
+ * compatible. */
+
+ /* these tests are true if more than one _src bit is set */
+ if ((cmd->start_src & (cmd->start_src - 1)) != 0)
+ err++;
+ if ((cmd->scan_begin_src & (cmd->scan_begin_src - 1)) != 0)
+ err++;
+ if ((cmd->convert_src & (cmd->convert_src - 1)) != 0)
+ err++;
+ if ((cmd->scan_end_src & (cmd->scan_end_src - 1)) != 0)
+ err++;
+ if ((cmd->stop_src & (cmd->stop_src - 1)) != 0)
+ err++;
+
+ /* There's only one external trigger signal (which makes these
+ * tests easier). Only one thing can use it. */
+ tmp = 0;
+ if (cmd->start_src & TRIG_EXT)
+ tmp++;
+ if (cmd->scan_begin_src & TRIG_EXT)
+ tmp++;
+ if (cmd->stop_src & TRIG_EXT)
+ tmp++;
+ if (tmp > 1)
+ err++;
+
+ if (err)
+ return 2;
+
+ /* Step 3: make sure arguments are trivially compatible. */
+
+ switch (cmd->start_src) {
+ case TRIG_INT:
+ if (cmd->start_arg != 0) {
+ cmd->start_arg = 0;
+ err++;
+ }
+ break;
+ case TRIG_EXT:
+ /* Force to external trigger 0. */
+ if ((cmd->start_arg & ~CR_FLAGS_MASK) != 0) {
+ cmd->start_arg = COMBINE(cmd->start_arg, 0,
+ ~CR_FLAGS_MASK);
+ err++;
+ }
+ /* The only flag allowed is CR_EDGE, which is ignored. */
+ if ((cmd->start_arg & CR_FLAGS_MASK & ~CR_EDGE) != 0) {
+ cmd->start_arg = COMBINE(cmd->start_arg, 0,
+ CR_FLAGS_MASK & ~CR_EDGE);
+ err++;
+ }
+ break;
+ }
+
+ switch (cmd->scan_begin_src) {
+ case TRIG_TIMER:
+ if (cmd->scan_begin_arg > MAX_SCAN_PERIOD) {
+ cmd->scan_begin_arg = MAX_SCAN_PERIOD;
+ err++;
+ }
+ tmp = cmd->chanlist_len * CONVERT_PERIOD;
+ if (tmp < MIN_SCAN_PERIOD) {
+ tmp = MIN_SCAN_PERIOD;
+ }
+ if (cmd->scan_begin_arg < tmp) {
+ cmd->scan_begin_arg = tmp;
+ err++;
+ }
+ break;
+ case TRIG_EXT:
+ /* Force to external trigger 0. */
+ if ((cmd->scan_begin_arg & ~CR_FLAGS_MASK) != 0) {
+ cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0,
+ ~CR_FLAGS_MASK);
+ err++;
+ }
+ /* Only allow flags CR_EDGE and CR_INVERT. Ignore CR_EDGE. */
+ if ((cmd->scan_begin_arg & CR_FLAGS_MASK &
+ ~(CR_EDGE | CR_INVERT)) != 0) {
+ cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0,
+ CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT));
+ err++;
+ }
+ break;
+ }
+
+ /* cmd->convert_src == TRIG_NOW */
+ if (cmd->convert_arg != 0) {
+ cmd->convert_arg = 0;
+ err++;
+ }
+
+ /* cmd->scan_end_arg == TRIG_COUNT */
+ if (cmd->scan_end_arg != cmd->chanlist_len) {
+ cmd->scan_end_arg = cmd->chanlist_len;
+ err++;
+ }
+
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ /* Any count allowed. */
+ break;
+ case TRIG_EXT:
+ /* Force to external trigger 0. */
+ if ((cmd->stop_arg & ~CR_FLAGS_MASK) != 0) {
+ cmd->stop_arg = COMBINE(cmd->stop_arg, 0,
+ ~CR_FLAGS_MASK);
+ err++;
+ }
+ /* The only flag allowed is CR_EDGE, which is ignored. */
+ if ((cmd->stop_arg & CR_FLAGS_MASK & ~CR_EDGE) != 0) {
+ cmd->stop_arg = COMBINE(cmd->stop_arg, 0,
+ CR_FLAGS_MASK & ~CR_EDGE);
+ }
+ break;
+ case TRIG_NONE:
+ if (cmd->stop_arg != 0) {
+ cmd->stop_arg = 0;
+ err++;
+ }
+ break;
+ }
+
+ if (err)
+ return 3;
+
+ /* Step 4: fix up any arguments. */
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ unsigned int div1, div2, round;
+ int round_mode = cmd->flags & TRIG_ROUND_MASK;
+
+ tmp = cmd->scan_begin_arg;
+ /* Check whether to use a single timer. */
+ switch (round_mode) {
+ case TRIG_ROUND_NEAREST:
+ default:
+ round = TIMEBASE_10MHZ / 2;
+ break;
+ case TRIG_ROUND_DOWN:
+ round = 0;
+ break;
+ case TRIG_ROUND_UP:
+ round = TIMEBASE_10MHZ - 1;
+ break;
+ }
+ /* Be careful to avoid overflow! */
+ div2 = cmd->scan_begin_arg / TIMEBASE_10MHZ;
+ div2 += (round + cmd->scan_begin_arg % TIMEBASE_10MHZ) /
+ TIMEBASE_10MHZ;
+ if (div2 <= 0x10000) {
+ /* A single timer will suffice. */
+ if (div2 < 2)
+ div2 = 2;
+ cmd->scan_begin_arg = div2 * TIMEBASE_10MHZ;
+ if (cmd->scan_begin_arg < div2 ||
+ cmd->scan_begin_arg < TIMEBASE_10MHZ) {
+ /* Overflow! */
+ cmd->scan_begin_arg = MAX_SCAN_PERIOD;
+ }
+ } else {
+ /* Use two timers. */
+ div1 = devpriv->cached_div1;
+ div2 = devpriv->cached_div2;
+ pci224_cascade_ns_to_timer(TIMEBASE_10MHZ, &div1, &div2,
+ &cmd->scan_begin_arg, round_mode);
+ devpriv->cached_div1 = div1;
+ devpriv->cached_div2 = div2;
+ }
+ if (tmp != cmd->scan_begin_arg) {
+ err++;
+ }
+ }
+
+ if (err)
+ return 4;
+
+ /* Step 5: check channel list. */
+
+ if (cmd->chanlist && (cmd->chanlist_len > 0)) {
+ unsigned int range;
+ enum { range_err = 1, dupchan_err = 2, };
+ unsigned errors;
+ unsigned int n;
+ unsigned int ch;
+
+ /*
+ * Check all channels have the same range index. Don't care
+ * about analogue reference, as we can't configure it.
+ *
+ * Check the list has no duplicate channels.
+ */
+ range = CR_RANGE(cmd->chanlist[0]);
+ errors = 0;
+ tmp = 0;
+ for (n = 0; n < cmd->chanlist_len; n++) {
+ ch = CR_CHAN(cmd->chanlist[n]);
+ if (tmp & (1U << ch)) {
+ errors |= dupchan_err;
+ }
+ tmp |= (1U << ch);
+ if (CR_RANGE(cmd->chanlist[n]) != range) {
+ errors |= range_err;
+ }
+ }
+ if (errors) {
+ if (errors & dupchan_err) {
+ DPRINTK("comedi%d: " DRIVER_NAME
+ ": ao_cmdtest: "
+ "entries in chanlist must contain no "
+ "duplicate channels\n", dev->minor);
+ }
+ if (errors & range_err) {
+ DPRINTK("comedi%d: " DRIVER_NAME
+ ": ao_cmdtest: "
+ "entries in chanlist must all have "
+ "the same range index\n", dev->minor);
+ }
+ err++;
+ }
+ }
+
+ if (err)
+ return 5;
+
+ return 0;
+}
+
+/*
+ * 'do_cmd' function for AO subdevice.
+ */
+static int pci224_ao_cmd(struct comedi_device * dev, struct comedi_subdevice * s)
+{
+ struct comedi_cmd *cmd = &s->async->cmd;
+ int range;
+ unsigned int i, j;
+ unsigned int ch;
+ unsigned int rank;
+ unsigned long flags;
+
+ /* Cannot handle null/empty chanlist. */
+ if (cmd->chanlist == NULL || cmd->chanlist_len == 0) {
+ return -EINVAL;
+ }
+
+ /* Determine which channels are enabled and their load order. */
+ devpriv->ao_enab = 0;
+
+ for (i = 0; i < cmd->chanlist_len; i++) {
+ ch = CR_CHAN(cmd->chanlist[i]);
+ devpriv->ao_enab |= 1U << ch;
+ rank = 0;
+ for (j = 0; j < cmd->chanlist_len; j++) {
+ if (CR_CHAN(cmd->chanlist[j]) < ch) {
+ rank++;
+ }
+ }
+ devpriv->ao_scan_order[rank] = i;
+ }
+
+ /* Set enabled channels. */
+ outw(devpriv->ao_enab, dev->iobase + PCI224_DACCEN);
+
+ /* Determine range and polarity. All channels the same. */
+ range = CR_RANGE(cmd->chanlist[0]);
+
+ /*
+ * Set DAC range and polarity.
+ * Set DAC scan trigger source to 'none'.
+ * Set DAC FIFO interrupt trigger level to 'not half full'.
+ * Reset DAC FIFO.
+ *
+ * N.B. DAC FIFO interrupts are currently disabled.
+ */
+ devpriv->daccon = COMBINE(devpriv->daccon,
+ (devpriv->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));
+ outw(devpriv->daccon | PCI224_DACCON_FIFORESET,
+ dev->iobase + PCI224_DACCON);
+
+ if (cmd->scan_begin_src == TRIG_TIMER) {
+ unsigned int div1, div2, round;
+ unsigned int ns = cmd->scan_begin_arg;
+ int round_mode = cmd->flags & TRIG_ROUND_MASK;
+
+ /* Check whether to use a single timer. */
+ switch (round_mode) {
+ case TRIG_ROUND_NEAREST:
+ default:
+ round = TIMEBASE_10MHZ / 2;
+ break;
+ case TRIG_ROUND_DOWN:
+ round = 0;
+ break;
+ case TRIG_ROUND_UP:
+ round = TIMEBASE_10MHZ - 1;
+ break;
+ }
+ /* Be careful to avoid overflow! */
+ div2 = cmd->scan_begin_arg / TIMEBASE_10MHZ;
+ div2 += (round + cmd->scan_begin_arg % TIMEBASE_10MHZ) /
+ TIMEBASE_10MHZ;
+ if (div2 <= 0x10000) {
+ /* A single timer will suffice. */
+ if (div2 < 2)
+ div2 = 2;
+ div2 &= 0xffff;
+ div1 = 1; /* Flag that single timer to be used. */
+ } else {
+ /* Use two timers. */
+ div1 = devpriv->cached_div1;
+ div2 = devpriv->cached_div2;
+ pci224_cascade_ns_to_timer(TIMEBASE_10MHZ, &div1, &div2,
+ &ns, round_mode);
+ }
+
+ /*
+ * The output of timer Z2-0 will be used as the scan trigger
+ * source.
+ */
+ /* Make sure Z2-0 is gated on. */
+ outb(GAT_CONFIG(0, GAT_VCC),
+ devpriv->iobase1 + PCI224_ZGAT_SCE);
+ if (div1 == 1) {
+ /* Not cascading. Z2-0 needs 10 MHz clock. */
+ outb(CLK_CONFIG(0, CLK_10MHZ),
+ devpriv->iobase1 + PCI224_ZCLK_SCE);
+ } else {
+ /* Cascading with Z2-2. */
+ /* Make sure Z2-2 is gated on. */
+ outb(GAT_CONFIG(2, GAT_VCC),
+ devpriv->iobase1 + PCI224_ZGAT_SCE);
+ /* Z2-2 needs 10 MHz clock. */
+ outb(CLK_CONFIG(2, CLK_10MHZ),
+ devpriv->iobase1 + PCI224_ZCLK_SCE);
+ /* Load Z2-2 mode (2) and counter (div1). */
+ i8254_load(devpriv->iobase1 + PCI224_Z2_CT0, 0,
+ 2, div1, 2);
+ /* Z2-0 is clocked from Z2-2's output. */
+ outb(CLK_CONFIG(0, CLK_OUTNM1),
+ devpriv->iobase1 + PCI224_ZCLK_SCE);
+ }
+ /* Load Z2-0 mode (2) and counter (div2). */
+ i8254_load(devpriv->iobase1 + PCI224_Z2_CT0, 0, 0, div2, 2);
+ }
+
+ /*
+ * Sort out end of acquisition.
+ */
+ switch (cmd->stop_src) {
+ case TRIG_COUNT:
+ /* Fixed number of scans. */
+ devpriv->ao_stop_continuous = 0;
+ devpriv->ao_stop_count = cmd->stop_arg;
+ break;
+ default:
+ /* Continuous scans. */
+ devpriv->ao_stop_continuous = 1;
+ devpriv->ao_stop_count = 0;
+ break;
+ }
+
+ /*
+ * Sort out start of acquisition.
+ */
+ switch (cmd->start_src) {
+ case TRIG_INT:
+ comedi_spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ s->async->inttrig = &pci224_ao_inttrig_start;
+ comedi_spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ break;
+ case TRIG_EXT:
+ /* Enable external interrupt trigger to start acquisition. */
+ comedi_spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ devpriv->intsce |= PCI224_INTR_EXT;
+ outb(devpriv->intsce, devpriv->iobase1 + PCI224_INT_SCE);
+ comedi_spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ break;
+ }
+
+ return 0;
+}
+
+/*
+ * 'cancel' function for AO subdevice.
+ */
+static int pci224_ao_cancel(struct comedi_device * dev, struct comedi_subdevice * s)
+{
+ pci224_ao_stop(dev, s);
+ return 0;
+}
+
+/*
+ * 'munge' data for AO command.
+ */
+static void
+pci224_ao_munge(struct comedi_device * dev, struct comedi_subdevice * s, void *data,
+ unsigned int num_bytes, unsigned int chan_index)
+{
+ struct comedi_async *async = s->async;
+ short *array = data;
+ unsigned int length = num_bytes / sizeof(*array);
+ unsigned int offset;
+ unsigned int shift;
+ unsigned int i;
+
+ /* The hardware expects 16-bit numbers. */
+ shift = 16 - thisboard->ao_bits;
+ /* Channels will be all bipolar or all unipolar. */
+ if ((devpriv->hwrange[CR_RANGE(async->cmd.chanlist[0])] &
+ PCI224_DACCON_POLAR_MASK) == PCI224_DACCON_POLAR_UNI) {
+ /* Unipolar */
+ offset = 0;
+ } else {
+ /* Bipolar */
+ offset = 32768;
+ }
+ /* Munge the data. */
+ for (i = 0; i < length; i++) {
+ array[i] = (array[i] << shift) - offset;
+ }
+}
+
+/*
+ * Interrupt handler.
+ */
+static irqreturn_t pci224_interrupt(int irq, void *d PT_REGS_ARG)
+{
+ struct comedi_device *dev = d;
+ struct comedi_subdevice *s = &dev->subdevices[0];
+ struct comedi_cmd *cmd;
+ unsigned char intstat, valid_intstat;
+ unsigned char curenab;
+ int retval = 0;
+ unsigned long flags;
+
+ intstat = inb(devpriv->iobase1 + PCI224_INT_SCE) & 0x3F;
+ if (intstat) {
+ retval = 1;
+ comedi_spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ valid_intstat = devpriv->intsce & intstat;
+ /* Temporarily disable interrupt sources. */
+ curenab = devpriv->intsce & ~intstat;
+ outb(curenab, devpriv->iobase1 + PCI224_INT_SCE);
+ devpriv->intr_running = 1;
+ devpriv->intr_cpuid = THISCPU;
+ comedi_spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ if (valid_intstat != 0) {
+ cmd = &s->async->cmd;
+ if (valid_intstat & PCI224_INTR_EXT) {
+ devpriv->intsce &= ~PCI224_INTR_EXT;
+ if (cmd->start_src == TRIG_EXT) {
+ pci224_ao_start(dev, s);
+ } else if (cmd->stop_src == TRIG_EXT) {
+ pci224_ao_stop(dev, s);
+ }
+ }
+ if (valid_intstat & PCI224_INTR_DAC) {
+ pci224_ao_handle_fifo(dev, s);
+ }
+ }
+ /* Reenable interrupt sources. */
+ comedi_spin_lock_irqsave(&devpriv->ao_spinlock, flags);
+ if (curenab != devpriv->intsce) {
+ outb(devpriv->intsce,
+ devpriv->iobase1 + PCI224_INT_SCE);
+ }
+ devpriv->intr_running = 0;
+ comedi_spin_unlock_irqrestore(&devpriv->ao_spinlock, flags);
+ }
+ return IRQ_RETVAL(retval);
+}
+
+/*
+ * This function looks for a PCI device matching the requested board name,
+ * bus and slot.
+ */
+static int
+pci224_find_pci(struct comedi_device * dev, int bus, int slot,
+ struct pci_dev **pci_dev_p)
+{
+ struct pci_dev *pci_dev = NULL;
+
+ *pci_dev_p = NULL;
+
+ /* Look for matching PCI device. */
+ for (pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID, NULL);
+ pci_dev != NULL;
+ pci_dev = pci_get_device(PCI_VENDOR_ID_AMPLICON, PCI_ANY_ID,
+ pci_dev)) {
+ /* If bus/slot specified, check them. */
+ if (bus || slot) {
+ if (bus != pci_dev->bus->number
+ || slot != PCI_SLOT(pci_dev->devfn))
+ continue;
+ }
+ if (thisboard->model == any_model) {
+ /* Match any supported model. */
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pci224_boards); i++) {
+ if (pci_dev->device == pci224_boards[i].devid) {
+ /* Change board_ptr to matched board. */
+ dev->board_ptr = &pci224_boards[i];
+ break;
+ }
+ }
+ if (i == ARRAY_SIZE(pci224_boards))
+ continue;
+ } else {
+ /* Match specific model name. */
+ if (thisboard->devid != pci_dev->device)
+ continue;
+ }
+
+ /* Found a match. */
+ *pci_dev_p = pci_dev;
+ return 0;
+ }
+ /* No match found. */
+ if (bus || slot) {
+ printk(KERN_ERR "comedi%d: error! "
+ "no %s found at pci %02x:%02x!\n",
+ dev->minor, thisboard->name, bus, slot);
+ } else {
+ printk(KERN_ERR "comedi%d: error! no %s found!\n",
+ dev->minor, thisboard->name);
+ }
+ return -EIO;
+}
+
+/*
+ * Attach is called by the Comedi core to configure the driver
+ * for a particular board. If you specified a board_name array
+ * in the driver structure, dev->board_ptr contains that
+ * address.
+ */
+static int pci224_attach(struct comedi_device * dev, struct comedi_devconfig * it)
+{
+ struct comedi_subdevice *s;
+ struct pci_dev *pci_dev;
+ unsigned int irq;
+ int bus = 0, slot = 0;
+ unsigned n;
+ int ret;
+
+ printk(KERN_DEBUG "comedi%d: %s: attach\n", dev->minor, DRIVER_NAME);
+
+ bus = it->options[0];
+ slot = it->options[1];
+ if ((ret = alloc_private(dev, sizeof(struct pci224_private))) < 0) {
+ printk(KERN_ERR "comedi%d: error! out of memory!\n",
+ dev->minor);
+ return ret;
+ }
+ if ((ret = pci224_find_pci(dev, bus, slot, &pci_dev)) < 0)
+ return ret;
+ devpriv->pci_dev = pci_dev;
+
+ if ((ret = comedi_pci_enable(pci_dev, DRIVER_NAME)) < 0) {
+ printk(KERN_ERR
+ "comedi%d: error! cannot enable PCI device "
+ "and request regions!\n", dev->minor);
+ return ret;
+ }
+ spin_lock_init(&devpriv->ao_spinlock);
+
+ devpriv->iobase1 = pci_resource_start(pci_dev, 2);
+ dev->iobase = pci_resource_start(pci_dev, 3);
+ irq = pci_dev->irq;
+
+ /* Allocate readback buffer for AO channels. */
+ devpriv->ao_readback = kmalloc(sizeof(devpriv->ao_readback[0]) *
+ thisboard->ao_chans, GFP_KERNEL);
+ if (!devpriv->ao_readback) {
+ return -ENOMEM;
+ }
+
+ /* Allocate buffer to hold values for AO channel scan. */
+ devpriv->ao_scan_vals = kmalloc(sizeof(devpriv->ao_scan_vals[0]) *
+ thisboard->ao_chans, GFP_KERNEL);
+ if (!devpriv->ao_scan_vals) {
+ return -ENOMEM;
+ }
+
+ /* Allocate buffer to hold AO channel scan order. */
+ devpriv->ao_scan_order = kmalloc(sizeof(devpriv->ao_scan_order[0]) *
+ thisboard->ao_chans, GFP_KERNEL);
+ if (!devpriv->ao_scan_order) {
+ return -ENOMEM;
+ }
+
+ /* Disable interrupt sources. */
+ devpriv->intsce = 0;
+ outb(0, devpriv->iobase1 + PCI224_INT_SCE);
+
+ /* Initialize the DAC hardware. */
+ outw(PCI224_DACCON_GLOBALRESET, dev->iobase + PCI224_DACCON);
+ outw(0, dev->iobase + PCI224_DACCEN);
+ outw(0, dev->iobase + PCI224_FIFOSIZ);
+ devpriv->daccon = (PCI224_DACCON_TRIG_SW | PCI224_DACCON_POLAR_BI |
+ PCI224_DACCON_FIFOENAB | PCI224_DACCON_FIFOINTR_EMPTY);
+ outw(devpriv->daccon | PCI224_DACCON_FIFORESET,
+ dev->iobase + PCI224_DACCON);
+
+ /* Allocate subdevices. There is only one! */
+ if ((ret = alloc_subdevices(dev, 1)) < 0) {
+ printk(KERN_ERR "comedi%d: error! out of memory!\n",
+ dev->minor);
+ return ret;
+ }
+
+ s = dev->subdevices + 0;
+ /* Analog output subdevice. */
+ s->type = COMEDI_SUBD_AO;
+ s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
+ s->n_chan = thisboard->ao_chans;
+ s->maxdata = (1 << thisboard->ao_bits) - 1;
+ s->insn_write = &pci224_ao_insn_write;
+ s->insn_read = &pci224_ao_insn_read;
+ s->len_chanlist = s->n_chan;
+
+ dev->write_subdev = s;
+ s->do_cmd = &pci224_ao_cmd;
+ s->do_cmdtest = &pci224_ao_cmdtest;
+ 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;
+
+ s->range_table_list = range_table_list =
+ kmalloc(sizeof(struct comedi_lrange *) * s->n_chan,
+ GFP_KERNEL);
+ if (!s->range_table_list) {
+ return -ENOMEM;
+ }
+ for (n = 2; n < 3 + s->n_chan; n++) {
+ if (it->options[n] < 0 || it->options[n] > 1) {
+ printk(KERN_WARNING "comedi%d: %s: warning! "
+ "bad options[%u]=%d\n",
+ dev->minor, DRIVER_NAME, n,
+ it->options[n]);
+ }
+ }
+ for (n = 0; n < s->n_chan; n++) {
+ if (n < COMEDI_NDEVCONFOPTS - 3 &&
+ it->options[3 + n] == 1) {
+ if (it->options[2] == 1) {
+ range_table_list[n] = &range_pci234_ext;
+ } else {
+ range_table_list[n] = &range_bipolar5;
+ }
+ } else {
+ if (it->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 (it->options[2] == 1) {
+ s->range_table = &range_pci224_external;
+ devpriv->hwrange = hwrange_pci224_external;
+ } else {
+ if (it->options[2] != 0) {
+ printk(KERN_WARNING "comedi%d: %s: warning! "
+ "bad options[2]=%d\n",
+ dev->minor, DRIVER_NAME,
+ it->options[2]);
+ }
+ s->range_table = &range_pci224_internal;
+ devpriv->hwrange = hwrange_pci224_internal;
+ }
+ }
+
+ dev->board_name = thisboard->name;
+
+ if (irq) {
+ ret = comedi_request_irq(irq, pci224_interrupt, IRQF_SHARED,
+ DRIVER_NAME, dev);
+ if (ret < 0) {
+ printk(KERN_ERR "comedi%d: error! "
+ "unable to allocate irq %u\n", dev->minor, irq);
+ return ret;
+ } else {
+ dev->irq = irq;
+ }
+ }
+
+ printk(KERN_INFO "comedi%d: %s ", dev->minor, dev->board_name);
+ printk("(pci %s) ", pci_name(pci_dev));
+ if (irq) {
+ printk("(irq %u%s) ", irq, (dev->irq ? "" : " UNAVAILABLE"));
+ } else {
+ printk("(no irq) ");
+ }
+
+ printk("attached\n");
+
+ return 1;
+}
+
+/*
+ * _detach is called to deconfigure a device. It should deallocate
+ * resources.
+ * This function is also called when _attach() fails, so it should be
+ * careful not to release resources that were not necessarily
+ * allocated by _attach(). dev->private and dev->subdevices are
+ * deallocated automatically by the core.
+ */
+static int pci224_detach(struct comedi_device * dev)
+{
+ printk(KERN_DEBUG "comedi%d: %s: detach\n", dev->minor, DRIVER_NAME);
+
+ if (dev->irq) {
+ comedi_free_irq(dev->irq, dev);
+ }
+ if (dev->subdevices) {
+ struct comedi_subdevice *s;
+
+ s = dev->subdevices + 0;
+ /* AO subdevice */
+ if (s->range_table_list) {
+ kfree(s->range_table_list);
+ }
+ }
+ if (devpriv) {
+ if (devpriv->ao_readback) {
+ kfree(devpriv->ao_readback);
+ }
+ if (devpriv->ao_scan_vals) {
+ kfree(devpriv->ao_scan_vals);
+ }
+ if (devpriv->ao_scan_order) {
+ kfree(devpriv->ao_scan_order);
+ }
+ if (devpriv->pci_dev) {
+ if (dev->iobase) {
+ comedi_pci_disable(devpriv->pci_dev);
+ }
+ pci_dev_put(devpriv->pci_dev);
+ }
+ }
+ if (dev->board_name) {
+ printk(KERN_INFO "comedi%d: %s removed\n",
+ dev->minor, dev->board_name);
+ }
+
+ return 0;
+}