diff options
Diffstat (limited to 'drivers/comedi/drivers/amplc_pci230.c')
| -rw-r--r-- | drivers/comedi/drivers/amplc_pci230.c | 2575 | 
1 files changed, 2575 insertions, 0 deletions
| diff --git a/drivers/comedi/drivers/amplc_pci230.c b/drivers/comedi/drivers/amplc_pci230.c new file mode 100644 index 000000000000..8911dc2bd2c6 --- /dev/null +++ b/drivers/comedi/drivers/amplc_pci230.c @@ -0,0 +1,2575 @@ +// 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(BIT(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, &lc_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 https://www.comedi.org"); +MODULE_DESCRIPTION("Comedi driver for Amplicon PCI230(+) and PCI260(+)"); +MODULE_LICENSE("GPL"); | 
