| /* |
| 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> |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| /* |
| Driver: amplc_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: Wed, 22 Oct 2008 12:34:49 +0100 |
| Devices: [Amplicon] PCI230 (pci230 or amplc_pci230), |
| PCI230+ (pci230+ or amplc_pci230), |
| PCI260 (pci260 or amplc_pci230), PCI260+ (pci260+ or amplc_pci230) |
| Status: works |
| |
| Configuration options: |
| [0] - PCI bus of device (optional). |
| [1] - PCI slot of device (optional). |
| If bus/slot is not specified, the first available PCI device |
| will be used. |
| |
| Configuring a "amplc_pci230" will match any supported card and it will |
| choose the best match, picking the "+" models if possible. Configuring |
| a "pci230" will match a PCI230 or PCI230+ card and it will be treated as |
| a PCI230. Configuring a "pci260" will match a PCI260 or PCI260+ card |
| and it will be treated as a PCI260. Configuring a "pci230+" will match |
| a PCI230+ card. Configuring a "pci260+" will match a PCI260+ card. |
| |
| 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 "../comedidev.h" |
| |
| #include <linux/delay.h> |
| #include <linux/interrupt.h> |
| |
| #include "comedi_pci.h" |
| #include "8253.h" |
| #include "8255.h" |
| |
| /* PCI230 PCI configuration register information */ |
| #define PCI_VENDOR_ID_AMPLICON 0x14dc |
| #define PCI_DEVICE_ID_PCI230 0x0000 |
| #define PCI_DEVICE_ID_PCI260 0x0006 |
| #define PCI_DEVICE_ID_INVALID 0xffff |
| |
| #define PCI230_IO1_SIZE 32 /* Size of I/O space 1 */ |
| #define PCI230_IO2_SIZE 16 /* Size of I/O space 2 */ |
| |
| /* 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_Z2_CT0 0x14 /* 82C54 counter/timer 0 */ |
| #define PCI230_Z2_CT1 0x15 /* 82C54 counter/timer 1 */ |
| #define PCI230_Z2_CT2 0x16 /* 82C54 counter/timer 2 */ |
| #define PCI230_Z2_CTC 0x17 /* 82C54 counter/timer control word */ |
| #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) */ |
| |
| /* Convertor related constants. */ |
| #define PCI230_DAC_SETTLE 5 /* Analogue output settling time in µs */ |
| /* (DAC itself is 1µs nominally). */ |
| #define PCI230_ADC_SETTLE 1 /* Analogue input settling time in µs */ |
| /* (ADC itself is 1.6µs nominally but we poll |
| * anyway). */ |
| #define PCI230_MUX_SETTLE 10 /* ADC MUX settling time in µS */ |
| /* - 10µs for se, 20µs de. */ |
| |
| /* DACCON read-write values. */ |
| #define PCI230_DAC_OR_UNI (0<<0) /* Output range unipolar */ |
| #define PCI230_DAC_OR_BIP (1<<0) /* Output range bipolar */ |
| #define PCI230_DAC_OR_MASK (1<<0) |
| /* 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 (1<<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_NONE (0<<2) /* No trigger */ |
| #define PCI230P2_DAC_TRIG_SW (1<<2) /* Software trigger trigger */ |
| #define PCI230P2_DAC_TRIG_EXTP (2<<2) /* EXTTRIG +ve edge trigger */ |
| #define PCI230P2_DAC_TRIG_EXTN (3<<2) /* EXTTRIG -ve edge trigger */ |
| #define PCI230P2_DAC_TRIG_Z2CT0 (4<<2) /* CT0-OUT +ve edge trigger */ |
| #define PCI230P2_DAC_TRIG_Z2CT1 (5<<2) /* CT1-OUT +ve edge trigger */ |
| #define PCI230P2_DAC_TRIG_Z2CT2 (6<<2) /* CT2-OUT +ve edge trigger */ |
| #define PCI230P2_DAC_TRIG_MASK (7<<2) |
| #define PCI230P2_DAC_FIFO_WRAP (1<<7) /* FIFO wraparound mode */ |
| #define PCI230P2_DAC_INT_FIFO_EMPTY (0<<9) /* FIFO interrupt empty */ |
| #define PCI230P2_DAC_INT_FIFO_NEMPTY (1<<9) |
| #define PCI230P2_DAC_INT_FIFO_NHALF (2<<9) /* FIFO intr not half full */ |
| #define PCI230P2_DAC_INT_FIFO_HALF (3<<9) |
| #define PCI230P2_DAC_INT_FIFO_NFULL (4<<9) /* FIFO interrupt not full */ |
| #define PCI230P2_DAC_INT_FIFO_FULL (5<<9) |
| #define PCI230P2_DAC_INT_FIFO_MASK (7<<9) |
| |
| /* DACCON read-only values. */ |
| #define PCI230_DAC_BUSY (1<<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 (1<<5) /* Underrun error */ |
| #define PCI230P2_DAC_FIFO_EMPTY (1<<13) /* FIFO empty */ |
| #define PCI230P2_DAC_FIFO_FULL (1<<14) /* FIFO full */ |
| #define PCI230P2_DAC_FIFO_HALF (1<<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 (1<<5) /* Clear underrun */ |
| #define PCI230P2_DAC_FIFO_RESET (1<<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_NONE (0<<0) /* No trigger */ |
| #define PCI230_ADC_TRIG_SW (1<<0) /* Software trigger trigger */ |
| #define PCI230_ADC_TRIG_EXTP (2<<0) /* EXTTRIG +ve edge trigger */ |
| #define PCI230_ADC_TRIG_EXTN (3<<0) /* EXTTRIG -ve edge trigger */ |
| #define PCI230_ADC_TRIG_Z2CT0 (4<<0) /* CT0-OUT +ve edge trigger */ |
| #define PCI230_ADC_TRIG_Z2CT1 (5<<0) /* CT1-OUT +ve edge trigger */ |
| #define PCI230_ADC_TRIG_Z2CT2 (6<<0) /* CT2-OUT +ve edge trigger */ |
| #define PCI230_ADC_TRIG_MASK (7<<0) |
| #define PCI230_ADC_IR_UNI (0<<3) /* Input range unipolar */ |
| #define PCI230_ADC_IR_BIP (1<<3) /* Input range bipolar */ |
| #define PCI230_ADC_IR_MASK (1<<3) |
| #define PCI230_ADC_IM_SE (0<<4) /* Input mode single ended */ |
| #define PCI230_ADC_IM_DIF (1<<4) /* Input mode differential */ |
| #define PCI230_ADC_IM_MASK (1<<4) |
| #define PCI230_ADC_FIFO_EN (1<<8) /* FIFO enable */ |
| #define PCI230_ADC_INT_FIFO_EMPTY (0<<9) |
| #define PCI230_ADC_INT_FIFO_NEMPTY (1<<9) /* FIFO interrupt not empty */ |
| #define PCI230_ADC_INT_FIFO_NHALF (2<<9) |
| #define PCI230_ADC_INT_FIFO_HALF (3<<9) /* FIFO interrupt half full */ |
| #define PCI230_ADC_INT_FIFO_NFULL (4<<9) |
| #define PCI230_ADC_INT_FIFO_FULL (5<<9) /* FIFO interrupt full */ |
| #define PCI230P_ADC_INT_FIFO_THRESH (7<<9) /* FIFO interrupt threshold */ |
| #define PCI230_ADC_INT_FIFO_MASK (7<<9) |
| |
| /* ADCCON write-only, transient values. */ |
| #define PCI230_ADC_FIFO_RESET (1<<12) /* FIFO reset */ |
| #define PCI230_ADC_GLOB_RESET (1<<13) /* Global reset */ |
| |
| /* ADCCON read-only values. */ |
| #define PCI230_ADC_BUSY (1<<15) /* ADC busy */ |
| #define PCI230_ADC_FIFO_EMPTY (1<<12) /* FIFO empty */ |
| #define PCI230_ADC_FIFO_FULL (1<<13) /* FIFO full */ |
| #define PCI230_ADC_FIFO_HALF (1<<14) /* FIFO half full */ |
| #define PCI230_ADC_FIFO_FULL_LATCHED (1<<5) /* Indicates 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 */ |
| |
| /* Value to write to ADCSWTRIG to trigger ADC conversion in software trigger |
| * mode. Can be anything. */ |
| #define PCI230_ADC_CONV 0xffff |
| |
| /* PCI230+ EXTFUNC values. */ |
| #define PCI230P_EXTFUNC_GAT_EXTTRIG (1<<0) |
| /* Route EXTTRIG pin to external gate inputs. */ |
| /* PCI230+ hardware version 2 values. */ |
| #define PCI230P2_EXTFUNC_DACFIFO (1<<1) |
| /* Allow DAC FIFO to be enabled. */ |
| |
| /* |
| * Counter/timer clock input configuration sources. |
| */ |
| #define CLK_CLK 0 /* reserved (channel-specific clock) */ |
| #define CLK_10MHZ 1 /* internal 10 MHz clock */ |
| #define CLK_1MHZ 2 /* internal 1 MHz clock */ |
| #define CLK_100KHZ 3 /* internal 100 kHz clock */ |
| #define CLK_10KHZ 4 /* internal 10 kHz clock */ |
| #define CLK_1KHZ 5 /* internal 1 kHz clock */ |
| #define CLK_OUTNM1 6 /* output of channel-1 modulo total */ |
| #define CLK_EXT 7 /* external clock */ |
| /* Macro to construct clock input configuration register value. */ |
| #define CLK_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7)) |
| /* Timebases in ns. */ |
| #define TIMEBASE_10MHZ 100 |
| #define TIMEBASE_1MHZ 1000 |
| #define TIMEBASE_100KHZ 10000 |
| #define TIMEBASE_10KHZ 100000 |
| #define TIMEBASE_1KHZ 1000000 |
| |
| /* |
| * Counter/timer gate input configuration sources. |
| */ |
| #define GAT_VCC 0 /* VCC (i.e. enabled) */ |
| #define GAT_GND 1 /* GND (i.e. disabled) */ |
| #define GAT_EXT 2 /* external gate input (PPCn on PCI230) */ |
| #define GAT_NOUTNM2 3 /* inverted output of channel-2 modulo total */ |
| /* Macro to construct gate input configuration register value. */ |
| #define GAT_CONFIG(chan, src) ((((chan) & 3) << 3) | ((src) & 7)) |
| |
| /* |
| * Summary of CLK_OUTNM1 and GAT_NOUTNM2 connections for 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 (1<<0) |
| #define PCI230_INT_PPI_C3 (1<<1) |
| #define PCI230_INT_ADC (1<<2) |
| #define PCI230_INT_ZCLK_CT1 (1<<5) |
| /* For PCI230+ hardware version 2 when DAC FIFO enabled. */ |
| #define PCI230P2_INT_DAC (1<<4) |
| |
| #define PCI230_TEST_BIT(val, n) ((val>>n)&1) |
| /* Assumes bits numbered with zero offset, ie. 0-15 */ |
| |
| /* (Potentially) shared resources and their owners */ |
| enum { |
| RES_Z2CT0, /* Z2-CT0 */ |
| RES_Z2CT1, /* Z2-CT1 */ |
| RES_Z2CT2, /* Z2-CT2 */ |
| NUM_RESOURCES /* Number of (potentially) shared resources. */ |
| }; |
| |
| enum { |
| OWNER_NONE, /* Not owned */ |
| OWNER_AICMD, /* Owned by AI command */ |
| OWNER_AOCMD /* Owned by AO command */ |
| }; |
| |
| /* |
| * Handy macros. |
| */ |
| |
| /* Combine old and new bits. */ |
| #define COMBINE(old, new, mask) (((old) & ~(mask)) | ((new) & (mask))) |
| |
| /* A generic null function pointer value. */ |
| #define NULLFUNC 0 |
| |
| /* Current CPU. XXX should this be hard_smp_processor_id()? */ |
| #define THISCPU smp_processor_id() |
| |
| /* State flags for atomic bit operations */ |
| #define AI_CMD_STARTED 0 |
| #define AO_CMD_STARTED 1 |
| |
| /* |
| * Board descriptions for the two boards supported. |
| */ |
| |
| struct pci230_board { |
| const char *name; |
| unsigned short id; |
| int ai_chans; |
| int ai_bits; |
| int ao_chans; |
| int ao_bits; |
| int have_dio; |
| unsigned int min_hwver; /* Minimum hardware version supported. */ |
| }; |
| static const struct pci230_board pci230_boards[] = { |
| { |
| .name = "pci230+", |
| .id = PCI_DEVICE_ID_PCI230, |
| .ai_chans = 16, |
| .ai_bits = 16, |
| .ao_chans = 2, |
| .ao_bits = 12, |
| .have_dio = 1, |
| .min_hwver = 1, |
| }, |
| { |
| .name = "pci260+", |
| .id = PCI_DEVICE_ID_PCI260, |
| .ai_chans = 16, |
| .ai_bits = 16, |
| .ao_chans = 0, |
| .ao_bits = 0, |
| .have_dio = 0, |
| .min_hwver = 1, |
| }, |
| { |
| .name = "pci230", |
| .id = PCI_DEVICE_ID_PCI230, |
| .ai_chans = 16, |
| .ai_bits = 12, |
| .ao_chans = 2, |
| .ao_bits = 12, |
| .have_dio = 1, |
| }, |
| { |
| .name = "pci260", |
| .id = PCI_DEVICE_ID_PCI260, |
| .ai_chans = 16, |
| .ai_bits = 12, |
| .ao_chans = 0, |
| .ao_bits = 0, |
| .have_dio = 0, |
| }, |
| { |
| .name = "amplc_pci230", /* Wildcard matches any above */ |
| .id = PCI_DEVICE_ID_INVALID, |
| }, |
| }; |
| |
| static DEFINE_PCI_DEVICE_TABLE(pci230_pci_table) = { |
| { |
| PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI230, PCI_ANY_ID, |
| PCI_ANY_ID, 0, 0, 0}, { |
| PCI_VENDOR_ID_AMPLICON, PCI_DEVICE_ID_PCI260, PCI_ANY_ID, |
| PCI_ANY_ID, 0, 0, 0}, { |
| 0} |
| }; |
| |
| MODULE_DEVICE_TABLE(pci, pci230_pci_table); |
| /* |
| * Useful for shorthand access to the particular board structure |
| */ |
| #define n_pci230_boards ARRAY_SIZE(pci230_boards) |
| #define thisboard ((const struct pci230_board *)dev->board_ptr) |
| |
| /* this structure is for data unique to this hardware driver. If |
| several hardware drivers keep similar information in this structure, |
| feel free to suggest moving the variable to the struct comedi_device struct. */ |
| struct pci230_private { |
| struct pci_dev *pci_dev; |
| 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 state; /* State flags */ |
| unsigned long iobase1; /* PCI230's I/O space 1 */ |
| unsigned int ao_readback[2]; /* Used for AO readback */ |
| unsigned int ai_scan_count; /* Number of analogue input scans |
| * remaining. */ |
| unsigned int ai_scan_pos; /* Current position within analogue |
| * input scan */ |
| unsigned int ao_scan_count; /* Number of analogue output scans |
| * remaining. */ |
| int intr_cpuid; /* ID of CPU running interrupt routine. */ |
| unsigned short hwver; /* Hardware version (for '+' models). */ |
| unsigned short adccon; /* ADCCON register value. */ |
| unsigned short daccon; /* DACCON register value. */ |
| unsigned short adcfifothresh; /* ADC FIFO programmable interrupt |
| * level threshold (PCI230+/260+). */ |
| unsigned short adcg; /* ADCG register value. */ |
| unsigned char int_en; /* Interrupt enables bits. */ |
| unsigned char ai_continuous; /* Flag set when cmd->stop_src == |
| * TRIG_NONE - user chooses to stop |
| * continuous conversion by |
| * cancelation. */ |
| unsigned char ao_continuous; /* Flag set when cmd->stop_src == |
| * TRIG_NONE - user chooses to stop |
| * continuous conversion by |
| * cancelation. */ |
| unsigned char ai_bipolar; /* Set if bipolar input range so we |
| * know to mangle it. */ |
| unsigned char ao_bipolar; /* Set if bipolar output range so we |
| * know to mangle it. */ |
| unsigned char ier; /* Copy of interrupt enables/status register. */ |
| unsigned char intr_running; /* Flag set in interrupt routine. */ |
| unsigned char res_owner[NUM_RESOURCES]; /* Shared resource owners. */ |
| }; |
| |
| #define devpriv ((struct pci230_private *)dev->private) |
| |
| /* PCI230 clock source periods in ns */ |
| static const unsigned int pci230_timebase[8] = { |
| [CLK_10MHZ] = TIMEBASE_10MHZ, |
| [CLK_1MHZ] = TIMEBASE_1MHZ, |
| [CLK_100KHZ] = TIMEBASE_100KHZ, |
| [CLK_10KHZ] = TIMEBASE_10KHZ, |
| [CLK_1KHZ] = TIMEBASE_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 adccon bipolar flag for each analogue input range. */ |
| static const unsigned char pci230_ai_bipolar[7] = { 1, 1, 1, 1, 0, 0, 0 }; |
| |
| /* PCI230 analogue output range table */ |
| static const struct comedi_lrange pci230_ao_range = { 2, { |
| UNI_RANGE(10), |
| BIP_RANGE(10) |
| } |
| }; |
| |
| /* PCI230 daccon bipolar flag for each analogue output range. */ |
| static const unsigned char pci230_ao_bipolar[2] = { 0, 1 }; |
| |
| /* |
| * The struct comedi_driver structure tells the Comedi core module |
| * which functions to call to configure/deconfigure (attach/detach) |
| * the board, and also about the kernel module that contains |
| * the device code. |
| */ |
| static int pci230_attach(struct comedi_device *dev, |
| struct comedi_devconfig *it); |
| static int pci230_detach(struct comedi_device *dev); |
| static struct comedi_driver driver_amplc_pci230 = { |
| .driver_name = "amplc_pci230", |
| .module = THIS_MODULE, |
| .attach = pci230_attach, |
| .detach = pci230_detach, |
| .board_name = &pci230_boards[0].name, |
| .offset = sizeof(pci230_boards[0]), |
| .num_names = ARRAY_SIZE(pci230_boards), |
| }; |
| |
| COMEDI_PCI_INITCLEANUP(driver_amplc_pci230, pci230_pci_table); |
| |
| static int pci230_ai_rinsn(struct comedi_device *dev, |
| struct comedi_subdevice *s, struct comedi_insn *insn, |
| unsigned int *data); |
| static int pci230_ao_winsn(struct comedi_device *dev, |
| struct comedi_subdevice *s, struct comedi_insn *insn, |
| unsigned int *data); |
| static int pci230_ao_rinsn(struct comedi_device *dev, |
| struct comedi_subdevice *s, struct comedi_insn *insn, |
| unsigned int *data); |
| static void pci230_ct_setup_ns_mode(struct comedi_device *dev, unsigned int ct, |
| unsigned int mode, uint64_t ns, |
| unsigned int round); |
| static void pci230_ns_to_single_timer(unsigned int *ns, unsigned int round); |
| static void pci230_cancel_ct(struct comedi_device *dev, unsigned int ct); |
| static irqreturn_t pci230_interrupt(int irq, void *d); |
| static int pci230_ao_cmdtest(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_cmd *cmd); |
| static int pci230_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s); |
| static int pci230_ao_cancel(struct comedi_device *dev, |
| struct comedi_subdevice *s); |
| static void pci230_ao_stop(struct comedi_device *dev, |
| struct comedi_subdevice *s); |
| static void pci230_handle_ao_nofifo(struct comedi_device *dev, |
| struct comedi_subdevice *s); |
| static int pci230_handle_ao_fifo(struct comedi_device *dev, |
| struct comedi_subdevice *s); |
| static int pci230_ai_cmdtest(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_cmd *cmd); |
| static int pci230_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s); |
| static int pci230_ai_cancel(struct comedi_device *dev, |
| struct comedi_subdevice *s); |
| static void pci230_ai_stop(struct comedi_device *dev, |
| struct comedi_subdevice *s); |
| static void pci230_handle_ai(struct comedi_device *dev, |
| struct comedi_subdevice *s); |
| |
| static short pci230_ai_read(struct comedi_device *dev) |
| { |
| /* Read sample. */ |
| short data = (short)inw(dev->iobase + 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. */ |
| data = data >> (16 - thisboard->ai_bits); |
| |
| /* If a bipolar range was specified, mangle it (twos |
| * complement->straight binary). */ |
| if (devpriv->ai_bipolar) |
| data ^= 1 << (thisboard->ai_bits - 1); |
| |
| return data; |
| } |
| |
| static inline unsigned short pci230_ao_mangle_datum(struct comedi_device *dev, |
| short datum) |
| { |
| /* If a bipolar range was specified, mangle it (straight binary->twos |
| * complement). */ |
| if (devpriv->ao_bipolar) |
| datum ^= 1 << (thisboard->ao_bits - 1); |
| |
| |
| /* 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 - thisboard->ao_bits); |
| return (unsigned short)datum; |
| } |
| |
| static inline void pci230_ao_write_nofifo(struct comedi_device *dev, |
| short datum, unsigned int chan) |
| { |
| /* Store unmangled datum to be read back later. */ |
| devpriv->ao_readback[chan] = datum; |
| |
| /* Write mangled datum to appropriate DACOUT register. */ |
| outw(pci230_ao_mangle_datum(dev, datum), dev->iobase + (((chan) == 0) |
| ? PCI230_DACOUT1 |
| : |
| PCI230_DACOUT2)); |
| } |
| |
| static inline void pci230_ao_write_fifo(struct comedi_device *dev, short datum, |
| unsigned int chan) |
| { |
| /* Store unmangled datum to be read back later. */ |
| devpriv->ao_readback[chan] = datum; |
| |
| /* Write mangled datum to appropriate DACDATA register. */ |
| outw(pci230_ao_mangle_datum(dev, datum), |
| dev->iobase + PCI230P2_DACDATA); |
| } |
| |
| /* |
| * Attach is called by the Comedi core to configure the driver |
| * for a particular board. If you specified a board_name array |
| * in the driver structure, dev->board_ptr contains that |
| * address. |
| */ |
| static int pci230_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
| { |
| struct comedi_subdevice *s; |
| unsigned long iobase1, iobase2; |
| /* PCI230's I/O spaces 1 and 2 respectively. */ |
| struct pci_dev *pci_dev; |
| int i = 0, irq_hdl, rc; |
| |
| printk("comedi%d: amplc_pci230: attach %s %d,%d\n", dev->minor, |
| thisboard->name, it->options[0], it->options[1]); |
| |
| /* Allocate the private structure area using alloc_private(). |
| * Macro defined in comedidev.h - memsets struct fields to 0. */ |
| if ((alloc_private(dev, sizeof(struct pci230_private))) < 0) |
| 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); |
| /* Find card */ |
| for (pci_dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL); |
| pci_dev != NULL; |
| pci_dev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pci_dev)) { |
| if (it->options[0] || it->options[1]) { |
| /* Match against bus/slot options. */ |
| if (it->options[0] != pci_dev->bus->number || |
| it->options[1] != PCI_SLOT(pci_dev->devfn)) |
| continue; |
| } |
| if (pci_dev->vendor != PCI_VENDOR_ID_AMPLICON) |
| continue; |
| if (thisboard->id == PCI_DEVICE_ID_INVALID) { |
| /* The name was specified as "amplc_pci230" which is |
| * used to match any supported device. Replace the |
| * current dev->board_ptr with one that matches the |
| * PCI device ID. */ |
| for (i = 0; i < n_pci230_boards; i++) { |
| if (pci_dev->device == pci230_boards[i].id) { |
| if (pci230_boards[i].min_hwver > 0) { |
| /* Check for a '+' model. |
| * First check length of |
| * registers. */ |
| if (pci_resource_len(pci_dev, 3) |
| < 32) { |
| /* Not a '+' model. */ |
| continue; |
| } |
| /* TODO: temporarily enable the |
| * PCI device and read the |
| * hardware version register. |
| * For now assume it's okay. */ |
| } |
| /* Change board_ptr to matched board */ |
| dev->board_ptr = &pci230_boards[i]; |
| break; |
| } |
| } |
| if (i < n_pci230_boards) |
| break; |
| } else { |
| /* The name was specified as a specific device name. |
| * The current dev->board_ptr is correct. Check |
| * whether it matches the PCI device ID. */ |
| if (thisboard->id == pci_dev->device) { |
| /* Check minimum hardware version. */ |
| if (thisboard->min_hwver > 0) { |
| /* Looking for a '+' model. First |
| * check length of registers. */ |
| if (pci_resource_len(pci_dev, 3) < 32) { |
| /* Not a '+' model. */ |
| continue; |
| } |
| /* TODO: temporarily enable the PCI |
| * device and read the hardware version |
| * register. For now, assume it's |
| * okay. */ |
| break; |
| } else { |
| break; |
| } |
| } |
| } |
| } |
| if (!pci_dev) { |
| printk("comedi%d: No %s card found\n", dev->minor, |
| thisboard->name); |
| return -EIO; |
| } |
| devpriv->pci_dev = pci_dev; |
| |
| /* |
| * Initialize dev->board_name. |
| */ |
| dev->board_name = thisboard->name; |
| |
| /* Enable PCI device and reserve I/O spaces. */ |
| if (comedi_pci_enable(pci_dev, "amplc_pci230") < 0) { |
| printk("comedi%d: failed to enable PCI device " |
| "and request regions\n", dev->minor); |
| return -EIO; |
| } |
| |
| /* Read base addresses of the PCI230's two I/O regions from PCI |
| * configuration register. */ |
| iobase1 = pci_resource_start(pci_dev, 2); |
| iobase2 = pci_resource_start(pci_dev, 3); |
| |
| printk("comedi%d: %s I/O region 1 0x%04lx I/O region 2 0x%04lx\n", |
| dev->minor, dev->board_name, iobase1, iobase2); |
| |
| devpriv->iobase1 = iobase1; |
| dev->iobase = iobase2; |
| |
| /* Read bits of DACCON register - only the output range. */ |
| devpriv->daccon = inw(dev->iobase + 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(dev->iobase + PCI230P_HWVER); |
| if (devpriv->hwver < thisboard->min_hwver) { |
| printk("comedi%d: %s - bad hardware version " |
| "- got %u, need %u\n", dev->minor, |
| dev->board_name, devpriv->hwver, |
| thisboard->min_hwver); |
| return -EIO; |
| } |
| if (devpriv->hwver > 0) { |
| if (!thisboard->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 ((thisboard->ao_chans > 0) |
| && (devpriv->hwver >= 2)) { |
| /* Enable DAC FIFO functionality. */ |
| extfunc |= PCI230P2_EXTFUNC_DACFIFO; |
| } |
| } |
| outw(extfunc, dev->iobase + PCI230P_EXTFUNC); |
| if ((extfunc & PCI230P2_EXTFUNC_DACFIFO) != 0) { |
| /* Temporarily enable DAC FIFO, reset it and disable |
| * FIFO wraparound. */ |
| outw(devpriv->daccon | PCI230P2_DAC_FIFO_EN |
| | PCI230P2_DAC_FIFO_RESET, |
| dev->iobase + PCI230_DACCON); |
| /* Clear DAC FIFO channel enable register. */ |
| outw(0, dev->iobase + PCI230P2_DACEN); |
| /* Disable DAC FIFO. */ |
| outw(devpriv->daccon, dev->iobase + PCI230_DACCON); |
| } |
| } |
| |
| /* Disable board's interrupts. */ |
| outb(0, devpriv->iobase1 + PCI230_INT_SCE); |
| |
| /* Set ADC to a reasonable state. */ |
| devpriv->adcg = 0; |
| devpriv->adccon = PCI230_ADC_TRIG_NONE | PCI230_ADC_IM_SE |
| | PCI230_ADC_IR_BIP; |
| outw(1 << 0, dev->iobase + PCI230_ADCEN); |
| outw(devpriv->adcg, dev->iobase + PCI230_ADCG); |
| outw(devpriv->adccon | PCI230_ADC_FIFO_RESET, |
| dev->iobase + PCI230_ADCCON); |
| |
| /* Register the interrupt handler. */ |
| irq_hdl = request_irq(devpriv->pci_dev->irq, pci230_interrupt, |
| IRQF_SHARED, "amplc_pci230", dev); |
| if (irq_hdl < 0) { |
| printk("comedi%d: unable to register irq, " |
| "commands will not be available %d\n", dev->minor, |
| devpriv->pci_dev->irq); |
| } else { |
| dev->irq = devpriv->pci_dev->irq; |
| printk("comedi%d: registered irq %u\n", dev->minor, |
| devpriv->pci_dev->irq); |
| } |
| |
| /* |
| * Allocate the subdevice structures. alloc_subdevice() is a |
| * convenient macro defined in comedidev.h. |
| */ |
| if (alloc_subdevices(dev, 3) < 0) |
| return -ENOMEM; |
| |
| s = dev->subdevices + 0; |
| /* analog input subdevice */ |
| s->type = COMEDI_SUBD_AI; |
| s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_GROUND; |
| s->n_chan = thisboard->ai_chans; |
| s->maxdata = (1 << thisboard->ai_bits) - 1; |
| s->range_table = &pci230_ai_range; |
| s->insn_read = &pci230_ai_rinsn; |
| s->len_chanlist = 256; /* but there are restrictions. */ |
| /* Only register commands if the interrupt handler is installed. */ |
| if (irq_hdl == 0) { |
| 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 (thisboard->ao_chans > 0) { |
| s->type = COMEDI_SUBD_AO; |
| s->subdev_flags = SDF_WRITABLE | SDF_GROUND; |
| s->n_chan = thisboard->ao_chans;; |
| s->maxdata = (1 << thisboard->ao_bits) - 1; |
| s->range_table = &pci230_ao_range; |
| s->insn_write = &pci230_ao_winsn; |
| s->insn_read = &pci230_ao_rinsn; |
| s->len_chanlist = thisboard->ao_chans; |
| /* Only register commands if the interrupt handler is |
| * installed. */ |
| if (irq_hdl == 0) { |
| 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; |
| } |
| } else { |
| s->type = COMEDI_SUBD_UNUSED; |
| } |
| |
| s = dev->subdevices + 2; |
| /* digital i/o subdevice */ |
| if (thisboard->have_dio) { |
| rc = subdev_8255_init(dev, s, NULL, |
| (devpriv->iobase1 + PCI230_PPI_X_BASE)); |
| if (rc < 0) |
| return rc; |
| } else { |
| s->type = COMEDI_SUBD_UNUSED; |
| } |
| |
| printk("comedi%d: attached\n", dev->minor); |
| |
| return 1; |
| } |
| |
| /* |
| * _detach is called to deconfigure a device. It should deallocate |
| * resources. |
| * This function is also called when _attach() fails, so it should be |
| * careful not to release resources that were not necessarily |
| * allocated by _attach(). dev->private and dev->subdevices are |
| * deallocated automatically by the core. |
| */ |
| static int pci230_detach(struct comedi_device *dev) |
| { |
| printk("comedi%d: amplc_pci230: remove\n", dev->minor); |
| |
| if (dev->subdevices && thisboard->have_dio) |
| /* Clean up dio subdevice. */ |
| subdev_8255_cleanup(dev, dev->subdevices + 2); |
| |
| if (dev->irq) |
| free_irq(dev->irq, dev); |
| |
| if (devpriv) { |
| if (devpriv->pci_dev) { |
| if (dev->iobase) |
| comedi_pci_disable(devpriv->pci_dev); |
| |
| pci_dev_put(devpriv->pci_dev); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int get_resources(struct comedi_device *dev, unsigned int res_mask, |
| unsigned char owner) |
| { |
| int ok; |
| unsigned int i; |
| unsigned int b; |
| unsigned int claimed; |
| unsigned long irqflags; |
| |
| ok = 1; |
| claimed = 0; |
| spin_lock_irqsave(&devpriv->res_spinlock, irqflags); |
| for (b = 1, i = 0; (i < NUM_RESOURCES) |
| && (res_mask != 0); b <<= 1, i++) { |
| if ((res_mask & b) != 0) { |
| res_mask &= ~b; |
| if (devpriv->res_owner[i] == OWNER_NONE) { |
| devpriv->res_owner[i] = owner; |
| claimed |= b; |
| } else if (devpriv->res_owner[i] != owner) { |
| for (b = 1, i = 0; claimed != 0; b <<= 1, i++) { |
| if ((claimed & b) != 0) { |
| devpriv->res_owner[i] |
| = OWNER_NONE; |
| claimed &= ~b; |
| } |
| } |
| ok = 0; |
| break; |
| } |
| } |
| } |
| spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags); |
| return ok; |
| } |
| |
| static inline int get_one_resource(struct comedi_device *dev, |
| unsigned int resource, unsigned char owner) |
| { |
| return get_resources(dev, (1U << resource), owner); |
| } |
| |
| static void put_resources(struct comedi_device *dev, unsigned int res_mask, |
| unsigned char owner) |
| { |
| unsigned int i; |
| unsigned int b; |
| unsigned long irqflags; |
| |
| spin_lock_irqsave(&devpriv->res_spinlock, irqflags); |
| for (b = 1, i = 0; (i < NUM_RESOURCES) |
| && (res_mask != 0); b <<= 1, i++) { |
| if ((res_mask & b) != 0) { |
| res_mask &= ~b; |
| if (devpriv->res_owner[i] == owner) |
| devpriv->res_owner[i] = OWNER_NONE; |
| |
| } |
| } |
| spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags); |
| } |
| |
| static inline void put_one_resource(struct comedi_device *dev, |
| unsigned int resource, unsigned char owner) |
| { |
| put_resources(dev, (1U << resource), owner); |
| } |
| |
| static inline void put_all_resources(struct comedi_device *dev, |
| unsigned char owner) |
| { |
| put_resources(dev, (1U << NUM_RESOURCES) - 1, owner); |
| } |
| |
| /* |
| * COMEDI_SUBD_AI instruction; |
| */ |
| static int pci230_ai_rinsn(struct comedi_device *dev, |
| struct comedi_subdevice *s, struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int n, i; |
| unsigned int chan, range, aref; |
| unsigned int gainshift; |
| unsigned int status; |
| unsigned short adccon, adcen; |
| |
| /* 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) { |
| DPRINTK("comedi%d: amplc_pci230: ai_rinsn: " |
| "differential channel number out of range " |
| "0 to %u\n", dev->minor, (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. */ |
| i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, I8254_MODE0); |
| devpriv->ai_bipolar = pci230_ai_bipolar[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, dev->iobase + PCI230_ADCEN); |
| |
| /* Set gain for channel. */ |
| outw(devpriv->adcg, dev->iobase + PCI230_ADCG); |
| |
| /* Specify uni/bip, se/diff, conversion source, and reset FIFO. */ |
| devpriv->adccon = adccon; |
| outw(adccon | PCI230_ADC_FIFO_RESET, dev->iobase + PCI230_ADCCON); |
| |
| /* Convert n samples */ |
| for (n = 0; n < insn->n; n++) { |
| /* Trigger conversion by toggling Z2-CT2 output (finish with |
| * output high). */ |
| i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, |
| I8254_MODE0); |
| i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, |
| I8254_MODE1); |
| |
| #define TIMEOUT 100 |
| /* wait for conversion to end */ |
| for (i = 0; i < TIMEOUT; i++) { |
| status = inw(dev->iobase + PCI230_ADCCON); |
| if (!(status & PCI230_ADC_FIFO_EMPTY)) |
| break; |
| udelay(1); |
| } |
| if (i == TIMEOUT) { |
| /* printk() should be used instead of printk() |
| * whenever the code can be called from real-time. */ |
| printk("timeout\n"); |
| return -ETIMEDOUT; |
| } |
| |
| /* read data */ |
| data[n] = pci230_ai_read(dev); |
| } |
| |
| /* return the number of samples read/written */ |
| return n; |
| } |
| |
| /* |
| * COMEDI_SUBD_AO instructions; |
| */ |
| static int pci230_ao_winsn(struct comedi_device *dev, |
| struct comedi_subdevice *s, struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| int i; |
| int chan, range; |
| |
| /* Unpack channel and range. */ |
| chan = CR_CHAN(insn->chanspec); |
| range = CR_RANGE(insn->chanspec); |
| |
| /* Set range - see analogue output range table; 0 => unipolar 10V, |
| * 1 => bipolar +/-10V range scale */ |
| devpriv->ao_bipolar = pci230_ao_bipolar[range]; |
| outw(range, dev->iobase + PCI230_DACCON); |
| |
| /* Writing a list of values to an AO channel is probably not |
| * very useful, but that's how the interface is defined. */ |
| for (i = 0; i < insn->n; i++) { |
| /* Write value to DAC and store it. */ |
| pci230_ao_write_nofifo(dev, data[i], chan); |
| } |
| |
| /* return the number of samples read/written */ |
| return i; |
| } |
| |
| /* AO subdevices should have a read insn as well as a write insn. |
| * Usually this means copying a value stored in devpriv. */ |
| static int pci230_ao_rinsn(struct comedi_device *dev, |
| struct comedi_subdevice *s, struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| int i; |
| int chan = CR_CHAN(insn->chanspec); |
| |
| for (i = 0; i < insn->n; i++) |
| data[i] = devpriv->ao_readback[chan]; |
| |
| return i; |
| } |
| |
| static int pci230_ao_cmdtest(struct comedi_device *dev, |
| struct comedi_subdevice *s, struct comedi_cmd *cmd) |
| { |
| int err = 0; |
| unsigned int tmp; |
| |
| /* cmdtest tests a particular command to see if it is valid. |
| * Using the cmdtest ioctl, a user can create a valid cmd |
| * and then have it executes by the cmd ioctl. |
| * |
| * cmdtest returns 1,2,3,4 or 0, depending on which tests |
| * the command passes. */ |
| |
| /* Step 1: make sure trigger sources are trivially valid. |
| * "invalid source" returned by comedilib to user mode process |
| * if this fails. */ |
| |
| tmp = cmd->start_src; |
| cmd->start_src &= TRIG_INT; |
| if (!cmd->start_src || tmp != cmd->start_src) |
| err++; |
| |
| tmp = cmd->scan_begin_src; |
| if ((thisboard->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 thisboard->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! |
| */ |
| cmd->scan_begin_src &= TRIG_TIMER | TRIG_INT | TRIG_EXT; |
| } else { |
| cmd->scan_begin_src &= TRIG_TIMER | TRIG_INT; |
| } |
| if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) |
| err++; |
| |
| tmp = cmd->convert_src; |
| cmd->convert_src &= TRIG_NOW; |
| if (!cmd->convert_src || tmp != cmd->convert_src) |
| err++; |
| |
| tmp = cmd->scan_end_src; |
| cmd->scan_end_src &= TRIG_COUNT; |
| if (!cmd->scan_end_src || tmp != cmd->scan_end_src) |
| err++; |
| |
| tmp = cmd->stop_src; |
| cmd->stop_src &= TRIG_COUNT | TRIG_NONE; |
| if (!cmd->stop_src || tmp != cmd->stop_src) |
| err++; |
| |
| if (err) |
| return 1; |
| |
| /* Step 2: make sure trigger sources are unique and mutually compatible |
| * "source conflict" returned by comedilib to user mode process |
| * if this fails. */ |
| |
| /* these tests are true if more than one _src bit is set */ |
| if ((cmd->start_src & (cmd->start_src - 1)) != 0) |
| err++; |
| if ((cmd->scan_begin_src & (cmd->scan_begin_src - 1)) != 0) |
| err++; |
| if ((cmd->convert_src & (cmd->convert_src - 1)) != 0) |
| err++; |
| if ((cmd->scan_end_src & (cmd->scan_end_src - 1)) != 0) |
| err++; |
| if ((cmd->stop_src & (cmd->stop_src - 1)) != 0) |
| err++; |
| |
| if (err) |
| return 2; |
| |
| /* Step 3: make sure arguments are trivially compatible. |
| * "invalid argument" returned by comedilib to user mode process |
| * if this fails. */ |
| |
| if (cmd->start_arg != 0) { |
| cmd->start_arg = 0; |
| err++; |
| } |
| #define MAX_SPEED_AO 8000 /* 8000 ns => 125 kHz */ |
| #define MIN_SPEED_AO 4294967295u /* 4294967295ns = 4.29s */ |
| /*- Comedi limit due to unsigned int cmd. Driver limit |
| * = 2^16 (16bit * counter) * 1000000ns (1kHz onboard |
| * clock) = 65.536s */ |
| |
| switch (cmd->scan_begin_src) { |
| case TRIG_TIMER: |
| if (cmd->scan_begin_arg < MAX_SPEED_AO) { |
| cmd->scan_begin_arg = MAX_SPEED_AO; |
| err++; |
| } |
| if (cmd->scan_begin_arg > MIN_SPEED_AO) { |
| cmd->scan_begin_arg = MIN_SPEED_AO; |
| err++; |
| } |
| 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) != 0) { |
| cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, |
| ~CR_FLAGS_MASK); |
| err++; |
| } |
| /* 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))) != 0) { |
| cmd->scan_begin_arg = |
| COMBINE(cmd->scan_begin_arg, 0, |
| CR_FLAGS_MASK & ~(CR_EDGE | CR_INVERT)); |
| err++; |
| } |
| break; |
| default: |
| if (cmd->scan_begin_arg != 0) { |
| cmd->scan_begin_arg = 0; |
| err++; |
| } |
| break; |
| } |
| |
| if (cmd->scan_end_arg != cmd->chanlist_len) { |
| cmd->scan_end_arg = cmd->chanlist_len; |
| err++; |
| } |
| if (cmd->stop_src == TRIG_NONE) { |
| /* TRIG_NONE */ |
| if (cmd->stop_arg != 0) { |
| cmd->stop_arg = 0; |
| err++; |
| } |
| } |
| |
| if (err) |
| return 3; |
| |
| /* Step 4: fix up any arguments. |
| * "argument conflict" returned by comedilib to user mode process |
| * if this fails. */ |
| |
| if (cmd->scan_begin_src == TRIG_TIMER) { |
| tmp = cmd->scan_begin_arg; |
| pci230_ns_to_single_timer(&cmd->scan_begin_arg, |
| cmd->flags & TRIG_ROUND_MASK); |
| 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) { |
| enum { |
| seq_err = (1 << 0), |
| range_err = (1 << 1) |
| }; |
| unsigned int errors; |
| unsigned int n; |
| unsigned int chan, prev_chan; |
| unsigned int range, first_range; |
| |
| prev_chan = CR_CHAN(cmd->chanlist[0]); |
| first_range = CR_RANGE(cmd->chanlist[0]); |
| errors = 0; |
| for (n = 1; n < cmd->chanlist_len; n++) { |
| chan = CR_CHAN(cmd->chanlist[n]); |
| range = CR_RANGE(cmd->chanlist[n]); |
| /* Channel numbers must strictly increase. */ |
| if (chan < prev_chan) |
| errors |= seq_err; |
| |
| /* Ranges must be the same. */ |
| if (range != first_range) |
| errors |= range_err; |
| |
| prev_chan = chan; |
| } |
| if (errors != 0) { |
| err++; |
| if ((errors & seq_err) != 0) { |
| DPRINTK("comedi%d: amplc_pci230: ao_cmdtest: " |
| "channel numbers must increase\n", |
| dev->minor); |
| } |
| if ((errors & range_err) != 0) { |
| DPRINTK("comedi%d: amplc_pci230: ao_cmdtest: " |
| "channels must have the same range\n", |
| dev->minor); |
| } |
| } |
| } |
| |
| if (err) |
| return 5; |
| |
| return 0; |
| } |
| |
| static int pci230_ao_inttrig_scan_begin(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| unsigned int trig_num) |
| { |
| unsigned long irqflags; |
| |
| if (trig_num != 0) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags); |
| if (test_bit(AO_CMD_STARTED, &devpriv->state)) { |
| /* 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_event(dev, s); |
| } else { |
| /* Using DAC FIFO. */ |
| /* Read DACSWTRIG register to trigger conversion. */ |
| inw(dev->iobase + 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 comedi_async *async = s->async; |
| struct comedi_cmd *cmd = &async->cmd; |
| unsigned long irqflags; |
| |
| set_bit(AO_CMD_STARTED, &devpriv->state); |
| if (!devpriv->ao_continuous && (devpriv->ao_scan_count == 0)) { |
| /* An empty acquisition! */ |
| async->events |= COMEDI_CB_EOA; |
| pci230_ao_stop(dev, s); |
| comedi_event(dev, s); |
| } else { |
| if (devpriv->hwver >= 2) { |
| /* Using DAC FIFO. */ |
| unsigned short scantrig; |
| int run; |
| |
| /* Preload FIFO data. */ |
| run = pci230_handle_ao_fifo(dev, s); |
| comedi_event(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, dev->iobase + 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->int_en |= PCI230_INT_ZCLK_CT1; |
| devpriv->ier |= PCI230_INT_ZCLK_CT1; |
| outb(devpriv->ier, |
| devpriv->iobase1 + PCI230_INT_SCE); |
| spin_unlock_irqrestore(&devpriv->isr_spinlock, |
| irqflags); |
| } |
| /* Set CT1 gate high to start counting. */ |
| outb(GAT_CONFIG(1, GAT_VCC), |
| devpriv->iobase1 + 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->int_en |= PCI230P2_INT_DAC; |
| devpriv->ier |= PCI230P2_INT_DAC; |
| outb(devpriv->ier, devpriv->iobase1 + 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) |
| { |
| if (trig_num != 0) |
| return -EINVAL; |
| |
| s->async->inttrig = NULLFUNC; |
| pci230_ao_start(dev, s); |
| |
| return 1; |
| } |
| |
| static int pci230_ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
| { |
| 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 (!get_one_resource(dev, RES_Z2CT1, OWNER_AOCMD)) |
| return -EBUSY; |
| |
| } |
| |
| /* Get number of scans required. */ |
| if (cmd->stop_src == TRIG_COUNT) { |
| devpriv->ao_scan_count = cmd->stop_arg; |
| devpriv->ao_continuous = 0; |
| } else { |
| /* TRIG_NONE, user calls cancel. */ |
| devpriv->ao_scan_count = 0; |
| devpriv->ao_continuous = 1; |
| } |
| |
| /* Set range - see analogue output range table; 0 => unipolar 10V, |
| * 1 => bipolar +/-10V range scale */ |
| range = CR_RANGE(cmd->chanlist[0]); |
| devpriv->ao_bipolar = pci230_ao_bipolar[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, dev->iobase + 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, dev->iobase + 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(GAT_CONFIG(1, GAT_GND), |
| devpriv->iobase1 + PCI230_ZGAT_SCE); |
| pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3, |
| cmd->scan_begin_arg, |
| cmd->flags & TRIG_ROUND_MASK); |
| } |
| |
| /* N.B. cmd->start_src == TRIG_INT */ |
| s->async->inttrig = pci230_ao_inttrig_start; |
| |
| 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_cmdtest(struct comedi_device *dev, |
| struct comedi_subdevice *s, struct comedi_cmd *cmd) |
| { |
| int err = 0; |
| unsigned int tmp; |
| |
| /* cmdtest tests a particular command to see if it is valid. |
| * Using the cmdtest ioctl, a user can create a valid cmd |
| * and then have it executes by the cmd ioctl. |
| * |
| * cmdtest returns 1,2,3,4,5 or 0, depending on which tests |
| * the command passes. */ |
| |
| /* Step 1: make sure trigger sources are trivially valid. |
| * "invalid source" returned by comedilib to user mode process |
| * if this fails. */ |
| |
| tmp = cmd->start_src; |
| cmd->start_src &= TRIG_NOW | TRIG_INT; |
| if (!cmd->start_src || tmp != cmd->start_src) |
| err++; |
| |
| tmp = cmd->scan_begin_src; |
| /* 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. */ |
| if ((thisboard->have_dio) || (thisboard->min_hwver > 0)) { |
| cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_INT |
| | TRIG_EXT; |
| } else { |
| cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_INT; |
| } |
| if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) |
| err++; |
| |
| tmp = cmd->convert_src; |
| cmd->convert_src &= TRIG_TIMER | TRIG_INT | TRIG_EXT; |
| if (!cmd->convert_src || tmp != cmd->convert_src) |
| err++; |
| |
| tmp = cmd->scan_end_src; |
| cmd->scan_end_src &= TRIG_COUNT; |
| if (!cmd->scan_end_src || tmp != cmd->scan_end_src) |
| err++; |
| |
| tmp = cmd->stop_src; |
| cmd->stop_src &= TRIG_COUNT | TRIG_NONE; |
| if (!cmd->stop_src || tmp != cmd->stop_src) |
| err++; |
| |
| if (err) |
| return 1; |
| |
| /* Step 2: make sure trigger sources are unique and mutually compatible |
| * "source conflict" returned by comedilib to user mode process |
| * if this fails. */ |
| |
| /* these tests are true if more than one _src bit is set */ |
| if ((cmd->start_src & (cmd->start_src - 1)) != 0) |
| err++; |
| if ((cmd->scan_begin_src & (cmd->scan_begin_src - 1)) != 0) |
| err++; |
| if ((cmd->convert_src & (cmd->convert_src - 1)) != 0) |
| err++; |
| if ((cmd->scan_end_src & (cmd->scan_end_src - 1)) != 0) |
| err++; |
| if ((cmd->stop_src & (cmd->stop_src - 1)) != 0) |
| err++; |
| |
| /* 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++; |
| |
| if (err) |
| return 2; |
| |
| /* Step 3: make sure arguments are trivially compatible. |
| * "invalid argument" returned by comedilib to user mode process |
| * if this fails. */ |
| |
| if (cmd->start_arg != 0) { |
| cmd->start_arg = 0; |
| err++; |
| } |
| #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 */ |
| #define MIN_SPEED_AI 4294967295u /* 4294967295ns = 4.29s */ |
| /*- Comedi limit due to unsigned int cmd. Driver limit |
| * = 2^16 (16bit * counter) * 1000000ns (1kHz onboard |
| * clock) = 65.536s */ |
| |
| 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; |
| } |
| |
| if (cmd->convert_arg < max_speed_ai) { |
| cmd->convert_arg = max_speed_ai; |
| err++; |
| } |
| if (cmd->convert_arg > MIN_SPEED_AI) { |
| cmd->convert_arg = MIN_SPEED_AI; |
| err++; |
| } |
| } 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) != 0) { |
| /* Trigger number must be 0. */ |
| if ((cmd->convert_arg & ~CR_FLAGS_MASK) != 0) { |
| cmd->convert_arg = COMBINE(cmd->convert_arg, 0, |
| ~CR_FLAGS_MASK); |
| err++; |
| } |
| /* 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++; |
| } |
| } else { |
| /* Backwards compatibility with previous versions. */ |
| /* convert_arg == 0 => trigger on -ve edge. */ |
| /* convert_arg == 1 => trigger on +ve edge. */ |
| if (cmd->convert_arg > 1) { |
| /* Default to trigger on +ve edge. */ |
| cmd->convert_arg = 1; |
| err++; |
| } |
| } |
| } else { |
| if (cmd->convert_arg != 0) { |
| cmd->convert_arg = 0; |
| err++; |
| } |
| } |
| |
| if (cmd->scan_end_arg != cmd->chanlist_len) { |
| cmd->scan_end_arg = cmd->chanlist_len; |
| err++; |
| } |
| |
| if (cmd->stop_src == TRIG_NONE) { |
| if (cmd->stop_arg != 0) { |
| cmd->stop_arg = 0; |
| err++; |
| } |
| } |
| |
| 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) != 0) { |
| cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, |
| ~CR_FLAGS_MASK); |
| err++; |
| } |
| /* The only flag allowed is CR_EDGE, which is ignored. */ |
| if ((cmd->scan_begin_arg & CR_FLAGS_MASK & ~CR_EDGE) != 0) { |
| cmd->scan_begin_arg = COMBINE(cmd->scan_begin_arg, 0, |
| CR_FLAGS_MASK & ~CR_EDGE); |
| err++; |
| } |
| } 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++; |
| |
| } else { |
| if (cmd->scan_begin_arg != 0) { |
| cmd->scan_begin_arg = 0; |
| err++; |
| } |
| } |
| |
| if (err) |
| return 3; |
| |
| /* Step 4: fix up any arguments. |
| * "argument conflict" returned by comedilib to user mode process |
| * if this fails. */ |
| |
| if (cmd->convert_src == TRIG_TIMER) { |
| tmp = cmd->convert_arg; |
| pci230_ns_to_single_timer(&cmd->convert_arg, |
| cmd->flags & TRIG_ROUND_MASK); |
| 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 & TRIG_ROUND_MASK); |
| if (!pci230_ai_check_scan_period(cmd)) { |
| /* Was below minimum required. Round up. */ |
| pci230_ns_to_single_timer(&cmd->scan_begin_arg, |
| TRIG_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) { |
| enum { |
| seq_err = 1 << 0, |
| rangepair_err = 1 << 1, |
| polarity_err = 1 << 2, |
| aref_err = 1 << 3, |
| diffchan_err = 1 << 4, |
| buggy_chan0_err = 1 << 5 |
| }; |
| unsigned int errors; |
| unsigned int chan, prev_chan; |
| unsigned int range, prev_range; |
| unsigned int polarity, prev_polarity; |
| unsigned int aref, prev_aref; |
| unsigned int subseq_len; |
| unsigned int n; |
| |
| subseq_len = 0; |
| errors = 0; |
| prev_chan = prev_aref = prev_range = prev_polarity = 0; |
| for (n = 0; n < cmd->chanlist_len; n++) { |
| chan = CR_CHAN(cmd->chanlist[n]); |
| range = CR_RANGE(cmd->chanlist[n]); |
| aref = CR_AREF(cmd->chanlist[n]); |
| polarity = pci230_ai_bipolar[range]; |
| /* Only the first half of the channels are available if |
| * differential. (These are remapped in software. In |
| * hardware, only the even channels are available.) */ |
| if ((aref == AREF_DIFF) |
| && (chan >= (s->n_chan / 2))) { |
| errors |= diffchan_err; |
| } |
| if (n > 0) { |
| /* Channel numbers must strictly increase or |
| * subsequence must repeat exactly. */ |
| if ((chan <= prev_chan) |
| && (subseq_len == 0)) { |
| subseq_len = n; |
| } |
| if ((subseq_len > 0) |
| && (cmd->chanlist[n] != |
| cmd->chanlist[n % subseq_len])) { |
| errors |= seq_err; |
| } |
| /* Channels must have same AREF. */ |
| if (aref != prev_aref) |
| errors |= aref_err; |
| |
| /* Channel ranges must have same polarity. */ |
| if (polarity != prev_polarity) |
| errors |= polarity_err; |
| |
| /* Single-ended channel pairs must have same |
| * range. */ |
| if ((aref != AREF_DIFF) |
| && (((chan ^ prev_chan) & ~1) == 0) |
| && (range != prev_range)) { |
| errors |= rangepair_err; |
| } |
| } |
| prev_chan = chan; |
| prev_range = range; |
| prev_aref = aref; |
| prev_polarity = polarity; |
| } |
| if (subseq_len == 0) { |
| /* Subsequence is whole sequence. */ |
| subseq_len = n; |
| } |
| /* If channel list is a repeating subsequence, need a whole |
| * number of repeats. */ |
| if ((n % subseq_len) != 0) |
| errors |= seq_err; |
| |
| if ((devpriv->hwver > 0) && (devpriv->hwver < 4)) { |
| /* |
| * 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 ((subseq_len > 1) |
| && (CR_CHAN(cmd->chanlist[0]) != 0)) { |
| errors |= buggy_chan0_err; |
| } |
| } |
| if (errors != 0) { |
| err++; |
| if ((errors & seq_err) != 0) { |
| DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " |
| "channel numbers must increase or " |
| "sequence must repeat exactly\n", |
| dev->minor); |
| } |
| if ((errors & rangepair_err) != 0) { |
| DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " |
| "single-ended channel pairs must " |
| "have the same range\n", dev->minor); |
| } |
| if ((errors & polarity_err) != 0) { |
| DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " |
| "channel sequence ranges must be all " |
| "bipolar or all unipolar\n", |
| dev->minor); |
| } |
| if ((errors & aref_err) != 0) { |
| DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " |
| "channel sequence analogue references " |
| "must be all the same (single-ended " |
| "or differential)\n", dev->minor); |
| } |
| if ((errors & diffchan_err) != 0) { |
| DPRINTK("comedi%d: amplc_pci230: ai_cmdtest: " |
| "differential channel number out of " |
| "range 0 to %u\n", dev->minor, |
| (s->n_chan / 2) - 1); |
| } |
| if ((errors & buggy_chan0_err) != 0) { |
| /* Use printk instead of DPRINTK here. */ |
| printk("comedi: comedi%d: 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", |
| dev->minor, devpriv->hwver); |
| } |
| } |
| } |
| |
| if (err) |
| return 5; |
| |
| return 0; |
| } |
| |
| static void pci230_ai_update_fifo_trigger_level(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| struct comedi_cmd *cmd = &s->async->cmd; |
| unsigned int scanlen = cmd->scan_end_arg; |
| unsigned int wake; |
| unsigned short triglev; |
| unsigned short adccon; |
| |
| if ((cmd->flags & TRIG_WAKE_EOS) != 0) { |
| /* Wake at end of scan. */ |
| wake = scanlen - devpriv->ai_scan_pos; |
| } else { |
| if (devpriv->ai_continuous |
| || (devpriv->ai_scan_count >= PCI230_ADC_FIFOLEVEL_HALFFULL) |
| || (scanlen >= PCI230_ADC_FIFOLEVEL_HALFFULL)) { |
| wake = PCI230_ADC_FIFOLEVEL_HALFFULL; |
| } else { |
| wake = (devpriv->ai_scan_count * scanlen) |
| - devpriv->ai_scan_pos; |
| } |
| } |
| 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, dev->iobase + 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, dev->iobase + PCI230_ADCCON); |
| } |
| } |
| |
| static int pci230_ai_inttrig_convert(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| unsigned int trig_num) |
| { |
| unsigned long irqflags; |
| |
| if (trig_num != 0) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); |
| if (test_bit(AI_CMD_STARTED, &devpriv->state)) { |
| unsigned int delayus; |
| |
| /* Trigger conversion by toggling Z2-CT2 output. Finish |
| * with output high. */ |
| i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 2, |
| I8254_MODE0); |
| i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 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); |
| } else { |
| spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); |
| } |
| |
| return 1; |
| } |
| |
| static int pci230_ai_inttrig_scan_begin(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| unsigned int trig_num) |
| { |
| unsigned long irqflags; |
| unsigned char zgat; |
| |
| if (trig_num != 0) |
| return -EINVAL; |
| |
| spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); |
| if (test_bit(AI_CMD_STARTED, &devpriv->state)) { |
| /* Trigger scan by waggling CT0 gate source. */ |
| zgat = GAT_CONFIG(0, GAT_GND); |
| outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); |
| zgat = GAT_CONFIG(0, GAT_VCC); |
| outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); |
| } |
| spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags); |
| |
| return 1; |
| } |
| |
| static void pci230_ai_start(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| unsigned long irqflags; |
| unsigned short conv; |
| struct comedi_async *async = s->async; |
| struct comedi_cmd *cmd = &async->cmd; |
| |
| set_bit(AI_CMD_STARTED, &devpriv->state); |
| if (!devpriv->ai_continuous && (devpriv->ai_scan_count == 0)) { |
| /* An empty acquisition! */ |
| async->events |= COMEDI_CB_EOA; |
| pci230_ai_stop(dev, s); |
| comedi_event(dev, s); |
| } else { |
| /* Enable ADC FIFO trigger level interrupt. */ |
| spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); |
| devpriv->int_en |= PCI230_INT_ADC; |
| devpriv->ier |= PCI230_INT_ADC; |
| outb(devpriv->ier, devpriv->iobase1 + 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) != 0) { |
| 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 != 0) { |
| /* 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, dev->iobase + 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 = GAT_CONFIG(2, GAT_NOUTNM2); |
| } else { |
| /* Conversion timer CT2 needs to be gated on |
| * continuously. */ |
| zgat = GAT_CONFIG(2, GAT_VCC); |
| } |
| outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); |
| if (cmd->scan_begin_src != TRIG_FOLLOW) { |
| /* Set monostable CT0 trigger source. */ |
| switch (cmd->scan_begin_src) { |
| default: |
| zgat = 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 = 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 = GAT_CONFIG(0, GAT_NOUTNM2); |
| break; |
| case TRIG_INT: |
| /* |
| * Monostable CT0 is triggered by |
| * inttrig function waggling the CT0 |
| * gate source. |
| */ |
| zgat = GAT_CONFIG(0, GAT_VCC); |
| break; |
| } |
| outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); |
| switch (cmd->scan_begin_src) { |
| case TRIG_TIMER: |
| /* Scan period timer CT1 needs to be |
| * gated on to start counting. */ |
| zgat = GAT_CONFIG(1, GAT_VCC); |
| outb(zgat, devpriv->iobase1 |
| + 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. */ |
| put_one_resource(dev, RES_Z2CT2, OWNER_AICMD); |
| } |
| } |
| } |
| |
| static int pci230_ai_inttrig_start(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| unsigned int trig_num) |
| { |
| if (trig_num != 0) |
| return -EINVAL; |
| |
| s->async->inttrig = NULLFUNC; |
| pci230_ai_start(dev, s); |
| |
| return 1; |
| } |
| |
| static int pci230_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
| { |
| 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 |= (1U << RES_Z2CT2); |
| if (cmd->scan_begin_src != TRIG_FOLLOW) { |
| /* Using Z2-CT0 monostable to gate Z2-CT2 conversion timer */ |
| res_mask |= (1U << RES_Z2CT0); |
| if (cmd->scan_begin_src == TRIG_TIMER) { |
| /* Using Z2-CT1 for scan frequency */ |
| res_mask |= (1U << RES_Z2CT1); |
| } |
| } |
| /* Claim resources. */ |
| if (!get_resources(dev, res_mask, OWNER_AICMD)) |
| return -EBUSY; |
| |
| |
| /* Get number of scans required. */ |
| if (cmd->stop_src == TRIG_COUNT) { |
| devpriv->ai_scan_count = cmd->stop_arg; |
| devpriv->ai_continuous = 0; |
| } else { |
| /* TRIG_NONE, user calls cancel. */ |
| devpriv->ai_scan_count = 0; |
| devpriv->ai_continuous = 1; |
| } |
| devpriv->ai_scan_pos = 0; /* Position within scan. */ |
| |
| /* 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 = pci230_ai_bipolar[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, dev->iobase + PCI230_ADCEN); |
| |
| /* Set channel gains. */ |
| outw(devpriv->adcg, dev->iobase + PCI230_ADCG); |
| |
| /* Set counter/timer 2 output high for use as the initial start |
| * conversion source. */ |
| i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, 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, dev->iobase + 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. */ |
| udelay(25); |
| |
| /* Reset FIFO again. */ |
| outw(adccon | PCI230_ADC_FIFO_RESET, dev->iobase + 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 = GAT_CONFIG(2, GAT_GND); |
| outb(zgat, devpriv->iobase1 + 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 & TRIG_ROUND_MASK); |
| 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 = GAT_CONFIG(0, GAT_VCC); |
| outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); |
| pci230_ct_setup_ns_mode(dev, 0, I8254_MODE1, |
| ((uint64_t) cmd->convert_arg |
| * cmd->scan_end_arg), |
| TRIG_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 = GAT_CONFIG(1, GAT_GND); |
| outb(zgat, devpriv->iobase1 + PCI230_ZGAT_SCE); |
| pci230_ct_setup_ns_mode(dev, 1, I8254_MODE3, |
| cmd->scan_begin_arg, |
| cmd-> |
| flags & |
| TRIG_ROUND_MASK); |
| } |
| } |
| } |
| |
| if (cmd->start_src == TRIG_INT) { |
| s->async->inttrig = pci230_ai_inttrig_start; |
| } else { |
| /* TRIG_NOW */ |
| pci230_ai_start(dev, s); |
| } |
| |
| return 0; |
| } |
| |
| static unsigned int divide_ns(uint64_t ns, unsigned int timebase, |
| unsigned int round_mode) |
| { |
| uint64_t div; |
| unsigned int rem; |
| |
| div = ns; |
| rem = do_div(div, timebase); |
| round_mode &= TRIG_ROUND_MASK; |
| switch (round_mode) { |
| default: |
| case TRIG_ROUND_NEAREST: |
| div += (rem + (timebase / 2)) / timebase; |
| break; |
| case TRIG_ROUND_DOWN: |
| break; |
| case TRIG_ROUND_UP: |
| div += (rem + timebase - 1) / 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(uint64_t ns, unsigned int *count, |
| unsigned int round_mode) |
| { |
| unsigned int clk_src, cnt; |
| |
| for (clk_src = CLK_10MHZ;; clk_src++) { |
| cnt = divide_ns(ns, pci230_timebase[clk_src], round_mode); |
| 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 round) |
| { |
| unsigned int count; |
| unsigned int clk_src; |
| |
| clk_src = pci230_choose_clk_count(*ns, &count, round); |
| *ns = count * pci230_timebase[clk_src]; |
| return; |
| } |
| |
| static void pci230_ct_setup_ns_mode(struct comedi_device *dev, unsigned int ct, |
| unsigned int mode, uint64_t ns, |
| unsigned int round) |
| { |
| unsigned int clk_src; |
| unsigned int count; |
| |
| /* Set mode. */ |
| i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct, mode); |
| /* Determine clock source and count. */ |
| clk_src = pci230_choose_clk_count(ns, &count, round); |
| /* Program clock source. */ |
| outb(CLK_CONFIG(ct, clk_src), devpriv->iobase1 + PCI230_ZCLK_SCE); |
| /* Set initial count. */ |
| if (count >= 65536) |
| count = 0; |
| |
| i8254_write(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct, count); |
| } |
| |
| static void pci230_cancel_ct(struct comedi_device *dev, unsigned int ct) |
| { |
| i8254_set_mode(devpriv->iobase1 + PCI230_Z2_CT_BASE, 0, ct, |
| I8254_MODE1); |
| /* Counter ct, 8254 mode 1, initial count not written. */ |
| } |
| |
| /* Interrupt handler */ |
| static irqreturn_t pci230_interrupt(int irq, void *d) |
| { |
| unsigned char status_int, valid_status_int; |
| struct comedi_device *dev = (struct comedi_device *)d; |
| struct comedi_subdevice *s; |
| unsigned long irqflags; |
| |
| /* Read interrupt status/enable register. */ |
| status_int = inb(devpriv->iobase1 + PCI230_INT_STAT); |
| |
| if (status_int == PCI230_INT_DISABLE) |
| return IRQ_NONE; |
| |
| |
| spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); |
| valid_status_int = devpriv->int_en & status_int; |
| /* Disable triggered interrupts. |
| * (Only those interrupts that need re-enabling, are, later in the |
| * handler). */ |
| devpriv->ier = devpriv->int_en & ~status_int; |
| outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); |
| devpriv->intr_running = 1; |
| 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) != 0) { |
| s = dev->write_subdev; |
| pci230_handle_ao_nofifo(dev, s); |
| comedi_event(dev, s); |
| } |
| |
| if ((valid_status_int & PCI230P2_INT_DAC) != 0) { |
| s = dev->write_subdev; |
| pci230_handle_ao_fifo(dev, s); |
| comedi_event(dev, s); |
| } |
| |
| if ((valid_status_int & PCI230_INT_ADC) != 0) { |
| s = dev->read_subdev; |
| pci230_handle_ai(dev, s); |
| comedi_event(dev, s); |
| } |
| |
| /* Reenable interrupts. */ |
| spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); |
| if (devpriv->ier != devpriv->int_en) { |
| devpriv->ier = devpriv->int_en; |
| outb(devpriv->ier, devpriv->iobase1 + PCI230_INT_SCE); |
| } |
| devpriv->intr_running = 0; |
| spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static void pci230_handle_ao_nofifo(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| short data; |
| int i, ret; |
| struct comedi_async *async = s->async; |
| struct comedi_cmd *cmd = &async->cmd; |
| |
| if (!devpriv->ao_continuous && (devpriv->ao_scan_count == 0)) |
| return; |
| |
| |
| for (i = 0; i < cmd->chanlist_len; i++) { |
| /* Read sample from Comedi's circular buffer. */ |
| ret = comedi_buf_get(s->async, &data); |
| if (ret == 0) { |
| s->async->events |= COMEDI_CB_OVERFLOW; |
| pci230_ao_stop(dev, s); |
| comedi_error(dev, "AO buffer underrun"); |
| return; |
| } |
| /* Write value to DAC. */ |
| pci230_ao_write_nofifo(dev, data, CR_CHAN(cmd->chanlist[i])); |
| } |
| |
| async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; |
| if (!devpriv->ao_continuous) { |
| devpriv->ao_scan_count--; |
| if (devpriv->ao_scan_count == 0) { |
| /* End of acquisition. */ |
| async->events |= COMEDI_CB_EOA; |
| pci230_ao_stop(dev, s); |
| } |
| } |
| } |
| |
| /* Loads DAC FIFO (if using it) from buffer. */ |
| /* Returns 0 if AO finished due to completion or error, 1 if still going. */ |
| static int pci230_handle_ao_fifo(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| struct comedi_async *async = s->async; |
| struct comedi_cmd *cmd = &async->cmd; |
| unsigned int num_scans; |
| unsigned int room; |
| unsigned short dacstat; |
| unsigned int i, n; |
| unsigned int bytes_per_scan; |
| unsigned int events = 0; |
| int running; |
| |
| /* Get DAC FIFO status. */ |
| dacstat = inw(dev->iobase + PCI230_DACCON); |
| |
| /* Determine number of scans available in buffer. */ |
| bytes_per_scan = cmd->chanlist_len * sizeof(short); |
| num_scans = comedi_buf_read_n_available(async) / bytes_per_scan; |
| if (!devpriv->ao_continuous) { |
| /* Fixed number of scans. */ |
| if (num_scans > devpriv->ao_scan_count) |
| num_scans = devpriv->ao_scan_count; |
| |
| if (devpriv->ao_scan_count == 0) { |
| /* End of acquisition. */ |
| events |= COMEDI_CB_EOA; |
| } |
| } |
| if (events == 0) { |
| /* Check for FIFO underrun. */ |
| if ((dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) != 0) { |
| comedi_error(dev, "AO FIFO underrun"); |
| 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)) { |
| comedi_error(dev, "AO buffer underrun"); |
| 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) != 0) |
| room = PCI230P2_DAC_FIFOROOM_FULL; |
| else if ((dacstat & PCI230P2_DAC_FIFO_HALF) != 0) |
| room = PCI230P2_DAC_FIFOROOM_HALFTOFULL; |
| else if ((dacstat & PCI230P2_DAC_FIFO_EMPTY) != 0) |
| 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++) { |
| short datum; |
| |
| comedi_buf_get(async, &datum); |
| pci230_ao_write_fifo(dev, datum, |
| CR_CHAN(cmd->chanlist[i])); |
| } |
| } |
| events |= COMEDI_CB_EOS | COMEDI_CB_BLOCK; |
| if (!devpriv->ao_continuous) { |
| devpriv->ao_scan_count -= num_scans; |
| if (devpriv->ao_scan_count == 0) { |
| /* All data for the command has been written |
| * to FIFO. Set FIFO interrupt trigger level |
| * to 'empty'. */ |
| devpriv->daccon = (devpriv->daccon |
| & |
| ~PCI230P2_DAC_INT_FIFO_MASK) |
| | PCI230P2_DAC_INT_FIFO_EMPTY; |
| outw(devpriv->daccon, |
| dev->iobase + PCI230_DACCON); |
| } |
| } |
| /* Check if FIFO underrun occurred while writing to FIFO. */ |
| dacstat = inw(dev->iobase + PCI230_DACCON); |
| if ((dacstat & PCI230P2_DAC_FIFO_UNDERRUN_LATCHED) != 0) { |
| comedi_error(dev, "AO FIFO underrun"); |
| events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; |
| } |
| } |
| if ((events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) |
| != 0) { |
| /* Stopping AO due to completion or error. */ |
| pci230_ao_stop(dev, s); |
| running = 0; |
| } else { |
| running = 1; |
| } |
| async->events |= events; |
| return running; |
| } |
| |
| static void pci230_handle_ai(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| unsigned int events = 0; |
| unsigned int status_fifo; |
| unsigned int i; |
| unsigned int todo; |
| unsigned int fifoamount; |
| struct comedi_async *async = s->async; |
| unsigned int scanlen = async->cmd.scan_end_arg; |
| |
| /* Determine number of samples to read. */ |
| if (devpriv->ai_continuous) { |
| todo = PCI230_ADC_FIFOLEVEL_HALFFULL; |
| } else if (devpriv->ai_scan_count == 0) { |
| todo = 0; |
| } else if ((devpriv->ai_scan_count > PCI230_ADC_FIFOLEVEL_HALFFULL) |
| || (scanlen > PCI230_ADC_FIFOLEVEL_HALFFULL)) { |
| todo = PCI230_ADC_FIFOLEVEL_HALFFULL; |
| } else { |
| todo = (devpriv->ai_scan_count * scanlen) |
| - devpriv->ai_scan_pos; |
| if (todo > PCI230_ADC_FIFOLEVEL_HALFFULL) |
| todo = PCI230_ADC_FIFOLEVEL_HALFFULL; |
| |
| } |
| |
| if (todo == 0) |
| return; |
| |
| |
| fifoamount = 0; |
| for (i = 0; i < todo; i++) { |
| if (fifoamount == 0) { |
| /* Read FIFO state. */ |
| status_fifo = inw(dev->iobase + PCI230_ADCCON); |
| |
| if ((status_fifo & PCI230_ADC_FIFO_FULL_LATCHED) != 0) { |
| /* Report error otherwise FIFO overruns will go |
| * unnoticed by the caller. */ |
| comedi_error(dev, "AI FIFO overrun"); |
| events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; |
| break; |
| } else if ((status_fifo & PCI230_ADC_FIFO_EMPTY) != 0) { |
| /* FIFO empty. */ |
| break; |
| } else if ((status_fifo & PCI230_ADC_FIFO_HALF) != 0) { |
| /* FIFO half full. */ |
| fifoamount = PCI230_ADC_FIFOLEVEL_HALFFULL; |
| } else { |
| /* FIFO not empty. */ |
| if (devpriv->hwver > 0) { |
| /* Read PCI230+/260+ ADC FIFO level. */ |
| fifoamount = inw(dev->iobase |
| + PCI230P_ADCFFLEV); |
| if (fifoamount == 0) { |
| /* Shouldn't happen. */ |
| break; |
| } |
| } else { |
| fifoamount = 1; |
| } |
| } |
| } |
| |
| /* Read sample and store in Comedi's circular buffer. */ |
| if (comedi_buf_put(async, pci230_ai_read(dev)) == 0) { |
| events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW; |
| comedi_error(dev, "AI buffer overflow"); |
| break; |
| } |
| fifoamount--; |
| devpriv->ai_scan_pos++; |
| if (devpriv->ai_scan_pos == scanlen) { |
| /* End of scan. */ |
| devpriv->ai_scan_pos = 0; |
| devpriv->ai_scan_count--; |
| async->events |= COMEDI_CB_EOS; |
| } |
| } |
| |
| if (!devpriv->ai_continuous && (devpriv->ai_scan_count == 0)) { |
| /* End of acquisition. */ |
| events |= COMEDI_CB_EOA; |
| } else { |
| /* More samples required, tell Comedi to block. */ |
| events |= COMEDI_CB_BLOCK; |
| } |
| async->events |= events; |
| |
| if ((async->events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | |
| COMEDI_CB_OVERFLOW)) != 0) { |
| /* disable hardware conversions */ |
| pci230_ai_stop(dev, s); |
| } else { |
| /* update FIFO interrupt trigger level */ |
| pci230_ai_update_fifo_trigger_level(dev, s); |
| } |
| } |
| |
| static void pci230_ao_stop(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| unsigned long irqflags; |
| unsigned char intsrc; |
| int started; |
| struct comedi_cmd *cmd; |
| |
| spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags); |
| started = test_and_clear_bit(AO_CMD_STARTED, &devpriv->state); |
| 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->int_en &= ~intsrc; |
| while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) { |
| spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags); |
| spin_lock_irqsave(&devpriv->isr_spinlock, irqflags); |
| } |
| if (devpriv->ier != devpriv->int_en) { |
| devpriv->ier = devpriv->int_en; |
| outb(devpriv->ier, devpriv->iobase1 + 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, |
| dev->iobase + PCI230_DACCON); |
| } |
| |
| /* Release resources. */ |
| put_all_resources(dev, OWNER_AOCMD); |
| } |
| |
| static int pci230_ao_cancel(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| pci230_ao_stop(dev, s); |
| return 0; |
| } |
| |
| static void pci230_ai_stop(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| unsigned long irqflags; |
| struct comedi_cmd *cmd; |
| int started; |
| |
| spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags); |
| started = test_and_clear_bit(AI_CMD_STARTED, &devpriv->state); |
| 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->int_en &= ~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); |
| } |
| if (devpriv->ier != devpriv->int_en) { |
| devpriv->ier = devpriv->int_en; |
| outb(devpriv->ier, devpriv->iobase1 + 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, |
| dev->iobase + PCI230_ADCCON); |
| |
| /* Release resources. */ |
| put_all_resources(dev, OWNER_AICMD); |
| } |
| |
| static int pci230_ai_cancel(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| pci230_ai_stop(dev, s); |
| return 0; |
| } |