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 <[email protected]> + * + * COMEDI - Linux Control and Measurement Device Interface + * Copyright (C) 2000 David A. Schleef <[email protected]> + */ + +/* + * Driver: amplc_pci230 + * Description: Amplicon PCI230, PCI260 Multifunction I/O boards + * Author: Allan Willcox <[email protected]>, + * Steve D Sharples <[email protected]>, + * Ian Abbott <[email protected]> + * 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"); |