text: // SPDX-License-Identifier: GPL-2.0+
  /*
   * comedi/drivers/amplc_pci230.c
   * Driver for Amplicon PCI230 and PCI260 Multifunction I/O boards.
   *
   * Copyright (C) 2001 Allan Willcox <allanwillcox@ozemail.com.au>
   *
   * COMEDI - Linux Control and Measurement Device Interface
   * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
   */
  
  /*
   * Driver: amplc_pci230
   * Description: Amplicon PCI230, PCI260 Multifunction I/O boards
   * Author: Allan Willcox <allanwillcox@ozemail.com.au>,
   *   Steve D Sharples <steve.sharples@nottingham.ac.uk>,
   *   Ian Abbott <abbotti@mev.co.uk>
   * Updated: Mon, 01 Sep 2014 10:09:16 +0000
   * Devices: [Amplicon] PCI230 (amplc_pci230), PCI230+, PCI260, PCI260+
   * Status: works
   *
   * Configuration options:
   *   none
   *
   * Manual configuration of PCI cards is not supported; they are configured
   * automatically.
   *
   * The PCI230+ and PCI260+ have the same PCI device IDs as the PCI230 and
   * PCI260, but can be distinguished by the size of the PCI regions.  A
   * card will be configured as a "+" model if detected as such.
   *
   * Subdevices:
   *
   *                 PCI230(+)    PCI260(+)
   *                 ---------    ---------
   *   Subdevices       3            1
   *         0          AI           AI
   *         1          AO
   *         2          DIO
   *
   * AI Subdevice:
   *
   *   The AI subdevice has 16 single-ended channels or 8 differential
   *   channels.
   *
   *   The PCI230 and PCI260 cards have 12-bit resolution.  The PCI230+ and
   *   PCI260+ cards have 16-bit resolution.
   *
   *   For differential mode, use inputs 2N and 2N+1 for channel N (e.g. use
   *   inputs 14 and 15 for channel 7).  If the card is physically a PCI230
   *   or PCI260 then it actually uses a "pseudo-differential" mode where the
   *   inputs are sampled a few microseconds apart.  The PCI230+ and PCI260+
   *   use true differential sampling.  Another difference is that if the
   *   card is physically a PCI230 or PCI260, the inverting input is 2N,
   *   whereas for a PCI230+ or PCI260+ the inverting input is 2N+1.  So if a
   *   PCI230 is physically replaced by a PCI230+ (or a PCI260 with a
   *   PCI260+) and differential mode is used, the differential inputs need
   *   to be physically swapped on the connector.
   *
   *   The following input ranges are supported:
   *
   *     0 => [-10, +10] V
   *     1 => [-5, +5] V
   *     2 => [-2.5, +2.5] V
   *     3 => [-1.25, +1.25] V
   *     4 => [0, 10] V
   *     5 => [0, 5] V
   *     6 => [0, 2.5] V
   *
   * AI Commands:
   *
   *   +=========+==============+===========+============+==========+
   *   |start_src|scan_begin_src|convert_src|scan_end_src| stop_src |
   *   +=========+==============+===========+============+==========+
   *   |TRIG_NOW | TRIG_FOLLOW  |TRIG_TIMER | TRIG_COUNT |TRIG_NONE |
   *   |TRIG_INT |              |TRIG_EXT(3)|            |TRIG_COUNT|
   *   |         |              |TRIG_INT   |            |          |
   *   |         |--------------|-----------|            |          |
   *   |         | TRIG_TIMER(1)|TRIG_TIMER |            |          |
   *   |         | TRIG_EXT(2)  |           |            |          |
   *   |         | TRIG_INT     |           |            |          |
   *   +---------+--------------+-----------+------------+----------+
   *
   *   Note 1: If AI command and AO command are used simultaneously, only
   *           one may have scan_begin_src == TRIG_TIMER.
   *
   *   Note 2: For PCI230 and PCI230+, scan_begin_src == TRIG_EXT uses
   *           DIO channel 16 (pin 49) which will need to be configured as
   *           a digital input.  For PCI260+, the EXTTRIG/EXTCONVCLK input
   *           (pin 17) is used instead.  For PCI230, scan_begin_src ==
   *           TRIG_EXT is not supported.  The trigger is a rising edge
   *           on the input.
   *
   *   Note 3: For convert_src == TRIG_EXT, the EXTTRIG/EXTCONVCLK input
   *           (pin 25 on PCI230(+), pin 17 on PCI260(+)) is used.  The
   *           convert_arg value is interpreted as follows:
   *
   *             convert_arg == (CR_EDGE | 0) => rising edge
   *             convert_arg == (CR_EDGE | CR_INVERT | 0) => falling edge
   *             convert_arg == 0 => falling edge (backwards compatibility)
   *             convert_arg == 1 => rising edge (backwards compatibility)
   *
   *   All entries in the channel list must use the same analogue reference.
   *   If the analogue reference is not AREF_DIFF (not differential) each
   *   pair of channel numbers (0 and 1, 2 and 3, etc.) must use the same
   *   input range.  The input ranges used in the sequence must be all
   *   bipolar (ranges 0 to 3) or all unipolar (ranges 4 to 6).  The channel
   *   sequence must consist of 1 or more identical subsequences.  Within the
   *   subsequence, channels must be in ascending order with no repeated
   *   channels.  For example, the following sequences are valid: 0 1 2 3
   *   (single valid subsequence), 0 2 3 5 0 2 3 5 (repeated valid
   *   subsequence), 1 1 1 1 (repeated valid subsequence).  The following
   *   sequences are invalid: 0 3 2 1 (invalid subsequence), 0 2 3 5 0 2 3
   *   (incompletely repeated subsequence).  Some versions of the PCI230+ and
   *   PCI260+ have a bug that requires a subsequence longer than one entry
   *   long to include channel 0.
   *
   * AO Subdevice:
   *
   *   The AO subdevice has 2 channels with 12-bit resolution.
   *   The following output ranges are supported:
   *     0 => [0, 10] V
   *     1 => [-10, +10] V
   *
   * AO Commands:
   *
   *   +=========+==============+===========+============+==========+
   *   |start_src|scan_begin_src|convert_src|scan_end_src| stop_src |
   *   +=========+==============+===========+============+==========+
   *   |TRIG_INT | TRIG_TIMER(1)| TRIG_NOW  | TRIG_COUNT |TRIG_NONE |
   *   |         | TRIG_EXT(2)  |           |            |TRIG_COUNT|
   *   |         | TRIG_INT     |           |            |          |
   *   +---------+--------------+-----------+------------+----------+
   *
   *   Note 1: If AI command and AO command are used simultaneously, only
   *           one may have scan_begin_src == TRIG_TIMER.
   *
   *   Note 2: scan_begin_src == TRIG_EXT is only supported if the card is
   *           configured as a PCI230+ and is only supported on later
   *           versions of the card.  As a card configured as a PCI230+ is
   *           not guaranteed to support external triggering, please consider
   *           this support to be a bonus.  It uses the EXTTRIG/ EXTCONVCLK
   *           input (PCI230+ pin 25).  Triggering will be on the rising edge
   *           unless the CR_INVERT flag is set in scan_begin_arg.
   *
   *   The channels in the channel sequence must be in ascending order with
   *   no repeats.  All entries in the channel sequence must use the same
   *   output range.
   *
   * DIO Subdevice:
   *
   *   The DIO subdevice is a 8255 chip providing 24 DIO channels.  The DIO
   *   channels are configurable as inputs or outputs in four groups:
   *
   *     Port A  - channels  0 to  7
   *     Port B  - channels  8 to 15
   *     Port CL - channels 16 to 19
   *     Port CH - channels 20 to 23
   *
   *   Only mode 0 of the 8255 chip is supported.
   *
   *   Bit 0 of port C (DIO channel 16) is also used as an external scan
   *   trigger input for AI commands on PCI230 and PCI230+, so would need to
   *   be configured as an input to use it for that purpose.
   */
  
  /*
   * Extra triggered scan functionality, interrupt bug-fix added by Steve
   * Sharples.  Support for PCI230+/260+, more triggered scan functionality,
   * and workarounds for (or detection of) various hardware problems added
   * by Ian Abbott.
   */
  
  #include <linux/module.h>
  #include <linux/delay.h>
  #include <linux/interrupt.h>
  
  #include "../comedi_pci.h"
  
  #include "comedi_8254.h"
  #include "8255.h"
  
  /*
   * PCI230 PCI configuration register information
   */
  #define PCI_DEVICE_ID_PCI230 0x0000
  #define PCI_DEVICE_ID_PCI260 0x0006
  
  /*
   * PCI230 i/o space 1 registers.
   */
  #define PCI230_PPI_X_BASE	0x00	/* User PPI (82C55) base */
  #define PCI230_PPI_X_A		0x00	/* User PPI (82C55) port A */
  #define PCI230_PPI_X_B		0x01	/* User PPI (82C55) port B */
  #define PCI230_PPI_X_C		0x02	/* User PPI (82C55) port C */
  #define PCI230_PPI_X_CMD	0x03	/* User PPI (82C55) control word */
  #define PCI230_Z2_CT_BASE	0x14	/* 82C54 counter/timer base */
  #define PCI230_ZCLK_SCE		0x1A	/* Group Z Clock Configuration */
  #define PCI230_ZGAT_SCE		0x1D	/* Group Z Gate Configuration */
  #define PCI230_INT_SCE		0x1E	/* Interrupt source mask (w) */
  #define PCI230_INT_STAT		0x1E	/* Interrupt status (r) */
  
  /*
   * PCI230 i/o space 2 registers.
   */
  #define PCI230_DACCON		0x00	/* DAC control */
  #define PCI230_DACOUT1		0x02	/* DAC channel 0 (w) */
  #define PCI230_DACOUT2		0x04	/* DAC channel 1 (w) (not FIFO mode) */
  #define PCI230_ADCDATA		0x08	/* ADC data (r) */
  #define PCI230_ADCSWTRIG	0x08	/* ADC software trigger (w) */
  #define PCI230_ADCCON		0x0A	/* ADC control */
  #define PCI230_ADCEN		0x0C	/* ADC channel enable bits */
  #define PCI230_ADCG		0x0E	/* ADC gain control bits */
  /* PCI230+ i/o space 2 additional registers. */
  #define PCI230P_ADCTRIG		0x10	/* ADC start acquisition trigger */
  #define PCI230P_ADCTH		0x12	/* ADC analog trigger threshold */
  #define PCI230P_ADCFFTH		0x14	/* ADC FIFO interrupt threshold */
  #define PCI230P_ADCFFLEV	0x16	/* ADC FIFO level (r) */
  #define PCI230P_ADCPTSC		0x18	/* ADC pre-trigger sample count (r) */
  #define PCI230P_ADCHYST		0x1A	/* ADC analog trigger hysteresys */
  #define PCI230P_EXTFUNC		0x1C	/* Extended functions */
  #define PCI230P_HWVER		0x1E	/* Hardware version (r) */
  /* PCI230+ hardware version 2 onwards. */
  #define PCI230P2_DACDATA	0x02	/* DAC data (FIFO mode) (w) */
  #define PCI230P2_DACSWTRIG	0x02	/* DAC soft trigger (FIFO mode) (r) */
  #define PCI230P2_DACEN		0x06	/* DAC channel enable (FIFO mode) */
  
  /*
   * DACCON read-write values.
   */
  #define PCI230_DAC_OR(x)		(((x) & 0x1) << 0)
  #define PCI230_DAC_OR_UNI		PCI230_DAC_OR(0) /* Output unipolar */
  #define PCI230_DAC_OR_BIP		PCI230_DAC_OR(1) /* Output bipolar */
  #define PCI230_DAC_OR_MASK		PCI230_DAC_OR(1)
  /*
   * The following applies only if DAC FIFO support is enabled in the EXTFUNC
   * register (and only for PCI230+ hardware version 2 onwards).
   */
  #define PCI230P2_DAC_FIFO_EN		BIT(8) /* FIFO enable */
  /*
   * The following apply only if the DAC FIFO is enabled (and only for PCI230+
   * hardware version 2 onwards).
   */
  #define PCI230P2_DAC_TRIG(x)		(((x) & 0x7) << 2)
  #define PCI230P2_DAC_TRIG_NONE		PCI230P2_DAC_TRIG(0) /* none */
  #define PCI230P2_DAC_TRIG_SW		PCI230P2_DAC_TRIG(1) /* soft trig */
  #define PCI230P2_DAC_TRIG_EXTP		PCI230P2_DAC_TRIG(2) /* ext + edge */
  #define PCI230P2_DAC_TRIG_EXTN		PCI230P2_DAC_TRIG(3) /* ext - edge */
  #define PCI230P2_DAC_TRIG_Z2CT0		PCI230P2_DAC_TRIG(4) /* Z2 CT0 out */
  #define PCI230P2_DAC_TRIG_Z2CT1		PCI230P2_DAC_TRIG(5) /* Z2 CT1 out */
  #define PCI230P2_DAC_TRIG_Z2CT2		PCI230P2_DAC_TRIG(6) /* Z2 CT2 out */
  #define PCI230P2_DAC_TRIG_MASK		PCI230P2_DAC_TRIG(7)
  #define PCI230P2_DAC_FIFO_WRAP		BIT(7) /* FIFO wraparound mode */
  #define PCI230P2_DAC_INT_FIFO(x)	(((x) & 7) << 9)
  #define PCI230P2_DAC_INT_FIFO_EMPTY	PCI230P2_DAC_INT_FIFO(0) /* empty */
  #define PCI230P2_DAC_INT_FIFO_NEMPTY	PCI230P2_DAC_INT_FIFO(1) /* !empty */
  #define PCI230P2_DAC_INT_FIFO_NHALF	PCI230P2_DAC_INT_FIFO(2) /* !half */
  #define PCI230P2_DAC_INT_FIFO_HALF	PCI230P2_DAC_INT_FIFO(3) /* half */
  #define PCI230P2_DAC_INT_FIFO_NFULL	PCI230P2_DAC_INT_FIFO(4) /* !full */
  #define PCI230P2_DAC_INT_FIFO_FULL	PCI230P2_DAC_INT_FIFO(5) /* full */
  #define PCI230P2_DAC_INT_FIFO_MASK	PCI230P2_DAC_INT_FIFO(7)
  
  /*
   * DACCON read-only values.
   */
  #define PCI230_DAC_BUSY			BIT(1) /* DAC busy. */
  /*
   * The following apply only if the DAC FIFO is enabled (and only for PCI230+
   * hardware version 2 onwards).
   */
  #define PCI230P2_DAC_FIFO_UNDERRUN_LATCHED	BIT(5) /* Underrun error */
  #define PCI230P2_DAC_FIFO_EMPTY		BIT(13) /* FIFO empty */
  #define PCI230P2_DAC_FIFO_FULL		BIT(14) /* FIFO full */
  #define PCI230P2_DAC_FIFO_HALF		BIT(15) /* FIFO half full */
  
  /*
   * DACCON write-only, transient values.
   */
  /*
   * The following apply only if the DAC FIFO is enabled (and only for PCI230+
   * hardware version 2 onwards).
   */
  #define PCI230P2_DAC_FIFO_UNDERRUN_CLEAR	BIT(5) /* Clear underrun */
  #define PCI230P2_DAC_FIFO_RESET		BIT(12) /* FIFO reset */
  
  /*
   * PCI230+ hardware version 2 DAC FIFO levels.
   */
  #define PCI230P2_DAC_FIFOLEVEL_HALF	512
  #define PCI230P2_DAC_FIFOLEVEL_FULL	1024
  /* Free space in DAC FIFO. */
  #define PCI230P2_DAC_FIFOROOM_EMPTY		PCI230P2_DAC_FIFOLEVEL_FULL
  #define PCI230P2_DAC_FIFOROOM_ONETOHALF		\
  	(PCI230P2_DAC_FIFOLEVEL_FULL - PCI230P2_DAC_FIFOLEVEL_HALF)
  #define PCI230P2_DAC_FIFOROOM_HALFTOFULL	1
  #define PCI230P2_DAC_FIFOROOM_FULL		0
  
  /*
   * ADCCON read/write values.
   */
  #define PCI230_ADC_TRIG(x)		(((x) & 0x7) << 0)
  #define PCI230_ADC_TRIG_NONE		PCI230_ADC_TRIG(0) /* none */
  #define PCI230_ADC_TRIG_SW		PCI230_ADC_TRIG(1) /* soft trig */
  #define PCI230_ADC_TRIG_EXTP		PCI230_ADC_TRIG(2) /* ext + edge */
  #define PCI230_ADC_TRIG_EXTN		PCI230_ADC_TRIG(3) /* ext - edge */
  #define PCI230_ADC_TRIG_Z2CT0		PCI230_ADC_TRIG(4) /* Z2 CT0 out*/
  #define PCI230_ADC_TRIG_Z2CT1		PCI230_ADC_TRIG(5) /* Z2 CT1 out */
  #define PCI230_ADC_TRIG_Z2CT2		PCI230_ADC_TRIG(6) /* Z2 CT2 out */
  #define PCI230_ADC_TRIG_MASK		PCI230_ADC_TRIG(7)
  #define PCI230_ADC_IR(x)		(((x) & 0x1) << 3)
  #define PCI230_ADC_IR_UNI		PCI230_ADC_IR(0) /* Input unipolar */
  #define PCI230_ADC_IR_BIP		PCI230_ADC_IR(1) /* Input bipolar */
  #define PCI230_ADC_IR_MASK		PCI230_ADC_IR(1)
  #define PCI230_ADC_IM(x)		(((x) & 0x1) << 4)
  #define PCI230_ADC_IM_SE		PCI230_ADC_IM(0) /* single ended */
  #define PCI230_ADC_IM_DIF		PCI230_ADC_IM(1) /* differential */
  #define PCI230_ADC_IM_MASK		PCI230_ADC_IM(1)
  #define PCI230_ADC_FIFO_EN		BIT(8) /* FIFO enable */
  #define PCI230_ADC_INT_FIFO(x)		(((x) & 0x7) << 9)
  #define PCI230_ADC_INT_FIFO_EMPTY	PCI230_ADC_INT_FIFO(0) /* empty */
  #define PCI230_ADC_INT_FIFO_NEMPTY	PCI230_ADC_INT_FIFO(1) /* !empty */
  #define PCI230_ADC_INT_FIFO_NHALF	PCI230_ADC_INT_FIFO(2) /* !half */
  #define PCI230_ADC_INT_FIFO_HALF	PCI230_ADC_INT_FIFO(3) /* half */
  #define PCI230_ADC_INT_FIFO_NFULL	PCI230_ADC_INT_FIFO(4) /* !full */
  #define PCI230_ADC_INT_FIFO_FULL	PCI230_ADC_INT_FIFO(5) /* full */
  #define PCI230P_ADC_INT_FIFO_THRESH	PCI230_ADC_INT_FIFO(7) /* threshold */
  #define PCI230_ADC_INT_FIFO_MASK	PCI230_ADC_INT_FIFO(7)
  
  /*
   * ADCCON write-only, transient values.
   */
  #define PCI230_ADC_FIFO_RESET		BIT(12) /* FIFO reset */
  #define PCI230_ADC_GLOB_RESET		BIT(13) /* Global reset */
  
  /*
   * ADCCON read-only values.
   */
  #define PCI230_ADC_BUSY			BIT(15) /* ADC busy */
  #define PCI230_ADC_FIFO_EMPTY		BIT(12) /* FIFO empty */
  #define PCI230_ADC_FIFO_FULL		BIT(13) /* FIFO full */
  #define PCI230_ADC_FIFO_HALF		BIT(14) /* FIFO half full */
  #define PCI230_ADC_FIFO_FULL_LATCHED	BIT(5)  /* FIFO overrun occurred */
  
  /*
   * PCI230 ADC FIFO levels.
   */
  #define PCI230_ADC_FIFOLEVEL_HALFFULL	2049	/* Value for FIFO half full */
  #define PCI230_ADC_FIFOLEVEL_FULL	4096	/* FIFO size */
  
  /*
   * PCI230+ EXTFUNC values.
   */
  /* Route EXTTRIG pin to external gate inputs. */
  #define PCI230P_EXTFUNC_GAT_EXTTRIG	BIT(0)
  /* PCI230+ hardware version 2 values. */
  /* Allow DAC FIFO to be enabled. */
  #define PCI230P2_EXTFUNC_DACFIFO	BIT(1)
  
  /*
   * 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 */
  
  static unsigned int pci230_clk_config(unsigned int chan, unsigned int src)
  {
  	return ((chan & 3) << 3) | (src & 7);
  }
  
  /*
   * 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	/* external gate input (PPCn on PCI230) */
  #define GAT_NOUTNM2	3	/* inverted output of channel-2 modulo total */
  
  static unsigned int pci230_gat_config(unsigned int chan, unsigned int src)
  {
  	return ((chan & 3) << 3) | (src & 7);
  }
  
  /*
   * Summary of CLK_OUTNM1 and GAT_NOUTNM2 connections for PCI230 and PCI260:
   *
   *              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 enables/status register values.
   */
  #define PCI230_INT_DISABLE		0
  #define PCI230_INT_PPI_C0		BIT(0)
  #define PCI230_INT_PPI_C3		BIT(1)
  #define PCI230_INT_ADC			BIT(2)
  #define PCI230_INT_ZCLK_CT1		BIT(5)
  /* For PCI230+ hardware version 2 when DAC FIFO enabled. */
  #define PCI230P2_INT_DAC		BIT(4)
  
  /*
   * (Potentially) shared resources and their owners
   */
  enum {
  	RES_Z2CT0 = BIT(0),	/* Z2-CT0 */
  	RES_Z2CT1 = BIT(1),	/* Z2-CT1 */
  	RES_Z2CT2 = BIT(2)	/* Z2-CT2 */
  };
  
  enum {
  	OWNER_AICMD,		/* Owned by AI command */
  	OWNER_AOCMD,		/* Owned by AO command */
  	NUM_OWNERS		/* Number of owners */
  };
  
  /*
   * Handy macros.
   */
  
  /* Combine old and new bits. */
  #define COMBINE(old, new, mask)	(((old) & ~(mask)) | ((new) & (mask)))
  
  /* Current CPU.  XXX should this be hard_smp_processor_id()? */
  #define THISCPU		smp_processor_id()
  
  /*
   * Board descriptions for the two boards supported.
   */
  
  struct pci230_board {
  	const char *name;
  	unsigned short id;
  	unsigned char ai_bits;
  	unsigned char ao_bits;
  	unsigned char min_hwver; /* Minimum hardware version supported. */
  	unsigned int have_dio:1;
  };
  
  static const struct pci230_board pci230_boards[] = {
  	{
  		.name		= "pci230+",
  		.id		= PCI_DEVICE_ID_PCI230,
  		.ai_bits	= 16,
  		.ao_bits	= 12,
  		.have_dio	= true,
  		.min_hwver	= 1,
  	},
  	{
  		.name		= "pci260+",
  		.id		= PCI_DEVICE_ID_PCI260,
  		.ai_bits	= 16,
  		.min_hwver	= 1,
  	},
  	{
  		.name		= "pci230",
  		.id		= PCI_DEVICE_ID_PCI230,
  		.ai_bits	= 12,
  		.ao_bits	= 12,
  		.have_dio	= true,
  	},
  	{
  		.name		= "pci260",
  		.id		= PCI_DEVICE_ID_PCI260,
  		.ai_bits	= 12,
  	},
  };
  
  struct pci230_private {
  	spinlock_t isr_spinlock;	/* Interrupt spin lock */
  	spinlock_t res_spinlock;	/* Shared resources spin lock */
  	spinlock_t ai_stop_spinlock;	/* Spin lock for stopping AI command */
  	spinlock_t ao_stop_spinlock;	/* Spin lock for stopping AO command */
  	unsigned long daqio;		/* PCI230's DAQ I/O space */
  	int intr_cpuid;			/* ID of CPU running ISR */
  	unsigned short hwver;		/* Hardware version (for '+' models) */
  	unsigned short adccon;		/* ADCCON register value */
  	unsigned short daccon;		/* DACCON register value */
  	unsigned short adcfifothresh;	/* ADC FIFO threshold (PCI230+/260+) */
  	unsigned short adcg;		/* ADCG register value */
  	unsigned char ier;		/* Interrupt enable bits */
  	unsigned char res_owned[NUM_OWNERS]; /* Owned resources */
  	unsigned int intr_running:1;	/* Flag set in interrupt routine */
  	unsigned int ai_bipolar:1;	/* Flag AI range is bipolar */
  	unsigned int ao_bipolar:1;	/* Flag AO range is bipolar */
  	unsigned int ai_cmd_started:1;	/* Flag AI command started */
  	unsigned int ao_cmd_started:1;	/* Flag AO command started */
  };
  
  /* PCI230 clock source periods in ns */
  static const unsigned int pci230_timebase[8] = {
  	[CLK_10MHZ]	= I8254_OSC_BASE_10MHZ,
  	[CLK_1MHZ]	= I8254_OSC_BASE_1MHZ,
  	[CLK_100KHZ]	= I8254_OSC_BASE_100KHZ,
  	[CLK_10KHZ]	= I8254_OSC_BASE_10KHZ,
  	[CLK_1KHZ]	= I8254_OSC_BASE_1KHZ,
  };
  
  /* PCI230 analogue input range table */
  static const struct comedi_lrange pci230_ai_range = {
  	7, {
  		BIP_RANGE(10),
  		BIP_RANGE(5),
  		BIP_RANGE(2.5),
  		BIP_RANGE(1.25),
  		UNI_RANGE(10),
  		UNI_RANGE(5),
  		UNI_RANGE(2.5)
  	}
  };
  
  /* PCI230 analogue gain bits for each input range. */
  static const unsigned char pci230_ai_gain[7] = { 0, 1, 2, 3, 1, 2, 3 };
  
  /* PCI230 analogue output range table */
  static const struct comedi_lrange pci230_ao_range = {
  	2, {
  		UNI_RANGE(10),
  		BIP_RANGE(10)
  	}
  };
  
  static unsigned short pci230_ai_read(struct comedi_device *dev)
  {
  	const struct pci230_board *board = dev->board_ptr;
  	struct pci230_private *devpriv = dev->private;
  	unsigned short data;
  
  	/* Read sample. */
  	data = inw(devpriv->daqio + PCI230_ADCDATA);
  	/*
  	 * PCI230 is 12 bit - stored in upper bits of 16 bit register
  	 * (lower four bits reserved for expansion).  PCI230+ is 16 bit AI.
  	 *
  	 * If a bipolar range was specified, mangle it
  	 * (twos complement->straight binary).
  	 */
  	if (devpriv->ai_bipolar)
  		data ^= 0x8000;
  	data >>= (16 - board->ai_bits);
  	return data;
  }
  
  static unsigned short pci230_ao_mangle_datum(struct comedi_device *dev,
  					     unsigned short datum)
  {
  	const struct pci230_board *board = dev->board_ptr;
  	struct pci230_private *devpriv = dev->private;
  
  	/*
  	 * PCI230 is 12 bit - stored in upper bits of 16 bit register (lower
  	 * four bits reserved for expansion).  PCI230+ is also 12 bit AO.
  	 */
  	datum <<= (16 - board->ao_bits);
  	/*
  	 * If a bipolar range was specified, mangle it
  	 * (straight binary->twos complement).
  	 */
  	if (devpriv->ao_bipolar)
  		datum ^= 0x8000;
  	return datum;
  }
  
  static void pci230_ao_write_nofifo(struct comedi_device *dev,
  				   unsigned short datum, unsigned int chan)
  {
  	struct pci230_private *devpriv = dev->private;
  
  	/* Write mangled datum to appropriate DACOUT register. */
  	outw(pci230_ao_mangle_datum(dev, datum),
  	     devpriv->daqio + ((chan == 0) ? PCI230_DACOUT1 : PCI230_DACOUT2));
  }
  
  static void pci230_ao_write_fifo(struct comedi_device *dev,
  				 unsigned short datum, unsigned int chan)
  {
  	struct pci230_private *devpriv = dev->private;
  
  	/* Write mangled datum to appropriate DACDATA register. */
  	outw(pci230_ao_mangle_datum(dev, datum),
  	     devpriv->daqio + PCI230P2_DACDATA);
  }
  
  static bool pci230_claim_shared(struct comedi_device *dev,
  				unsigned char res_mask, unsigned int owner)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned int o;
  	unsigned long irqflags;
  
  	spin_lock_irqsave(&devpriv->res_spinlock, irqflags);
  	for (o = 0; o < NUM_OWNERS; o++) {
  		if (o == owner)
  			continue;
  		if (devpriv->res_owned[o] & res_mask) {
  			spin_unlock_irqrestore(&devpriv->res_spinlock,
  					       irqflags);
  			return false;
  		}
  	}
  	devpriv->res_owned[owner] |= res_mask;
  	spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags);
  	return true;
  }
  
  static void pci230_release_shared(struct comedi_device *dev,
  				  unsigned char res_mask, unsigned int owner)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned long irqflags;
  
  	spin_lock_irqsave(&devpriv->res_spinlock, irqflags);
  	devpriv->res_owned[owner] &= ~res_mask;
  	spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags);
  }
  
  static void pci230_release_all_resources(struct comedi_device *dev,
  					 unsigned int owner)
  {
  	pci230_release_shared(dev, (unsigned char)~0, owner);
  }
  
  static unsigned int pci230_divide_ns(u64 ns, unsigned int timebase,
  				     unsigned int flags)
  {
  	u64 div;
  	unsigned int rem;
  
  	div = ns;
  	rem = do_div(div, timebase);
  	switch (flags & CMDF_ROUND_MASK) {
  	default:
  	case CMDF_ROUND_NEAREST:
  		div += DIV_ROUND_CLOSEST(rem, timebase);
  		break;
  	case CMDF_ROUND_DOWN:
  		break;
  	case CMDF_ROUND_UP:
  		div += DIV_ROUND_UP(rem, timebase);
  		break;
  	}
  	return div > UINT_MAX ? UINT_MAX : (unsigned int)div;
  }
  
  /*
   * Given desired period in ns, returns the required internal clock source
   * and gets the initial count.
   */
  static unsigned int pci230_choose_clk_count(u64 ns, unsigned int *count,
  					    unsigned int flags)
  {
  	unsigned int clk_src, cnt;
  
  	for (clk_src = CLK_10MHZ;; clk_src++) {
  		cnt = pci230_divide_ns(ns, pci230_timebase[clk_src], flags);
  		if (cnt <= 65536 || clk_src == CLK_1KHZ)
  			break;
  	}
  	*count = cnt;
  	return clk_src;
  }
  
  static void pci230_ns_to_single_timer(unsigned int *ns, unsigned int flags)
  {
  	unsigned int count;
  	unsigned int clk_src;
  
  	clk_src = pci230_choose_clk_count(*ns, &count, flags);
  	*ns = count * pci230_timebase[clk_src];
  }
  
  static void pci230_ct_setup_ns_mode(struct comedi_device *dev, unsigned int ct,
  				    unsigned int mode, u64 ns,
  				    unsigned int flags)
  {
  	unsigned int clk_src;
  	unsigned int count;
  
  	/* Set mode. */
  	comedi_8254_set_mode(dev->pacer, ct, mode);
  	/* Determine clock source and count. */
  	clk_src = pci230_choose_clk_count(ns, &count, flags);
  	/* Program clock source. */
  	outb(pci230_clk_config(ct, clk_src), dev->iobase + PCI230_ZCLK_SCE);
  	/* Set initial count. */
  	if (count >= 65536)
  		count = 0;
  
  	comedi_8254_write(dev->pacer, ct, count);
  }
  
  static void pci230_cancel_ct(struct comedi_device *dev, unsigned int ct)
  {
  	/* Counter ct, 8254 mode 1, initial count not written. */
  	comedi_8254_set_mode(dev->pacer, ct, I8254_MODE1);
  }
  
  static int pci230_ai_eoc(struct comedi_device *dev,
  			 struct comedi_subdevice *s,
  			 struct comedi_insn *insn,
  			 unsigned long context)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned int status;
  
  	status = inw(devpriv->daqio + PCI230_ADCCON);
  	if ((status & PCI230_ADC_FIFO_EMPTY) == 0)
  		return 0;
  	return -EBUSY;
  }
  
  static int pci230_ai_insn_read(struct comedi_device *dev,
  			       struct comedi_subdevice *s,
  			       struct comedi_insn *insn, unsigned int *data)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned int n;
  	unsigned int chan, range, aref;
  	unsigned int gainshift;
  	unsigned short adccon, adcen;
  	int ret;
  
  	/* Unpack channel and range. */
  	chan = CR_CHAN(insn->chanspec);
  	range = CR_RANGE(insn->chanspec);
  	aref = CR_AREF(insn->chanspec);
  	if (aref == AREF_DIFF) {
  		/* Differential. */
  		if (chan >= s->n_chan / 2) {
  			dev_dbg(dev->class_dev,
  				"%s: differential channel number out of range 0 to %u\n",
  				__func__, (s->n_chan / 2) - 1);
  			return -EINVAL;
  		}
  	}
  
  	/*
  	 * Use Z2-CT2 as a conversion trigger instead of the built-in
  	 * software trigger, as otherwise triggering of differential channels
  	 * doesn't work properly for some versions of PCI230/260.  Also set
  	 * FIFO mode because the ADC busy bit only works for software triggers.
  	 */
  	adccon = PCI230_ADC_TRIG_Z2CT2 | PCI230_ADC_FIFO_EN;
  	/* Set Z2-CT2 output low to avoid any false triggers. */
  	comedi_8254_set_mode(dev->pacer, 2, I8254_MODE0);
  	devpriv->ai_bipolar = comedi_range_is_bipolar(s, range);
  	if (aref == AREF_DIFF) {
  		/* Differential. */
  		gainshift = chan * 2;
  		if (devpriv->hwver == 0) {
  			/*
  			 * Original PCI230/260 expects both inputs of the
  			 * differential channel to be enabled.
  			 */
  			adcen = 3 << gainshift;
  		} else {
  			/*
  			 * PCI230+/260+ expects only one input of the
  			 * differential channel to be enabled.
  			 */
  			adcen = 1 << gainshift;
  		}
  		adccon |= PCI230_ADC_IM_DIF;
  	} else {
  		/* Single ended. */
  		adcen = 1 << chan;
  		gainshift = chan & ~1;
  		adccon |= PCI230_ADC_IM_SE;
  	}
  	devpriv->adcg = (devpriv->adcg & ~(3 << gainshift)) |
  			(pci230_ai_gain[range] << gainshift);
  	if (devpriv->ai_bipolar)
  		adccon |= PCI230_ADC_IR_BIP;
  	else
  		adccon |= PCI230_ADC_IR_UNI;
  
  	/*
  	 * Enable only this channel in the scan list - otherwise by default
  	 * we'll get one sample from each channel.
  	 */
  	outw(adcen, devpriv->daqio + PCI230_ADCEN);
  
  	/* Set gain for channel. */
  	outw(devpriv->adcg, devpriv->daqio + PCI230_ADCG);
  
  	/* Specify uni/bip, se/diff, conversion source, and reset FIFO. */
  	devpriv->adccon = adccon;
  	outw(adccon | PCI230_ADC_FIFO_RESET, devpriv->daqio + PCI230_ADCCON);
  
  	/* Convert n samples */
  	for (n = 0; n < insn->n; n++) {
  		/*
  		 * Trigger conversion by toggling Z2-CT2 output
  		 * (finish with output high).
  		 */
  		comedi_8254_set_mode(dev->pacer, 2, I8254_MODE0);
  		comedi_8254_set_mode(dev->pacer, 2, I8254_MODE1);
  
  		/* wait for conversion to end */
  		ret = comedi_timeout(dev, s, insn, pci230_ai_eoc, 0);
  		if (ret)
  			return ret;
  
  		/* read data */
  		data[n] = pci230_ai_read(dev);
  	}
  
  	/* return the number of samples read/written */
  	return n;
  }
  
  static int pci230_ao_insn_write(struct comedi_device *dev,
  				struct comedi_subdevice *s,
  				struct comedi_insn *insn,
  				unsigned int *data)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned int chan = CR_CHAN(insn->chanspec);
  	unsigned int range = CR_RANGE(insn->chanspec);
  	unsigned int val = s->readback[chan];
  	int i;
  
  	/*
  	 * Set range - see analogue output range table; 0 => unipolar 10V,
  	 * 1 => bipolar +/-10V range scale
  	 */
  	devpriv->ao_bipolar = comedi_range_is_bipolar(s, range);
  	outw(range, devpriv->daqio + PCI230_DACCON);
  
  	for (i = 0; i < insn->n; i++) {
  		val = data[i];
  		pci230_ao_write_nofifo(dev, val, chan);
  	}
  	s->readback[chan] = val;
  
  	return insn->n;
  }
  
  static int pci230_ao_check_chanlist(struct comedi_device *dev,
  				    struct comedi_subdevice *s,
  				    struct comedi_cmd *cmd)
  {
  	unsigned int prev_chan = CR_CHAN(cmd->chanlist[0]);
  	unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
  	int i;
  
  	for (i = 1; i < cmd->chanlist_len; i++) {
  		unsigned int chan = CR_CHAN(cmd->chanlist[i]);
  		unsigned int range = CR_RANGE(cmd->chanlist[i]);
  
  		if (chan < prev_chan) {
  			dev_dbg(dev->class_dev,
  				"%s: channel numbers must increase\n",
  				__func__);
  			return -EINVAL;
  		}
  
  		if (range != range0) {
  			dev_dbg(dev->class_dev,
  				"%s: channels must have the same range\n",
  				__func__);
  			return -EINVAL;
  		}
  
  		prev_chan = chan;
  	}
  
  	return 0;
  }
  
  static int pci230_ao_cmdtest(struct comedi_device *dev,
  			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
  {
  	const struct pci230_board *board = dev->board_ptr;
  	struct pci230_private *devpriv = dev->private;
  	int err = 0;
  	unsigned int tmp;
  
  	/* Step 1 : check if triggers are trivially valid */
  
  	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_INT);
  
  	tmp = TRIG_TIMER | TRIG_INT;
  	if (board->min_hwver > 0 && devpriv->hwver >= 2) {
  		/*
  		 * For PCI230+ hardware version 2 onwards, allow external
  		 * trigger from EXTTRIG/EXTCONVCLK input (PCI230+ pin 25).
  		 *
  		 * FIXME: The permitted scan_begin_src values shouldn't depend
  		 * on devpriv->hwver (the detected card's actual hardware
  		 * version).  They should only depend on board->min_hwver
  		 * (the static capabilities of the configured card).  To fix
  		 * it, a new card model, e.g. "pci230+2" would have to be
  		 * defined with min_hwver set to 2.  It doesn't seem worth it
  		 * for this alone.  At the moment, please consider
  		 * scan_begin_src==TRIG_EXT support to be a bonus rather than a
  		 * guarantee!
  		 */
  		tmp |= TRIG_EXT;
  	}
  	err |= comedi_check_trigger_src(&cmd->scan_begin_src, tmp);
  
  	err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_NOW);
  	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->scan_begin_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);
  
  #define MAX_SPEED_AO	8000	/* 8000 ns => 125 kHz */
  /*
   * Comedi limit due to unsigned int cmd.  Driver limit =
   * 2^16 (16bit * counter) * 1000000ns (1kHz onboard clock) = 65.536s
   */
  #define MIN_SPEED_AO	4294967295u	/* 4294967295ns = 4.29s */
  
  	switch (cmd->scan_begin_src) {
  	case TRIG_TIMER:
  		err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
  						    MAX_SPEED_AO);
  		err |= comedi_check_trigger_arg_max(&cmd->scan_begin_arg,
  						    MIN_SPEED_AO);
  		break;
  	case TRIG_EXT:
  		/*
  		 * External trigger - for PCI230+ hardware version 2 onwards.
  		 */
  		/* Trigger number must be 0. */
  		if (cmd->scan_begin_arg & ~CR_FLAGS_MASK) {
  			cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0,
  						      ~CR_FLAGS_MASK);
  			err |= -EINVAL;
  		}
  		/*
  		 * The only flags allowed are CR_EDGE and CR_INVERT.
  		 * The CR_EDGE flag is ignored.
  		 */
  		if (cmd->scan_begin_arg & CR_FLAGS_MASK &
  		    ~(CR_EDGE | CR_INVERT)) {
  			cmd->scan_begin_arg =
  			    COMBINE(cmd->scan_begin_arg, 0,
  				    CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT));
  			err |= -EINVAL;
  		}
  		break;
  	default:
  		err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
  		break;
  	}
  
  	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->scan_begin_src == TRIG_TIMER) {
  		tmp = cmd->scan_begin_arg;
  		pci230_ns_to_single_timer(&cmd->scan_begin_arg, cmd->flags);
  		if (tmp != cmd->scan_begin_arg)
  			err++;
  	}
  
  	if (err)
  		return 4;
  
  	/* Step 5: check channel list if it exists */
  	if (cmd->chanlist && cmd->chanlist_len > 0)
  		err |= pci230_ao_check_chanlist(dev, s, cmd);
  
  	if (err)
  		return 5;
  
  	return 0;
  }
  
  static void pci230_ao_stop(struct comedi_device *dev,
  			   struct comedi_subdevice *s)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned long irqflags;
  	unsigned char intsrc;
  	bool started;
  	struct comedi_cmd *cmd;
  
  	spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags);
  	started = devpriv->ao_cmd_started;
  	devpriv->ao_cmd_started = false;
  	spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags);
  	if (!started)
  		return;
  	cmd = &s->async->cmd;
  	if (cmd->scan_begin_src == TRIG_TIMER) {
  		/* Stop scan rate generator. */
  		pci230_cancel_ct(dev, 1);
  	}
  	/* Determine interrupt source. */
  	if (devpriv->hwver < 2) {
  		/* Not using DAC FIFO.  Using CT1 interrupt. */
  		intsrc = PCI230_INT_ZCLK_CT1;
  	} else {
  		/* Using DAC FIFO interrupt. */
  		intsrc = PCI230P2_INT_DAC;
  	}
  	/*
  	 * Disable interrupt and wait for interrupt routine to finish running
  	 * unless we are called from the interrupt routine.
  	 */
  	spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
  	devpriv->ier &= ~intsrc;
  	while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) {
  		spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
  		spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
  	}
  	outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
  	spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
  	if (devpriv->hwver >= 2) {
  		/*
  		 * Using DAC FIFO.  Reset FIFO, clear underrun error,
  		 * disable FIFO.
  		 */
  		devpriv->daccon &= PCI230_DAC_OR_MASK;
  		outw(devpriv->daccon | PCI230P2_DAC_FIFO_RESET |
  		     PCI230P2_DAC_FIFO_UNDERRUN_CLEAR,
  		     devpriv->daqio + PCI230_DACCON);
  	}
  	/* Release resources. */
  	pci230_release_all_resources(dev, OWNER_AOCMD);
  }
  
  static void pci230_handle_ao_nofifo(struct comedi_device *dev,
  				    struct comedi_subdevice *s)
  {
  	struct comedi_async *async = s->async;
  	struct comedi_cmd *cmd = &async->cmd;
  	unsigned short data;
  	int i;
  
  	if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
  		return;
  
  	for (i = 0; i < cmd->chanlist_len; i++) {
  		unsigned int chan = CR_CHAN(cmd->chanlist[i]);
  
  		if (!comedi_buf_read_samples(s, &data, 1)) {
  			async->events |= COMEDI_CB_OVERFLOW;
  			return;
  		}
  		pci230_ao_write_nofifo(dev, data, chan);
  		s->readback[chan] = data;
  	}
  
  	if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
  		async->events |= COMEDI_CB_EOA;
  }
  
  /*
   * Loads DAC FIFO (if using it) from buffer.
   * Returns false if AO finished due to completion or error, true if still going.
   */
  static bool pci230_handle_ao_fifo(struct comedi_device *dev,
  				  struct comedi_subdevice *s)
  {
  	struct pci230_private *devpriv = dev->private;
  	struct comedi_async *async = s->async;
  	struct comedi_cmd *cmd = &async->cmd;
  	unsigned int num_scans = comedi_nscans_left(s, 0);
  	unsigned int room;
  	unsigned short dacstat;
  	unsigned int i, n;
  	unsigned int events = 0;
  
  	/* Get DAC FIFO status. */
  	dacstat = inw(devpriv->daqio + PCI230_DACCON);
  
  	if (cmd->stop_src == TRIG_COUNT && num_scans == 0)
  		events |= COMEDI_CB_EOA;
  
  	if (events == 0) {
  		/* Check for FIFO underrun. */
  		if (dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) {
  			dev_err(dev->class_dev, "AO FIFO underrun\n");
  			events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR;
  		}
  		/*
  		 * Check for buffer underrun if FIFO less than half full
  		 * (otherwise there will be loads of "DAC FIFO not half full"
  		 * interrupts).
  		 */
  		if (num_scans == 0 &&
  		    (dacstat & PCI230P2_DAC_FIFO_HALF) == 0) {
  			dev_err(dev->class_dev, "AO buffer underrun\n");
  			events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR;
  		}
  	}
  	if (events == 0) {
  		/* Determine how much room is in the FIFO (in samples). */
  		if (dacstat & PCI230P2_DAC_FIFO_FULL)
  			room = PCI230P2_DAC_FIFOROOM_FULL;
  		else if (dacstat & PCI230P2_DAC_FIFO_HALF)
  			room = PCI230P2_DAC_FIFOROOM_HALFTOFULL;
  		else if (dacstat & PCI230P2_DAC_FIFO_EMPTY)
  			room = PCI230P2_DAC_FIFOROOM_EMPTY;
  		else
  			room = PCI230P2_DAC_FIFOROOM_ONETOHALF;
  		/* Convert room to number of scans that can be added. */
  		room /= cmd->chanlist_len;
  		/* Determine number of scans to process. */
  		if (num_scans > room)
  			num_scans = room;
  		/* Process scans. */
  		for (n = 0; n < num_scans; n++) {
  			for (i = 0; i < cmd->chanlist_len; i++) {
  				unsigned int chan = CR_CHAN(cmd->chanlist[i]);
  				unsigned short datum;
  
  				comedi_buf_read_samples(s, &datum, 1);
  				pci230_ao_write_fifo(dev, datum, chan);
  				s->readback[chan] = datum;
  			}
  		}
  
  		if (cmd->stop_src == TRIG_COUNT &&
  		    async->scans_done >= cmd->stop_arg) {
  			/*
  			 * All data for the command has been written
  			 * to FIFO.  Set FIFO interrupt trigger level
  			 * to 'empty'.
  			 */
  			devpriv->daccon &= ~PCI230P2_DAC_INT_FIFO_MASK;
  			devpriv->daccon |= PCI230P2_DAC_INT_FIFO_EMPTY;
  			outw(devpriv->daccon, devpriv->daqio + PCI230_DACCON);
  		}
  		/* Check if FIFO underrun occurred while writing to FIFO. */
  		dacstat = inw(devpriv->daqio + PCI230_DACCON);
  		if (dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) {
  			dev_err(dev->class_dev, "AO FIFO underrun\n");
  			events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR;
  		}
  	}
  	async->events |= events;
  	return !(async->events & COMEDI_CB_CANCEL_MASK);
  }
  
  static int pci230_ao_inttrig_scan_begin(struct comedi_device *dev,
  					struct comedi_subdevice *s,
  					unsigned int trig_num)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned long irqflags;
  
  	if (trig_num)
  		return -EINVAL;
  
  	spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags);
  	if (!devpriv->ao_cmd_started) {
  		spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags);
  		return 1;
  	}
  	/* Perform scan. */
  	if (devpriv->hwver < 2) {
  		/* Not using DAC FIFO. */
  		spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags);
  		pci230_handle_ao_nofifo(dev, s);
  		comedi_handle_events(dev, s);
  	} else {
  		/* Using DAC FIFO. */
  		/* Read DACSWTRIG register to trigger conversion. */
  		inw(devpriv->daqio + PCI230P2_DACSWTRIG);
  		spin_unlock_irqrestore(&devpriv->ao_stop_spinlock, irqflags);
  	}
  	/* Delay.  Should driver be responsible for this? */
  	/* XXX TODO: See if DAC busy bit can be used. */
  	udelay(8);
  	return 1;
  }
  
  static void pci230_ao_start(struct comedi_device *dev,
  			    struct comedi_subdevice *s)
  {
  	struct pci230_private *devpriv = dev->private;
  	struct comedi_async *async = s->async;
  	struct comedi_cmd *cmd = &async->cmd;
  	unsigned long irqflags;
  
  	devpriv->ao_cmd_started = true;
  
  	if (devpriv->hwver >= 2) {
  		/* Using DAC FIFO. */
  		unsigned short scantrig;
  		bool run;
  
  		/* Preload FIFO data. */
  		run = pci230_handle_ao_fifo(dev, s);
  		comedi_handle_events(dev, s);
  		if (!run) {
  			/* Stopped. */
  			return;
  		}
  		/* Set scan trigger source. */
  		switch (cmd->scan_begin_src) {
  		case TRIG_TIMER:
  			scantrig = PCI230P2_DAC_TRIG_Z2CT1;
  			break;
  		case TRIG_EXT:
  			/* Trigger on EXTTRIG/EXTCONVCLK pin. */
  			if ((cmd->scan_begin_arg & CR_INVERT) == 0) {
  				/* +ve edge */
  				scantrig = PCI230P2_DAC_TRIG_EXTP;
  			} else {
  				/* -ve edge */
  				scantrig = PCI230P2_DAC_TRIG_EXTN;
  			}
  			break;
  		case TRIG_INT:
  			scantrig = PCI230P2_DAC_TRIG_SW;
  			break;
  		default:
  			/* Shouldn't get here. */
  			scantrig = PCI230P2_DAC_TRIG_NONE;
  			break;
  		}
  		devpriv->daccon =
  		    (devpriv->daccon & ~PCI230P2_DAC_TRIG_MASK) | scantrig;
  		outw(devpriv->daccon, devpriv->daqio + PCI230_DACCON);
  	}
  	switch (cmd->scan_begin_src) {
  	case TRIG_TIMER:
  		if (devpriv->hwver < 2) {
  			/* Not using DAC FIFO. */
  			/* Enable CT1 timer interrupt. */
  			spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
  			devpriv->ier |= PCI230_INT_ZCLK_CT1;
  			outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
  			spin_unlock_irqrestore(&devpriv->isr_spinlock,
  					       irqflags);
  		}
  		/* Set CT1 gate high to start counting. */
  		outb(pci230_gat_config(1, GAT_VCC),
  		     dev->iobase + PCI230_ZGAT_SCE);
  		break;
  	case TRIG_INT:
  		async->inttrig = pci230_ao_inttrig_scan_begin;
  		break;
  	}
  	if (devpriv->hwver >= 2) {
  		/* Using DAC FIFO.  Enable DAC FIFO interrupt. */
  		spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
  		devpriv->ier |= PCI230P2_INT_DAC;
  		outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
  		spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
  	}
  }
  
  static int pci230_ao_inttrig_start(struct comedi_device *dev,
  				   struct comedi_subdevice *s,
  				   unsigned int trig_num)
  {
  	struct comedi_cmd *cmd = &s->async->cmd;
  
  	if (trig_num != cmd->start_src)
  		return -EINVAL;
  
  	s->async->inttrig = NULL;
  	pci230_ao_start(dev, s);
  
  	return 1;
  }
  
  static int pci230_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned short daccon;
  	unsigned int range;
  
  	/* Get the command. */
  	struct comedi_cmd *cmd = &s->async->cmd;
  
  	if (cmd->scan_begin_src == TRIG_TIMER) {
  		/* Claim Z2-CT1. */
  		if (!pci230_claim_shared(dev, RES_Z2CT1, OWNER_AOCMD))
  			return -EBUSY;
  	}
  
  	/*
  	 * Set range - see analogue output range table; 0 => unipolar 10V,
  	 * 1 => bipolar +/-10V range scale
  	 */
  	range = CR_RANGE(cmd->chanlist[0]);
  	devpriv->ao_bipolar = comedi_range_is_bipolar(s, range);
  	daccon = devpriv->ao_bipolar ? PCI230_DAC_OR_BIP : PCI230_DAC_OR_UNI;
  	/* Use DAC FIFO for hardware version 2 onwards. */
  	if (devpriv->hwver >= 2) {
  		unsigned short dacen;
  		unsigned int i;
  
  		dacen = 0;
  		for (i = 0; i < cmd->chanlist_len; i++)
  			dacen |= 1 << CR_CHAN(cmd->chanlist[i]);
  
  		/* Set channel scan list. */
  		outw(dacen, devpriv->daqio + PCI230P2_DACEN);
  		/*
  		 * Enable DAC FIFO.
  		 * Set DAC scan source to 'none'.
  		 * Set DAC FIFO interrupt trigger level to 'not half full'.
  		 * Reset DAC FIFO and clear underrun.
  		 *
  		 * N.B. DAC FIFO interrupts are currently disabled.
  		 */
  		daccon |= PCI230P2_DAC_FIFO_EN | PCI230P2_DAC_FIFO_RESET |
  			  PCI230P2_DAC_FIFO_UNDERRUN_CLEAR |
  			  PCI230P2_DAC_TRIG_NONE | PCI230P2_DAC_INT_FIFO_NHALF;
  	}
  
  	/* Set DACCON. */
  	outw(daccon, devpriv->daqio + PCI230_DACCON);
  	/* Preserve most of DACCON apart from write-only, transient bits. */
  	devpriv->daccon = daccon & ~(PCI230P2_DAC_FIFO_RESET |
  				     PCI230P2_DAC_FIFO_UNDERRUN_CLEAR);
  
  	if (cmd->scan_begin_src == TRIG_TIMER) {
  		/*
  		 * Set the counter timer 1 to the specified scan frequency.
  		 * cmd->scan_begin_arg is sampling period in ns.
  		 * Gate it off for now.
  		 */
  		outb(pci230_gat_config(1, GAT_GND),
  		     dev->iobase + PCI230_ZGAT_SCE);
  		pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3,
  					cmd->scan_begin_arg,
  					cmd->flags);
  	}
  
  	/* N.B. cmd->start_src == TRIG_INT */
  	s->async->inttrig = pci230_ao_inttrig_start;
  
  	return 0;
  }
  
  static int pci230_ao_cancel(struct comedi_device *dev,
  			    struct comedi_subdevice *s)
  {
  	pci230_ao_stop(dev, s);
  	return 0;
  }
  
  static int pci230_ai_check_scan_period(struct comedi_cmd *cmd)
  {
  	unsigned int min_scan_period, chanlist_len;
  	int err = 0;
  
  	chanlist_len = cmd->chanlist_len;
  	if (cmd->chanlist_len == 0)
  		chanlist_len = 1;
  
  	min_scan_period = chanlist_len * cmd->convert_arg;
  	if (min_scan_period < chanlist_len ||
  	    min_scan_period < cmd->convert_arg) {
  		/* Arithmetic overflow. */
  		min_scan_period = UINT_MAX;
  		err++;
  	}
  	if (cmd->scan_begin_arg < min_scan_period) {
  		cmd->scan_begin_arg = min_scan_period;
  		err++;
  	}
  
  	return !err;
  }
  
  static int pci230_ai_check_chanlist(struct comedi_device *dev,
  				    struct comedi_subdevice *s,
  				    struct comedi_cmd *cmd)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned int max_diff_chan = (s->n_chan / 2) - 1;
  	unsigned int prev_chan = 0;
  	unsigned int prev_range = 0;
  	unsigned int prev_aref = 0;
  	bool prev_bipolar = false;
  	unsigned int subseq_len = 0;
  	int i;
  
  	for (i = 0; i < cmd->chanlist_len; i++) {
  		unsigned int chanspec = cmd->chanlist[i];
  		unsigned int chan = CR_CHAN(chanspec);
  		unsigned int range = CR_RANGE(chanspec);
  		unsigned int aref = CR_AREF(chanspec);
  		bool bipolar = comedi_range_is_bipolar(s, range);
  
  		if (aref == AREF_DIFF && chan >= max_diff_chan) {
  			dev_dbg(dev->class_dev,
  				"%s: differential channel number out of range 0 to %u\n",
  				__func__, max_diff_chan);
  			return -EINVAL;
  		}
  
  		if (i > 0) {
  			/*
  			 * Channel numbers must strictly increase or
  			 * subsequence must repeat exactly.
  			 */
  			if (chan <= prev_chan && subseq_len == 0)
  				subseq_len = i;
  
  			if (subseq_len > 0 &&
  			    cmd->chanlist[i % subseq_len] != chanspec) {
  				dev_dbg(dev->class_dev,
  					"%s: channel numbers must increase or sequence must repeat exactly\n",
  					__func__);
  				return -EINVAL;
  			}
  
  			if (aref != prev_aref) {
  				dev_dbg(dev->class_dev,
  					"%s: channel sequence analogue references must be all the same (single-ended or differential)\n",
  					__func__);
  				return -EINVAL;
  			}
  
  			if (bipolar != prev_bipolar) {
  				dev_dbg(dev->class_dev,
  					"%s: channel sequence ranges must be all bipolar or all unipolar\n",
  					__func__);
  				return -EINVAL;
  			}
  
  			if (aref != AREF_DIFF && range != prev_range &&
  			    ((chan ^ prev_chan) & ~1) == 0) {
  				dev_dbg(dev->class_dev,
  					"%s: single-ended channel pairs must have the same range\n",
  					__func__);
  				return -EINVAL;
  			}
  		}
  		prev_chan = chan;
  		prev_range = range;
  		prev_aref = aref;
  		prev_bipolar = bipolar;
  	}
  
  	if (subseq_len == 0)
  		subseq_len = cmd->chanlist_len;
  
  	if (cmd->chanlist_len % subseq_len) {
  		dev_dbg(dev->class_dev,
  			"%s: sequence must repeat exactly\n", __func__);
  		return -EINVAL;
  	}
  
  	/*
  	 * Buggy PCI230+ or PCI260+ requires channel 0 to be (first) in the
  	 * sequence if the sequence contains more than one channel. Hardware
  	 * versions 1 and 2 have the bug. There is no hardware version 3.
  	 *
  	 * Actually, there are two firmwares that report themselves as
  	 * hardware version 1 (the boards have different ADC chips with
  	 * slightly different timing requirements, which was supposed to
  	 * be invisible to software). The first one doesn't seem to have
  	 * the bug, but the second one does, and we can't tell them apart!
  	 */
  	if (devpriv->hwver > 0 && devpriv->hwver < 4) {
  		if (subseq_len > 1 && CR_CHAN(cmd->chanlist[0])) {
  			dev_info(dev->class_dev,
  				 "amplc_pci230: ai_cmdtest: Buggy PCI230+/260+ h/w version %u requires first channel of multi-channel sequence to be 0 (corrected in h/w version 4)\n",
  				 devpriv->hwver);
  			return -EINVAL;
  		}
  	}
  
  	return 0;
  }
  
  static int pci230_ai_cmdtest(struct comedi_device *dev,
  			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
  {
  	const struct pci230_board *board = dev->board_ptr;
  	struct pci230_private *devpriv = dev->private;
  	int err = 0;
  	unsigned int tmp;
  
  	/* Step 1 : check if triggers are trivially valid */
  
  	err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
  
  	tmp = TRIG_FOLLOW | TRIG_TIMER | TRIG_INT;
  	if (board->have_dio || board->min_hwver > 0) {
  		/*
  		 * Unfortunately, we cannot trigger a scan off an external
  		 * source on the PCI260 board, since it uses the PPIC0 (DIO)
  		 * input, which isn't present on the PCI260.  For PCI260+
  		 * we can use the EXTTRIG/EXTCONVCLK input on pin 17 instead.
  		 */
  		tmp |= TRIG_EXT;
  	}
  	err |= comedi_check_trigger_src(&cmd->scan_begin_src, tmp);
  	err |= comedi_check_trigger_src(&cmd->convert_src,
  					TRIG_TIMER | TRIG_INT | 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->scan_begin_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 scan_begin_src is not TRIG_FOLLOW, then a monostable will be
  	 * set up to generate a fixed number of timed conversion pulses.
  	 */
  	if (cmd->scan_begin_src != TRIG_FOLLOW &&
  	    cmd->convert_src != TRIG_TIMER)
  		err |= -EINVAL;
  
  	if (err)
  		return 2;
  
  	/* Step 3: check if arguments are trivially valid */
  
  	err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
  
  #define MAX_SPEED_AI_SE		3200	/* PCI230 SE:   3200 ns => 312.5 kHz */
  #define MAX_SPEED_AI_DIFF	8000	/* PCI230 DIFF: 8000 ns => 125 kHz */
  #define MAX_SPEED_AI_PLUS	4000	/* PCI230+:     4000 ns => 250 kHz */
  /*
   * Comedi limit due to unsigned int cmd.  Driver limit =
   * 2^16 (16bit * counter) * 1000000ns (1kHz onboard clock) = 65.536s
   */
  #define MIN_SPEED_AI	4294967295u	/* 4294967295ns = 4.29s */
  
  	if (cmd->convert_src == TRIG_TIMER) {
  		unsigned int max_speed_ai;
  
  		if (devpriv->hwver == 0) {
  			/*
  			 * PCI230 or PCI260.  Max speed depends whether
  			 * single-ended or pseudo-differential.
  			 */
  			if (cmd->chanlist && cmd->chanlist_len > 0) {
  				/* Peek analogue reference of first channel. */
  				if (CR_AREF(cmd->chanlist[0]) == AREF_DIFF)
  					max_speed_ai = MAX_SPEED_AI_DIFF;
  				else
  					max_speed_ai = MAX_SPEED_AI_SE;
  
  			} else {
  				/* No channel list.  Assume single-ended. */
  				max_speed_ai = MAX_SPEED_AI_SE;
  			}
  		} else {
  			/* PCI230+ or PCI260+. */
  			max_speed_ai = MAX_SPEED_AI_PLUS;
  		}
  
  		err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
  						    max_speed_ai);
  		err |= comedi_check_trigger_arg_max(&cmd->convert_arg,
  						    MIN_SPEED_AI);
  	} else if (cmd->convert_src == TRIG_EXT) {
  		/*
  		 * external trigger
  		 *
  		 * convert_arg == (CR_EDGE | 0)
  		 *                => trigger on +ve edge.
  		 * convert_arg == (CR_EDGE | CR_INVERT | 0)
  		 *                => trigger on -ve edge.
  		 */
  		if (cmd->convert_arg & CR_FLAGS_MASK) {
  			/* Trigger number must be 0. */
  			if (cmd->convert_arg & ~CR_FLAGS_MASK) {
  				cmd->convert_arg = COMBINE(cmd->convert_arg, 0,
  							   ~CR_FLAGS_MASK);
  				err |= -EINVAL;
  			}
  			/*
  			 * The only flags allowed are CR_INVERT and CR_EDGE.
  			 * CR_EDGE is required.
  			 */
  			if ((cmd->convert_arg & CR_FLAGS_MASK & ~CR_INVERT) !=
  			    CR_EDGE) {
  				/* Set CR_EDGE, preserve CR_INVERT. */
  				cmd->convert_arg =
  				    COMBINE(cmd->start_arg, CR_EDGE | 0,
  					    CR_FLAGS_MASK & ~CR_INVERT);
  				err |= -EINVAL;
  			}
  		} else {
  			/*
  			 * Backwards compatibility with previous versions:
  			 * convert_arg == 0 => trigger on -ve edge.
  			 * convert_arg == 1 => trigger on +ve edge.
  			 */
  			err |= comedi_check_trigger_arg_max(&cmd->convert_arg,
  							    1);
  		}
  	} else {
  		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 (cmd->scan_begin_src == TRIG_EXT) {
  		/*
  		 * external "trigger" to begin each scan:
  		 * scan_begin_arg==0 => use PPC0 input -> gate of CT0 -> gate
  		 * of CT2 (sample convert trigger is CT2)
  		 */
  		if (cmd->scan_begin_arg & ~CR_FLAGS_MASK) {
  			cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0,
  						      ~CR_FLAGS_MASK);
  			err |= -EINVAL;
  		}
  		/* The only flag allowed is CR_EDGE, which is ignored. */
  		if (cmd->scan_begin_arg & CR_FLAGS_MASK & ~CR_EDGE) {
  			cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0,
  						      CR_FLAGS_MASK & ~CR_EDGE);
  			err |= -EINVAL;
  		}
  	} else if (cmd->scan_begin_src == TRIG_TIMER) {
  		/* N.B. cmd->convert_arg is also TRIG_TIMER */
  		if (!pci230_ai_check_scan_period(cmd))
  			err |= -EINVAL;
  
  	} else {
  		err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
  	}
  
  	if (err)
  		return 3;
  
  	/* Step 4: fix up any arguments */
  
  	if (cmd->convert_src == TRIG_TIMER) {
  		tmp = cmd->convert_arg;
  		pci230_ns_to_single_timer(&cmd->convert_arg, cmd->flags);
  		if (tmp != cmd->convert_arg)
  			err++;
  	}
  
  	if (cmd->scan_begin_src == TRIG_TIMER) {
  		/* N.B. cmd->convert_arg is also TRIG_TIMER */
  		tmp = cmd->scan_begin_arg;
  		pci230_ns_to_single_timer(&cmd->scan_begin_arg, cmd->flags);
  		if (!pci230_ai_check_scan_period(cmd)) {
  			/* Was below minimum required.  Round up. */
  			pci230_ns_to_single_timer(&cmd->scan_begin_arg,
  						  CMDF_ROUND_UP);
  			pci230_ai_check_scan_period(cmd);
  		}
  		if (tmp != cmd->scan_begin_arg)
  			err++;
  	}
  
  	if (err)
  		return 4;
  
  	/* Step 5: check channel list if it exists */
  	if (cmd->chanlist && cmd->chanlist_len > 0)
  		err |= pci230_ai_check_chanlist(dev, s, cmd);
  
  	if (err)
  		return 5;
  
  	return 0;
  }
  
  static void pci230_ai_update_fifo_trigger_level(struct comedi_device *dev,
  						struct comedi_subdevice *s)
  {
  	struct pci230_private *devpriv = dev->private;
  	struct comedi_cmd *cmd = &s->async->cmd;
  	unsigned int wake;
  	unsigned short triglev;
  	unsigned short adccon;
  
  	if (cmd->flags & CMDF_WAKE_EOS)
  		wake = cmd->scan_end_arg - s->async->cur_chan;
  	else
  		wake = comedi_nsamples_left(s, PCI230_ADC_FIFOLEVEL_HALFFULL);
  
  	if (wake >= PCI230_ADC_FIFOLEVEL_HALFFULL) {
  		triglev = PCI230_ADC_INT_FIFO_HALF;
  	} else if (wake > 1 && devpriv->hwver > 0) {
  		/* PCI230+/260+ programmable FIFO interrupt level. */
  		if (devpriv->adcfifothresh != wake) {
  			devpriv->adcfifothresh = wake;
  			outw(wake, devpriv->daqio + PCI230P_ADCFFTH);
  		}
  		triglev = PCI230P_ADC_INT_FIFO_THRESH;
  	} else {
  		triglev = PCI230_ADC_INT_FIFO_NEMPTY;
  	}
  	adccon = (devpriv->adccon & ~PCI230_ADC_INT_FIFO_MASK) | triglev;
  	if (adccon != devpriv->adccon) {
  		devpriv->adccon = adccon;
  		outw(adccon, devpriv->daqio + PCI230_ADCCON);
  	}
  }
  
  static int pci230_ai_inttrig_convert(struct comedi_device *dev,
  				     struct comedi_subdevice *s,
  				     unsigned int trig_num)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned long irqflags;
  	unsigned int delayus;
  
  	if (trig_num)
  		return -EINVAL;
  
  	spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags);
  	if (!devpriv->ai_cmd_started) {
  		spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags);
  		return 1;
  	}
  	/*
  	 * Trigger conversion by toggling Z2-CT2 output.
  	 * Finish with output high.
  	 */
  	comedi_8254_set_mode(dev->pacer, 2, I8254_MODE0);
  	comedi_8254_set_mode(dev->pacer, 2, I8254_MODE1);
  	/*
  	 * Delay.  Should driver be responsible for this?  An
  	 * alternative would be to wait until conversion is complete,
  	 * but we can't tell when it's complete because the ADC busy
  	 * bit has a different meaning when FIFO enabled (and when
  	 * FIFO not enabled, it only works for software triggers).
  	 */
  	if ((devpriv->adccon & PCI230_ADC_IM_MASK) == PCI230_ADC_IM_DIF &&
  	    devpriv->hwver == 0) {
  		/* PCI230/260 in differential mode */
  		delayus = 8;
  	} else {
  		/* single-ended or PCI230+/260+ */
  		delayus = 4;
  	}
  	spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags);
  	udelay(delayus);
  	return 1;
  }
  
  static int pci230_ai_inttrig_scan_begin(struct comedi_device *dev,
  					struct comedi_subdevice *s,
  					unsigned int trig_num)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned long irqflags;
  	unsigned char zgat;
  
  	if (trig_num)
  		return -EINVAL;
  
  	spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags);
  	if (devpriv->ai_cmd_started) {
  		/* Trigger scan by waggling CT0 gate source. */
  		zgat = pci230_gat_config(0, GAT_GND);
  		outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
  		zgat = pci230_gat_config(0, GAT_VCC);
  		outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
  	}
  	spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags);
  
  	return 1;
  }
  
  static void pci230_ai_stop(struct comedi_device *dev,
  			   struct comedi_subdevice *s)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned long irqflags;
  	struct comedi_cmd *cmd;
  	bool started;
  
  	spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags);
  	started = devpriv->ai_cmd_started;
  	devpriv->ai_cmd_started = false;
  	spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags);
  	if (!started)
  		return;
  	cmd = &s->async->cmd;
  	if (cmd->convert_src == TRIG_TIMER) {
  		/* Stop conversion rate generator. */
  		pci230_cancel_ct(dev, 2);
  	}
  	if (cmd->scan_begin_src != TRIG_FOLLOW) {
  		/* Stop scan period monostable. */
  		pci230_cancel_ct(dev, 0);
  	}
  	spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
  	/*
  	 * Disable ADC interrupt and wait for interrupt routine to finish
  	 * running unless we are called from the interrupt routine.
  	 */
  	devpriv->ier &= ~PCI230_INT_ADC;
  	while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) {
  		spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
  		spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
  	}
  	outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
  	spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
  	/*
  	 * Reset FIFO, disable FIFO and set start conversion source to none.
  	 * Keep se/diff and bip/uni settings.
  	 */
  	devpriv->adccon =
  	    (devpriv->adccon & (PCI230_ADC_IR_MASK | PCI230_ADC_IM_MASK)) |
  	    PCI230_ADC_TRIG_NONE;
  	outw(devpriv->adccon | PCI230_ADC_FIFO_RESET,
  	     devpriv->daqio + PCI230_ADCCON);
  	/* Release resources. */
  	pci230_release_all_resources(dev, OWNER_AICMD);
  }
  
  static void pci230_ai_start(struct comedi_device *dev,
  			    struct comedi_subdevice *s)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned long irqflags;
  	unsigned short conv;
  	struct comedi_async *async = s->async;
  	struct comedi_cmd *cmd = &async->cmd;
  
  	devpriv->ai_cmd_started = true;
  
  	/* Enable ADC FIFO trigger level interrupt. */
  	spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
  	devpriv->ier |= PCI230_INT_ADC;
  	outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
  	spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
  
  	/*
  	 * Update conversion trigger source which is currently set
  	 * to CT2 output, which is currently stuck high.
  	 */
  	switch (cmd->convert_src) {
  	default:
  		conv = PCI230_ADC_TRIG_NONE;
  		break;
  	case TRIG_TIMER:
  		/* Using CT2 output. */
  		conv = PCI230_ADC_TRIG_Z2CT2;
  		break;
  	case TRIG_EXT:
  		if (cmd->convert_arg & CR_EDGE) {
  			if ((cmd->convert_arg & CR_INVERT) == 0) {
  				/* Trigger on +ve edge. */
  				conv = PCI230_ADC_TRIG_EXTP;
  			} else {
  				/* Trigger on -ve edge. */
  				conv = PCI230_ADC_TRIG_EXTN;
  			}
  		} else {
  			/* Backwards compatibility. */
  			if (cmd->convert_arg) {
  				/* Trigger on +ve edge. */
  				conv = PCI230_ADC_TRIG_EXTP;
  			} else {
  				/* Trigger on -ve edge. */
  				conv = PCI230_ADC_TRIG_EXTN;
  			}
  		}
  		break;
  	case TRIG_INT:
  		/*
  		 * Use CT2 output for software trigger due to problems
  		 * in differential mode on PCI230/260.
  		 */
  		conv = PCI230_ADC_TRIG_Z2CT2;
  		break;
  	}
  	devpriv->adccon = (devpriv->adccon & ~PCI230_ADC_TRIG_MASK) | conv;
  	outw(devpriv->adccon, devpriv->daqio + PCI230_ADCCON);
  	if (cmd->convert_src == TRIG_INT)
  		async->inttrig = pci230_ai_inttrig_convert;
  
  	/*
  	 * Update FIFO interrupt trigger level, which is currently
  	 * set to "full".
  	 */
  	pci230_ai_update_fifo_trigger_level(dev, s);
  	if (cmd->convert_src == TRIG_TIMER) {
  		/* Update timer gates. */
  		unsigned char zgat;
  
  		if (cmd->scan_begin_src != TRIG_FOLLOW) {
  			/*
  			 * Conversion timer CT2 needs to be gated by
  			 * inverted output of monostable CT2.
  			 */
  			zgat = pci230_gat_config(2, GAT_NOUTNM2);
  		} else {
  			/*
  			 * Conversion timer CT2 needs to be gated on
  			 * continuously.
  			 */
  			zgat = pci230_gat_config(2, GAT_VCC);
  		}
  		outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
  		if (cmd->scan_begin_src != TRIG_FOLLOW) {
  			/* Set monostable CT0 trigger source. */
  			switch (cmd->scan_begin_src) {
  			default:
  				zgat = pci230_gat_config(0, GAT_VCC);
  				break;
  			case TRIG_EXT:
  				/*
  				 * For CT0 on PCI230, the external trigger
  				 * (gate) signal comes from PPC0, which is
  				 * channel 16 of the DIO subdevice.  The
  				 * application needs to configure this as an
  				 * input in order to use it as an external scan
  				 * trigger.
  				 */
  				zgat = pci230_gat_config(0, GAT_EXT);
  				break;
  			case TRIG_TIMER:
  				/*
  				 * Monostable CT0 triggered by rising edge on
  				 * inverted output of CT1 (falling edge on CT1).
  				 */
  				zgat = pci230_gat_config(0, GAT_NOUTNM2);
  				break;
  			case TRIG_INT:
  				/*
  				 * Monostable CT0 is triggered by inttrig
  				 * function waggling the CT0 gate source.
  				 */
  				zgat = pci230_gat_config(0, GAT_VCC);
  				break;
  			}
  			outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
  			switch (cmd->scan_begin_src) {
  			case TRIG_TIMER:
  				/*
  				 * Scan period timer CT1 needs to be
  				 * gated on to start counting.
  				 */
  				zgat = pci230_gat_config(1, GAT_VCC);
  				outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
  				break;
  			case TRIG_INT:
  				async->inttrig = pci230_ai_inttrig_scan_begin;
  				break;
  			}
  		}
  	} else if (cmd->convert_src != TRIG_INT) {
  		/* No longer need Z2-CT2. */
  		pci230_release_shared(dev, RES_Z2CT2, OWNER_AICMD);
  	}
  }
  
  static int pci230_ai_inttrig_start(struct comedi_device *dev,
  				   struct comedi_subdevice *s,
  				   unsigned int trig_num)
  {
  	struct comedi_cmd *cmd = &s->async->cmd;
  
  	if (trig_num != cmd->start_arg)
  		return -EINVAL;
  
  	s->async->inttrig = NULL;
  	pci230_ai_start(dev, s);
  
  	return 1;
  }
  
  static void pci230_handle_ai(struct comedi_device *dev,
  			     struct comedi_subdevice *s)
  {
  	struct pci230_private *devpriv = dev->private;
  	struct comedi_async *async = s->async;
  	struct comedi_cmd *cmd = &async->cmd;
  	unsigned int status_fifo;
  	unsigned int i;
  	unsigned int nsamples;
  	unsigned int fifoamount;
  	unsigned short val;
  
  	/* Determine number of samples to read. */
  	nsamples = comedi_nsamples_left(s, PCI230_ADC_FIFOLEVEL_HALFFULL);
  	if (nsamples == 0)
  		return;
  
  	fifoamount = 0;
  	for (i = 0; i < nsamples; i++) {
  		if (fifoamount == 0) {
  			/* Read FIFO state. */
  			status_fifo = inw(devpriv->daqio + PCI230_ADCCON);
  			if (status_fifo & PCI230_ADC_FIFO_FULL_LATCHED) {
  				/*
  				 * Report error otherwise FIFO overruns will go
  				 * unnoticed by the caller.
  				 */
  				dev_err(dev->class_dev, "AI FIFO overrun\n");
  				async->events |= COMEDI_CB_ERROR;
  				break;
  			} else if (status_fifo & PCI230_ADC_FIFO_EMPTY) {
  				/* FIFO empty. */
  				break;
  			} else if (status_fifo & PCI230_ADC_FIFO_HALF) {
  				/* FIFO half full. */
  				fifoamount = PCI230_ADC_FIFOLEVEL_HALFFULL;
  			} else if (devpriv->hwver > 0) {
  				/* Read PCI230+/260+ ADC FIFO level. */
  				fifoamount = inw(devpriv->daqio +
  						 PCI230P_ADCFFLEV);
  				if (fifoamount == 0)
  					break;	/* Shouldn't happen. */
  			} else {
  				/* FIFO not empty. */
  				fifoamount = 1;
  			}
  		}
  
  		val = pci230_ai_read(dev);
  		if (!comedi_buf_write_samples(s, &val, 1))
  			break;
  
  		fifoamount--;
  
  		if (cmd->stop_src == TRIG_COUNT &&
  		    async->scans_done >= cmd->stop_arg) {
  			async->events |= COMEDI_CB_EOA;
  			break;
  		}
  	}
  
  	/* update FIFO interrupt trigger level if still running */
  	if (!(async->events & COMEDI_CB_CANCEL_MASK))
  		pci230_ai_update_fifo_trigger_level(dev, s);
  }
  
  static int pci230_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
  {
  	struct pci230_private *devpriv = dev->private;
  	unsigned int i, chan, range, diff;
  	unsigned int res_mask;
  	unsigned short adccon, adcen;
  	unsigned char zgat;
  
  	/* Get the command. */
  	struct comedi_async *async = s->async;
  	struct comedi_cmd *cmd = &async->cmd;
  
  	/*
  	 * Determine which shared resources are needed.
  	 */
  	res_mask = 0;
  	/*
  	 * Need Z2-CT2 to supply a conversion trigger source at a high
  	 * logic level, even if not doing timed conversions.
  	 */
  	res_mask |= RES_Z2CT2;
  	if (cmd->scan_begin_src != TRIG_FOLLOW) {
  		/* Using Z2-CT0 monostable to gate Z2-CT2 conversion timer */
  		res_mask |= RES_Z2CT0;
  		if (cmd->scan_begin_src == TRIG_TIMER) {
  			/* Using Z2-CT1 for scan frequency */
  			res_mask |= RES_Z2CT1;
  		}
  	}
  	/* Claim resources. */
  	if (!pci230_claim_shared(dev, res_mask, OWNER_AICMD))
  		return -EBUSY;
  
  	/*
  	 * Steps:
  	 * - Set channel scan list.
  	 * - Set channel gains.
  	 * - Enable and reset FIFO, specify uni/bip, se/diff, and set
  	 *   start conversion source to point to something at a high logic
  	 *   level (we use the output of counter/timer 2 for this purpose.
  	 * - PAUSE to allow things to settle down.
  	 * - Reset the FIFO again because it needs resetting twice and there
  	 *   may have been a false conversion trigger on some versions of
  	 *   PCI230/260 due to the start conversion source being set to a
  	 *   high logic level.
  	 * - Enable ADC FIFO level interrupt.
  	 * - Set actual conversion trigger source and FIFO interrupt trigger
  	 *   level.
  	 * - If convert_src is TRIG_TIMER, set up the timers.
  	 */
  
  	adccon = PCI230_ADC_FIFO_EN;
  	adcen = 0;
  
  	if (CR_AREF(cmd->chanlist[0]) == AREF_DIFF) {
  		/* Differential - all channels must be differential. */
  		diff = 1;
  		adccon |= PCI230_ADC_IM_DIF;
  	} else {
  		/* Single ended - all channels must be single-ended. */
  		diff = 0;
  		adccon |= PCI230_ADC_IM_SE;
  	}
  
  	range = CR_RANGE(cmd->chanlist[0]);
  	devpriv->ai_bipolar = comedi_range_is_bipolar(s, range);
  	if (devpriv->ai_bipolar)
  		adccon |= PCI230_ADC_IR_BIP;
  	else
  		adccon |= PCI230_ADC_IR_UNI;
  
  	for (i = 0; i < cmd->chanlist_len; i++) {
  		unsigned int gainshift;
  
  		chan = CR_CHAN(cmd->chanlist[i]);
  		range = CR_RANGE(cmd->chanlist[i]);
  		if (diff) {
  			gainshift = 2 * chan;
  			if (devpriv->hwver == 0) {
  				/*
  				 * Original PCI230/260 expects both inputs of
  				 * the differential channel to be enabled.
  				 */
  				adcen |= 3 << gainshift;
  			} else {
  				/*
  				 * PCI230+/260+ expects only one input of the
  				 * differential channel to be enabled.
  				 */
  				adcen |= 1 << gainshift;
  			}
  		} else {
  			gainshift = chan & ~1;
  			adcen |= 1 << chan;
  		}
  		devpriv->adcg = (devpriv->adcg & ~(3 << gainshift)) |
  				(pci230_ai_gain[range] << gainshift);
  	}
  
  	/* Set channel scan list. */
  	outw(adcen, devpriv->daqio + PCI230_ADCEN);
  
  	/* Set channel gains. */
  	outw(devpriv->adcg, devpriv->daqio + PCI230_ADCG);
  
  	/*
  	 * Set counter/timer 2 output high for use as the initial start
  	 * conversion source.
  	 */
  	comedi_8254_set_mode(dev->pacer, 2, I8254_MODE1);
  
  	/*
  	 * Temporarily use CT2 output as conversion trigger source and
  	 * temporarily set FIFO interrupt trigger level to 'full'.
  	 */
  	adccon |= PCI230_ADC_INT_FIFO_FULL | PCI230_ADC_TRIG_Z2CT2;
  
  	/*
  	 * Enable and reset FIFO, specify FIFO trigger level full, specify
  	 * uni/bip, se/diff, and temporarily set the start conversion source
  	 * to CT2 output.  Note that CT2 output is currently high, and this
  	 * will produce a false conversion trigger on some versions of the
  	 * PCI230/260, but that will be dealt with later.
  	 */
  	devpriv->adccon = adccon;
  	outw(adccon | PCI230_ADC_FIFO_RESET, devpriv->daqio + PCI230_ADCCON);
  
  	/*
  	 * Delay -
  	 * Failure to include this will result in the first few channels'-worth
  	 * of data being corrupt, normally manifesting itself by large negative
  	 * voltages. It seems the board needs time to settle between the first
  	 * FIFO reset (above) and the second FIFO reset (below). Setting the
  	 * channel gains and scan list _before_ the first FIFO reset also
  	 * helps, though only slightly.
  	 */
  	usleep_range(25, 100);
  
  	/* Reset FIFO again. */
  	outw(adccon | PCI230_ADC_FIFO_RESET, devpriv->daqio + PCI230_ADCCON);
  
  	if (cmd->convert_src == TRIG_TIMER) {
  		/*
  		 * Set up CT2 as conversion timer, but gate it off for now.
  		 * Note, counter/timer output 2 can be monitored on the
  		 * connector: PCI230 pin 21, PCI260 pin 18.
  		 */
  		zgat = pci230_gat_config(2, GAT_GND);
  		outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
  		/* Set counter/timer 2 to the specified conversion period. */
  		pci230_ct_setup_ns_mode(dev, 2, I8254_MODE3, cmd->convert_arg,
  					cmd->flags);
  		if (cmd->scan_begin_src != TRIG_FOLLOW) {
  			/*
  			 * Set up monostable on CT0 output for scan timing.  A
  			 * rising edge on the trigger (gate) input of CT0 will
  			 * trigger the monostable, causing its output to go low
  			 * for the configured period.  The period depends on
  			 * the conversion period and the number of conversions
  			 * in the scan.
  			 *
  			 * Set the trigger high before setting up the
  			 * monostable to stop it triggering.  The trigger
  			 * source will be changed later.
  			 */
  			zgat = pci230_gat_config(0, GAT_VCC);
  			outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
  			pci230_ct_setup_ns_mode(dev, 0, I8254_MODE1,
  						((u64)cmd->convert_arg *
  						 cmd->scan_end_arg),
  						CMDF_ROUND_UP);
  			if (cmd->scan_begin_src == TRIG_TIMER) {
  				/*
  				 * Monostable on CT0 will be triggered by
  				 * output of CT1 at configured scan frequency.
  				 *
  				 * Set up CT1 but gate it off for now.
  				 */
  				zgat = pci230_gat_config(1, GAT_GND);
  				outb(zgat, dev->iobase + PCI230_ZGAT_SCE);
  				pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3,
  							cmd->scan_begin_arg,
  							cmd->flags);
  			}
  		}
  	}
  
  	if (cmd->start_src == TRIG_INT)
  		s->async->inttrig = pci230_ai_inttrig_start;
  	else	/* TRIG_NOW */
  		pci230_ai_start(dev, s);
  
  	return 0;
  }
  
  static int pci230_ai_cancel(struct comedi_device *dev,
  			    struct comedi_subdevice *s)
  {
  	pci230_ai_stop(dev, s);
  	return 0;
  }
  
  /* Interrupt handler */
  static irqreturn_t pci230_interrupt(int irq, void *d)
  {
  	unsigned char status_int, valid_status_int, temp_ier;
  	struct comedi_device *dev = d;
  	struct pci230_private *devpriv = dev->private;
  	struct comedi_subdevice *s_ao = dev->write_subdev;
  	struct comedi_subdevice *s_ai = dev->read_subdev;
  	unsigned long irqflags;
  
  	/* Read interrupt status/enable register. */
  	status_int = inb(dev->iobase + PCI230_INT_STAT);
  
  	if (status_int == PCI230_INT_DISABLE)
  		return IRQ_NONE;
  
  	spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
  	valid_status_int = devpriv->ier & status_int;
  	/*
  	 * Disable triggered interrupts.
  	 * (Only those interrupts that need re-enabling, are, later in the
  	 * handler).
  	 */
  	temp_ier = devpriv->ier & ~status_int;
  	outb(temp_ier, dev->iobase + PCI230_INT_SCE);
  	devpriv->intr_running = true;
  	devpriv->intr_cpuid = THISCPU;
  	spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
  
  	/*
  	 * Check the source of interrupt and handle it.
  	 * The PCI230 can cope with concurrent ADC, DAC, PPI C0 and C3
  	 * interrupts.  However, at present (Comedi-0.7.60) does not allow
  	 * concurrent execution of commands, instructions or a mixture of the
  	 * two.
  	 */
  
  	if (valid_status_int & PCI230_INT_ZCLK_CT1)
  		pci230_handle_ao_nofifo(dev, s_ao);
  
  	if (valid_status_int & PCI230P2_INT_DAC)
  		pci230_handle_ao_fifo(dev, s_ao);
  
  	if (valid_status_int & PCI230_INT_ADC)
  		pci230_handle_ai(dev, s_ai);
  
  	/* Reenable interrupts. */
  	spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
  	if (devpriv->ier != temp_ier)
  		outb(devpriv->ier, dev->iobase + PCI230_INT_SCE);
  	devpriv->intr_running = false;
  	spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
  
  	if (s_ao)
  		comedi_handle_events(dev, s_ao);
  	comedi_handle_events(dev, s_ai);
  
  	return IRQ_HANDLED;
  }
  
  /* Check if PCI device matches a specific board. */
  static bool pci230_match_pci_board(const struct pci230_board *board,
  				   struct pci_dev *pci_dev)
  {
  	/* assume pci_dev->device != PCI_DEVICE_ID_INVALID */
  	if (board->id != pci_dev->device)
  		return false;
  	if (board->min_hwver == 0)
  		return true;
  	/* Looking for a '+' model.  First check length of registers. */
  	if (pci_resource_len(pci_dev, 3) < 32)
  		return false;	/* Not a '+' model. */
  	/*
  	 * TODO: temporarily enable PCI device and read the hardware version
  	 * register.  For now, assume it's okay.
  	 */
  	return true;
  }
  
  /* Look for board matching PCI device. */
  static const struct pci230_board *pci230_find_pci_board(struct pci_dev *pci_dev)
  {
  	unsigned int i;
  
  	for (i = 0; i < ARRAY_SIZE(pci230_boards); i++)
  		if (pci230_match_pci_board(&pci230_boards[i], pci_dev))
  			return &pci230_boards[i];
  	return NULL;
  }
  
  static int pci230_auto_attach(struct comedi_device *dev,
  			      unsigned long context_unused)
  {
  	struct pci_dev *pci_dev = comedi_to_pci_dev(dev);
  	const struct pci230_board *board;
  	struct pci230_private *devpriv;
  	struct comedi_subdevice *s;
  	int rc;
  
  	dev_info(dev->class_dev, "amplc_pci230: attach pci %s\n",
  		 pci_name(pci_dev));
  
  	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
  	if (!devpriv)
  		return -ENOMEM;
  
  	spin_lock_init(&devpriv->isr_spinlock);
  	spin_lock_init(&devpriv->res_spinlock);
  	spin_lock_init(&devpriv->ai_stop_spinlock);
  	spin_lock_init(&devpriv->ao_stop_spinlock);
  
  	board = pci230_find_pci_board(pci_dev);
  	if (!board) {
  		dev_err(dev->class_dev,
  			"amplc_pci230: BUG! cannot determine board type!\n");
  		return -EINVAL;
  	}
  	dev->board_ptr = board;
  	dev->board_name = board->name;
  
  	rc = comedi_pci_enable(dev);
  	if (rc)
  		return rc;
  
  	/*
  	 * Read base addresses of the PCI230's two I/O regions from PCI
  	 * configuration register.
  	 */
  	dev->iobase = pci_resource_start(pci_dev, 2);
  	devpriv->daqio = pci_resource_start(pci_dev, 3);
  	dev_dbg(dev->class_dev,
  		"%s I/O region 1 0x%04lx I/O region 2 0x%04lx\n",
  		dev->board_name, dev->iobase, devpriv->daqio);
  	/* Read bits of DACCON register - only the output range. */
  	devpriv->daccon = inw(devpriv->daqio + PCI230_DACCON) &
  			  PCI230_DAC_OR_MASK;
  	/*
  	 * Read hardware version register and set extended function register
  	 * if they exist.
  	 */
  	if (pci_resource_len(pci_dev, 3) >= 32) {
  		unsigned short extfunc = 0;
  
  		devpriv->hwver = inw(devpriv->daqio + PCI230P_HWVER);
  		if (devpriv->hwver < board->min_hwver) {
  			dev_err(dev->class_dev,
  				"%s - bad hardware version - got %u, need %u\n",
  				dev->board_name, devpriv->hwver,
  				board->min_hwver);
  			return -EIO;
  		}
  		if (devpriv->hwver > 0) {
  			if (!board->have_dio) {
  				/*
  				 * No DIO ports.  Route counters' external gates
  				 * to the EXTTRIG signal (PCI260+ pin 17).
  				 * (Otherwise, they would be routed to DIO
  				 * inputs PC0, PC1 and PC2 which don't exist
  				 * on PCI260[+].)
  				 */
  				extfunc |= PCI230P_EXTFUNC_GAT_EXTTRIG;
  			}
  			if (board->ao_bits && devpriv->hwver >= 2) {
  				/* Enable DAC FIFO functionality. */
  				extfunc |= PCI230P2_EXTFUNC_DACFIFO;
  			}
  		}
  		outw(extfunc, devpriv->daqio + PCI230P_EXTFUNC);
  		if (extfunc & PCI230P2_EXTFUNC_DACFIFO) {
  			/*
  			 * Temporarily enable DAC FIFO, reset it and disable
  			 * FIFO wraparound.
  			 */
  			outw(devpriv->daccon | PCI230P2_DAC_FIFO_EN |
  			     PCI230P2_DAC_FIFO_RESET,
  			     devpriv->daqio + PCI230_DACCON);
  			/* Clear DAC FIFO channel enable register. */
  			outw(0, devpriv->daqio + PCI230P2_DACEN);
  			/* Disable DAC FIFO. */
  			outw(devpriv->daccon, devpriv->daqio + PCI230_DACCON);
  		}
  	}
  	/* Disable board's interrupts. */
  	outb(0, dev->iobase + PCI230_INT_SCE);
  	/* Set ADC to a reasonable state. */
  	devpriv->adcg = 0;
  	devpriv->adccon = PCI230_ADC_TRIG_NONE | PCI230_ADC_IM_SE |
  			  PCI230_ADC_IR_BIP;
  	outw(1 << 0, devpriv->daqio + PCI230_ADCEN);
  	outw(devpriv->adcg, devpriv->daqio + PCI230_ADCG);
  	outw(devpriv->adccon | PCI230_ADC_FIFO_RESET,
  	     devpriv->daqio + PCI230_ADCCON);
  
  	if (pci_dev->irq) {
  		rc = request_irq(pci_dev->irq, pci230_interrupt, IRQF_SHARED,
  				 dev->board_name, dev);
  		if (rc == 0)
  			dev->irq = pci_dev->irq;
  	}
  
  	dev->pacer = comedi_8254_init(dev->iobase + PCI230_Z2_CT_BASE,
  				      0, I8254_IO8, 0);
  	if (!dev->pacer)
  		return -ENOMEM;
  
  	rc = comedi_alloc_subdevices(dev, 3);
  	if (rc)
  		return rc;
  
  	s = &dev->subdevices[0];
  	/* analog input subdevice */
  	s->type = COMEDI_SUBD_AI;
  	s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_GROUND;
  	s->n_chan = 16;
  	s->maxdata = (1 << board->ai_bits) - 1;
  	s->range_table = &pci230_ai_range;
  	s->insn_read = pci230_ai_insn_read;
  	s->len_chanlist = 256;	/* but there are restrictions. */
  	if (dev->irq) {
  		dev->read_subdev = s;
  		s->subdev_flags |= SDF_CMD_READ;
  		s->do_cmd = pci230_ai_cmd;
  		s->do_cmdtest = pci230_ai_cmdtest;
  		s->cancel = pci230_ai_cancel;
  	}
  
  	s = &dev->subdevices[1];
  	/* analog output subdevice */
  	if (board->ao_bits) {
  		s->type = COMEDI_SUBD_AO;
  		s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
  		s->n_chan = 2;
  		s->maxdata = (1 << board->ao_bits) - 1;
  		s->range_table = &pci230_ao_range;
  		s->insn_write = pci230_ao_insn_write;
  		s->len_chanlist = 2;
  		if (dev->irq) {
  			dev->write_subdev = s;
  			s->subdev_flags |= SDF_CMD_WRITE;
  			s->do_cmd = pci230_ao_cmd;
  			s->do_cmdtest = pci230_ao_cmdtest;
  			s->cancel = pci230_ao_cancel;
  		}
  
  		rc = comedi_alloc_subdev_readback(s);
  		if (rc)
  			return rc;
  	} else {
  		s->type = COMEDI_SUBD_UNUSED;
  	}
  
  	s = &dev->subdevices[2];
  	/* digital i/o subdevice */
  	if (board->have_dio) {
  		rc = subdev_8255_init(dev, s, NULL, PCI230_PPI_X_BASE);
  		if (rc)
  			return rc;
  	} else {
  		s->type = COMEDI_SUBD_UNUSED;
  	}
  
  	return 0;
  }
  
  static struct comedi_driver amplc_pci230_driver = {
  	.driver_name	= "amplc_pci230",
  	.module		= THIS_MODULE,
  	.auto_attach	= pci230_auto_attach,
  	.detach		= comedi_pci_detach,
  };
  
  static int amplc_pci230_pci_probe(struct pci_dev *dev,
  				  const struct pci_device_id *id)
  {
  	return comedi_pci_auto_config(dev, &amplc_pci230_driver,
  				      id->driver_data);
  }
  
  static const struct pci_device_id amplc_pci230_pci_table[] = {
  	{ PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI230) },
  	{ PCI_DEVICE(PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI260) },
  	{ 0 }
  };
  MODULE_DEVICE_TABLE(pci, amplc_pci230_pci_table);
  
  static struct pci_driver amplc_pci230_pci_driver = {
  	.name		= "amplc_pci230",
  	.id_table	= amplc_pci230_pci_table,
  	.probe		= amplc_pci230_pci_probe,
  	.remove		= comedi_pci_auto_unconfig,
  };
  module_comedi_pci_driver(amplc_pci230_driver, amplc_pci230_pci_driver);
  
  MODULE_AUTHOR("Comedi http://www.comedi.org");
  MODULE_DESCRIPTION("Comedi driver for Amplicon PCI230(+) and PCI260(+)");
  MODULE_LICENSE("GPL");
  