Commit 70a350c3 authored by Allan Willcox's avatar Allan Willcox Committed by Greg Kroah-Hartman

Staging: comedi: add amplc_pci230 driver

Driver for Amplicon PCI230 and PCI260 Multifunction I/O boards

Cc: Ian Abbott <abbotti@mev.co.uk>
Cc: David Schleef <ds@schleef.org>
Cc: Frank Mori Hess <fmhess@users.sourceforge.net>
Cc: Steve D Sharples <steve.sharples@nottingham.ac.uk>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent ea1aeae4
/*
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 "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.
*/
typedef struct pci230_board_struct {
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. */
} pci230_board;
static const 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 (sizeof(pci230_boards)/sizeof(pci230_boards[0]))
#define thisboard ((const 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 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 */
lsampl_t 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 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 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 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(comedi_device * dev, comedi_devconfig * it);
static int pci230_detach(comedi_device * dev);
static 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:sizeof(pci230_boards) / sizeof(pci230_boards[0]),
};
COMEDI_PCI_INITCLEANUP(driver_amplc_pci230, pci230_pci_table);
static int pci230_ai_rinsn(comedi_device * dev, comedi_subdevice * s,
comedi_insn * insn, lsampl_t * data);
static int pci230_ao_winsn(comedi_device * dev, comedi_subdevice * s,
comedi_insn * insn, lsampl_t * data);
static int pci230_ao_rinsn(comedi_device * dev, comedi_subdevice * s,
comedi_insn * insn, lsampl_t * data);
static void pci230_ct_setup_ns_mode(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(comedi_device * dev, unsigned int ct);
static irqreturn_t pci230_interrupt(int irq, void *d PT_REGS_ARG);
static int pci230_ao_cmdtest(comedi_device * dev, comedi_subdevice * s,
comedi_cmd * cmd);
static int pci230_ao_cmd(comedi_device * dev, comedi_subdevice * s);
static int pci230_ao_cancel(comedi_device * dev, comedi_subdevice * s);
static void pci230_ao_stop(comedi_device * dev, comedi_subdevice * s);
static void pci230_handle_ao_nofifo(comedi_device * dev, comedi_subdevice * s);
static int pci230_handle_ao_fifo(comedi_device * dev, comedi_subdevice * s);
static int pci230_ai_cmdtest(comedi_device * dev, comedi_subdevice * s,
comedi_cmd * cmd);
static int pci230_ai_cmd(comedi_device * dev, comedi_subdevice * s);
static int pci230_ai_cancel(comedi_device * dev, comedi_subdevice * s);
static void pci230_ai_stop(comedi_device * dev, comedi_subdevice * s);
static void pci230_handle_ai(comedi_device * dev, comedi_subdevice * s);
static sampl_t pci230_ai_read(comedi_device * dev)
{
/* Read sample. */
sampl_t data = (sampl_t) 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(comedi_device * dev,
sampl_t 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(comedi_device * dev, sampl_t 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(comedi_device * dev, sampl_t 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(comedi_device * dev, comedi_devconfig * it)
{
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 = comedi_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(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)
comedi_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(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;
comedi_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;
}
}
}
comedi_spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags);
return ok;
}
static inline int get_one_resource(comedi_device * dev, unsigned int resource,
unsigned char owner)
{
return get_resources(dev, (1U << resource), owner);
}
static void put_resources(comedi_device * dev, unsigned int res_mask,
unsigned char owner)
{
unsigned int i;
unsigned int b;
unsigned long irqflags;
comedi_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;
}
}
}
comedi_spin_unlock_irqrestore(&devpriv->res_spinlock, irqflags);
}
static inline void put_one_resource(comedi_device * dev, unsigned int resource,
unsigned char owner)
{
put_resources(dev, (1U << resource), owner);
}
static inline void put_all_resources(comedi_device * dev, unsigned char owner)
{
put_resources(dev, (1U << NUM_RESOURCES) - 1, owner);
}
/*
* COMEDI_SUBD_AI instruction;
*/
static int pci230_ai_rinsn(comedi_device * dev, comedi_subdevice * s,
comedi_insn * insn, lsampl_t * 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;
comedi_udelay(1);
}
if (i == TIMEOUT) {
/* rt_printk() should be used instead of printk()
* whenever the code can be called from real-time. */
rt_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(comedi_device * dev, comedi_subdevice * s,
comedi_insn * insn, lsampl_t * 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(comedi_device * dev, comedi_subdevice * s,
comedi_insn * insn, lsampl_t * 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(comedi_device * dev, comedi_subdevice * s,
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(comedi_device * dev,
comedi_subdevice * s, unsigned int trig_num)
{
unsigned long irqflags;
if (trig_num != 0)
return -EINVAL;
comedi_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. */
comedi_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);
comedi_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. */
comedi_udelay(8);
}
return 1;
}
static void pci230_ao_start(comedi_device * dev, comedi_subdevice * s)
{
comedi_async *async = s->async;
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. */
comedi_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);
comedi_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. */
comedi_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);
comedi_spin_unlock_irqrestore(&devpriv->isr_spinlock,
irqflags);
}
}
}
static int pci230_ao_inttrig_start(comedi_device * dev, 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(comedi_device * dev, comedi_subdevice * s)
{
unsigned short daccon;
unsigned int range;
/* Get the command. */
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(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(comedi_device * dev, comedi_subdevice * s,
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(comedi_device * dev,
comedi_subdevice * s)
{
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(comedi_device * dev, comedi_subdevice * s,
unsigned int trig_num)
{
unsigned long irqflags;
if (trig_num != 0)
return -EINVAL;
comedi_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;
}
comedi_spin_unlock_irqrestore(&devpriv->ai_stop_spinlock,
irqflags);
comedi_udelay(delayus);
} else {
comedi_spin_unlock_irqrestore(&devpriv->ai_stop_spinlock,
irqflags);
}
return 1;
}
static int pci230_ai_inttrig_scan_begin(comedi_device * dev,
comedi_subdevice * s, unsigned int trig_num)
{
unsigned long irqflags;
unsigned char zgat;
if (trig_num != 0)
return -EINVAL;
comedi_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);
}
comedi_spin_unlock_irqrestore(&devpriv->ai_stop_spinlock, irqflags);
return 1;
}
static void pci230_ai_start(comedi_device * dev, comedi_subdevice * s)
{
unsigned long irqflags;
unsigned short conv;
comedi_async *async = s->async;
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. */
comedi_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);
comedi_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(comedi_device * dev, 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(comedi_device * dev, comedi_subdevice * s)
{
unsigned int i, chan, range, diff;
unsigned int res_mask;
unsigned short adccon, adcen;
unsigned char zgat;
/* Get the command. */
comedi_async *async = s->async;
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. */
comedi_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(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(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 PT_REGS_ARG)
{
unsigned char status_int, valid_status_int;
comedi_device *dev = (comedi_device *) d;
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;
}
comedi_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;
comedi_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. */
comedi_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;
comedi_spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
return IRQ_HANDLED;
}
static void pci230_handle_ao_nofifo(comedi_device * dev, comedi_subdevice * s)
{
sampl_t data;
int i, ret;
comedi_async *async = s->async;
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(comedi_device * dev, comedi_subdevice * s)
{
comedi_async *async = s->async;
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(sampl_t);
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++) {
sampl_t 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(comedi_device * dev, comedi_subdevice * s)
{
unsigned int events = 0;
unsigned int status_fifo;
unsigned int i;
unsigned int todo;
unsigned int fifoamount;
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(comedi_device * dev, comedi_subdevice * s)
{
unsigned long irqflags;
unsigned char intsrc;
int started;
comedi_cmd *cmd;
comedi_spin_lock_irqsave(&devpriv->ao_stop_spinlock, irqflags);
started = test_and_clear_bit(AO_CMD_STARTED, &devpriv->state);
comedi_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. */
comedi_spin_lock_irqsave(&devpriv->isr_spinlock, irqflags);
devpriv->int_en &= ~intsrc;
while (devpriv->intr_running && devpriv->intr_cpuid != THISCPU) {
comedi_spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
comedi_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);
}
comedi_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(comedi_device * dev, comedi_subdevice * s)
{
pci230_ao_stop(dev, s);
return 0;
}
static void pci230_ai_stop(comedi_device * dev, comedi_subdevice * s)
{
unsigned long irqflags;
comedi_cmd *cmd;
int started;
comedi_spin_lock_irqsave(&devpriv->ai_stop_spinlock, irqflags);
started = test_and_clear_bit(AI_CMD_STARTED, &devpriv->state);
comedi_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);
}
comedi_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) {
comedi_spin_unlock_irqrestore(&devpriv->isr_spinlock, irqflags);
comedi_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);
}
comedi_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(comedi_device * dev, comedi_subdevice * s)
{
pci230_ai_stop(dev, s);
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment