4300 lines
122 KiB
C
4300 lines
122 KiB
C
/*
|
|
comedi/drivers/cb_pcidas64.c
|
|
This is a driver for the ComputerBoards/MeasurementComputing PCI-DAS
|
|
64xx, 60xx, and 4020 cards.
|
|
|
|
Author: Frank Mori Hess <fmhess@users.sourceforge.net>
|
|
Copyright (C) 2001, 2002 Frank Mori Hess
|
|
|
|
Thanks also go to the following people:
|
|
|
|
Steve Rosenbluth, for providing the source code for
|
|
his pci-das6402 driver, and source code for working QNX pci-6402
|
|
drivers by Greg Laird and Mariusz Bogacz. None of the code was
|
|
used directly here, but it was useful as an additional source of
|
|
documentation on how to program the boards.
|
|
|
|
John Sims, for much testing and feedback on pcidas-4020 support.
|
|
|
|
COMEDI - Linux Control and Measurement Device Interface
|
|
Copyright (C) 1997-8 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: cb_pcidas64
|
|
Description: MeasurementComputing PCI-DAS64xx, 60XX, and 4020 series with the PLX 9080 PCI controller
|
|
Author: Frank Mori Hess <fmhess@users.sourceforge.net>
|
|
Status: works
|
|
Updated: 2002-10-09
|
|
Devices: [Measurement Computing] PCI-DAS6402/16 (cb_pcidas64),
|
|
PCI-DAS6402/12, PCI-DAS64/M1/16, PCI-DAS64/M2/16,
|
|
PCI-DAS64/M3/16, PCI-DAS6402/16/JR, PCI-DAS64/M1/16/JR,
|
|
PCI-DAS64/M2/16/JR, PCI-DAS64/M3/16/JR, PCI-DAS64/M1/14,
|
|
PCI-DAS64/M2/14, PCI-DAS64/M3/14, PCI-DAS6013, PCI-DAS6014,
|
|
PCI-DAS6023, PCI-DAS6025, PCI-DAS6030,
|
|
PCI-DAS6031, PCI-DAS6032, PCI-DAS6033, PCI-DAS6034,
|
|
PCI-DAS6035, PCI-DAS6036, PCI-DAS6040, PCI-DAS6052,
|
|
PCI-DAS6070, PCI-DAS6071, PCI-DAS4020/12
|
|
|
|
Configuration options:
|
|
[0] - PCI bus of device (optional)
|
|
[1] - PCI slot of device (optional)
|
|
|
|
These boards may be autocalibrated with the comedi_calibrate utility.
|
|
|
|
To select the bnc trigger input on the 4020 (instead of the dio input),
|
|
specify a nonzero channel in the chanspec. If you wish to use an external
|
|
master clock on the 4020, you may do so by setting the scan_begin_src
|
|
to TRIG_OTHER, and using an INSN_CONFIG_TIMER_1 configuration insn
|
|
to configure the divisor to use for the external clock.
|
|
|
|
Some devices are not identified because the PCI device IDs are not yet
|
|
known. If you have such a board, please file a bug report at
|
|
https://bugs.comedi.org.
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
TODO:
|
|
make it return error if user attempts an ai command that uses the
|
|
external queue, and an ao command simultaneously
|
|
user counter subdevice
|
|
there are a number of boards this driver will support when they are
|
|
fully released, but does not yet since the pci device id numbers
|
|
are not yet available.
|
|
support prescaled 100khz clock for slow pacing (not available on 6000 series?)
|
|
make ao fifo size adjustable like ai fifo
|
|
*/
|
|
|
|
#include "../comedidev.h"
|
|
#include <linux/delay.h>
|
|
#include <linux/interrupt.h>
|
|
#include <asm/system.h>
|
|
|
|
#include "comedi_pci.h"
|
|
#include "8253.h"
|
|
#include "8255.h"
|
|
#include "plx9080.h"
|
|
#include "comedi_fc.h"
|
|
|
|
#undef PCIDAS64_DEBUG /* disable debugging code */
|
|
/* #define PCIDAS64_DEBUG enable debugging code */
|
|
|
|
#ifdef PCIDAS64_DEBUG
|
|
#define DEBUG_PRINT(format, args...) printk(format , ## args)
|
|
#else
|
|
#define DEBUG_PRINT(format, args...)
|
|
#endif
|
|
|
|
#define TIMER_BASE 25 /* 40MHz master clock */
|
|
#define PRESCALED_TIMER_BASE 10000 /* 100kHz 'prescaled' clock for slow aquisition, maybe I'll support this someday */
|
|
#define DMA_BUFFER_SIZE 0x1000
|
|
|
|
/* maximum value that can be loaded into board's 24-bit counters*/
|
|
static const int max_counter_value = 0xffffff;
|
|
|
|
/* PCI-DAS64xxx base addresses */
|
|
|
|
/* indices of base address regions */
|
|
enum base_address_regions {
|
|
PLX9080_BADDRINDEX = 0,
|
|
MAIN_BADDRINDEX = 2,
|
|
DIO_COUNTER_BADDRINDEX = 3,
|
|
};
|
|
|
|
/* priv(dev)->main_iobase registers */
|
|
enum write_only_registers {
|
|
INTR_ENABLE_REG = 0x0, /* interrupt enable register */
|
|
HW_CONFIG_REG = 0x2, /* hardware config register */
|
|
DAQ_SYNC_REG = 0xc,
|
|
DAQ_ATRIG_LOW_4020_REG = 0xc,
|
|
ADC_CONTROL0_REG = 0x10, /* adc control register 0 */
|
|
ADC_CONTROL1_REG = 0x12, /* adc control register 1 */
|
|
CALIBRATION_REG = 0x14,
|
|
ADC_SAMPLE_INTERVAL_LOWER_REG = 0x16, /* lower 16 bits of adc sample interval counter */
|
|
ADC_SAMPLE_INTERVAL_UPPER_REG = 0x18, /* upper 8 bits of adc sample interval counter */
|
|
ADC_DELAY_INTERVAL_LOWER_REG = 0x1a, /* lower 16 bits of delay interval counter */
|
|
ADC_DELAY_INTERVAL_UPPER_REG = 0x1c, /* upper 8 bits of delay interval counter */
|
|
ADC_COUNT_LOWER_REG = 0x1e, /* lower 16 bits of hardware conversion/scan counter */
|
|
ADC_COUNT_UPPER_REG = 0x20, /* upper 8 bits of hardware conversion/scan counter */
|
|
ADC_START_REG = 0x22, /* software trigger to start aquisition */
|
|
ADC_CONVERT_REG = 0x24, /* initiates single conversion */
|
|
ADC_QUEUE_CLEAR_REG = 0x26, /* clears adc queue */
|
|
ADC_QUEUE_LOAD_REG = 0x28, /* loads adc queue */
|
|
ADC_BUFFER_CLEAR_REG = 0x2a,
|
|
ADC_QUEUE_HIGH_REG = 0x2c, /* high channel for internal queue, use adc_chan_bits() inline above */
|
|
DAC_CONTROL0_REG = 0x50, /* dac control register 0 */
|
|
DAC_CONTROL1_REG = 0x52, /* dac control register 0 */
|
|
DAC_SAMPLE_INTERVAL_LOWER_REG = 0x54, /* lower 16 bits of dac sample interval counter */
|
|
DAC_SAMPLE_INTERVAL_UPPER_REG = 0x56, /* upper 8 bits of dac sample interval counter */
|
|
DAC_SELECT_REG = 0x60,
|
|
DAC_START_REG = 0x64,
|
|
DAC_BUFFER_CLEAR_REG = 0x66, /* clear dac buffer */
|
|
};
|
|
static inline unsigned int dac_convert_reg(unsigned int channel)
|
|
{
|
|
return 0x70 + (2 * (channel & 0x1));
|
|
}
|
|
|
|
static inline unsigned int dac_lsb_4020_reg(unsigned int channel)
|
|
{
|
|
return 0x70 + (4 * (channel & 0x1));
|
|
}
|
|
|
|
static inline unsigned int dac_msb_4020_reg(unsigned int channel)
|
|
{
|
|
return 0x72 + (4 * (channel & 0x1));
|
|
}
|
|
|
|
enum read_only_registers {
|
|
HW_STATUS_REG = 0x0, /* hardware status register, reading this apparently clears pending interrupts as well */
|
|
PIPE1_READ_REG = 0x4,
|
|
ADC_READ_PNTR_REG = 0x8,
|
|
LOWER_XFER_REG = 0x10,
|
|
ADC_WRITE_PNTR_REG = 0xc,
|
|
PREPOST_REG = 0x14,
|
|
};
|
|
|
|
enum read_write_registers {
|
|
I8255_4020_REG = 0x48, /* 8255 offset, for 4020 only */
|
|
ADC_QUEUE_FIFO_REG = 0x100, /* external channel/gain queue, uses same bits as ADC_QUEUE_LOAD_REG */
|
|
ADC_FIFO_REG = 0x200, /* adc data fifo */
|
|
DAC_FIFO_REG = 0x300, /* dac data fifo, has weird interactions with external channel queue */
|
|
};
|
|
|
|
/* priv(dev)->dio_counter_iobase registers */
|
|
enum dio_counter_registers {
|
|
DIO_8255_OFFSET = 0x0,
|
|
DO_REG = 0x20,
|
|
DI_REG = 0x28,
|
|
DIO_DIRECTION_60XX_REG = 0x40,
|
|
DIO_DATA_60XX_REG = 0x48,
|
|
};
|
|
|
|
/* bit definitions for write-only registers */
|
|
|
|
enum intr_enable_contents {
|
|
ADC_INTR_SRC_MASK = 0x3, /* bits that set adc interrupt source */
|
|
ADC_INTR_QFULL_BITS = 0x0, /* interrupt fifo quater full */
|
|
ADC_INTR_EOC_BITS = 0x1, /* interrupt end of conversion */
|
|
ADC_INTR_EOSCAN_BITS = 0x2, /* interrupt end of scan */
|
|
ADC_INTR_EOSEQ_BITS = 0x3, /* interrupt end of sequence (probably wont use this it's pretty fancy) */
|
|
EN_ADC_INTR_SRC_BIT = 0x4, /* enable adc interrupt source */
|
|
EN_ADC_DONE_INTR_BIT = 0x8, /* enable adc aquisition done interrupt */
|
|
DAC_INTR_SRC_MASK = 0x30,
|
|
DAC_INTR_QEMPTY_BITS = 0x0,
|
|
DAC_INTR_HIGH_CHAN_BITS = 0x10,
|
|
EN_DAC_INTR_SRC_BIT = 0x40, /* enable dac interrupt source */
|
|
EN_DAC_DONE_INTR_BIT = 0x80,
|
|
EN_ADC_ACTIVE_INTR_BIT = 0x200, /* enable adc active interrupt */
|
|
EN_ADC_STOP_INTR_BIT = 0x400, /* enable adc stop trigger interrupt */
|
|
EN_DAC_ACTIVE_INTR_BIT = 0x800, /* enable dac active interrupt */
|
|
EN_DAC_UNDERRUN_BIT = 0x4000, /* enable dac underrun status bit */
|
|
EN_ADC_OVERRUN_BIT = 0x8000, /* enable adc overrun status bit */
|
|
};
|
|
|
|
enum hw_config_contents {
|
|
MASTER_CLOCK_4020_MASK = 0x3, /* bits that specify master clock source for 4020 */
|
|
INTERNAL_CLOCK_4020_BITS = 0x1, /* use 40 MHz internal master clock for 4020 */
|
|
BNC_CLOCK_4020_BITS = 0x2, /* use BNC input for master clock */
|
|
EXT_CLOCK_4020_BITS = 0x3, /* use dio input for master clock */
|
|
EXT_QUEUE_BIT = 0x200, /* use external channel/gain queue (more versatile than internal queue) */
|
|
SLOW_DAC_BIT = 0x400, /* use 225 nanosec strobe when loading dac instead of 50 nanosec */
|
|
HW_CONFIG_DUMMY_BITS = 0x2000, /* bit with unknown function yet given as default value in pci-das64 manual */
|
|
DMA_CH_SELECT_BIT = 0x8000, /* bit selects channels 1/0 for analog input/output, otherwise 0/1 */
|
|
FIFO_SIZE_REG = 0x4, /* allows adjustment of fifo sizes */
|
|
DAC_FIFO_SIZE_MASK = 0xff00, /* bits that set dac fifo size */
|
|
DAC_FIFO_BITS = 0xf800, /* 8k sample ao fifo */
|
|
};
|
|
#define DAC_FIFO_SIZE 0x2000
|
|
|
|
enum daq_atrig_low_4020_contents {
|
|
EXT_AGATE_BNC_BIT = 0x8000, /* use trig/ext clk bnc input for analog gate signal */
|
|
EXT_STOP_TRIG_BNC_BIT = 0x4000, /* use trig/ext clk bnc input for external stop trigger signal */
|
|
EXT_START_TRIG_BNC_BIT = 0x2000, /* use trig/ext clk bnc input for external start trigger signal */
|
|
};
|
|
static inline uint16_t analog_trig_low_threshold_bits(uint16_t threshold)
|
|
{
|
|
return threshold & 0xfff;
|
|
}
|
|
|
|
enum adc_control0_contents {
|
|
ADC_GATE_SRC_MASK = 0x3, /* bits that select gate */
|
|
ADC_SOFT_GATE_BITS = 0x1, /* software gate */
|
|
ADC_EXT_GATE_BITS = 0x2, /* external digital gate */
|
|
ADC_ANALOG_GATE_BITS = 0x3, /* analog level gate */
|
|
ADC_GATE_LEVEL_BIT = 0x4, /* level-sensitive gate (for digital) */
|
|
ADC_GATE_POLARITY_BIT = 0x8, /* gate active low */
|
|
ADC_START_TRIG_SOFT_BITS = 0x10,
|
|
ADC_START_TRIG_EXT_BITS = 0x20,
|
|
ADC_START_TRIG_ANALOG_BITS = 0x30,
|
|
ADC_START_TRIG_MASK = 0x30,
|
|
ADC_START_TRIG_FALLING_BIT = 0x40, /* trig 1 uses falling edge */
|
|
ADC_EXT_CONV_FALLING_BIT = 0x800, /* external pacing uses falling edge */
|
|
ADC_SAMPLE_COUNTER_EN_BIT = 0x1000, /* enable hardware scan counter */
|
|
ADC_DMA_DISABLE_BIT = 0x4000, /* disables dma */
|
|
ADC_ENABLE_BIT = 0x8000, /* master adc enable */
|
|
};
|
|
|
|
enum adc_control1_contents {
|
|
ADC_QUEUE_CONFIG_BIT = 0x1, /* should be set for boards with > 16 channels */
|
|
CONVERT_POLARITY_BIT = 0x10,
|
|
EOC_POLARITY_BIT = 0x20,
|
|
ADC_SW_GATE_BIT = 0x40, /* software gate of adc */
|
|
ADC_DITHER_BIT = 0x200, /* turn on extra noise for dithering */
|
|
RETRIGGER_BIT = 0x800,
|
|
ADC_LO_CHANNEL_4020_MASK = 0x300,
|
|
ADC_HI_CHANNEL_4020_MASK = 0xc00,
|
|
TWO_CHANNEL_4020_BITS = 0x1000, /* two channel mode for 4020 */
|
|
FOUR_CHANNEL_4020_BITS = 0x2000, /* four channel mode for 4020 */
|
|
CHANNEL_MODE_4020_MASK = 0x3000,
|
|
ADC_MODE_MASK = 0xf000,
|
|
};
|
|
static inline uint16_t adc_lo_chan_4020_bits(unsigned int channel)
|
|
{
|
|
return (channel & 0x3) << 8;
|
|
};
|
|
|
|
static inline uint16_t adc_hi_chan_4020_bits(unsigned int channel)
|
|
{
|
|
return (channel & 0x3) << 10;
|
|
};
|
|
|
|
static inline uint16_t adc_mode_bits(unsigned int mode)
|
|
{
|
|
return (mode & 0xf) << 12;
|
|
};
|
|
|
|
enum calibration_contents {
|
|
SELECT_8800_BIT = 0x1,
|
|
SELECT_8402_64XX_BIT = 0x2,
|
|
SELECT_1590_60XX_BIT = 0x2,
|
|
CAL_EN_64XX_BIT = 0x40, /* calibration enable for 64xx series */
|
|
SERIAL_DATA_IN_BIT = 0x80,
|
|
SERIAL_CLOCK_BIT = 0x100,
|
|
CAL_EN_60XX_BIT = 0x200, /* calibration enable for 60xx series */
|
|
CAL_GAIN_BIT = 0x800,
|
|
};
|
|
/* calibration sources for 6025 are:
|
|
* 0 : ground
|
|
* 1 : 10V
|
|
* 2 : 5V
|
|
* 3 : 0.5V
|
|
* 4 : 0.05V
|
|
* 5 : ground
|
|
* 6 : dac channel 0
|
|
* 7 : dac channel 1
|
|
*/
|
|
static inline uint16_t adc_src_bits(unsigned int source)
|
|
{
|
|
return (source & 0xf) << 3;
|
|
};
|
|
|
|
static inline uint16_t adc_convert_chan_4020_bits(unsigned int channel)
|
|
{
|
|
return (channel & 0x3) << 8;
|
|
};
|
|
|
|
enum adc_queue_load_contents {
|
|
UNIP_BIT = 0x800, /* unipolar/bipolar bit */
|
|
ADC_SE_DIFF_BIT = 0x1000, /* single-ended/ differential bit */
|
|
ADC_COMMON_BIT = 0x2000, /* non-referenced single-ended (common-mode input) */
|
|
QUEUE_EOSEQ_BIT = 0x4000, /* queue end of sequence */
|
|
QUEUE_EOSCAN_BIT = 0x8000, /* queue end of scan */
|
|
};
|
|
static inline uint16_t adc_chan_bits(unsigned int channel)
|
|
{
|
|
return channel & 0x3f;
|
|
};
|
|
|
|
enum dac_control0_contents {
|
|
DAC_ENABLE_BIT = 0x8000, /* dac controller enable bit */
|
|
DAC_CYCLIC_STOP_BIT = 0x4000,
|
|
DAC_WAVEFORM_MODE_BIT = 0x100,
|
|
DAC_EXT_UPDATE_FALLING_BIT = 0x80,
|
|
DAC_EXT_UPDATE_ENABLE_BIT = 0x40,
|
|
WAVEFORM_TRIG_MASK = 0x30,
|
|
WAVEFORM_TRIG_DISABLED_BITS = 0x0,
|
|
WAVEFORM_TRIG_SOFT_BITS = 0x10,
|
|
WAVEFORM_TRIG_EXT_BITS = 0x20,
|
|
WAVEFORM_TRIG_ADC1_BITS = 0x30,
|
|
WAVEFORM_TRIG_FALLING_BIT = 0x8,
|
|
WAVEFORM_GATE_LEVEL_BIT = 0x4,
|
|
WAVEFORM_GATE_ENABLE_BIT = 0x2,
|
|
WAVEFORM_GATE_SELECT_BIT = 0x1,
|
|
};
|
|
|
|
enum dac_control1_contents {
|
|
DAC_WRITE_POLARITY_BIT = 0x800, /* board-dependent setting */
|
|
DAC1_EXT_REF_BIT = 0x200,
|
|
DAC0_EXT_REF_BIT = 0x100,
|
|
DAC_OUTPUT_ENABLE_BIT = 0x80, /* dac output enable bit */
|
|
DAC_UPDATE_POLARITY_BIT = 0x40, /* board-dependent setting */
|
|
DAC_SW_GATE_BIT = 0x20,
|
|
DAC1_UNIPOLAR_BIT = 0x8,
|
|
DAC0_UNIPOLAR_BIT = 0x2,
|
|
};
|
|
|
|
/* bit definitions for read-only registers */
|
|
enum hw_status_contents {
|
|
DAC_UNDERRUN_BIT = 0x1,
|
|
ADC_OVERRUN_BIT = 0x2,
|
|
DAC_ACTIVE_BIT = 0x4,
|
|
ADC_ACTIVE_BIT = 0x8,
|
|
DAC_INTR_PENDING_BIT = 0x10,
|
|
ADC_INTR_PENDING_BIT = 0x20,
|
|
DAC_DONE_BIT = 0x40,
|
|
ADC_DONE_BIT = 0x80,
|
|
EXT_INTR_PENDING_BIT = 0x100,
|
|
ADC_STOP_BIT = 0x200,
|
|
};
|
|
static inline uint16_t pipe_full_bits(uint16_t hw_status_bits)
|
|
{
|
|
return (hw_status_bits >> 10) & 0x3;
|
|
};
|
|
|
|
static inline unsigned int dma_chain_flag_bits(uint16_t prepost_bits)
|
|
{
|
|
return (prepost_bits >> 6) & 0x3;
|
|
}
|
|
|
|
static inline unsigned int adc_upper_read_ptr_code(uint16_t prepost_bits)
|
|
{
|
|
return (prepost_bits >> 12) & 0x3;
|
|
}
|
|
|
|
static inline unsigned int adc_upper_write_ptr_code(uint16_t prepost_bits)
|
|
{
|
|
return (prepost_bits >> 14) & 0x3;
|
|
}
|
|
|
|
/* I2C addresses for 4020 */
|
|
enum i2c_addresses {
|
|
RANGE_CAL_I2C_ADDR = 0x20,
|
|
CALDAC0_I2C_ADDR = 0xc,
|
|
CALDAC1_I2C_ADDR = 0xd,
|
|
};
|
|
|
|
enum range_cal_i2c_contents {
|
|
ADC_SRC_4020_MASK = 0x70, /* bits that set what source the adc converter measures */
|
|
BNC_TRIG_THRESHOLD_0V_BIT = 0x80, /* make bnc trig/ext clock threshold 0V instead of 2.5V */
|
|
};
|
|
static inline uint8_t adc_src_4020_bits(unsigned int source)
|
|
{
|
|
return (source << 4) & ADC_SRC_4020_MASK;
|
|
};
|
|
|
|
static inline uint8_t attenuate_bit(unsigned int channel)
|
|
{
|
|
/* attenuate channel (+-5V input range) */
|
|
return 1 << (channel & 0x3);
|
|
};
|
|
|
|
/* analog input ranges for 64xx boards */
|
|
static const struct comedi_lrange ai_ranges_64xx = {
|
|
8,
|
|
{
|
|
BIP_RANGE(10),
|
|
BIP_RANGE(5),
|
|
BIP_RANGE(2.5),
|
|
BIP_RANGE(1.25),
|
|
UNI_RANGE(10),
|
|
UNI_RANGE(5),
|
|
UNI_RANGE(2.5),
|
|
UNI_RANGE(1.25)
|
|
}
|
|
};
|
|
|
|
/* analog input ranges for 60xx boards */
|
|
static const struct comedi_lrange ai_ranges_60xx = {
|
|
4,
|
|
{
|
|
BIP_RANGE(10),
|
|
BIP_RANGE(5),
|
|
BIP_RANGE(0.5),
|
|
BIP_RANGE(0.05),
|
|
}
|
|
};
|
|
|
|
/* analog input ranges for 6030, etc boards */
|
|
static const struct comedi_lrange ai_ranges_6030 = {
|
|
14,
|
|
{
|
|
BIP_RANGE(10),
|
|
BIP_RANGE(5),
|
|
BIP_RANGE(2),
|
|
BIP_RANGE(1),
|
|
BIP_RANGE(0.5),
|
|
BIP_RANGE(0.2),
|
|
BIP_RANGE(0.1),
|
|
UNI_RANGE(10),
|
|
UNI_RANGE(5),
|
|
UNI_RANGE(2),
|
|
UNI_RANGE(1),
|
|
UNI_RANGE(0.5),
|
|
UNI_RANGE(0.2),
|
|
UNI_RANGE(0.1),
|
|
}
|
|
};
|
|
|
|
/* analog input ranges for 6052, etc boards */
|
|
static const struct comedi_lrange ai_ranges_6052 = {
|
|
15,
|
|
{
|
|
BIP_RANGE(10),
|
|
BIP_RANGE(5),
|
|
BIP_RANGE(2.5),
|
|
BIP_RANGE(1),
|
|
BIP_RANGE(0.5),
|
|
BIP_RANGE(0.25),
|
|
BIP_RANGE(0.1),
|
|
BIP_RANGE(0.05),
|
|
UNI_RANGE(10),
|
|
UNI_RANGE(5),
|
|
UNI_RANGE(2),
|
|
UNI_RANGE(1),
|
|
UNI_RANGE(0.5),
|
|
UNI_RANGE(0.2),
|
|
UNI_RANGE(0.1),
|
|
}
|
|
};
|
|
|
|
/* analog input ranges for 4020 board */
|
|
static const struct comedi_lrange ai_ranges_4020 = {
|
|
2,
|
|
{
|
|
BIP_RANGE(5),
|
|
BIP_RANGE(1),
|
|
}
|
|
};
|
|
|
|
/* analog output ranges */
|
|
static const struct comedi_lrange ao_ranges_64xx = {
|
|
4,
|
|
{
|
|
BIP_RANGE(5),
|
|
BIP_RANGE(10),
|
|
UNI_RANGE(5),
|
|
UNI_RANGE(10),
|
|
}
|
|
};
|
|
|
|
static const int ao_range_code_64xx[] = {
|
|
0x0,
|
|
0x1,
|
|
0x2,
|
|
0x3,
|
|
};
|
|
|
|
static const struct comedi_lrange ao_ranges_60xx = {
|
|
1,
|
|
{
|
|
BIP_RANGE(10),
|
|
}
|
|
};
|
|
|
|
static const int ao_range_code_60xx[] = {
|
|
0x0,
|
|
};
|
|
|
|
static const struct comedi_lrange ao_ranges_6030 = {
|
|
2,
|
|
{
|
|
BIP_RANGE(10),
|
|
UNI_RANGE(10),
|
|
}
|
|
};
|
|
|
|
static const int ao_range_code_6030[] = {
|
|
0x0,
|
|
0x2,
|
|
};
|
|
|
|
static const struct comedi_lrange ao_ranges_4020 = {
|
|
2,
|
|
{
|
|
BIP_RANGE(5),
|
|
BIP_RANGE(10),
|
|
}
|
|
};
|
|
|
|
static const int ao_range_code_4020[] = {
|
|
0x1,
|
|
0x0,
|
|
};
|
|
|
|
enum register_layout {
|
|
LAYOUT_60XX,
|
|
LAYOUT_64XX,
|
|
LAYOUT_4020,
|
|
};
|
|
|
|
struct hw_fifo_info {
|
|
unsigned int num_segments;
|
|
unsigned int max_segment_length;
|
|
unsigned int sample_packing_ratio;
|
|
uint16_t fifo_size_reg_mask;
|
|
};
|
|
|
|
struct pcidas64_board {
|
|
const char *name;
|
|
int device_id; /* pci device id */
|
|
int ai_se_chans; /* number of ai inputs in single-ended mode */
|
|
int ai_bits; /* analog input resolution */
|
|
int ai_speed; /* fastest conversion period in ns */
|
|
const struct comedi_lrange *ai_range_table;
|
|
int ao_nchan; /* number of analog out channels */
|
|
int ao_bits; /* analog output resolution */
|
|
int ao_scan_speed; /* analog output speed (for a scan, not conversion) */
|
|
const struct comedi_lrange *ao_range_table;
|
|
const int *ao_range_code;
|
|
const struct hw_fifo_info *const ai_fifo;
|
|
enum register_layout layout; /* different board families have slightly different registers */
|
|
unsigned has_8255:1;
|
|
};
|
|
|
|
static const struct hw_fifo_info ai_fifo_4020 = {
|
|
.num_segments = 2,
|
|
.max_segment_length = 0x8000,
|
|
.sample_packing_ratio = 2,
|
|
.fifo_size_reg_mask = 0x7f,
|
|
};
|
|
|
|
static const struct hw_fifo_info ai_fifo_64xx = {
|
|
.num_segments = 4,
|
|
.max_segment_length = 0x800,
|
|
.sample_packing_ratio = 1,
|
|
.fifo_size_reg_mask = 0x3f,
|
|
};
|
|
|
|
static const struct hw_fifo_info ai_fifo_60xx = {
|
|
.num_segments = 4,
|
|
.max_segment_length = 0x800,
|
|
.sample_packing_ratio = 1,
|
|
.fifo_size_reg_mask = 0x7f,
|
|
};
|
|
|
|
/* maximum number of dma transfers we will chain together into a ring
|
|
* (and the maximum number of dma buffers we maintain) */
|
|
#define MAX_AI_DMA_RING_COUNT (0x80000 / DMA_BUFFER_SIZE)
|
|
#define MIN_AI_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE)
|
|
#define AO_DMA_RING_COUNT (0x10000 / DMA_BUFFER_SIZE)
|
|
static inline unsigned int ai_dma_ring_count(struct pcidas64_board *board)
|
|
{
|
|
if (board->layout == LAYOUT_4020)
|
|
return MAX_AI_DMA_RING_COUNT;
|
|
else
|
|
return MIN_AI_DMA_RING_COUNT;
|
|
}
|
|
|
|
static const int bytes_in_sample = 2;
|
|
|
|
static const struct pcidas64_board pcidas64_boards[] = {
|
|
{
|
|
.name = "pci-das6402/16",
|
|
.device_id = 0x1d,
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 16,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ao_range_table = &ao_ranges_64xx,
|
|
.ao_range_code = ao_range_code_64xx,
|
|
.ai_fifo = &ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das6402/12", /* XXX check */
|
|
.device_id = 0x1e,
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 12,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 12,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ao_range_table = &ao_ranges_64xx,
|
|
.ao_range_code = ao_range_code_64xx,
|
|
.ai_fifo = &ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das64/m1/16",
|
|
.device_id = 0x35,
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 1000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 16,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ao_range_table = &ao_ranges_64xx,
|
|
.ao_range_code = ao_range_code_64xx,
|
|
.ai_fifo = &ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das64/m2/16",
|
|
.device_id = 0x36,
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 500,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 16,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ao_range_table = &ao_ranges_64xx,
|
|
.ao_range_code = ao_range_code_64xx,
|
|
.ai_fifo = &ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das64/m3/16",
|
|
.device_id = 0x37,
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 333,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 16,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ao_range_table = &ao_ranges_64xx,
|
|
.ao_range_code = ao_range_code_64xx,
|
|
.ai_fifo = &ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das6013",
|
|
.device_id = 0x78,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 16,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 0,
|
|
.ao_bits = 16,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_60xx,
|
|
.ao_range_table = &ao_ranges_60xx,
|
|
.ao_range_code = ao_range_code_60xx,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6014",
|
|
.device_id = 0x79,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 16,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 16,
|
|
.ao_scan_speed = 100000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_60xx,
|
|
.ao_range_table = &ao_ranges_60xx,
|
|
.ao_range_code = ao_range_code_60xx,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6023",
|
|
.device_id = 0x5d,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 12,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 0,
|
|
.ao_scan_speed = 100000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_60xx,
|
|
.ao_range_table = &ao_ranges_60xx,
|
|
.ao_range_code = ao_range_code_60xx,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das6025",
|
|
.device_id = 0x5e,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 12,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 12,
|
|
.ao_scan_speed = 100000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_60xx,
|
|
.ao_range_table = &ao_ranges_60xx,
|
|
.ao_range_code = ao_range_code_60xx,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das6030",
|
|
.device_id = 0x5f,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 16,
|
|
.ai_speed = 10000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 16,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_6030,
|
|
.ao_range_table = &ao_ranges_6030,
|
|
.ao_range_code = ao_range_code_6030,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6031",
|
|
.device_id = 0x60,
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 10000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 16,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_6030,
|
|
.ao_range_table = &ao_ranges_6030,
|
|
.ao_range_code = ao_range_code_6030,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6032",
|
|
.device_id = 0x61,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 16,
|
|
.ai_speed = 10000,
|
|
.ao_nchan = 0,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_6030,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6033",
|
|
.device_id = 0x62,
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 10000,
|
|
.ao_nchan = 0,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_6030,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6034",
|
|
.device_id = 0x63,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 16,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 0,
|
|
.ao_scan_speed = 0,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_60xx,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6035",
|
|
.device_id = 0x64,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 16,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 12,
|
|
.ao_scan_speed = 100000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_60xx,
|
|
.ao_range_table = &ao_ranges_60xx,
|
|
.ao_range_code = ao_range_code_60xx,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6036",
|
|
.device_id = 0x6f,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 16,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 16,
|
|
.ao_scan_speed = 100000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_60xx,
|
|
.ao_range_table = &ao_ranges_60xx,
|
|
.ao_range_code = ao_range_code_60xx,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6040",
|
|
.device_id = 0x65,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 12,
|
|
.ai_speed = 2000,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 12,
|
|
.ao_scan_speed = 1000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_6052,
|
|
.ao_range_table = &ao_ranges_6030,
|
|
.ao_range_code = ao_range_code_6030,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6052",
|
|
.device_id = 0x66,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 16,
|
|
.ai_speed = 3333,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 16,
|
|
.ao_scan_speed = 3333,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_6052,
|
|
.ao_range_table = &ao_ranges_6030,
|
|
.ao_range_code = ao_range_code_6030,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6070",
|
|
.device_id = 0x67,
|
|
.ai_se_chans = 16,
|
|
.ai_bits = 12,
|
|
.ai_speed = 800,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 12,
|
|
.ao_scan_speed = 1000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_6052,
|
|
.ao_range_table = &ao_ranges_6030,
|
|
.ao_range_code = ao_range_code_6030,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das6071",
|
|
.device_id = 0x68,
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 12,
|
|
.ai_speed = 800,
|
|
.ao_nchan = 2,
|
|
.ao_bits = 12,
|
|
.ao_scan_speed = 1000,
|
|
.layout = LAYOUT_60XX,
|
|
.ai_range_table = &ai_ranges_6052,
|
|
.ao_range_table = &ao_ranges_6030,
|
|
.ao_range_code = ao_range_code_6030,
|
|
.ai_fifo = &ai_fifo_60xx,
|
|
.has_8255 = 0,
|
|
},
|
|
{
|
|
.name = "pci-das4020/12",
|
|
.device_id = 0x52,
|
|
.ai_se_chans = 4,
|
|
.ai_bits = 12,
|
|
.ai_speed = 50,
|
|
.ao_bits = 12,
|
|
.ao_nchan = 2,
|
|
.ao_scan_speed = 0, /* no hardware pacing on ao */
|
|
.layout = LAYOUT_4020,
|
|
.ai_range_table = &ai_ranges_4020,
|
|
.ao_range_table = &ao_ranges_4020,
|
|
.ao_range_code = ao_range_code_4020,
|
|
.ai_fifo = &ai_fifo_4020,
|
|
.has_8255 = 1,
|
|
},
|
|
#if 0
|
|
{
|
|
.name = "pci-das6402/16/jr",
|
|
.device_id = 0 /* XXX, */
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 5000,
|
|
.ao_nchan = 0,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ai_fifo = ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das64/m1/16/jr",
|
|
.device_id = 0 /* XXX, */
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 1000,
|
|
.ao_nchan = 0,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ai_fifo = ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das64/m2/16/jr",
|
|
.device_id = 0 /* XXX, */
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 500,
|
|
.ao_nchan = 0,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ai_fifo = ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das64/m3/16/jr",
|
|
.device_id = 0 /* XXX, */
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 16,
|
|
.ai_speed = 333,
|
|
.ao_nchan = 0,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ai_fifo = ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das64/m1/14",
|
|
.device_id = 0, /* XXX */
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 14,
|
|
.ai_speed = 1000,
|
|
.ao_nchan = 2,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ai_fifo = ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das64/m2/14",
|
|
.device_id = 0, /* XXX */
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 14,
|
|
.ai_speed = 500,
|
|
.ao_nchan = 2,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ai_fifo = ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
{
|
|
.name = "pci-das64/m3/14",
|
|
.device_id = 0, /* XXX */
|
|
.ai_se_chans = 64,
|
|
.ai_bits = 14,
|
|
.ai_speed = 333,
|
|
.ao_nchan = 2,
|
|
.ao_scan_speed = 10000,
|
|
.layout = LAYOUT_64XX,
|
|
.ai_range_table = &ai_ranges_64xx,
|
|
.ai_fifo = ai_fifo_64xx,
|
|
.has_8255 = 1,
|
|
},
|
|
#endif
|
|
};
|
|
|
|
static DEFINE_PCI_DEVICE_TABLE(pcidas64_pci_table) = {
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x001d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x001e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0035, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0036, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0037, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0052, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x005d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x005e, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x005f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0061, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0062, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0063, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0064, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0066, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0067, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0068, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x006f, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0078, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
PCI_VENDOR_ID_COMPUTERBOARDS, 0x0079, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
|
|
{
|
|
0}
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, pcidas64_pci_table);
|
|
|
|
static inline struct pcidas64_board *board(const struct comedi_device *dev)
|
|
{
|
|
return (struct pcidas64_board *)dev->board_ptr;
|
|
}
|
|
|
|
static inline unsigned short se_diff_bit_6xxx(struct comedi_device *dev,
|
|
int use_differential)
|
|
{
|
|
if ((board(dev)->layout == LAYOUT_64XX && !use_differential) ||
|
|
(board(dev)->layout == LAYOUT_60XX && use_differential))
|
|
return ADC_SE_DIFF_BIT;
|
|
else
|
|
return 0;
|
|
};
|
|
|
|
struct ext_clock_info {
|
|
unsigned int divisor; /* master clock divisor to use for scans with external master clock */
|
|
unsigned int chanspec; /* chanspec for master clock input when used as scan begin src */
|
|
};
|
|
|
|
/* this structure is for data unique to this hardware driver. */
|
|
struct pcidas64_private {
|
|
|
|
struct pci_dev *hw_dev; /* pointer to board's pci_dev struct */
|
|
/* base addresses (physical) */
|
|
resource_size_t plx9080_phys_iobase;
|
|
resource_size_t main_phys_iobase;
|
|
resource_size_t dio_counter_phys_iobase;
|
|
/* base addresses (ioremapped) */
|
|
void *plx9080_iobase;
|
|
void *main_iobase;
|
|
void *dio_counter_iobase;
|
|
/* local address (used by dma controller) */
|
|
uint32_t local0_iobase;
|
|
uint32_t local1_iobase;
|
|
volatile unsigned int ai_count; /* number of analog input samples remaining */
|
|
uint16_t *ai_buffer[MAX_AI_DMA_RING_COUNT]; /* dma buffers for analog input */
|
|
dma_addr_t ai_buffer_bus_addr[MAX_AI_DMA_RING_COUNT]; /* physical addresses of ai dma buffers */
|
|
struct plx_dma_desc *ai_dma_desc; /* array of ai dma descriptors read by plx9080, allocated to get proper alignment */
|
|
dma_addr_t ai_dma_desc_bus_addr; /* physical address of ai dma descriptor array */
|
|
volatile unsigned int ai_dma_index; /* index of the ai dma descriptor/buffer that is currently being used */
|
|
uint16_t *ao_buffer[AO_DMA_RING_COUNT]; /* dma buffers for analog output */
|
|
dma_addr_t ao_buffer_bus_addr[AO_DMA_RING_COUNT]; /* physical addresses of ao dma buffers */
|
|
struct plx_dma_desc *ao_dma_desc;
|
|
dma_addr_t ao_dma_desc_bus_addr;
|
|
volatile unsigned int ao_dma_index; /* keeps track of buffer where the next ao sample should go */
|
|
volatile unsigned long ao_count; /* number of analog output samples remaining */
|
|
volatile unsigned int ao_value[2]; /* remember what the analog outputs are set to, to allow readback */
|
|
unsigned int hw_revision; /* stc chip hardware revision number */
|
|
volatile unsigned int intr_enable_bits; /* last bits sent to INTR_ENABLE_REG register */
|
|
volatile uint16_t adc_control1_bits; /* last bits sent to ADC_CONTROL1_REG register */
|
|
volatile uint16_t fifo_size_bits; /* last bits sent to FIFO_SIZE_REG register */
|
|
volatile uint16_t hw_config_bits; /* last bits sent to HW_CONFIG_REG register */
|
|
volatile uint16_t dac_control1_bits;
|
|
volatile uint32_t plx_control_bits; /* last bits written to plx9080 control register */
|
|
volatile uint32_t plx_intcsr_bits; /* last bits written to plx interrupt control and status register */
|
|
volatile int calibration_source; /* index of calibration source readable through ai ch0 */
|
|
volatile uint8_t i2c_cal_range_bits; /* bits written to i2c calibration/range register */
|
|
volatile unsigned int ext_trig_falling; /* configure digital triggers to trigger on falling edge */
|
|
/* states of various devices stored to enable read-back */
|
|
unsigned int ad8402_state[2];
|
|
unsigned int caldac_state[8];
|
|
volatile short ai_cmd_running;
|
|
unsigned int ai_fifo_segment_length;
|
|
struct ext_clock_info ext_clock;
|
|
short ao_bounce_buffer[DAC_FIFO_SIZE];
|
|
};
|
|
|
|
/* inline function that makes it easier to
|
|
* access the private structure.
|
|
*/
|
|
static inline struct pcidas64_private *priv(struct comedi_device *dev)
|
|
{
|
|
return dev->private;
|
|
}
|
|
|
|
/*
|
|
* 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 attach(struct comedi_device *dev, struct comedi_devconfig *it);
|
|
static int detach(struct comedi_device *dev);
|
|
static struct comedi_driver driver_cb_pcidas = {
|
|
.driver_name = "cb_pcidas64",
|
|
.module = THIS_MODULE,
|
|
.attach = attach,
|
|
.detach = detach,
|
|
};
|
|
|
|
static int ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int ao_readback_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s);
|
|
static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_cmd *cmd);
|
|
static int ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s);
|
|
static int ao_inttrig(struct comedi_device *dev,
|
|
struct comedi_subdevice *subdev, unsigned int trig_num);
|
|
static int ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_cmd *cmd);
|
|
static irqreturn_t handle_interrupt(int irq, void *d);
|
|
static int ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s);
|
|
static int ao_cancel(struct comedi_device *dev, struct comedi_subdevice *s);
|
|
static int dio_callback(int dir, int port, int data, unsigned long arg);
|
|
static int dio_callback_4020(int dir, int port, int data, unsigned long arg);
|
|
static int di_rbits(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int do_wbits(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int dio_60xx_config_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int dio_60xx_wbits(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int calib_read_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s, struct comedi_insn *insn,
|
|
unsigned int *data);
|
|
static int calib_write_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int ad8402_read_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static void ad8402_write(struct comedi_device *dev, unsigned int channel,
|
|
unsigned int value);
|
|
static int ad8402_write_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static int eeprom_read_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data);
|
|
static void check_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd);
|
|
static unsigned int get_divisor(unsigned int ns, unsigned int flags);
|
|
static void i2c_write(struct comedi_device *dev, unsigned int address,
|
|
const uint8_t * data, unsigned int length);
|
|
static void caldac_write(struct comedi_device *dev, unsigned int channel,
|
|
unsigned int value);
|
|
static int caldac_8800_write(struct comedi_device *dev, unsigned int address,
|
|
uint8_t value);
|
|
/* static int dac_1590_write(struct comedi_device *dev, unsigned int dac_a, unsigned int dac_b); */
|
|
static int caldac_i2c_write(struct comedi_device *dev,
|
|
unsigned int caldac_channel, unsigned int value);
|
|
static void abort_dma(struct comedi_device *dev, unsigned int channel);
|
|
static void disable_plx_interrupts(struct comedi_device *dev);
|
|
static int set_ai_fifo_size(struct comedi_device *dev,
|
|
unsigned int num_samples);
|
|
static unsigned int ai_fifo_size(struct comedi_device *dev);
|
|
static int set_ai_fifo_segment_length(struct comedi_device *dev,
|
|
unsigned int num_entries);
|
|
static void disable_ai_pacing(struct comedi_device *dev);
|
|
static void disable_ai_interrupts(struct comedi_device *dev);
|
|
static void enable_ai_interrupts(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd);
|
|
static unsigned int get_ao_divisor(unsigned int ns, unsigned int flags);
|
|
static void load_ao_dma(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd);
|
|
|
|
COMEDI_PCI_INITCLEANUP(driver_cb_pcidas, pcidas64_pci_table);
|
|
|
|
static unsigned int ai_range_bits_6xxx(const struct comedi_device *dev,
|
|
unsigned int range_index)
|
|
{
|
|
const struct comedi_krange *range =
|
|
&board(dev)->ai_range_table->range[range_index];
|
|
unsigned int bits = 0;
|
|
|
|
switch (range->max) {
|
|
case 10000000:
|
|
bits = 0x000;
|
|
break;
|
|
case 5000000:
|
|
bits = 0x100;
|
|
break;
|
|
case 2000000:
|
|
case 2500000:
|
|
bits = 0x200;
|
|
break;
|
|
case 1000000:
|
|
case 1250000:
|
|
bits = 0x300;
|
|
break;
|
|
case 500000:
|
|
bits = 0x400;
|
|
break;
|
|
case 200000:
|
|
case 250000:
|
|
bits = 0x500;
|
|
break;
|
|
case 100000:
|
|
bits = 0x600;
|
|
break;
|
|
case 50000:
|
|
bits = 0x700;
|
|
break;
|
|
default:
|
|
comedi_error(dev, "bug! in ai_range_bits_6xxx");
|
|
break;
|
|
}
|
|
if (range->min == 0)
|
|
bits += 0x900;
|
|
return bits;
|
|
}
|
|
|
|
static unsigned int hw_revision(const struct comedi_device *dev,
|
|
uint16_t hw_status_bits)
|
|
{
|
|
if (board(dev)->layout == LAYOUT_4020)
|
|
return (hw_status_bits >> 13) & 0x7;
|
|
|
|
return (hw_status_bits >> 12) & 0xf;
|
|
}
|
|
|
|
static void set_dac_range_bits(struct comedi_device *dev,
|
|
volatile uint16_t * bits, unsigned int channel,
|
|
unsigned int range)
|
|
{
|
|
unsigned int code = board(dev)->ao_range_code[range];
|
|
|
|
if (channel > 1)
|
|
comedi_error(dev, "bug! bad channel?");
|
|
if (code & ~0x3)
|
|
comedi_error(dev, "bug! bad range code?");
|
|
|
|
*bits &= ~(0x3 << (2 * channel));
|
|
*bits |= code << (2 * channel);
|
|
};
|
|
|
|
static inline int ao_cmd_is_supported(const struct pcidas64_board *board)
|
|
{
|
|
return board->ao_nchan && board->layout != LAYOUT_4020;
|
|
}
|
|
|
|
/* initialize plx9080 chip */
|
|
static void init_plx9080(struct comedi_device *dev)
|
|
{
|
|
uint32_t bits;
|
|
void *plx_iobase = priv(dev)->plx9080_iobase;
|
|
|
|
priv(dev)->plx_control_bits =
|
|
readl(priv(dev)->plx9080_iobase + PLX_CONTROL_REG);
|
|
|
|
/* plx9080 dump */
|
|
DEBUG_PRINT(" plx interrupt status 0x%x\n",
|
|
readl(plx_iobase + PLX_INTRCS_REG));
|
|
DEBUG_PRINT(" plx id bits 0x%x\n", readl(plx_iobase + PLX_ID_REG));
|
|
DEBUG_PRINT(" plx control reg 0x%x\n", priv(dev)->plx_control_bits);
|
|
DEBUG_PRINT(" plx mode/arbitration reg 0x%x\n",
|
|
readl(plx_iobase + PLX_MARB_REG));
|
|
DEBUG_PRINT(" plx region0 reg 0x%x\n",
|
|
readl(plx_iobase + PLX_REGION0_REG));
|
|
DEBUG_PRINT(" plx region1 reg 0x%x\n",
|
|
readl(plx_iobase + PLX_REGION1_REG));
|
|
|
|
DEBUG_PRINT(" plx revision 0x%x\n",
|
|
readl(plx_iobase + PLX_REVISION_REG));
|
|
DEBUG_PRINT(" plx dma channel 0 mode 0x%x\n",
|
|
readl(plx_iobase + PLX_DMA0_MODE_REG));
|
|
DEBUG_PRINT(" plx dma channel 1 mode 0x%x\n",
|
|
readl(plx_iobase + PLX_DMA1_MODE_REG));
|
|
DEBUG_PRINT(" plx dma channel 0 pci address 0x%x\n",
|
|
readl(plx_iobase + PLX_DMA0_PCI_ADDRESS_REG));
|
|
DEBUG_PRINT(" plx dma channel 0 local address 0x%x\n",
|
|
readl(plx_iobase + PLX_DMA0_LOCAL_ADDRESS_REG));
|
|
DEBUG_PRINT(" plx dma channel 0 transfer size 0x%x\n",
|
|
readl(plx_iobase + PLX_DMA0_TRANSFER_SIZE_REG));
|
|
DEBUG_PRINT(" plx dma channel 0 descriptor 0x%x\n",
|
|
readl(plx_iobase + PLX_DMA0_DESCRIPTOR_REG));
|
|
DEBUG_PRINT(" plx dma channel 0 command status 0x%x\n",
|
|
readb(plx_iobase + PLX_DMA0_CS_REG));
|
|
DEBUG_PRINT(" plx dma channel 0 threshold 0x%x\n",
|
|
readl(plx_iobase + PLX_DMA0_THRESHOLD_REG));
|
|
DEBUG_PRINT(" plx bigend 0x%x\n", readl(plx_iobase + PLX_BIGEND_REG));
|
|
|
|
#ifdef __BIG_ENDIAN
|
|
bits = BIGEND_DMA0 | BIGEND_DMA1;
|
|
#else
|
|
bits = 0;
|
|
#endif
|
|
writel(bits, priv(dev)->plx9080_iobase + PLX_BIGEND_REG);
|
|
|
|
disable_plx_interrupts(dev);
|
|
|
|
abort_dma(dev, 0);
|
|
abort_dma(dev, 1);
|
|
|
|
/* configure dma0 mode */
|
|
bits = 0;
|
|
/* enable ready input, not sure if this is necessary */
|
|
bits |= PLX_DMA_EN_READYIN_BIT;
|
|
/* enable bterm, not sure if this is necessary */
|
|
bits |= PLX_EN_BTERM_BIT;
|
|
/* enable dma chaining */
|
|
bits |= PLX_EN_CHAIN_BIT;
|
|
/* enable interrupt on dma done (probably don't need this, since chain never finishes) */
|
|
bits |= PLX_EN_DMA_DONE_INTR_BIT;
|
|
/* don't increment local address during transfers (we are transferring from a fixed fifo register) */
|
|
bits |= PLX_LOCAL_ADDR_CONST_BIT;
|
|
/* route dma interrupt to pci bus */
|
|
bits |= PLX_DMA_INTR_PCI_BIT;
|
|
/* enable demand mode */
|
|
bits |= PLX_DEMAND_MODE_BIT;
|
|
/* enable local burst mode */
|
|
bits |= PLX_DMA_LOCAL_BURST_EN_BIT;
|
|
/* 4020 uses 32 bit dma */
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
bits |= PLX_LOCAL_BUS_32_WIDE_BITS;
|
|
} else { /* localspace0 bus is 16 bits wide */
|
|
bits |= PLX_LOCAL_BUS_16_WIDE_BITS;
|
|
}
|
|
writel(bits, plx_iobase + PLX_DMA1_MODE_REG);
|
|
if (ao_cmd_is_supported(board(dev)))
|
|
writel(bits, plx_iobase + PLX_DMA0_MODE_REG);
|
|
|
|
/* enable interrupts on plx 9080 */
|
|
priv(dev)->plx_intcsr_bits |=
|
|
ICS_AERR | ICS_PERR | ICS_PIE | ICS_PLIE | ICS_PAIE | ICS_LIE |
|
|
ICS_DMA0_E | ICS_DMA1_E;
|
|
writel(priv(dev)->plx_intcsr_bits,
|
|
priv(dev)->plx9080_iobase + PLX_INTRCS_REG);
|
|
}
|
|
|
|
/* Allocate and initialize the subdevice structures.
|
|
*/
|
|
static int setup_subdevices(struct comedi_device *dev)
|
|
{
|
|
struct comedi_subdevice *s;
|
|
void *dio_8255_iobase;
|
|
int i;
|
|
|
|
if (alloc_subdevices(dev, 10) < 0)
|
|
return -ENOMEM;
|
|
|
|
s = dev->subdevices + 0;
|
|
/* analog input subdevice */
|
|
dev->read_subdev = s;
|
|
s->type = COMEDI_SUBD_AI;
|
|
s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_DITHER | SDF_CMD_READ;
|
|
if (board(dev)->layout == LAYOUT_60XX)
|
|
s->subdev_flags |= SDF_COMMON | SDF_DIFF;
|
|
else if (board(dev)->layout == LAYOUT_64XX)
|
|
s->subdev_flags |= SDF_DIFF;
|
|
/* XXX Number of inputs in differential mode is ignored */
|
|
s->n_chan = board(dev)->ai_se_chans;
|
|
s->len_chanlist = 0x2000;
|
|
s->maxdata = (1 << board(dev)->ai_bits) - 1;
|
|
s->range_table = board(dev)->ai_range_table;
|
|
s->insn_read = ai_rinsn;
|
|
s->insn_config = ai_config_insn;
|
|
s->do_cmd = ai_cmd;
|
|
s->do_cmdtest = ai_cmdtest;
|
|
s->cancel = ai_cancel;
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
unsigned int i;
|
|
uint8_t data;
|
|
/* set adc to read from inputs (not internal calibration sources) */
|
|
priv(dev)->i2c_cal_range_bits = adc_src_4020_bits(4);
|
|
/* set channels to +-5 volt input ranges */
|
|
for (i = 0; i < s->n_chan; i++)
|
|
priv(dev)->i2c_cal_range_bits |= attenuate_bit(i);
|
|
data = priv(dev)->i2c_cal_range_bits;
|
|
i2c_write(dev, RANGE_CAL_I2C_ADDR, &data, sizeof(data));
|
|
}
|
|
|
|
/* analog output subdevice */
|
|
s = dev->subdevices + 1;
|
|
if (board(dev)->ao_nchan) {
|
|
s->type = COMEDI_SUBD_AO;
|
|
s->subdev_flags =
|
|
SDF_READABLE | SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE;
|
|
s->n_chan = board(dev)->ao_nchan;
|
|
s->maxdata = (1 << board(dev)->ao_bits) - 1;
|
|
s->range_table = board(dev)->ao_range_table;
|
|
s->insn_read = ao_readback_insn;
|
|
s->insn_write = ao_winsn;
|
|
if (ao_cmd_is_supported(board(dev))) {
|
|
dev->write_subdev = s;
|
|
s->do_cmdtest = ao_cmdtest;
|
|
s->do_cmd = ao_cmd;
|
|
s->len_chanlist = board(dev)->ao_nchan;
|
|
s->cancel = ao_cancel;
|
|
}
|
|
} else {
|
|
s->type = COMEDI_SUBD_UNUSED;
|
|
}
|
|
|
|
/* digital input */
|
|
s = dev->subdevices + 2;
|
|
if (board(dev)->layout == LAYOUT_64XX) {
|
|
s->type = COMEDI_SUBD_DI;
|
|
s->subdev_flags = SDF_READABLE;
|
|
s->n_chan = 4;
|
|
s->maxdata = 1;
|
|
s->range_table = &range_digital;
|
|
s->insn_bits = di_rbits;
|
|
} else
|
|
s->type = COMEDI_SUBD_UNUSED;
|
|
|
|
/* digital output */
|
|
if (board(dev)->layout == LAYOUT_64XX) {
|
|
s = dev->subdevices + 3;
|
|
s->type = COMEDI_SUBD_DO;
|
|
s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
|
|
s->n_chan = 4;
|
|
s->maxdata = 1;
|
|
s->range_table = &range_digital;
|
|
s->insn_bits = do_wbits;
|
|
} else
|
|
s->type = COMEDI_SUBD_UNUSED;
|
|
|
|
/* 8255 */
|
|
s = dev->subdevices + 4;
|
|
if (board(dev)->has_8255) {
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
dio_8255_iobase =
|
|
priv(dev)->main_iobase + I8255_4020_REG;
|
|
subdev_8255_init(dev, s, dio_callback_4020,
|
|
(unsigned long)dio_8255_iobase);
|
|
} else {
|
|
dio_8255_iobase =
|
|
priv(dev)->dio_counter_iobase + DIO_8255_OFFSET;
|
|
subdev_8255_init(dev, s, dio_callback,
|
|
(unsigned long)dio_8255_iobase);
|
|
}
|
|
} else
|
|
s->type = COMEDI_SUBD_UNUSED;
|
|
|
|
/* 8 channel dio for 60xx */
|
|
s = dev->subdevices + 5;
|
|
if (board(dev)->layout == LAYOUT_60XX) {
|
|
s->type = COMEDI_SUBD_DIO;
|
|
s->subdev_flags = SDF_WRITABLE | SDF_READABLE;
|
|
s->n_chan = 8;
|
|
s->maxdata = 1;
|
|
s->range_table = &range_digital;
|
|
s->insn_config = dio_60xx_config_insn;
|
|
s->insn_bits = dio_60xx_wbits;
|
|
} else
|
|
s->type = COMEDI_SUBD_UNUSED;
|
|
|
|
/* caldac */
|
|
s = dev->subdevices + 6;
|
|
s->type = COMEDI_SUBD_CALIB;
|
|
s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
|
|
s->n_chan = 8;
|
|
if (board(dev)->layout == LAYOUT_4020)
|
|
s->maxdata = 0xfff;
|
|
else
|
|
s->maxdata = 0xff;
|
|
s->insn_read = calib_read_insn;
|
|
s->insn_write = calib_write_insn;
|
|
for (i = 0; i < s->n_chan; i++)
|
|
caldac_write(dev, i, s->maxdata / 2);
|
|
|
|
/* 2 channel ad8402 potentiometer */
|
|
s = dev->subdevices + 7;
|
|
if (board(dev)->layout == LAYOUT_64XX) {
|
|
s->type = COMEDI_SUBD_CALIB;
|
|
s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
|
|
s->n_chan = 2;
|
|
s->insn_read = ad8402_read_insn;
|
|
s->insn_write = ad8402_write_insn;
|
|
s->maxdata = 0xff;
|
|
for (i = 0; i < s->n_chan; i++)
|
|
ad8402_write(dev, i, s->maxdata / 2);
|
|
} else
|
|
s->type = COMEDI_SUBD_UNUSED;
|
|
|
|
/* serial EEPROM, if present */
|
|
s = dev->subdevices + 8;
|
|
if (readl(priv(dev)->plx9080_iobase + PLX_CONTROL_REG) & CTL_EECHK) {
|
|
s->type = COMEDI_SUBD_MEMORY;
|
|
s->subdev_flags = SDF_READABLE | SDF_INTERNAL;
|
|
s->n_chan = 128;
|
|
s->maxdata = 0xffff;
|
|
s->insn_read = eeprom_read_insn;
|
|
} else
|
|
s->type = COMEDI_SUBD_UNUSED;
|
|
|
|
/* user counter subd XXX */
|
|
s = dev->subdevices + 9;
|
|
s->type = COMEDI_SUBD_UNUSED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void disable_plx_interrupts(struct comedi_device *dev)
|
|
{
|
|
priv(dev)->plx_intcsr_bits = 0;
|
|
writel(priv(dev)->plx_intcsr_bits,
|
|
priv(dev)->plx9080_iobase + PLX_INTRCS_REG);
|
|
}
|
|
|
|
static void init_stc_registers(struct comedi_device *dev)
|
|
{
|
|
uint16_t bits;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
|
|
/* bit should be set for 6025, although docs say boards with <= 16 chans should be cleared XXX */
|
|
if (1)
|
|
priv(dev)->adc_control1_bits |= ADC_QUEUE_CONFIG_BIT;
|
|
writew(priv(dev)->adc_control1_bits,
|
|
priv(dev)->main_iobase + ADC_CONTROL1_REG);
|
|
|
|
/* 6402/16 manual says this register must be initialized to 0xff? */
|
|
writew(0xff, priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
|
|
|
|
bits = SLOW_DAC_BIT | DMA_CH_SELECT_BIT;
|
|
if (board(dev)->layout == LAYOUT_4020)
|
|
bits |= INTERNAL_CLOCK_4020_BITS;
|
|
priv(dev)->hw_config_bits |= bits;
|
|
writew(priv(dev)->hw_config_bits,
|
|
priv(dev)->main_iobase + HW_CONFIG_REG);
|
|
|
|
writew(0, priv(dev)->main_iobase + DAQ_SYNC_REG);
|
|
writew(0, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
|
|
/* set fifos to maximum size */
|
|
priv(dev)->fifo_size_bits |= DAC_FIFO_BITS;
|
|
set_ai_fifo_segment_length(dev,
|
|
board(dev)->ai_fifo->max_segment_length);
|
|
|
|
priv(dev)->dac_control1_bits = DAC_OUTPUT_ENABLE_BIT;
|
|
priv(dev)->intr_enable_bits = /* EN_DAC_INTR_SRC_BIT | DAC_INTR_QEMPTY_BITS | */
|
|
EN_DAC_DONE_INTR_BIT | EN_DAC_UNDERRUN_BIT;
|
|
writew(priv(dev)->intr_enable_bits,
|
|
priv(dev)->main_iobase + INTR_ENABLE_REG);
|
|
|
|
disable_ai_pacing(dev);
|
|
};
|
|
|
|
int alloc_and_init_dma_members(struct comedi_device *dev)
|
|
{
|
|
int i;
|
|
|
|
/* alocate pci dma buffers */
|
|
for (i = 0; i < ai_dma_ring_count(board(dev)); i++) {
|
|
priv(dev)->ai_buffer[i] =
|
|
pci_alloc_consistent(priv(dev)->hw_dev, DMA_BUFFER_SIZE,
|
|
&priv(dev)->ai_buffer_bus_addr[i]);
|
|
if (priv(dev)->ai_buffer[i] == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
for (i = 0; i < AO_DMA_RING_COUNT; i++) {
|
|
if (ao_cmd_is_supported(board(dev))) {
|
|
priv(dev)->ao_buffer[i] =
|
|
pci_alloc_consistent(priv(dev)->hw_dev,
|
|
DMA_BUFFER_SIZE,
|
|
&priv(dev)->
|
|
ao_buffer_bus_addr[i]);
|
|
if (priv(dev)->ao_buffer[i] == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
}
|
|
/* allocate dma descriptors */
|
|
priv(dev)->ai_dma_desc =
|
|
pci_alloc_consistent(priv(dev)->hw_dev,
|
|
sizeof(struct plx_dma_desc) *
|
|
ai_dma_ring_count(board(dev)),
|
|
&priv(dev)->ai_dma_desc_bus_addr);
|
|
if (priv(dev)->ai_dma_desc == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
DEBUG_PRINT("ai dma descriptors start at bus addr 0x%x\n",
|
|
priv(dev)->ai_dma_desc_bus_addr);
|
|
if (ao_cmd_is_supported(board(dev))) {
|
|
priv(dev)->ao_dma_desc =
|
|
pci_alloc_consistent(priv(dev)->hw_dev,
|
|
sizeof(struct plx_dma_desc) *
|
|
AO_DMA_RING_COUNT,
|
|
&priv(dev)->ao_dma_desc_bus_addr);
|
|
if (priv(dev)->ao_dma_desc == NULL) {
|
|
return -ENOMEM;
|
|
}
|
|
DEBUG_PRINT("ao dma descriptors start at bus addr 0x%x\n",
|
|
priv(dev)->ao_dma_desc_bus_addr);
|
|
}
|
|
/* initialize dma descriptors */
|
|
for (i = 0; i < ai_dma_ring_count(board(dev)); i++) {
|
|
priv(dev)->ai_dma_desc[i].pci_start_addr =
|
|
cpu_to_le32(priv(dev)->ai_buffer_bus_addr[i]);
|
|
if (board(dev)->layout == LAYOUT_4020)
|
|
priv(dev)->ai_dma_desc[i].local_start_addr =
|
|
cpu_to_le32(priv(dev)->local1_iobase +
|
|
ADC_FIFO_REG);
|
|
else
|
|
priv(dev)->ai_dma_desc[i].local_start_addr =
|
|
cpu_to_le32(priv(dev)->local0_iobase +
|
|
ADC_FIFO_REG);
|
|
priv(dev)->ai_dma_desc[i].transfer_size = cpu_to_le32(0);
|
|
priv(dev)->ai_dma_desc[i].next =
|
|
cpu_to_le32((priv(dev)->ai_dma_desc_bus_addr + ((i +
|
|
1) %
|
|
ai_dma_ring_count
|
|
(board
|
|
(dev))) *
|
|
sizeof(priv(dev)->ai_dma_desc[0])) |
|
|
PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT |
|
|
PLX_XFER_LOCAL_TO_PCI);
|
|
}
|
|
if (ao_cmd_is_supported(board(dev))) {
|
|
for (i = 0; i < AO_DMA_RING_COUNT; i++) {
|
|
priv(dev)->ao_dma_desc[i].pci_start_addr =
|
|
cpu_to_le32(priv(dev)->ao_buffer_bus_addr[i]);
|
|
priv(dev)->ao_dma_desc[i].local_start_addr =
|
|
cpu_to_le32(priv(dev)->local0_iobase +
|
|
DAC_FIFO_REG);
|
|
priv(dev)->ao_dma_desc[i].transfer_size =
|
|
cpu_to_le32(0);
|
|
priv(dev)->ao_dma_desc[i].next =
|
|
cpu_to_le32((priv(dev)->ao_dma_desc_bus_addr +
|
|
((i + 1) % (AO_DMA_RING_COUNT)) *
|
|
sizeof(priv(dev)->ao_dma_desc[0])) |
|
|
PLX_DESC_IN_PCI_BIT |
|
|
PLX_INTR_TERM_COUNT);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline void warn_external_queue(struct comedi_device *dev)
|
|
{
|
|
comedi_error(dev,
|
|
"AO command and AI external channel queue cannot be used simultaneously.");
|
|
comedi_error(dev,
|
|
"Use internal AI channel queue (channels must be consecutive and use same range/aref)");
|
|
}
|
|
|
|
/*
|
|
* Attach is called by the Comedi core to configure the driver
|
|
* for a particular board.
|
|
*/
|
|
static int attach(struct comedi_device *dev, struct comedi_devconfig *it)
|
|
{
|
|
struct pci_dev *pcidev;
|
|
int index;
|
|
uint32_t local_range, local_decode;
|
|
int retval;
|
|
|
|
printk("comedi%d: cb_pcidas64\n", dev->minor);
|
|
|
|
/*
|
|
* Allocate the private structure area.
|
|
*/
|
|
if (alloc_private(dev, sizeof(struct pcidas64_private)) < 0)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* Probe the device to determine what device in the series it is.
|
|
*/
|
|
|
|
for (pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL);
|
|
pcidev != NULL;
|
|
pcidev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pcidev)) {
|
|
/* is it not a computer boards card? */
|
|
if (pcidev->vendor != PCI_VENDOR_ID_COMPUTERBOARDS)
|
|
continue;
|
|
/* loop through cards supported by this driver */
|
|
for (index = 0; index < ARRAY_SIZE(pcidas64_boards); index++) {
|
|
if (pcidas64_boards[index].device_id != pcidev->device)
|
|
continue;
|
|
/* was a particular bus/slot requested? */
|
|
if (it->options[0] || it->options[1]) {
|
|
/* are we on the wrong bus/slot? */
|
|
if (pcidev->bus->number != it->options[0] ||
|
|
PCI_SLOT(pcidev->devfn) != it->options[1]) {
|
|
continue;
|
|
}
|
|
}
|
|
priv(dev)->hw_dev = pcidev;
|
|
dev->board_ptr = pcidas64_boards + index;
|
|
break;
|
|
}
|
|
if (dev->board_ptr)
|
|
break;
|
|
}
|
|
|
|
if (dev->board_ptr == NULL) {
|
|
printk
|
|
("No supported ComputerBoards/MeasurementComputing card found\n");
|
|
return -EIO;
|
|
}
|
|
|
|
printk("Found %s on bus %i, slot %i\n", board(dev)->name,
|
|
pcidev->bus->number, PCI_SLOT(pcidev->devfn));
|
|
|
|
if (comedi_pci_enable(pcidev, driver_cb_pcidas.driver_name)) {
|
|
printk(KERN_WARNING
|
|
" failed to enable PCI device and request regions\n");
|
|
return -EIO;
|
|
}
|
|
pci_set_master(pcidev);
|
|
|
|
/* Initialize dev->board_name */
|
|
dev->board_name = board(dev)->name;
|
|
|
|
priv(dev)->plx9080_phys_iobase =
|
|
pci_resource_start(pcidev, PLX9080_BADDRINDEX);
|
|
priv(dev)->main_phys_iobase =
|
|
pci_resource_start(pcidev, MAIN_BADDRINDEX);
|
|
priv(dev)->dio_counter_phys_iobase =
|
|
pci_resource_start(pcidev, DIO_COUNTER_BADDRINDEX);
|
|
|
|
/* remap, won't work with 2.0 kernels but who cares */
|
|
priv(dev)->plx9080_iobase = ioremap(priv(dev)->plx9080_phys_iobase,
|
|
pci_resource_len(pcidev,
|
|
PLX9080_BADDRINDEX));
|
|
priv(dev)->main_iobase =
|
|
ioremap(priv(dev)->main_phys_iobase,
|
|
pci_resource_len(pcidev, MAIN_BADDRINDEX));
|
|
priv(dev)->dio_counter_iobase =
|
|
ioremap(priv(dev)->dio_counter_phys_iobase,
|
|
pci_resource_len(pcidev, DIO_COUNTER_BADDRINDEX));
|
|
|
|
if (!priv(dev)->plx9080_iobase || !priv(dev)->main_iobase
|
|
|| !priv(dev)->dio_counter_iobase) {
|
|
printk(" failed to remap io memory\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
DEBUG_PRINT(" plx9080 remapped to 0x%p\n", priv(dev)->plx9080_iobase);
|
|
DEBUG_PRINT(" main remapped to 0x%p\n", priv(dev)->main_iobase);
|
|
DEBUG_PRINT(" diocounter remapped to 0x%p\n",
|
|
priv(dev)->dio_counter_iobase);
|
|
|
|
/* figure out what local addresses are */
|
|
local_range =
|
|
readl(priv(dev)->plx9080_iobase + PLX_LAS0RNG_REG) & LRNG_MEM_MASK;
|
|
local_decode =
|
|
readl(priv(dev)->plx9080_iobase +
|
|
PLX_LAS0MAP_REG) & local_range & LMAP_MEM_MASK;
|
|
priv(dev)->local0_iobase =
|
|
((uint32_t) priv(dev)->main_phys_iobase & ~local_range) |
|
|
local_decode;
|
|
local_range =
|
|
readl(priv(dev)->plx9080_iobase + PLX_LAS1RNG_REG) & LRNG_MEM_MASK;
|
|
local_decode =
|
|
readl(priv(dev)->plx9080_iobase +
|
|
PLX_LAS1MAP_REG) & local_range & LMAP_MEM_MASK;
|
|
priv(dev)->local1_iobase =
|
|
((uint32_t) priv(dev)->dio_counter_phys_iobase & ~local_range) |
|
|
local_decode;
|
|
|
|
DEBUG_PRINT(" local 0 io addr 0x%x\n", priv(dev)->local0_iobase);
|
|
DEBUG_PRINT(" local 1 io addr 0x%x\n", priv(dev)->local1_iobase);
|
|
|
|
retval = alloc_and_init_dma_members(dev);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
priv(dev)->hw_revision =
|
|
hw_revision(dev, readw(priv(dev)->main_iobase + HW_STATUS_REG));
|
|
printk(" stc hardware revision %i\n", priv(dev)->hw_revision);
|
|
init_plx9080(dev);
|
|
init_stc_registers(dev);
|
|
/* get irq */
|
|
if (request_irq(pcidev->irq, handle_interrupt, IRQF_SHARED,
|
|
"cb_pcidas64", dev)) {
|
|
printk(" unable to allocate irq %u\n", pcidev->irq);
|
|
return -EINVAL;
|
|
}
|
|
dev->irq = pcidev->irq;
|
|
printk(" irq %u\n", dev->irq);
|
|
|
|
retval = setup_subdevices(dev);
|
|
if (retval < 0) {
|
|
return retval;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* _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 detach(struct comedi_device *dev)
|
|
{
|
|
unsigned int i;
|
|
|
|
printk("comedi%d: cb_pcidas: remove\n", dev->minor);
|
|
|
|
if (dev->irq)
|
|
free_irq(dev->irq, dev);
|
|
if (priv(dev)) {
|
|
if (priv(dev)->hw_dev) {
|
|
if (priv(dev)->plx9080_iobase) {
|
|
disable_plx_interrupts(dev);
|
|
iounmap((void *)priv(dev)->plx9080_iobase);
|
|
}
|
|
if (priv(dev)->main_iobase)
|
|
iounmap((void *)priv(dev)->main_iobase);
|
|
if (priv(dev)->dio_counter_iobase)
|
|
iounmap((void *)priv(dev)->dio_counter_iobase);
|
|
/* free pci dma buffers */
|
|
for (i = 0; i < ai_dma_ring_count(board(dev)); i++) {
|
|
if (priv(dev)->ai_buffer[i])
|
|
pci_free_consistent(priv(dev)->hw_dev,
|
|
DMA_BUFFER_SIZE,
|
|
priv(dev)->
|
|
ai_buffer[i],
|
|
priv
|
|
(dev)->ai_buffer_bus_addr
|
|
[i]);
|
|
}
|
|
for (i = 0; i < AO_DMA_RING_COUNT; i++) {
|
|
if (priv(dev)->ao_buffer[i])
|
|
pci_free_consistent(priv(dev)->hw_dev,
|
|
DMA_BUFFER_SIZE,
|
|
priv(dev)->
|
|
ao_buffer[i],
|
|
priv
|
|
(dev)->ao_buffer_bus_addr
|
|
[i]);
|
|
}
|
|
/* free dma descriptors */
|
|
if (priv(dev)->ai_dma_desc)
|
|
pci_free_consistent(priv(dev)->hw_dev,
|
|
sizeof(struct plx_dma_desc)
|
|
*
|
|
ai_dma_ring_count(board
|
|
(dev)),
|
|
priv(dev)->ai_dma_desc,
|
|
priv(dev)->
|
|
ai_dma_desc_bus_addr);
|
|
if (priv(dev)->ao_dma_desc)
|
|
pci_free_consistent(priv(dev)->hw_dev,
|
|
sizeof(struct plx_dma_desc)
|
|
* AO_DMA_RING_COUNT,
|
|
priv(dev)->ao_dma_desc,
|
|
priv(dev)->
|
|
ao_dma_desc_bus_addr);
|
|
if (priv(dev)->main_phys_iobase) {
|
|
comedi_pci_disable(priv(dev)->hw_dev);
|
|
}
|
|
pci_dev_put(priv(dev)->hw_dev);
|
|
}
|
|
}
|
|
if (dev->subdevices)
|
|
subdev_8255_cleanup(dev, dev->subdevices + 4);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
unsigned int bits = 0, n, i;
|
|
unsigned int channel, range, aref;
|
|
unsigned long flags;
|
|
static const int timeout = 100;
|
|
|
|
DEBUG_PRINT("chanspec 0x%x\n", insn->chanspec);
|
|
channel = CR_CHAN(insn->chanspec);
|
|
range = CR_RANGE(insn->chanspec);
|
|
aref = CR_AREF(insn->chanspec);
|
|
|
|
/* disable card's analog input interrupt sources and pacing */
|
|
/* 4020 generates dac done interrupts even though they are disabled */
|
|
disable_ai_pacing(dev);
|
|
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
if (insn->chanspec & CR_ALT_FILTER)
|
|
priv(dev)->adc_control1_bits |= ADC_DITHER_BIT;
|
|
else
|
|
priv(dev)->adc_control1_bits &= ~ADC_DITHER_BIT;
|
|
writew(priv(dev)->adc_control1_bits,
|
|
priv(dev)->main_iobase + ADC_CONTROL1_REG);
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
|
|
if (board(dev)->layout != LAYOUT_4020) {
|
|
/* use internal queue */
|
|
priv(dev)->hw_config_bits &= ~EXT_QUEUE_BIT;
|
|
writew(priv(dev)->hw_config_bits,
|
|
priv(dev)->main_iobase + HW_CONFIG_REG);
|
|
|
|
/* ALT_SOURCE is internal calibration reference */
|
|
if (insn->chanspec & CR_ALT_SOURCE) {
|
|
unsigned int cal_en_bit;
|
|
|
|
DEBUG_PRINT("reading calibration source\n");
|
|
if (board(dev)->layout == LAYOUT_60XX)
|
|
cal_en_bit = CAL_EN_60XX_BIT;
|
|
else
|
|
cal_en_bit = CAL_EN_64XX_BIT;
|
|
/* select internal reference source to connect to channel 0 */
|
|
writew(cal_en_bit |
|
|
adc_src_bits(priv(dev)->calibration_source),
|
|
priv(dev)->main_iobase + CALIBRATION_REG);
|
|
} else {
|
|
/* make sure internal calibration source is turned off */
|
|
writew(0, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
}
|
|
/* load internal queue */
|
|
bits = 0;
|
|
/* set gain */
|
|
bits |= ai_range_bits_6xxx(dev, CR_RANGE(insn->chanspec));
|
|
/* set single-ended / differential */
|
|
bits |= se_diff_bit_6xxx(dev, aref == AREF_DIFF);
|
|
if (aref == AREF_COMMON)
|
|
bits |= ADC_COMMON_BIT;
|
|
bits |= adc_chan_bits(channel);
|
|
/* set stop channel */
|
|
writew(adc_chan_bits(channel),
|
|
priv(dev)->main_iobase + ADC_QUEUE_HIGH_REG);
|
|
/* set start channel, and rest of settings */
|
|
writew(bits, priv(dev)->main_iobase + ADC_QUEUE_LOAD_REG);
|
|
} else {
|
|
uint8_t old_cal_range_bits = priv(dev)->i2c_cal_range_bits;
|
|
|
|
priv(dev)->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK;
|
|
if (insn->chanspec & CR_ALT_SOURCE) {
|
|
DEBUG_PRINT("reading calibration source\n");
|
|
priv(dev)->i2c_cal_range_bits |=
|
|
adc_src_4020_bits(priv(dev)->calibration_source);
|
|
} else { /* select BNC inputs */
|
|
priv(dev)->i2c_cal_range_bits |= adc_src_4020_bits(4);
|
|
}
|
|
/* select range */
|
|
if (range == 0)
|
|
priv(dev)->i2c_cal_range_bits |= attenuate_bit(channel);
|
|
else
|
|
priv(dev)->i2c_cal_range_bits &=
|
|
~attenuate_bit(channel);
|
|
/* update calibration/range i2c register only if necessary, as it is very slow */
|
|
if (old_cal_range_bits != priv(dev)->i2c_cal_range_bits) {
|
|
uint8_t i2c_data = priv(dev)->i2c_cal_range_bits;
|
|
i2c_write(dev, RANGE_CAL_I2C_ADDR, &i2c_data,
|
|
sizeof(i2c_data));
|
|
}
|
|
|
|
/* 4020 manual asks that sample interval register to be set before writing to convert register.
|
|
* Using somewhat arbitrary setting of 4 master clock ticks = 0.1 usec */
|
|
writew(0,
|
|
priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
|
|
writew(2,
|
|
priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
|
|
}
|
|
|
|
for (n = 0; n < insn->n; n++) {
|
|
|
|
/* clear adc buffer (inside loop for 4020 sake) */
|
|
writew(0, priv(dev)->main_iobase + ADC_BUFFER_CLEAR_REG);
|
|
|
|
/* trigger conversion, bits sent only matter for 4020 */
|
|
writew(adc_convert_chan_4020_bits(CR_CHAN(insn->chanspec)),
|
|
priv(dev)->main_iobase + ADC_CONVERT_REG);
|
|
|
|
/* wait for data */
|
|
for (i = 0; i < timeout; i++) {
|
|
bits = readw(priv(dev)->main_iobase + HW_STATUS_REG);
|
|
DEBUG_PRINT(" pipe bits 0x%x\n", pipe_full_bits(bits));
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
if (readw(priv(dev)->main_iobase +
|
|
ADC_WRITE_PNTR_REG))
|
|
break;
|
|
} else {
|
|
if (pipe_full_bits(bits))
|
|
break;
|
|
}
|
|
udelay(1);
|
|
}
|
|
DEBUG_PRINT(" looped %i times waiting for data\n", i);
|
|
if (i == timeout) {
|
|
comedi_error(dev, " analog input read insn timed out");
|
|
printk(" status 0x%x\n", bits);
|
|
return -ETIME;
|
|
}
|
|
if (board(dev)->layout == LAYOUT_4020)
|
|
data[n] =
|
|
readl(priv(dev)->dio_counter_iobase +
|
|
ADC_FIFO_REG) & 0xffff;
|
|
else
|
|
data[n] =
|
|
readw(priv(dev)->main_iobase + PIPE1_READ_REG);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
static int ai_config_calibration_source(struct comedi_device *dev,
|
|
unsigned int *data)
|
|
{
|
|
unsigned int source = data[1];
|
|
int num_calibration_sources;
|
|
|
|
if (board(dev)->layout == LAYOUT_60XX)
|
|
num_calibration_sources = 16;
|
|
else
|
|
num_calibration_sources = 8;
|
|
if (source >= num_calibration_sources) {
|
|
printk("invalid calibration source: %i\n", source);
|
|
return -EINVAL;
|
|
}
|
|
|
|
DEBUG_PRINT("setting calibration source to %i\n", source);
|
|
priv(dev)->calibration_source = source;
|
|
|
|
return 2;
|
|
}
|
|
|
|
static int ai_config_block_size(struct comedi_device *dev, unsigned int *data)
|
|
{
|
|
int fifo_size;
|
|
const struct hw_fifo_info *const fifo = board(dev)->ai_fifo;
|
|
unsigned int block_size, requested_block_size;
|
|
int retval;
|
|
|
|
requested_block_size = data[1];
|
|
|
|
if (requested_block_size) {
|
|
fifo_size =
|
|
requested_block_size * fifo->num_segments / bytes_in_sample;
|
|
|
|
retval = set_ai_fifo_size(dev, fifo_size);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
}
|
|
|
|
block_size = ai_fifo_size(dev) / fifo->num_segments * bytes_in_sample;
|
|
|
|
data[1] = block_size;
|
|
|
|
return 2;
|
|
}
|
|
|
|
static int ai_config_master_clock_4020(struct comedi_device *dev,
|
|
unsigned int *data)
|
|
{
|
|
unsigned int divisor = data[4];
|
|
int retval = 0;
|
|
|
|
if (divisor < 2) {
|
|
divisor = 2;
|
|
retval = -EAGAIN;
|
|
}
|
|
|
|
switch (data[1]) {
|
|
case COMEDI_EV_SCAN_BEGIN:
|
|
priv(dev)->ext_clock.divisor = divisor;
|
|
priv(dev)->ext_clock.chanspec = data[2];
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
|
|
data[4] = divisor;
|
|
|
|
return retval ? retval : 5;
|
|
}
|
|
|
|
/* XXX could add support for 60xx series */
|
|
static int ai_config_master_clock(struct comedi_device *dev, unsigned int *data)
|
|
{
|
|
|
|
switch (board(dev)->layout) {
|
|
case LAYOUT_4020:
|
|
return ai_config_master_clock_4020(dev, data);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int ai_config_insn(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
int id = data[0];
|
|
|
|
switch (id) {
|
|
case INSN_CONFIG_ALT_SOURCE:
|
|
return ai_config_calibration_source(dev, data);
|
|
break;
|
|
case INSN_CONFIG_BLOCK_SIZE:
|
|
return ai_config_block_size(dev, data);
|
|
break;
|
|
case INSN_CONFIG_TIMER_1:
|
|
return ai_config_master_clock(dev, data);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
break;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static int ai_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_cmd *cmd)
|
|
{
|
|
int err = 0;
|
|
int tmp;
|
|
unsigned int tmp_arg, tmp_arg2;
|
|
int i;
|
|
int aref;
|
|
unsigned int triggers;
|
|
|
|
/* step 1: make sure trigger sources are trivially valid */
|
|
|
|
tmp = cmd->start_src;
|
|
cmd->start_src &= TRIG_NOW | TRIG_EXT;
|
|
if (!cmd->start_src || tmp != cmd->start_src)
|
|
err++;
|
|
|
|
tmp = cmd->scan_begin_src;
|
|
triggers = TRIG_TIMER;
|
|
if (board(dev)->layout == LAYOUT_4020)
|
|
triggers |= TRIG_OTHER;
|
|
else
|
|
triggers |= TRIG_FOLLOW;
|
|
cmd->scan_begin_src &= triggers;
|
|
if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
|
|
err++;
|
|
|
|
tmp = cmd->convert_src;
|
|
triggers = TRIG_TIMER;
|
|
if (board(dev)->layout == LAYOUT_4020)
|
|
triggers |= TRIG_NOW;
|
|
else
|
|
triggers |= TRIG_EXT;
|
|
cmd->convert_src &= triggers;
|
|
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_EXT | 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 */
|
|
|
|
/* uniqueness check */
|
|
if (cmd->start_src != TRIG_NOW && cmd->start_src != TRIG_EXT)
|
|
err++;
|
|
if (cmd->scan_begin_src != TRIG_TIMER &&
|
|
cmd->scan_begin_src != TRIG_OTHER &&
|
|
cmd->scan_begin_src != TRIG_FOLLOW)
|
|
err++;
|
|
if (cmd->convert_src != TRIG_TIMER &&
|
|
cmd->convert_src != TRIG_EXT && cmd->convert_src != TRIG_NOW)
|
|
err++;
|
|
if (cmd->stop_src != TRIG_COUNT &&
|
|
cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT)
|
|
err++;
|
|
|
|
/* compatibility check */
|
|
if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER)
|
|
err++;
|
|
if (cmd->stop_src != TRIG_COUNT &&
|
|
cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT)
|
|
err++;
|
|
|
|
if (err)
|
|
return 2;
|
|
|
|
/* step 3: make sure arguments are trivially compatible */
|
|
|
|
if (cmd->convert_src == TRIG_TIMER) {
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
if (cmd->convert_arg) {
|
|
cmd->convert_arg = 0;
|
|
err++;
|
|
}
|
|
} else {
|
|
if (cmd->convert_arg < board(dev)->ai_speed) {
|
|
cmd->convert_arg = board(dev)->ai_speed;
|
|
err++;
|
|
}
|
|
if (cmd->scan_begin_src == TRIG_TIMER) {
|
|
/* if scans are timed faster than conversion rate allows */
|
|
if (cmd->convert_arg * cmd->chanlist_len >
|
|
cmd->scan_begin_arg) {
|
|
cmd->scan_begin_arg =
|
|
cmd->convert_arg *
|
|
cmd->chanlist_len;
|
|
err++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cmd->chanlist_len) {
|
|
cmd->chanlist_len = 1;
|
|
err++;
|
|
}
|
|
if (cmd->scan_end_arg != cmd->chanlist_len) {
|
|
cmd->scan_end_arg = cmd->chanlist_len;
|
|
err++;
|
|
}
|
|
|
|
switch (cmd->stop_src) {
|
|
case TRIG_EXT:
|
|
break;
|
|
case TRIG_COUNT:
|
|
if (!cmd->stop_arg) {
|
|
cmd->stop_arg = 1;
|
|
err++;
|
|
}
|
|
break;
|
|
case TRIG_NONE:
|
|
if (cmd->stop_arg != 0) {
|
|
cmd->stop_arg = 0;
|
|
err++;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (err)
|
|
return 3;
|
|
|
|
/* step 4: fix up any arguments */
|
|
|
|
if (cmd->convert_src == TRIG_TIMER) {
|
|
tmp_arg = cmd->convert_arg;
|
|
tmp_arg2 = cmd->scan_begin_arg;
|
|
check_adc_timing(dev, cmd);
|
|
if (tmp_arg != cmd->convert_arg)
|
|
err++;
|
|
if (tmp_arg2 != cmd->scan_begin_arg)
|
|
err++;
|
|
}
|
|
|
|
if (err)
|
|
return 4;
|
|
|
|
/* make sure user is doesn't change analog reference mid chanlist */
|
|
if (cmd->chanlist) {
|
|
aref = CR_AREF(cmd->chanlist[0]);
|
|
for (i = 1; i < cmd->chanlist_len; i++) {
|
|
if (aref != CR_AREF(cmd->chanlist[i])) {
|
|
comedi_error(dev,
|
|
"all elements in chanlist must use the same analog reference");
|
|
err++;
|
|
break;
|
|
}
|
|
}
|
|
/* check 4020 chanlist */
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
unsigned int first_channel = CR_CHAN(cmd->chanlist[0]);
|
|
for (i = 1; i < cmd->chanlist_len; i++) {
|
|
if (CR_CHAN(cmd->chanlist[i]) !=
|
|
first_channel + i) {
|
|
comedi_error(dev,
|
|
"chanlist must use consecutive channels");
|
|
err++;
|
|
break;
|
|
}
|
|
}
|
|
if (cmd->chanlist_len == 3) {
|
|
comedi_error(dev,
|
|
"chanlist cannot be 3 channels long, use 1, 2, or 4 channels");
|
|
err++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (err)
|
|
return 5;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int use_hw_sample_counter(struct comedi_cmd *cmd)
|
|
{
|
|
/* disable for now until I work out a race */
|
|
return 0;
|
|
|
|
if (cmd->stop_src == TRIG_COUNT && cmd->stop_arg <= max_counter_value)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static void setup_sample_counters(struct comedi_device *dev,
|
|
struct comedi_cmd *cmd)
|
|
{
|
|
if (cmd->stop_src == TRIG_COUNT) {
|
|
/* set software count */
|
|
priv(dev)->ai_count = cmd->stop_arg * cmd->chanlist_len;
|
|
}
|
|
/* load hardware conversion counter */
|
|
if (use_hw_sample_counter(cmd)) {
|
|
writew(cmd->stop_arg & 0xffff,
|
|
priv(dev)->main_iobase + ADC_COUNT_LOWER_REG);
|
|
writew((cmd->stop_arg >> 16) & 0xff,
|
|
priv(dev)->main_iobase + ADC_COUNT_UPPER_REG);
|
|
} else {
|
|
writew(1, priv(dev)->main_iobase + ADC_COUNT_LOWER_REG);
|
|
}
|
|
}
|
|
|
|
static inline unsigned int dma_transfer_size(struct comedi_device *dev)
|
|
{
|
|
unsigned int num_samples;
|
|
|
|
num_samples =
|
|
priv(dev)->ai_fifo_segment_length *
|
|
board(dev)->ai_fifo->sample_packing_ratio;
|
|
if (num_samples > DMA_BUFFER_SIZE / sizeof(uint16_t))
|
|
num_samples = DMA_BUFFER_SIZE / sizeof(uint16_t);
|
|
|
|
return num_samples;
|
|
}
|
|
|
|
static void disable_ai_pacing(struct comedi_device *dev)
|
|
{
|
|
unsigned long flags;
|
|
|
|
disable_ai_interrupts(dev);
|
|
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
priv(dev)->adc_control1_bits &= ~ADC_SW_GATE_BIT;
|
|
writew(priv(dev)->adc_control1_bits,
|
|
priv(dev)->main_iobase + ADC_CONTROL1_REG);
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
|
|
/* disable pacing, triggering, etc */
|
|
writew(ADC_DMA_DISABLE_BIT | ADC_SOFT_GATE_BITS | ADC_GATE_LEVEL_BIT,
|
|
priv(dev)->main_iobase + ADC_CONTROL0_REG);
|
|
}
|
|
|
|
static void disable_ai_interrupts(struct comedi_device *dev)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
priv(dev)->intr_enable_bits &=
|
|
~EN_ADC_INTR_SRC_BIT & ~EN_ADC_DONE_INTR_BIT &
|
|
~EN_ADC_ACTIVE_INTR_BIT & ~EN_ADC_STOP_INTR_BIT &
|
|
~EN_ADC_OVERRUN_BIT & ~ADC_INTR_SRC_MASK;
|
|
writew(priv(dev)->intr_enable_bits,
|
|
priv(dev)->main_iobase + INTR_ENABLE_REG);
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
|
|
DEBUG_PRINT("intr enable bits 0x%x\n", priv(dev)->intr_enable_bits);
|
|
}
|
|
|
|
static void enable_ai_interrupts(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
uint32_t bits;
|
|
unsigned long flags;
|
|
|
|
bits = EN_ADC_OVERRUN_BIT | EN_ADC_DONE_INTR_BIT |
|
|
EN_ADC_ACTIVE_INTR_BIT | EN_ADC_STOP_INTR_BIT;
|
|
/* Use pio transfer and interrupt on end of conversion if TRIG_WAKE_EOS flag is set. */
|
|
if (cmd->flags & TRIG_WAKE_EOS) {
|
|
/* 4020 doesn't support pio transfers except for fifo dregs */
|
|
if (board(dev)->layout != LAYOUT_4020)
|
|
bits |= ADC_INTR_EOSCAN_BITS | EN_ADC_INTR_SRC_BIT;
|
|
}
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
priv(dev)->intr_enable_bits |= bits;
|
|
writew(priv(dev)->intr_enable_bits,
|
|
priv(dev)->main_iobase + INTR_ENABLE_REG);
|
|
DEBUG_PRINT("intr enable bits 0x%x\n", priv(dev)->intr_enable_bits);
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
}
|
|
|
|
static uint32_t ai_convert_counter_6xxx(const struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
/* supposed to load counter with desired divisor minus 3 */
|
|
return cmd->convert_arg / TIMER_BASE - 3;
|
|
}
|
|
|
|
static uint32_t ai_scan_counter_6xxx(struct comedi_device *dev,
|
|
struct comedi_cmd *cmd)
|
|
{
|
|
uint32_t count;
|
|
/* figure out how long we need to delay at end of scan */
|
|
switch (cmd->scan_begin_src) {
|
|
case TRIG_TIMER:
|
|
count = (cmd->scan_begin_arg -
|
|
(cmd->convert_arg * (cmd->chanlist_len - 1)))
|
|
/ TIMER_BASE;
|
|
break;
|
|
case TRIG_FOLLOW:
|
|
count = cmd->convert_arg / TIMER_BASE;
|
|
break;
|
|
default:
|
|
return 0;
|
|
break;
|
|
}
|
|
return count - 3;
|
|
}
|
|
|
|
static uint32_t ai_convert_counter_4020(struct comedi_device *dev,
|
|
struct comedi_cmd *cmd)
|
|
{
|
|
unsigned int divisor;
|
|
|
|
switch (cmd->scan_begin_src) {
|
|
case TRIG_TIMER:
|
|
divisor = cmd->scan_begin_arg / TIMER_BASE;
|
|
break;
|
|
case TRIG_OTHER:
|
|
divisor = priv(dev)->ext_clock.divisor;
|
|
break;
|
|
default: /* should never happen */
|
|
comedi_error(dev, "bug! failed to set ai pacing!");
|
|
divisor = 1000;
|
|
break;
|
|
}
|
|
|
|
/* supposed to load counter with desired divisor minus 2 for 4020 */
|
|
return divisor - 2;
|
|
}
|
|
|
|
static void select_master_clock_4020(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
/* select internal/external master clock */
|
|
priv(dev)->hw_config_bits &= ~MASTER_CLOCK_4020_MASK;
|
|
if (cmd->scan_begin_src == TRIG_OTHER) {
|
|
int chanspec = priv(dev)->ext_clock.chanspec;
|
|
|
|
if (CR_CHAN(chanspec))
|
|
priv(dev)->hw_config_bits |= BNC_CLOCK_4020_BITS;
|
|
else
|
|
priv(dev)->hw_config_bits |= EXT_CLOCK_4020_BITS;
|
|
} else {
|
|
priv(dev)->hw_config_bits |= INTERNAL_CLOCK_4020_BITS;
|
|
}
|
|
writew(priv(dev)->hw_config_bits,
|
|
priv(dev)->main_iobase + HW_CONFIG_REG);
|
|
}
|
|
|
|
static void select_master_clock(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
switch (board(dev)->layout) {
|
|
case LAYOUT_4020:
|
|
select_master_clock_4020(dev, cmd);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static inline void dma_start_sync(struct comedi_device *dev,
|
|
unsigned int channel)
|
|
{
|
|
unsigned long flags;
|
|
|
|
/* spinlock for plx dma control/status reg */
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
if (channel)
|
|
writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT |
|
|
PLX_CLEAR_DMA_INTR_BIT,
|
|
priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG);
|
|
else
|
|
writeb(PLX_DMA_EN_BIT | PLX_DMA_START_BIT |
|
|
PLX_CLEAR_DMA_INTR_BIT,
|
|
priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG);
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
}
|
|
|
|
static void set_ai_pacing(struct comedi_device *dev, struct comedi_cmd *cmd)
|
|
{
|
|
uint32_t convert_counter = 0, scan_counter = 0;
|
|
|
|
check_adc_timing(dev, cmd);
|
|
|
|
select_master_clock(dev, cmd);
|
|
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
convert_counter = ai_convert_counter_4020(dev, cmd);
|
|
} else {
|
|
convert_counter = ai_convert_counter_6xxx(dev, cmd);
|
|
scan_counter = ai_scan_counter_6xxx(dev, cmd);
|
|
}
|
|
|
|
/* load lower 16 bits of convert interval */
|
|
writew(convert_counter & 0xffff,
|
|
priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_LOWER_REG);
|
|
DEBUG_PRINT("convert counter 0x%x\n", convert_counter);
|
|
/* load upper 8 bits of convert interval */
|
|
writew((convert_counter >> 16) & 0xff,
|
|
priv(dev)->main_iobase + ADC_SAMPLE_INTERVAL_UPPER_REG);
|
|
/* load lower 16 bits of scan delay */
|
|
writew(scan_counter & 0xffff,
|
|
priv(dev)->main_iobase + ADC_DELAY_INTERVAL_LOWER_REG);
|
|
/* load upper 8 bits of scan delay */
|
|
writew((scan_counter >> 16) & 0xff,
|
|
priv(dev)->main_iobase + ADC_DELAY_INTERVAL_UPPER_REG);
|
|
DEBUG_PRINT("scan counter 0x%x\n", scan_counter);
|
|
}
|
|
|
|
static int use_internal_queue_6xxx(const struct comedi_cmd *cmd)
|
|
{
|
|
int i;
|
|
for (i = 0; i + 1 < cmd->chanlist_len; i++) {
|
|
if (CR_CHAN(cmd->chanlist[i + 1]) !=
|
|
CR_CHAN(cmd->chanlist[i]) + 1)
|
|
return 0;
|
|
if (CR_RANGE(cmd->chanlist[i + 1]) !=
|
|
CR_RANGE(cmd->chanlist[i]))
|
|
return 0;
|
|
if (CR_AREF(cmd->chanlist[i + 1]) != CR_AREF(cmd->chanlist[i]))
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int setup_channel_queue(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
unsigned short bits;
|
|
int i;
|
|
|
|
if (board(dev)->layout != LAYOUT_4020) {
|
|
if (use_internal_queue_6xxx(cmd)) {
|
|
priv(dev)->hw_config_bits &= ~EXT_QUEUE_BIT;
|
|
writew(priv(dev)->hw_config_bits,
|
|
priv(dev)->main_iobase + HW_CONFIG_REG);
|
|
bits = 0;
|
|
/* set channel */
|
|
bits |= adc_chan_bits(CR_CHAN(cmd->chanlist[0]));
|
|
/* set gain */
|
|
bits |= ai_range_bits_6xxx(dev,
|
|
CR_RANGE(cmd->chanlist[0]));
|
|
/* set single-ended / differential */
|
|
bits |= se_diff_bit_6xxx(dev,
|
|
CR_AREF(cmd->chanlist[0]) ==
|
|
AREF_DIFF);
|
|
if (CR_AREF(cmd->chanlist[0]) == AREF_COMMON)
|
|
bits |= ADC_COMMON_BIT;
|
|
/* set stop channel */
|
|
writew(adc_chan_bits
|
|
(CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1])),
|
|
priv(dev)->main_iobase + ADC_QUEUE_HIGH_REG);
|
|
/* set start channel, and rest of settings */
|
|
writew(bits,
|
|
priv(dev)->main_iobase + ADC_QUEUE_LOAD_REG);
|
|
} else {
|
|
/* use external queue */
|
|
if (dev->write_subdev && dev->write_subdev->busy) {
|
|
warn_external_queue(dev);
|
|
return -EBUSY;
|
|
}
|
|
priv(dev)->hw_config_bits |= EXT_QUEUE_BIT;
|
|
writew(priv(dev)->hw_config_bits,
|
|
priv(dev)->main_iobase + HW_CONFIG_REG);
|
|
/* clear DAC buffer to prevent weird interactions */
|
|
writew(0,
|
|
priv(dev)->main_iobase + DAC_BUFFER_CLEAR_REG);
|
|
/* clear queue pointer */
|
|
writew(0, priv(dev)->main_iobase + ADC_QUEUE_CLEAR_REG);
|
|
/* load external queue */
|
|
for (i = 0; i < cmd->chanlist_len; i++) {
|
|
bits = 0;
|
|
/* set channel */
|
|
bits |=
|
|
adc_chan_bits(CR_CHAN(cmd->chanlist[i]));
|
|
/* set gain */
|
|
bits |= ai_range_bits_6xxx(dev,
|
|
CR_RANGE(cmd->
|
|
chanlist
|
|
[i]));
|
|
/* set single-ended / differential */
|
|
bits |= se_diff_bit_6xxx(dev,
|
|
CR_AREF(cmd->
|
|
chanlist[i]) ==
|
|
AREF_DIFF);
|
|
if (CR_AREF(cmd->chanlist[i]) == AREF_COMMON)
|
|
bits |= ADC_COMMON_BIT;
|
|
/* mark end of queue */
|
|
if (i == cmd->chanlist_len - 1)
|
|
bits |= QUEUE_EOSCAN_BIT |
|
|
QUEUE_EOSEQ_BIT;
|
|
writew(bits,
|
|
priv(dev)->main_iobase +
|
|
ADC_QUEUE_FIFO_REG);
|
|
DEBUG_PRINT
|
|
("wrote 0x%x to external channel queue\n",
|
|
bits);
|
|
}
|
|
/* doing a queue clear is not specified in board docs,
|
|
* but required for reliable operation */
|
|
writew(0, priv(dev)->main_iobase + ADC_QUEUE_CLEAR_REG);
|
|
/* prime queue holding register */
|
|
writew(0, priv(dev)->main_iobase + ADC_QUEUE_LOAD_REG);
|
|
}
|
|
} else {
|
|
unsigned short old_cal_range_bits =
|
|
priv(dev)->i2c_cal_range_bits;
|
|
|
|
priv(dev)->i2c_cal_range_bits &= ~ADC_SRC_4020_MASK;
|
|
/* select BNC inputs */
|
|
priv(dev)->i2c_cal_range_bits |= adc_src_4020_bits(4);
|
|
/* select ranges */
|
|
for (i = 0; i < cmd->chanlist_len; i++) {
|
|
unsigned int channel = CR_CHAN(cmd->chanlist[i]);
|
|
unsigned int range = CR_RANGE(cmd->chanlist[i]);
|
|
|
|
if (range == 0)
|
|
priv(dev)->i2c_cal_range_bits |=
|
|
attenuate_bit(channel);
|
|
else
|
|
priv(dev)->i2c_cal_range_bits &=
|
|
~attenuate_bit(channel);
|
|
}
|
|
/* update calibration/range i2c register only if necessary, as it is very slow */
|
|
if (old_cal_range_bits != priv(dev)->i2c_cal_range_bits) {
|
|
uint8_t i2c_data = priv(dev)->i2c_cal_range_bits;
|
|
i2c_write(dev, RANGE_CAL_I2C_ADDR, &i2c_data,
|
|
sizeof(i2c_data));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline void load_first_dma_descriptor(struct comedi_device *dev,
|
|
unsigned int dma_channel,
|
|
unsigned int descriptor_bits)
|
|
{
|
|
/* The transfer size, pci address, and local address registers
|
|
* are supposedly unused during chained dma,
|
|
* but I have found that left over values from last operation
|
|
* occasionally cause problems with transfer of first dma
|
|
* block. Initializing them to zero seems to fix the problem. */
|
|
if (dma_channel) {
|
|
writel(0,
|
|
priv(dev)->plx9080_iobase + PLX_DMA1_TRANSFER_SIZE_REG);
|
|
writel(0, priv(dev)->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG);
|
|
writel(0,
|
|
priv(dev)->plx9080_iobase + PLX_DMA1_LOCAL_ADDRESS_REG);
|
|
writel(descriptor_bits,
|
|
priv(dev)->plx9080_iobase + PLX_DMA1_DESCRIPTOR_REG);
|
|
} else {
|
|
writel(0,
|
|
priv(dev)->plx9080_iobase + PLX_DMA0_TRANSFER_SIZE_REG);
|
|
writel(0, priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG);
|
|
writel(0,
|
|
priv(dev)->plx9080_iobase + PLX_DMA0_LOCAL_ADDRESS_REG);
|
|
writel(descriptor_bits,
|
|
priv(dev)->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG);
|
|
}
|
|
}
|
|
|
|
static int ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
|
|
{
|
|
struct comedi_async *async = s->async;
|
|
struct comedi_cmd *cmd = &async->cmd;
|
|
uint32_t bits;
|
|
unsigned int i;
|
|
unsigned long flags;
|
|
int retval;
|
|
|
|
disable_ai_pacing(dev);
|
|
abort_dma(dev, 1);
|
|
|
|
retval = setup_channel_queue(dev, cmd);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
/* make sure internal calibration source is turned off */
|
|
writew(0, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
|
|
set_ai_pacing(dev, cmd);
|
|
|
|
setup_sample_counters(dev, cmd);
|
|
|
|
enable_ai_interrupts(dev, cmd);
|
|
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
/* set mode, allow conversions through software gate */
|
|
priv(dev)->adc_control1_bits |= ADC_SW_GATE_BIT;
|
|
priv(dev)->adc_control1_bits &= ~ADC_DITHER_BIT;
|
|
if (board(dev)->layout != LAYOUT_4020) {
|
|
priv(dev)->adc_control1_bits &= ~ADC_MODE_MASK;
|
|
if (cmd->convert_src == TRIG_EXT)
|
|
priv(dev)->adc_control1_bits |= adc_mode_bits(13); /* good old mode 13 */
|
|
else
|
|
priv(dev)->adc_control1_bits |= adc_mode_bits(8); /* mode 8. What else could you need? */
|
|
} else {
|
|
priv(dev)->adc_control1_bits &= ~CHANNEL_MODE_4020_MASK;
|
|
if (cmd->chanlist_len == 4)
|
|
priv(dev)->adc_control1_bits |= FOUR_CHANNEL_4020_BITS;
|
|
else if (cmd->chanlist_len == 2)
|
|
priv(dev)->adc_control1_bits |= TWO_CHANNEL_4020_BITS;
|
|
priv(dev)->adc_control1_bits &= ~ADC_LO_CHANNEL_4020_MASK;
|
|
priv(dev)->adc_control1_bits |=
|
|
adc_lo_chan_4020_bits(CR_CHAN(cmd->chanlist[0]));
|
|
priv(dev)->adc_control1_bits &= ~ADC_HI_CHANNEL_4020_MASK;
|
|
priv(dev)->adc_control1_bits |=
|
|
adc_hi_chan_4020_bits(CR_CHAN
|
|
(cmd->
|
|
chanlist[cmd->chanlist_len - 1]));
|
|
}
|
|
writew(priv(dev)->adc_control1_bits,
|
|
priv(dev)->main_iobase + ADC_CONTROL1_REG);
|
|
DEBUG_PRINT("control1 bits 0x%x\n", priv(dev)->adc_control1_bits);
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
|
|
/* clear adc buffer */
|
|
writew(0, priv(dev)->main_iobase + ADC_BUFFER_CLEAR_REG);
|
|
|
|
if ((cmd->flags & TRIG_WAKE_EOS) == 0 ||
|
|
board(dev)->layout == LAYOUT_4020) {
|
|
priv(dev)->ai_dma_index = 0;
|
|
|
|
/* set dma transfer size */
|
|
for (i = 0; i < ai_dma_ring_count(board(dev)); i++)
|
|
priv(dev)->ai_dma_desc[i].transfer_size =
|
|
cpu_to_le32(dma_transfer_size(dev) *
|
|
sizeof(uint16_t));
|
|
|
|
/* give location of first dma descriptor */
|
|
load_first_dma_descriptor(dev, 1,
|
|
priv(dev)->ai_dma_desc_bus_addr |
|
|
PLX_DESC_IN_PCI_BIT |
|
|
PLX_INTR_TERM_COUNT |
|
|
PLX_XFER_LOCAL_TO_PCI);
|
|
|
|
dma_start_sync(dev, 1);
|
|
}
|
|
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
/* set source for external triggers */
|
|
bits = 0;
|
|
if (cmd->start_src == TRIG_EXT && CR_CHAN(cmd->start_arg))
|
|
bits |= EXT_START_TRIG_BNC_BIT;
|
|
if (cmd->stop_src == TRIG_EXT && CR_CHAN(cmd->stop_arg))
|
|
bits |= EXT_STOP_TRIG_BNC_BIT;
|
|
writew(bits, priv(dev)->main_iobase + DAQ_ATRIG_LOW_4020_REG);
|
|
}
|
|
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
|
|
/* enable pacing, triggering, etc */
|
|
bits = ADC_ENABLE_BIT | ADC_SOFT_GATE_BITS | ADC_GATE_LEVEL_BIT;
|
|
if (cmd->flags & TRIG_WAKE_EOS)
|
|
bits |= ADC_DMA_DISABLE_BIT;
|
|
/* set start trigger */
|
|
if (cmd->start_src == TRIG_EXT) {
|
|
bits |= ADC_START_TRIG_EXT_BITS;
|
|
if (cmd->start_arg & CR_INVERT)
|
|
bits |= ADC_START_TRIG_FALLING_BIT;
|
|
} else if (cmd->start_src == TRIG_NOW)
|
|
bits |= ADC_START_TRIG_SOFT_BITS;
|
|
if (use_hw_sample_counter(cmd))
|
|
bits |= ADC_SAMPLE_COUNTER_EN_BIT;
|
|
writew(bits, priv(dev)->main_iobase + ADC_CONTROL0_REG);
|
|
DEBUG_PRINT("control0 bits 0x%x\n", bits);
|
|
|
|
priv(dev)->ai_cmd_running = 1;
|
|
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
|
|
/* start aquisition */
|
|
if (cmd->start_src == TRIG_NOW) {
|
|
writew(0, priv(dev)->main_iobase + ADC_START_REG);
|
|
DEBUG_PRINT("soft trig\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* read num_samples from 16 bit wide ai fifo */
|
|
static void pio_drain_ai_fifo_16(struct comedi_device *dev)
|
|
{
|
|
struct comedi_subdevice *s = dev->read_subdev;
|
|
struct comedi_async *async = s->async;
|
|
struct comedi_cmd *cmd = &async->cmd;
|
|
unsigned int i;
|
|
uint16_t prepost_bits;
|
|
int read_segment, read_index, write_segment, write_index;
|
|
int num_samples;
|
|
|
|
do {
|
|
/* get least significant 15 bits */
|
|
read_index =
|
|
readw(priv(dev)->main_iobase + ADC_READ_PNTR_REG) & 0x7fff;
|
|
write_index =
|
|
readw(priv(dev)->main_iobase + ADC_WRITE_PNTR_REG) & 0x7fff;
|
|
/* Get most significant bits (grey code). Different boards use different code
|
|
* so use a scheme that doesn't depend on encoding. This read must
|
|
* occur after reading least significant 15 bits to avoid race
|
|
* with fifo switching to next segment. */
|
|
prepost_bits = readw(priv(dev)->main_iobase + PREPOST_REG);
|
|
|
|
/* if read and write pointers are not on the same fifo segment, read to the
|
|
* end of the read segment */
|
|
read_segment = adc_upper_read_ptr_code(prepost_bits);
|
|
write_segment = adc_upper_write_ptr_code(prepost_bits);
|
|
|
|
DEBUG_PRINT(" rd seg %i, wrt seg %i, rd idx %i, wrt idx %i\n",
|
|
read_segment, write_segment, read_index,
|
|
write_index);
|
|
|
|
if (read_segment != write_segment)
|
|
num_samples =
|
|
priv(dev)->ai_fifo_segment_length - read_index;
|
|
else
|
|
num_samples = write_index - read_index;
|
|
|
|
if (cmd->stop_src == TRIG_COUNT) {
|
|
if (priv(dev)->ai_count == 0)
|
|
break;
|
|
if (num_samples > priv(dev)->ai_count) {
|
|
num_samples = priv(dev)->ai_count;
|
|
}
|
|
priv(dev)->ai_count -= num_samples;
|
|
}
|
|
|
|
if (num_samples < 0) {
|
|
printk(" cb_pcidas64: bug! num_samples < 0\n");
|
|
break;
|
|
}
|
|
|
|
DEBUG_PRINT(" read %i samples from fifo\n", num_samples);
|
|
|
|
for (i = 0; i < num_samples; i++) {
|
|
cfc_write_to_buffer(s,
|
|
readw(priv(dev)->main_iobase +
|
|
ADC_FIFO_REG));
|
|
}
|
|
|
|
} while (read_segment != write_segment);
|
|
}
|
|
|
|
/* Read from 32 bit wide ai fifo of 4020 - deal with insane grey coding of pointers.
|
|
* The pci-4020 hardware only supports
|
|
* dma transfers (it only supports the use of pio for draining the last remaining
|
|
* points from the fifo when a data aquisition operation has completed).
|
|
*/
|
|
static void pio_drain_ai_fifo_32(struct comedi_device *dev)
|
|
{
|
|
struct comedi_subdevice *s = dev->read_subdev;
|
|
struct comedi_async *async = s->async;
|
|
struct comedi_cmd *cmd = &async->cmd;
|
|
unsigned int i;
|
|
unsigned int max_transfer = 100000;
|
|
uint32_t fifo_data;
|
|
int write_code =
|
|
readw(priv(dev)->main_iobase + ADC_WRITE_PNTR_REG) & 0x7fff;
|
|
int read_code =
|
|
readw(priv(dev)->main_iobase + ADC_READ_PNTR_REG) & 0x7fff;
|
|
|
|
if (cmd->stop_src == TRIG_COUNT) {
|
|
if (max_transfer > priv(dev)->ai_count) {
|
|
max_transfer = priv(dev)->ai_count;
|
|
}
|
|
}
|
|
for (i = 0; read_code != write_code && i < max_transfer;) {
|
|
fifo_data = readl(priv(dev)->dio_counter_iobase + ADC_FIFO_REG);
|
|
cfc_write_to_buffer(s, fifo_data & 0xffff);
|
|
i++;
|
|
if (i < max_transfer) {
|
|
cfc_write_to_buffer(s, (fifo_data >> 16) & 0xffff);
|
|
i++;
|
|
}
|
|
read_code =
|
|
readw(priv(dev)->main_iobase + ADC_READ_PNTR_REG) & 0x7fff;
|
|
}
|
|
priv(dev)->ai_count -= i;
|
|
}
|
|
|
|
/* empty fifo */
|
|
static void pio_drain_ai_fifo(struct comedi_device *dev)
|
|
{
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
pio_drain_ai_fifo_32(dev);
|
|
} else
|
|
pio_drain_ai_fifo_16(dev);
|
|
}
|
|
|
|
static void drain_dma_buffers(struct comedi_device *dev, unsigned int channel)
|
|
{
|
|
struct comedi_async *async = dev->read_subdev->async;
|
|
uint32_t next_transfer_addr;
|
|
int j;
|
|
int num_samples = 0;
|
|
void *pci_addr_reg;
|
|
|
|
if (channel)
|
|
pci_addr_reg =
|
|
priv(dev)->plx9080_iobase + PLX_DMA1_PCI_ADDRESS_REG;
|
|
else
|
|
pci_addr_reg =
|
|
priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG;
|
|
|
|
/* loop until we have read all the full buffers */
|
|
for (j = 0, next_transfer_addr = readl(pci_addr_reg);
|
|
(next_transfer_addr <
|
|
priv(dev)->ai_buffer_bus_addr[priv(dev)->ai_dma_index]
|
|
|| next_transfer_addr >=
|
|
priv(dev)->ai_buffer_bus_addr[priv(dev)->ai_dma_index] +
|
|
DMA_BUFFER_SIZE) && j < ai_dma_ring_count(board(dev)); j++) {
|
|
/* transfer data from dma buffer to comedi buffer */
|
|
num_samples = dma_transfer_size(dev);
|
|
if (async->cmd.stop_src == TRIG_COUNT) {
|
|
if (num_samples > priv(dev)->ai_count)
|
|
num_samples = priv(dev)->ai_count;
|
|
priv(dev)->ai_count -= num_samples;
|
|
}
|
|
cfc_write_array_to_buffer(dev->read_subdev,
|
|
priv(dev)->ai_buffer[priv(dev)->
|
|
ai_dma_index],
|
|
num_samples * sizeof(uint16_t));
|
|
priv(dev)->ai_dma_index =
|
|
(priv(dev)->ai_dma_index +
|
|
1) % ai_dma_ring_count(board(dev));
|
|
|
|
DEBUG_PRINT("next buffer addr 0x%lx\n",
|
|
(unsigned long)priv(dev)->
|
|
ai_buffer_bus_addr[priv(dev)->ai_dma_index]);
|
|
DEBUG_PRINT("pci addr reg 0x%x\n", next_transfer_addr);
|
|
}
|
|
/* XXX check for dma ring buffer overrun (use end-of-chain bit to mark last
|
|
* unused buffer) */
|
|
}
|
|
|
|
void handle_ai_interrupt(struct comedi_device *dev, unsigned short status,
|
|
unsigned int plx_status)
|
|
{
|
|
struct comedi_subdevice *s = dev->read_subdev;
|
|
struct comedi_async *async = s->async;
|
|
struct comedi_cmd *cmd = &async->cmd;
|
|
uint8_t dma1_status;
|
|
unsigned long flags;
|
|
|
|
/* check for fifo overrun */
|
|
if (status & ADC_OVERRUN_BIT) {
|
|
comedi_error(dev, "fifo overrun");
|
|
async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR;
|
|
}
|
|
/* spin lock makes sure noone else changes plx dma control reg */
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
dma1_status = readb(priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG);
|
|
if (plx_status & ICS_DMA1_A) { /* dma chan 1 interrupt */
|
|
writeb((dma1_status & PLX_DMA_EN_BIT) | PLX_CLEAR_DMA_INTR_BIT,
|
|
priv(dev)->plx9080_iobase + PLX_DMA1_CS_REG);
|
|
DEBUG_PRINT("dma1 status 0x%x\n", dma1_status);
|
|
|
|
if (dma1_status & PLX_DMA_EN_BIT) {
|
|
drain_dma_buffers(dev, 1);
|
|
}
|
|
DEBUG_PRINT(" cleared dma ch1 interrupt\n");
|
|
}
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
|
|
if (status & ADC_DONE_BIT)
|
|
DEBUG_PRINT("adc done interrupt\n");
|
|
|
|
/* drain fifo with pio */
|
|
if ((status & ADC_DONE_BIT) ||
|
|
((cmd->flags & TRIG_WAKE_EOS) &&
|
|
(status & ADC_INTR_PENDING_BIT) &&
|
|
(board(dev)->layout != LAYOUT_4020))) {
|
|
DEBUG_PRINT("pio fifo drain\n");
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
if (priv(dev)->ai_cmd_running) {
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
pio_drain_ai_fifo(dev);
|
|
} else
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
}
|
|
/* if we are have all the data, then quit */
|
|
if ((cmd->stop_src == TRIG_COUNT && priv(dev)->ai_count <= 0) ||
|
|
(cmd->stop_src == TRIG_EXT && (status & ADC_STOP_BIT))) {
|
|
async->events |= COMEDI_CB_EOA;
|
|
}
|
|
|
|
cfc_handle_events(dev, s);
|
|
}
|
|
|
|
static inline unsigned int prev_ao_dma_index(struct comedi_device *dev)
|
|
{
|
|
unsigned int buffer_index;
|
|
|
|
if (priv(dev)->ao_dma_index == 0)
|
|
buffer_index = AO_DMA_RING_COUNT - 1;
|
|
else
|
|
buffer_index = priv(dev)->ao_dma_index - 1;
|
|
return buffer_index;
|
|
}
|
|
|
|
static int last_ao_dma_load_completed(struct comedi_device *dev)
|
|
{
|
|
unsigned int buffer_index;
|
|
unsigned int transfer_address;
|
|
unsigned short dma_status;
|
|
|
|
buffer_index = prev_ao_dma_index(dev);
|
|
dma_status = readb(priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG);
|
|
if ((dma_status & PLX_DMA_DONE_BIT) == 0)
|
|
return 0;
|
|
|
|
transfer_address =
|
|
readl(priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG);
|
|
if (transfer_address != priv(dev)->ao_buffer_bus_addr[buffer_index])
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int ao_stopped_by_error(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
if (cmd->stop_src == TRIG_NONE)
|
|
return 1;
|
|
if (cmd->stop_src == TRIG_COUNT) {
|
|
if (priv(dev)->ao_count)
|
|
return 1;
|
|
if (last_ao_dma_load_completed(dev) == 0)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static inline int ao_dma_needs_restart(struct comedi_device *dev,
|
|
unsigned short dma_status)
|
|
{
|
|
if ((dma_status & PLX_DMA_DONE_BIT) == 0 ||
|
|
(dma_status & PLX_DMA_EN_BIT) == 0)
|
|
return 0;
|
|
if (last_ao_dma_load_completed(dev))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void restart_ao_dma(struct comedi_device *dev)
|
|
{
|
|
unsigned int dma_desc_bits;
|
|
|
|
dma_desc_bits =
|
|
readl(priv(dev)->plx9080_iobase + PLX_DMA0_DESCRIPTOR_REG);
|
|
dma_desc_bits &= ~PLX_END_OF_CHAIN_BIT;
|
|
DEBUG_PRINT("restarting ao dma, descriptor reg 0x%x\n", dma_desc_bits);
|
|
load_first_dma_descriptor(dev, 0, dma_desc_bits);
|
|
|
|
dma_start_sync(dev, 0);
|
|
}
|
|
|
|
static void handle_ao_interrupt(struct comedi_device *dev,
|
|
unsigned short status, unsigned int plx_status)
|
|
{
|
|
struct comedi_subdevice *s = dev->write_subdev;
|
|
struct comedi_async *async;
|
|
struct comedi_cmd *cmd;
|
|
uint8_t dma0_status;
|
|
unsigned long flags;
|
|
|
|
/* board might not support ao, in which case write_subdev is NULL */
|
|
if (s == NULL)
|
|
return;
|
|
async = s->async;
|
|
cmd = &async->cmd;
|
|
|
|
/* spin lock makes sure noone else changes plx dma control reg */
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
dma0_status = readb(priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG);
|
|
if (plx_status & ICS_DMA0_A) { /* dma chan 0 interrupt */
|
|
if ((dma0_status & PLX_DMA_EN_BIT)
|
|
&& !(dma0_status & PLX_DMA_DONE_BIT))
|
|
writeb(PLX_DMA_EN_BIT | PLX_CLEAR_DMA_INTR_BIT,
|
|
priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG);
|
|
else
|
|
writeb(PLX_CLEAR_DMA_INTR_BIT,
|
|
priv(dev)->plx9080_iobase + PLX_DMA0_CS_REG);
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
DEBUG_PRINT("dma0 status 0x%x\n", dma0_status);
|
|
if (dma0_status & PLX_DMA_EN_BIT) {
|
|
load_ao_dma(dev, cmd);
|
|
/* try to recover from dma end-of-chain event */
|
|
if (ao_dma_needs_restart(dev, dma0_status))
|
|
restart_ao_dma(dev);
|
|
}
|
|
DEBUG_PRINT(" cleared dma ch0 interrupt\n");
|
|
} else
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
|
|
if ((status & DAC_DONE_BIT)) {
|
|
async->events |= COMEDI_CB_EOA;
|
|
if (ao_stopped_by_error(dev, cmd))
|
|
async->events |= COMEDI_CB_ERROR;
|
|
DEBUG_PRINT("plx dma0 desc reg 0x%x\n",
|
|
readl(priv(dev)->plx9080_iobase +
|
|
PLX_DMA0_DESCRIPTOR_REG));
|
|
DEBUG_PRINT("plx dma0 address reg 0x%x\n",
|
|
readl(priv(dev)->plx9080_iobase +
|
|
PLX_DMA0_PCI_ADDRESS_REG));
|
|
}
|
|
cfc_handle_events(dev, s);
|
|
}
|
|
|
|
static irqreturn_t handle_interrupt(int irq, void *d)
|
|
{
|
|
struct comedi_device *dev = d;
|
|
unsigned short status;
|
|
uint32_t plx_status;
|
|
uint32_t plx_bits;
|
|
|
|
plx_status = readl(priv(dev)->plx9080_iobase + PLX_INTRCS_REG);
|
|
status = readw(priv(dev)->main_iobase + HW_STATUS_REG);
|
|
|
|
DEBUG_PRINT("cb_pcidas64: hw status 0x%x ", status);
|
|
DEBUG_PRINT("plx status 0x%x\n", plx_status);
|
|
|
|
/* an interrupt before all the postconfig stuff gets done could
|
|
* cause a NULL dereference if we continue through the
|
|
* interrupt handler */
|
|
if (dev->attached == 0) {
|
|
DEBUG_PRINT("cb_pcidas64: premature interrupt, ignoring",
|
|
status);
|
|
return IRQ_HANDLED;
|
|
}
|
|
handle_ai_interrupt(dev, status, plx_status);
|
|
handle_ao_interrupt(dev, status, plx_status);
|
|
|
|
/* clear possible plx9080 interrupt sources */
|
|
if (plx_status & ICS_LDIA) { /* clear local doorbell interrupt */
|
|
plx_bits = readl(priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG);
|
|
writel(plx_bits, priv(dev)->plx9080_iobase + PLX_DBR_OUT_REG);
|
|
DEBUG_PRINT(" cleared local doorbell bits 0x%x\n", plx_bits);
|
|
}
|
|
|
|
DEBUG_PRINT("exiting handler\n");
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
void abort_dma(struct comedi_device *dev, unsigned int channel)
|
|
{
|
|
unsigned long flags;
|
|
|
|
/* spinlock for plx dma control/status reg */
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
|
|
plx9080_abort_dma(priv(dev)->plx9080_iobase, channel);
|
|
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
}
|
|
|
|
static int ai_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&dev->spinlock, flags);
|
|
if (priv(dev)->ai_cmd_running == 0) {
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
return 0;
|
|
}
|
|
priv(dev)->ai_cmd_running = 0;
|
|
spin_unlock_irqrestore(&dev->spinlock, flags);
|
|
|
|
disable_ai_pacing(dev);
|
|
|
|
abort_dma(dev, 1);
|
|
|
|
DEBUG_PRINT("ai canceled\n");
|
|
return 0;
|
|
}
|
|
|
|
static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
int chan = CR_CHAN(insn->chanspec);
|
|
int range = CR_RANGE(insn->chanspec);
|
|
|
|
/* do some initializing */
|
|
writew(0, priv(dev)->main_iobase + DAC_CONTROL0_REG);
|
|
|
|
/* set range */
|
|
set_dac_range_bits(dev, &priv(dev)->dac_control1_bits, chan, range);
|
|
writew(priv(dev)->dac_control1_bits,
|
|
priv(dev)->main_iobase + DAC_CONTROL1_REG);
|
|
|
|
/* write to channel */
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
writew(data[0] & 0xff,
|
|
priv(dev)->main_iobase + dac_lsb_4020_reg(chan));
|
|
writew((data[0] >> 8) & 0xf,
|
|
priv(dev)->main_iobase + dac_msb_4020_reg(chan));
|
|
} else {
|
|
writew(data[0], priv(dev)->main_iobase + dac_convert_reg(chan));
|
|
}
|
|
|
|
/* remember output value */
|
|
priv(dev)->ao_value[chan] = data[0];
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int ao_readback_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
data[0] = priv(dev)->ao_value[CR_CHAN(insn->chanspec)];
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void set_dac_control0_reg(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
unsigned int bits = DAC_ENABLE_BIT | WAVEFORM_GATE_LEVEL_BIT |
|
|
WAVEFORM_GATE_ENABLE_BIT | WAVEFORM_GATE_SELECT_BIT;
|
|
|
|
if (cmd->start_src == TRIG_EXT) {
|
|
bits |= WAVEFORM_TRIG_EXT_BITS;
|
|
if (cmd->start_arg & CR_INVERT)
|
|
bits |= WAVEFORM_TRIG_FALLING_BIT;
|
|
} else {
|
|
bits |= WAVEFORM_TRIG_SOFT_BITS;
|
|
}
|
|
if (cmd->scan_begin_src == TRIG_EXT) {
|
|
bits |= DAC_EXT_UPDATE_ENABLE_BIT;
|
|
if (cmd->scan_begin_arg & CR_INVERT)
|
|
bits |= DAC_EXT_UPDATE_FALLING_BIT;
|
|
}
|
|
writew(bits, priv(dev)->main_iobase + DAC_CONTROL0_REG);
|
|
}
|
|
|
|
static void set_dac_control1_reg(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cmd->chanlist_len; i++) {
|
|
int channel, range;
|
|
|
|
channel = CR_CHAN(cmd->chanlist[i]);
|
|
range = CR_RANGE(cmd->chanlist[i]);
|
|
set_dac_range_bits(dev, &priv(dev)->dac_control1_bits, channel,
|
|
range);
|
|
}
|
|
priv(dev)->dac_control1_bits |= DAC_SW_GATE_BIT;
|
|
writew(priv(dev)->dac_control1_bits,
|
|
priv(dev)->main_iobase + DAC_CONTROL1_REG);
|
|
}
|
|
|
|
static void set_dac_select_reg(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
uint16_t bits;
|
|
unsigned int first_channel, last_channel;
|
|
|
|
first_channel = CR_CHAN(cmd->chanlist[0]);
|
|
last_channel = CR_CHAN(cmd->chanlist[cmd->chanlist_len - 1]);
|
|
if (last_channel < first_channel)
|
|
comedi_error(dev, "bug! last ao channel < first ao channel");
|
|
|
|
bits = (first_channel & 0x7) | (last_channel & 0x7) << 3;
|
|
|
|
writew(bits, priv(dev)->main_iobase + DAC_SELECT_REG);
|
|
}
|
|
|
|
static void set_dac_interval_regs(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
unsigned int divisor;
|
|
|
|
if (cmd->scan_begin_src != TRIG_TIMER)
|
|
return;
|
|
|
|
divisor = get_ao_divisor(cmd->scan_begin_arg, cmd->flags);
|
|
if (divisor > max_counter_value) {
|
|
comedi_error(dev, "bug! ao divisor too big");
|
|
divisor = max_counter_value;
|
|
}
|
|
writew(divisor & 0xffff,
|
|
priv(dev)->main_iobase + DAC_SAMPLE_INTERVAL_LOWER_REG);
|
|
writew((divisor >> 16) & 0xff,
|
|
priv(dev)->main_iobase + DAC_SAMPLE_INTERVAL_UPPER_REG);
|
|
}
|
|
|
|
static unsigned int load_ao_dma_buffer(struct comedi_device *dev,
|
|
const struct comedi_cmd *cmd)
|
|
{
|
|
unsigned int num_bytes, buffer_index, prev_buffer_index;
|
|
unsigned int next_bits;
|
|
|
|
buffer_index = priv(dev)->ao_dma_index;
|
|
prev_buffer_index = prev_ao_dma_index(dev);
|
|
|
|
DEBUG_PRINT("attempting to load ao buffer %i (0x%x)\n", buffer_index,
|
|
priv(dev)->ao_buffer_bus_addr[buffer_index]);
|
|
|
|
num_bytes = comedi_buf_read_n_available(dev->write_subdev->async);
|
|
if (num_bytes > DMA_BUFFER_SIZE)
|
|
num_bytes = DMA_BUFFER_SIZE;
|
|
if (cmd->stop_src == TRIG_COUNT && num_bytes > priv(dev)->ao_count)
|
|
num_bytes = priv(dev)->ao_count;
|
|
num_bytes -= num_bytes % bytes_in_sample;
|
|
|
|
if (num_bytes == 0)
|
|
return 0;
|
|
|
|
DEBUG_PRINT("loading %i bytes\n", num_bytes);
|
|
|
|
num_bytes = cfc_read_array_from_buffer(dev->write_subdev,
|
|
priv(dev)->
|
|
ao_buffer[buffer_index],
|
|
num_bytes);
|
|
priv(dev)->ao_dma_desc[buffer_index].transfer_size =
|
|
cpu_to_le32(num_bytes);
|
|
/* set end of chain bit so we catch underruns */
|
|
next_bits = le32_to_cpu(priv(dev)->ao_dma_desc[buffer_index].next);
|
|
next_bits |= PLX_END_OF_CHAIN_BIT;
|
|
priv(dev)->ao_dma_desc[buffer_index].next = cpu_to_le32(next_bits);
|
|
/* clear end of chain bit on previous buffer now that we have set it
|
|
* for the last buffer */
|
|
next_bits = le32_to_cpu(priv(dev)->ao_dma_desc[prev_buffer_index].next);
|
|
next_bits &= ~PLX_END_OF_CHAIN_BIT;
|
|
priv(dev)->ao_dma_desc[prev_buffer_index].next = cpu_to_le32(next_bits);
|
|
|
|
priv(dev)->ao_dma_index = (buffer_index + 1) % AO_DMA_RING_COUNT;
|
|
priv(dev)->ao_count -= num_bytes;
|
|
|
|
return num_bytes;
|
|
}
|
|
|
|
static void load_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd)
|
|
{
|
|
unsigned int num_bytes;
|
|
unsigned int next_transfer_addr;
|
|
void *pci_addr_reg =
|
|
priv(dev)->plx9080_iobase + PLX_DMA0_PCI_ADDRESS_REG;
|
|
unsigned int buffer_index;
|
|
|
|
do {
|
|
buffer_index = priv(dev)->ao_dma_index;
|
|
/* don't overwrite data that hasn't been transferred yet */
|
|
next_transfer_addr = readl(pci_addr_reg);
|
|
if (next_transfer_addr >=
|
|
priv(dev)->ao_buffer_bus_addr[buffer_index]
|
|
&& next_transfer_addr <
|
|
priv(dev)->ao_buffer_bus_addr[buffer_index] +
|
|
DMA_BUFFER_SIZE)
|
|
return;
|
|
num_bytes = load_ao_dma_buffer(dev, cmd);
|
|
} while (num_bytes >= DMA_BUFFER_SIZE);
|
|
}
|
|
|
|
static int prep_ao_dma(struct comedi_device *dev, const struct comedi_cmd *cmd)
|
|
{
|
|
unsigned int num_bytes;
|
|
int i;
|
|
|
|
/* clear queue pointer too, since external queue has
|
|
* weird interactions with ao fifo */
|
|
writew(0, priv(dev)->main_iobase + ADC_QUEUE_CLEAR_REG);
|
|
writew(0, priv(dev)->main_iobase + DAC_BUFFER_CLEAR_REG);
|
|
|
|
num_bytes = (DAC_FIFO_SIZE / 2) * bytes_in_sample;
|
|
if (cmd->stop_src == TRIG_COUNT &&
|
|
num_bytes / bytes_in_sample > priv(dev)->ao_count)
|
|
num_bytes = priv(dev)->ao_count * bytes_in_sample;
|
|
num_bytes = cfc_read_array_from_buffer(dev->write_subdev,
|
|
priv(dev)->ao_bounce_buffer,
|
|
num_bytes);
|
|
for (i = 0; i < num_bytes / bytes_in_sample; i++) {
|
|
writew(priv(dev)->ao_bounce_buffer[i],
|
|
priv(dev)->main_iobase + DAC_FIFO_REG);
|
|
}
|
|
priv(dev)->ao_count -= num_bytes / bytes_in_sample;
|
|
if (cmd->stop_src == TRIG_COUNT && priv(dev)->ao_count == 0)
|
|
return 0;
|
|
num_bytes = load_ao_dma_buffer(dev, cmd);
|
|
if (num_bytes == 0)
|
|
return -1;
|
|
if (num_bytes >= DMA_BUFFER_SIZE) ;
|
|
load_ao_dma(dev, cmd);
|
|
|
|
dma_start_sync(dev, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline int external_ai_queue_in_use(struct comedi_device *dev)
|
|
{
|
|
if (dev->read_subdev->busy)
|
|
return 0;
|
|
if (board(dev)->layout == LAYOUT_4020)
|
|
return 0;
|
|
else if (use_internal_queue_6xxx(&dev->read_subdev->async->cmd))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
static int ao_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
|
|
{
|
|
struct comedi_cmd *cmd = &s->async->cmd;
|
|
|
|
if (external_ai_queue_in_use(dev)) {
|
|
warn_external_queue(dev);
|
|
return -EBUSY;
|
|
}
|
|
/* disable analog output system during setup */
|
|
writew(0x0, priv(dev)->main_iobase + DAC_CONTROL0_REG);
|
|
|
|
priv(dev)->ao_dma_index = 0;
|
|
priv(dev)->ao_count = cmd->stop_arg * cmd->chanlist_len;
|
|
|
|
set_dac_select_reg(dev, cmd);
|
|
set_dac_interval_regs(dev, cmd);
|
|
load_first_dma_descriptor(dev, 0, priv(dev)->ao_dma_desc_bus_addr |
|
|
PLX_DESC_IN_PCI_BIT | PLX_INTR_TERM_COUNT);
|
|
|
|
set_dac_control1_reg(dev, cmd);
|
|
s->async->inttrig = ao_inttrig;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ao_inttrig(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
unsigned int trig_num)
|
|
{
|
|
struct comedi_cmd *cmd = &s->async->cmd;
|
|
int retval;
|
|
|
|
if (trig_num != 0)
|
|
return -EINVAL;
|
|
|
|
retval = prep_ao_dma(dev, cmd);
|
|
if (retval < 0)
|
|
return -EPIPE;
|
|
|
|
set_dac_control0_reg(dev, cmd);
|
|
|
|
if (cmd->start_src == TRIG_INT)
|
|
writew(0, priv(dev)->main_iobase + DAC_START_REG);
|
|
|
|
s->async->inttrig = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ao_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_cmd *cmd)
|
|
{
|
|
int err = 0;
|
|
int tmp;
|
|
unsigned int tmp_arg;
|
|
int i;
|
|
|
|
/* step 1: make sure trigger sources are trivially valid */
|
|
|
|
tmp = cmd->start_src;
|
|
cmd->start_src &= TRIG_INT | TRIG_EXT;
|
|
if (!cmd->start_src || tmp != cmd->start_src)
|
|
err++;
|
|
|
|
tmp = cmd->scan_begin_src;
|
|
cmd->scan_begin_src &= TRIG_TIMER | TRIG_EXT;
|
|
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_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 */
|
|
|
|
/* uniqueness check */
|
|
if (cmd->start_src != TRIG_INT && cmd->start_src != TRIG_EXT)
|
|
err++;
|
|
if (cmd->scan_begin_src != TRIG_TIMER &&
|
|
cmd->scan_begin_src != TRIG_EXT)
|
|
err++;
|
|
|
|
/* compatibility check */
|
|
if (cmd->convert_src == TRIG_EXT && cmd->scan_begin_src == TRIG_TIMER)
|
|
err++;
|
|
if (cmd->stop_src != TRIG_COUNT &&
|
|
cmd->stop_src != TRIG_NONE && cmd->stop_src != TRIG_EXT)
|
|
err++;
|
|
|
|
if (err)
|
|
return 2;
|
|
|
|
/* step 3: make sure arguments are trivially compatible */
|
|
|
|
if (cmd->scan_begin_src == TRIG_TIMER) {
|
|
if (cmd->scan_begin_arg < board(dev)->ao_scan_speed) {
|
|
cmd->scan_begin_arg = board(dev)->ao_scan_speed;
|
|
err++;
|
|
}
|
|
if (get_ao_divisor(cmd->scan_begin_arg,
|
|
cmd->flags) > max_counter_value) {
|
|
cmd->scan_begin_arg =
|
|
(max_counter_value + 2) * TIMER_BASE;
|
|
err++;
|
|
}
|
|
}
|
|
|
|
if (!cmd->chanlist_len) {
|
|
cmd->chanlist_len = 1;
|
|
err++;
|
|
}
|
|
if (cmd->scan_end_arg != cmd->chanlist_len) {
|
|
cmd->scan_end_arg = cmd->chanlist_len;
|
|
err++;
|
|
}
|
|
|
|
if (err)
|
|
return 3;
|
|
|
|
/* step 4: fix up any arguments */
|
|
|
|
if (cmd->scan_begin_src == TRIG_TIMER) {
|
|
tmp_arg = cmd->scan_begin_arg;
|
|
cmd->scan_begin_arg =
|
|
get_divisor(cmd->scan_begin_arg, cmd->flags) * TIMER_BASE;
|
|
if (tmp_arg != cmd->scan_begin_arg)
|
|
err++;
|
|
}
|
|
|
|
if (err)
|
|
return 4;
|
|
|
|
if (cmd->chanlist) {
|
|
unsigned int first_channel = CR_CHAN(cmd->chanlist[0]);
|
|
for (i = 1; i < cmd->chanlist_len; i++) {
|
|
if (CR_CHAN(cmd->chanlist[i]) != first_channel + i) {
|
|
comedi_error(dev,
|
|
"chanlist must use consecutive channels");
|
|
err++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (err)
|
|
return 5;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ao_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
|
|
{
|
|
writew(0x0, priv(dev)->main_iobase + DAC_CONTROL0_REG);
|
|
abort_dma(dev, 0);
|
|
return 0;
|
|
}
|
|
|
|
static int dio_callback(int dir, int port, int data, unsigned long iobase)
|
|
{
|
|
if (dir) {
|
|
writeb(data, (void *)(iobase + port));
|
|
DEBUG_PRINT("wrote 0x%x to port %i\n", data, port);
|
|
return 0;
|
|
} else {
|
|
return readb((void *)(iobase + port));
|
|
}
|
|
}
|
|
|
|
static int dio_callback_4020(int dir, int port, int data, unsigned long iobase)
|
|
{
|
|
if (dir) {
|
|
writew(data, (void *)(iobase + 2 * port));
|
|
return 0;
|
|
} else {
|
|
return readw((void *)(iobase + 2 * port));
|
|
}
|
|
}
|
|
|
|
static int di_rbits(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
unsigned int bits;
|
|
|
|
bits = readb(priv(dev)->dio_counter_iobase + DI_REG);
|
|
bits &= 0xf;
|
|
data[1] = bits;
|
|
data[0] = 0;
|
|
|
|
return 2;
|
|
}
|
|
|
|
static int do_wbits(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
data[0] &= 0xf;
|
|
/* zero bits we are going to change */
|
|
s->state &= ~data[0];
|
|
/* set new bits */
|
|
s->state |= data[0] & data[1];
|
|
|
|
writeb(s->state, priv(dev)->dio_counter_iobase + DO_REG);
|
|
|
|
data[1] = s->state;
|
|
|
|
return 2;
|
|
}
|
|
|
|
static int dio_60xx_config_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
unsigned int mask;
|
|
|
|
mask = 1 << CR_CHAN(insn->chanspec);
|
|
|
|
switch (data[0]) {
|
|
case INSN_CONFIG_DIO_INPUT:
|
|
s->io_bits &= ~mask;
|
|
break;
|
|
case INSN_CONFIG_DIO_OUTPUT:
|
|
s->io_bits |= mask;
|
|
break;
|
|
case INSN_CONFIG_DIO_QUERY:
|
|
data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT;
|
|
return 2;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
writeb(s->io_bits,
|
|
priv(dev)->dio_counter_iobase + DIO_DIRECTION_60XX_REG);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int dio_60xx_wbits(struct comedi_device *dev, struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
if (data[0]) {
|
|
s->state &= ~data[0];
|
|
s->state |= (data[0] & data[1]);
|
|
writeb(s->state,
|
|
priv(dev)->dio_counter_iobase + DIO_DATA_60XX_REG);
|
|
}
|
|
|
|
data[1] = readb(priv(dev)->dio_counter_iobase + DIO_DATA_60XX_REG);
|
|
|
|
return 2;
|
|
}
|
|
|
|
static void caldac_write(struct comedi_device *dev, unsigned int channel,
|
|
unsigned int value)
|
|
{
|
|
priv(dev)->caldac_state[channel] = value;
|
|
|
|
switch (board(dev)->layout) {
|
|
case LAYOUT_60XX:
|
|
case LAYOUT_64XX:
|
|
caldac_8800_write(dev, channel, value);
|
|
break;
|
|
case LAYOUT_4020:
|
|
caldac_i2c_write(dev, channel, value);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int calib_write_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
int channel = CR_CHAN(insn->chanspec);
|
|
|
|
/* return immediately if setting hasn't changed, since
|
|
* programming these things is slow */
|
|
if (priv(dev)->caldac_state[channel] == data[0])
|
|
return 1;
|
|
|
|
caldac_write(dev, channel, data[0]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int calib_read_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s, struct comedi_insn *insn,
|
|
unsigned int *data)
|
|
{
|
|
unsigned int channel = CR_CHAN(insn->chanspec);
|
|
|
|
data[0] = priv(dev)->caldac_state[channel];
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void ad8402_write(struct comedi_device *dev, unsigned int channel,
|
|
unsigned int value)
|
|
{
|
|
static const int bitstream_length = 10;
|
|
unsigned int bit, register_bits;
|
|
unsigned int bitstream = ((channel & 0x3) << 8) | (value & 0xff);
|
|
static const int ad8402_udelay = 1;
|
|
|
|
priv(dev)->ad8402_state[channel] = value;
|
|
|
|
register_bits = SELECT_8402_64XX_BIT;
|
|
udelay(ad8402_udelay);
|
|
writew(register_bits, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
|
|
for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
|
|
if (bitstream & bit)
|
|
register_bits |= SERIAL_DATA_IN_BIT;
|
|
else
|
|
register_bits &= ~SERIAL_DATA_IN_BIT;
|
|
udelay(ad8402_udelay);
|
|
writew(register_bits, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
udelay(ad8402_udelay);
|
|
writew(register_bits | SERIAL_CLOCK_BIT,
|
|
priv(dev)->main_iobase + CALIBRATION_REG);
|
|
}
|
|
|
|
udelay(ad8402_udelay);
|
|
writew(0, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
}
|
|
|
|
/* for pci-das6402/16, channel 0 is analog input gain and channel 1 is offset */
|
|
static int ad8402_write_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
int channel = CR_CHAN(insn->chanspec);
|
|
|
|
/* return immediately if setting hasn't changed, since
|
|
* programming these things is slow */
|
|
if (priv(dev)->ad8402_state[channel] == data[0])
|
|
return 1;
|
|
|
|
priv(dev)->ad8402_state[channel] = data[0];
|
|
|
|
ad8402_write(dev, channel, data[0]);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int ad8402_read_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
unsigned int channel = CR_CHAN(insn->chanspec);
|
|
|
|
data[0] = priv(dev)->ad8402_state[channel];
|
|
|
|
return 1;
|
|
}
|
|
|
|
static uint16_t read_eeprom(struct comedi_device *dev, uint8_t address)
|
|
{
|
|
static const int bitstream_length = 11;
|
|
static const int read_command = 0x6;
|
|
unsigned int bitstream = (read_command << 8) | address;
|
|
unsigned int bit;
|
|
void *const plx_control_addr =
|
|
priv(dev)->plx9080_iobase + PLX_CONTROL_REG;
|
|
uint16_t value;
|
|
static const int value_length = 16;
|
|
static const int eeprom_udelay = 1;
|
|
|
|
udelay(eeprom_udelay);
|
|
priv(dev)->plx_control_bits &= ~CTL_EE_CLK & ~CTL_EE_CS;
|
|
/* make sure we don't send anything to the i2c bus on 4020 */
|
|
priv(dev)->plx_control_bits |= CTL_USERO;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
/* activate serial eeprom */
|
|
udelay(eeprom_udelay);
|
|
priv(dev)->plx_control_bits |= CTL_EE_CS;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
|
|
/* write read command and desired memory address */
|
|
for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
|
|
/* set bit to be written */
|
|
udelay(eeprom_udelay);
|
|
if (bitstream & bit)
|
|
priv(dev)->plx_control_bits |= CTL_EE_W;
|
|
else
|
|
priv(dev)->plx_control_bits &= ~CTL_EE_W;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
/* clock in bit */
|
|
udelay(eeprom_udelay);
|
|
priv(dev)->plx_control_bits |= CTL_EE_CLK;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
udelay(eeprom_udelay);
|
|
priv(dev)->plx_control_bits &= ~CTL_EE_CLK;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
}
|
|
/* read back value from eeprom memory location */
|
|
value = 0;
|
|
for (bit = 1 << (value_length - 1); bit; bit >>= 1) {
|
|
/* clock out bit */
|
|
udelay(eeprom_udelay);
|
|
priv(dev)->plx_control_bits |= CTL_EE_CLK;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
udelay(eeprom_udelay);
|
|
priv(dev)->plx_control_bits &= ~CTL_EE_CLK;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
udelay(eeprom_udelay);
|
|
if (readl(plx_control_addr) & CTL_EE_R)
|
|
value |= bit;
|
|
}
|
|
|
|
/* deactivate eeprom serial input */
|
|
udelay(eeprom_udelay);
|
|
priv(dev)->plx_control_bits &= ~CTL_EE_CS;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
|
|
return value;
|
|
}
|
|
|
|
static int eeprom_read_insn(struct comedi_device *dev,
|
|
struct comedi_subdevice *s,
|
|
struct comedi_insn *insn, unsigned int *data)
|
|
{
|
|
data[0] = read_eeprom(dev, CR_CHAN(insn->chanspec));
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* utility function that rounds desired timing to an achievable time, and
|
|
* sets cmd members appropriately.
|
|
* adc paces conversions from master clock by dividing by (x + 3) where x is 24 bit number
|
|
*/
|
|
static void check_adc_timing(struct comedi_device *dev, struct comedi_cmd *cmd)
|
|
{
|
|
unsigned int convert_divisor = 0, scan_divisor;
|
|
static const int min_convert_divisor = 3;
|
|
static const int max_convert_divisor =
|
|
max_counter_value + min_convert_divisor;
|
|
static const int min_scan_divisor_4020 = 2;
|
|
unsigned long long max_scan_divisor, min_scan_divisor;
|
|
|
|
if (cmd->convert_src == TRIG_TIMER) {
|
|
if (board(dev)->layout == LAYOUT_4020) {
|
|
cmd->convert_arg = 0;
|
|
} else {
|
|
convert_divisor =
|
|
get_divisor(cmd->convert_arg, cmd->flags);
|
|
if (convert_divisor > max_convert_divisor)
|
|
convert_divisor = max_convert_divisor;
|
|
if (convert_divisor < min_convert_divisor)
|
|
convert_divisor = min_convert_divisor;
|
|
cmd->convert_arg = convert_divisor * TIMER_BASE;
|
|
}
|
|
} else if (cmd->convert_src == TRIG_NOW)
|
|
cmd->convert_arg = 0;
|
|
|
|
if (cmd->scan_begin_src == TRIG_TIMER) {
|
|
scan_divisor = get_divisor(cmd->scan_begin_arg, cmd->flags);
|
|
if (cmd->convert_src == TRIG_TIMER) {
|
|
/* XXX check for integer overflows */
|
|
min_scan_divisor = convert_divisor * cmd->chanlist_len;
|
|
max_scan_divisor =
|
|
(convert_divisor * cmd->chanlist_len - 1) +
|
|
max_counter_value;
|
|
} else {
|
|
min_scan_divisor = min_scan_divisor_4020;
|
|
max_scan_divisor = max_counter_value + min_scan_divisor;
|
|
}
|
|
if (scan_divisor > max_scan_divisor)
|
|
scan_divisor = max_scan_divisor;
|
|
if (scan_divisor < min_scan_divisor)
|
|
scan_divisor = min_scan_divisor;
|
|
cmd->scan_begin_arg = scan_divisor * TIMER_BASE;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* Gets nearest achievable timing given master clock speed, does not
|
|
* take into account possible minimum/maximum divisor values. Used
|
|
* by other timing checking functions. */
|
|
static unsigned int get_divisor(unsigned int ns, unsigned int flags)
|
|
{
|
|
unsigned int divisor;
|
|
|
|
switch (flags & TRIG_ROUND_MASK) {
|
|
case TRIG_ROUND_UP:
|
|
divisor = (ns + TIMER_BASE - 1) / TIMER_BASE;
|
|
break;
|
|
case TRIG_ROUND_DOWN:
|
|
divisor = ns / TIMER_BASE;
|
|
break;
|
|
case TRIG_ROUND_NEAREST:
|
|
default:
|
|
divisor = (ns + TIMER_BASE / 2) / TIMER_BASE;
|
|
break;
|
|
}
|
|
return divisor;
|
|
}
|
|
|
|
static unsigned int get_ao_divisor(unsigned int ns, unsigned int flags)
|
|
{
|
|
return get_divisor(ns, flags) - 2;
|
|
}
|
|
|
|
/* adjusts the size of hardware fifo (which determines block size for dma xfers) */
|
|
static int set_ai_fifo_size(struct comedi_device *dev, unsigned int num_samples)
|
|
{
|
|
unsigned int num_fifo_entries;
|
|
int retval;
|
|
const struct hw_fifo_info *const fifo = board(dev)->ai_fifo;
|
|
|
|
num_fifo_entries = num_samples / fifo->sample_packing_ratio;
|
|
|
|
retval = set_ai_fifo_segment_length(dev,
|
|
num_fifo_entries /
|
|
fifo->num_segments);
|
|
if (retval < 0)
|
|
return retval;
|
|
|
|
num_samples = retval * fifo->num_segments * fifo->sample_packing_ratio;
|
|
|
|
DEBUG_PRINT("set hardware fifo size to %i\n", num_samples);
|
|
|
|
return num_samples;
|
|
}
|
|
|
|
/* query length of fifo */
|
|
static unsigned int ai_fifo_size(struct comedi_device *dev)
|
|
{
|
|
return priv(dev)->ai_fifo_segment_length *
|
|
board(dev)->ai_fifo->num_segments *
|
|
board(dev)->ai_fifo->sample_packing_ratio;
|
|
}
|
|
|
|
static int set_ai_fifo_segment_length(struct comedi_device *dev,
|
|
unsigned int num_entries)
|
|
{
|
|
static const int increment_size = 0x100;
|
|
const struct hw_fifo_info *const fifo = board(dev)->ai_fifo;
|
|
unsigned int num_increments;
|
|
uint16_t bits;
|
|
|
|
if (num_entries < increment_size)
|
|
num_entries = increment_size;
|
|
if (num_entries > fifo->max_segment_length)
|
|
num_entries = fifo->max_segment_length;
|
|
|
|
/* 1 == 256 entries, 2 == 512 entries, etc */
|
|
num_increments = (num_entries + increment_size / 2) / increment_size;
|
|
|
|
bits = (~(num_increments - 1)) & fifo->fifo_size_reg_mask;
|
|
priv(dev)->fifo_size_bits &= ~fifo->fifo_size_reg_mask;
|
|
priv(dev)->fifo_size_bits |= bits;
|
|
writew(priv(dev)->fifo_size_bits,
|
|
priv(dev)->main_iobase + FIFO_SIZE_REG);
|
|
|
|
priv(dev)->ai_fifo_segment_length = num_increments * increment_size;
|
|
|
|
DEBUG_PRINT("set hardware fifo segment length to %i\n",
|
|
priv(dev)->ai_fifo_segment_length);
|
|
|
|
return priv(dev)->ai_fifo_segment_length;
|
|
}
|
|
|
|
/* pci-6025 8800 caldac:
|
|
* address 0 == dac channel 0 offset
|
|
* address 1 == dac channel 0 gain
|
|
* address 2 == dac channel 1 offset
|
|
* address 3 == dac channel 1 gain
|
|
* address 4 == fine adc offset
|
|
* address 5 == coarse adc offset
|
|
* address 6 == coarse adc gain
|
|
* address 7 == fine adc gain
|
|
*/
|
|
/* pci-6402/16 uses all 8 channels for dac:
|
|
* address 0 == dac channel 0 fine gain
|
|
* address 1 == dac channel 0 coarse gain
|
|
* address 2 == dac channel 0 coarse offset
|
|
* address 3 == dac channel 1 coarse offset
|
|
* address 4 == dac channel 1 fine gain
|
|
* address 5 == dac channel 1 coarse gain
|
|
* address 6 == dac channel 0 fine offset
|
|
* address 7 == dac channel 1 fine offset
|
|
*/
|
|
|
|
static int caldac_8800_write(struct comedi_device *dev, unsigned int address,
|
|
uint8_t value)
|
|
{
|
|
static const int num_caldac_channels = 8;
|
|
static const int bitstream_length = 11;
|
|
unsigned int bitstream = ((address & 0x7) << 8) | value;
|
|
unsigned int bit, register_bits;
|
|
static const int caldac_8800_udelay = 1;
|
|
|
|
if (address >= num_caldac_channels) {
|
|
comedi_error(dev, "illegal caldac channel");
|
|
return -1;
|
|
}
|
|
for (bit = 1 << (bitstream_length - 1); bit; bit >>= 1) {
|
|
register_bits = 0;
|
|
if (bitstream & bit)
|
|
register_bits |= SERIAL_DATA_IN_BIT;
|
|
udelay(caldac_8800_udelay);
|
|
writew(register_bits, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
register_bits |= SERIAL_CLOCK_BIT;
|
|
udelay(caldac_8800_udelay);
|
|
writew(register_bits, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
}
|
|
udelay(caldac_8800_udelay);
|
|
writew(SELECT_8800_BIT, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
udelay(caldac_8800_udelay);
|
|
writew(0, priv(dev)->main_iobase + CALIBRATION_REG);
|
|
udelay(caldac_8800_udelay);
|
|
return 0;
|
|
}
|
|
|
|
/* 4020 caldacs */
|
|
static int caldac_i2c_write(struct comedi_device *dev,
|
|
unsigned int caldac_channel, unsigned int value)
|
|
{
|
|
uint8_t serial_bytes[3];
|
|
uint8_t i2c_addr;
|
|
enum pointer_bits {
|
|
/* manual has gain and offset bits switched */
|
|
OFFSET_0_2 = 0x1,
|
|
GAIN_0_2 = 0x2,
|
|
OFFSET_1_3 = 0x4,
|
|
GAIN_1_3 = 0x8,
|
|
};
|
|
enum data_bits {
|
|
NOT_CLEAR_REGISTERS = 0x20,
|
|
};
|
|
|
|
switch (caldac_channel) {
|
|
case 0: /* chan 0 offset */
|
|
i2c_addr = CALDAC0_I2C_ADDR;
|
|
serial_bytes[0] = OFFSET_0_2;
|
|
break;
|
|
case 1: /* chan 1 offset */
|
|
i2c_addr = CALDAC0_I2C_ADDR;
|
|
serial_bytes[0] = OFFSET_1_3;
|
|
break;
|
|
case 2: /* chan 2 offset */
|
|
i2c_addr = CALDAC1_I2C_ADDR;
|
|
serial_bytes[0] = OFFSET_0_2;
|
|
break;
|
|
case 3: /* chan 3 offset */
|
|
i2c_addr = CALDAC1_I2C_ADDR;
|
|
serial_bytes[0] = OFFSET_1_3;
|
|
break;
|
|
case 4: /* chan 0 gain */
|
|
i2c_addr = CALDAC0_I2C_ADDR;
|
|
serial_bytes[0] = GAIN_0_2;
|
|
break;
|
|
case 5: /* chan 1 gain */
|
|
i2c_addr = CALDAC0_I2C_ADDR;
|
|
serial_bytes[0] = GAIN_1_3;
|
|
break;
|
|
case 6: /* chan 2 gain */
|
|
i2c_addr = CALDAC1_I2C_ADDR;
|
|
serial_bytes[0] = GAIN_0_2;
|
|
break;
|
|
case 7: /* chan 3 gain */
|
|
i2c_addr = CALDAC1_I2C_ADDR;
|
|
serial_bytes[0] = GAIN_1_3;
|
|
break;
|
|
default:
|
|
comedi_error(dev, "invalid caldac channel\n");
|
|
return -1;
|
|
break;
|
|
}
|
|
serial_bytes[1] = NOT_CLEAR_REGISTERS | ((value >> 8) & 0xf);
|
|
serial_bytes[2] = value & 0xff;
|
|
i2c_write(dev, i2c_addr, serial_bytes, 3);
|
|
return 0;
|
|
}
|
|
|
|
/* Their i2c requires a huge delay on setting clock or data high for some reason */
|
|
static const int i2c_high_udelay = 1000;
|
|
static const int i2c_low_udelay = 10;
|
|
|
|
/* set i2c data line high or low */
|
|
static void i2c_set_sda(struct comedi_device *dev, int state)
|
|
{
|
|
static const int data_bit = CTL_EE_W;
|
|
void *plx_control_addr = priv(dev)->plx9080_iobase + PLX_CONTROL_REG;
|
|
|
|
if (state) {
|
|
/* set data line high */
|
|
priv(dev)->plx_control_bits &= ~data_bit;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
udelay(i2c_high_udelay);
|
|
} else { /* set data line low */
|
|
|
|
priv(dev)->plx_control_bits |= data_bit;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
udelay(i2c_low_udelay);
|
|
}
|
|
}
|
|
|
|
/* set i2c clock line high or low */
|
|
static void i2c_set_scl(struct comedi_device *dev, int state)
|
|
{
|
|
static const int clock_bit = CTL_USERO;
|
|
void *plx_control_addr = priv(dev)->plx9080_iobase + PLX_CONTROL_REG;
|
|
|
|
if (state) {
|
|
/* set clock line high */
|
|
priv(dev)->plx_control_bits &= ~clock_bit;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
udelay(i2c_high_udelay);
|
|
} else { /* set clock line low */
|
|
|
|
priv(dev)->plx_control_bits |= clock_bit;
|
|
writel(priv(dev)->plx_control_bits, plx_control_addr);
|
|
udelay(i2c_low_udelay);
|
|
}
|
|
}
|
|
|
|
static void i2c_write_byte(struct comedi_device *dev, uint8_t byte)
|
|
{
|
|
uint8_t bit;
|
|
unsigned int num_bits = 8;
|
|
|
|
DEBUG_PRINT("writing to i2c byte 0x%x\n", byte);
|
|
|
|
for (bit = 1 << (num_bits - 1); bit; bit >>= 1) {
|
|
i2c_set_scl(dev, 0);
|
|
if ((byte & bit))
|
|
i2c_set_sda(dev, 1);
|
|
else
|
|
i2c_set_sda(dev, 0);
|
|
i2c_set_scl(dev, 1);
|
|
}
|
|
}
|
|
|
|
/* we can't really read the lines, so fake it */
|
|
static int i2c_read_ack(struct comedi_device *dev)
|
|
{
|
|
i2c_set_scl(dev, 0);
|
|
i2c_set_sda(dev, 1);
|
|
i2c_set_scl(dev, 1);
|
|
|
|
return 0; /* return fake acknowledge bit */
|
|
}
|
|
|
|
/* send start bit */
|
|
static void i2c_start(struct comedi_device *dev)
|
|
{
|
|
i2c_set_scl(dev, 1);
|
|
i2c_set_sda(dev, 1);
|
|
i2c_set_sda(dev, 0);
|
|
}
|
|
|
|
/* send stop bit */
|
|
static void i2c_stop(struct comedi_device *dev)
|
|
{
|
|
i2c_set_scl(dev, 0);
|
|
i2c_set_sda(dev, 0);
|
|
i2c_set_scl(dev, 1);
|
|
i2c_set_sda(dev, 1);
|
|
}
|
|
|
|
static void i2c_write(struct comedi_device *dev, unsigned int address,
|
|
const uint8_t * data, unsigned int length)
|
|
{
|
|
unsigned int i;
|
|
uint8_t bitstream;
|
|
static const int read_bit = 0x1;
|
|
|
|
/* XXX need mutex to prevent simultaneous attempts to access eeprom and i2c bus */
|
|
|
|
/* make sure we dont send anything to eeprom */
|
|
priv(dev)->plx_control_bits &= ~CTL_EE_CS;
|
|
|
|
i2c_stop(dev);
|
|
i2c_start(dev);
|
|
|
|
/* send address and write bit */
|
|
bitstream = (address << 1) & ~read_bit;
|
|
i2c_write_byte(dev, bitstream);
|
|
|
|
/* get acknowledge */
|
|
if (i2c_read_ack(dev) != 0) {
|
|
comedi_error(dev, "i2c write failed: no acknowledge");
|
|
i2c_stop(dev);
|
|
return;
|
|
}
|
|
/* write data bytes */
|
|
for (i = 0; i < length; i++) {
|
|
i2c_write_byte(dev, data[i]);
|
|
if (i2c_read_ack(dev) != 0) {
|
|
comedi_error(dev, "i2c write failed: no acknowledge");
|
|
i2c_stop(dev);
|
|
return;
|
|
}
|
|
}
|
|
i2c_stop(dev);
|
|
}
|