diff options
Diffstat (limited to 'drivers/staging/comedi')
49 files changed, 28851 insertions, 0 deletions
diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig new file mode 100644 index 00000000000..b501bfb9c75 --- /dev/null +++ b/drivers/staging/comedi/Kconfig @@ -0,0 +1,27 @@ +config COMEDI + tristate "Data Acquision support (comedi)" + default N + ---help--- + Enable support a wide range of data acquision devices + for Linux. + +config COMEDI_RT + tristate "Comedi Real-time support" + depends on COMEDI && RT + default N + ---help--- + Enable Real time support for the Comedi core. + +config COMEDI_PCI_DRIVERS + tristate "Comedi PCI drivers" + depends on COMEDI && PCI + default N + ---help--- + Enable lots of comedi PCI drivers to be built + +config COMEDI_USB_DRIVERS + tristate "Comedi USB drivers" + depends on COMEDI && USB + default N + ---help--- + Enable lots of comedi USB drivers to be built diff --git a/drivers/staging/comedi/Makefile b/drivers/staging/comedi/Makefile new file mode 100644 index 00000000000..afd1a19c1b8 --- /dev/null +++ b/drivers/staging/comedi/Makefile @@ -0,0 +1,17 @@ +obj-$(CONFIG_COMEDI) += comedi.o +obj-$(CONFIG_COMEDI_RT) += comedi_rt.o + +obj-$(CONFIG_COMEDI) += kcomedilib/ +obj-$(CONFIG_COMEDI) += drivers/ + +comedi-objs := \ + comedi_fops.o \ + proc.o \ + range.o \ + drivers.o \ + comedi_compat32.o \ + comedi_ksyms.o \ + +comedi_rt-objs := \ + rt_pend_tq.o \ + rt.o diff --git a/drivers/staging/comedi/TODO b/drivers/staging/comedi/TODO new file mode 100644 index 00000000000..55781295846 --- /dev/null +++ b/drivers/staging/comedi/TODO @@ -0,0 +1,14 @@ +TODO: + - checkpatch.pl cleanups + - Lindent + - remove all wrappers + - remove typedefs + - audit userspace interface + - reserve major number + - cleanup the individual comedi drivers as well + +Please send patches to Greg Kroah-Hartman <greg@kroah.com> and +copy: + Ian Abbott <abbotti@mev.co.uk> + Frank Mori Hess <fmhess@users.sourceforge.net> + David Schleef <ds@schleef.org> diff --git a/drivers/staging/comedi/comedi.h b/drivers/staging/comedi/comedi.h new file mode 100644 index 00000000000..36d2e1b01e7 --- /dev/null +++ b/drivers/staging/comedi/comedi.h @@ -0,0 +1,916 @@ +/* + include/comedi.h (installed as /usr/include/comedi.h) + header file for comedi + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998-2001 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 Lesser 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. + +*/ + +#ifndef _COMEDI_H +#define _COMEDI_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define COMEDI_MAJORVERSION 0 +#define COMEDI_MINORVERSION 7 +#define COMEDI_MICROVERSION 76 +#define VERSION "0.7.76" + +/* comedi's major device number */ +#define COMEDI_MAJOR 98 + +/* + maximum number of minor devices. This can be increased, although + kernel structures are currently statically allocated, thus you + don't want this to be much more than you actually use. + */ +#define COMEDI_NDEVICES 16 + +/* number of config options in the config structure */ +#define COMEDI_NDEVCONFOPTS 32 +/*length of nth chunk of firmware data*/ +#define COMEDI_DEVCONF_AUX_DATA3_LENGTH 25 +#define COMEDI_DEVCONF_AUX_DATA2_LENGTH 26 +#define COMEDI_DEVCONF_AUX_DATA1_LENGTH 27 +#define COMEDI_DEVCONF_AUX_DATA0_LENGTH 28 +#define COMEDI_DEVCONF_AUX_DATA_HI 29 /* most significant 32 bits of pointer address (if needed) */ +#define COMEDI_DEVCONF_AUX_DATA_LO 30 /* least significant 32 bits of pointer address */ +#define COMEDI_DEVCONF_AUX_DATA_LENGTH 31 /* total data length */ + +/* max length of device and driver names */ +#define COMEDI_NAMELEN 20 + + typedef unsigned int lsampl_t; + typedef unsigned short sampl_t; + +/* packs and unpacks a channel/range number */ + +#define CR_PACK(chan, rng, aref) ((((aref)&0x3)<<24) | (((rng)&0xff)<<16) | (chan)) +#define CR_PACK_FLAGS(chan, range, aref, flags) (CR_PACK(chan, range, aref) | ((flags) & CR_FLAGS_MASK)) + +#define CR_CHAN(a) ((a)&0xffff) +#define CR_RANGE(a) (((a)>>16)&0xff) +#define CR_AREF(a) (((a)>>24)&0x03) + +#define CR_FLAGS_MASK 0xfc000000 +#define CR_ALT_FILTER (1<<26) +#define CR_DITHER CR_ALT_FILTER +#define CR_DEGLITCH CR_ALT_FILTER +#define CR_ALT_SOURCE (1<<27) +#define CR_EDGE (1<<30) +#define CR_INVERT (1<<31) + +#define AREF_GROUND 0x00 /* analog ref = analog ground */ +#define AREF_COMMON 0x01 /* analog ref = analog common */ +#define AREF_DIFF 0x02 /* analog ref = differential */ +#define AREF_OTHER 0x03 /* analog ref = other (undefined) */ + +/* counters -- these are arbitrary values */ +#define GPCT_RESET 0x0001 +#define GPCT_SET_SOURCE 0x0002 +#define GPCT_SET_GATE 0x0004 +#define GPCT_SET_DIRECTION 0x0008 +#define GPCT_SET_OPERATION 0x0010 +#define GPCT_ARM 0x0020 +#define GPCT_DISARM 0x0040 +#define GPCT_GET_INT_CLK_FRQ 0x0080 + +#define GPCT_INT_CLOCK 0x0001 +#define GPCT_EXT_PIN 0x0002 +#define GPCT_NO_GATE 0x0004 +#define GPCT_UP 0x0008 +#define GPCT_DOWN 0x0010 +#define GPCT_HWUD 0x0020 +#define GPCT_SIMPLE_EVENT 0x0040 +#define GPCT_SINGLE_PERIOD 0x0080 +#define GPCT_SINGLE_PW 0x0100 +#define GPCT_CONT_PULSE_OUT 0x0200 +#define GPCT_SINGLE_PULSE_OUT 0x0400 + +/* instructions */ + +#define INSN_MASK_WRITE 0x8000000 +#define INSN_MASK_READ 0x4000000 +#define INSN_MASK_SPECIAL 0x2000000 + +#define INSN_READ (0 | INSN_MASK_READ) +#define INSN_WRITE (1 | INSN_MASK_WRITE) +#define INSN_BITS (2 | INSN_MASK_READ|INSN_MASK_WRITE) +#define INSN_CONFIG (3 | INSN_MASK_READ|INSN_MASK_WRITE) +#define INSN_GTOD (4 | INSN_MASK_READ|INSN_MASK_SPECIAL) +#define INSN_WAIT (5 | INSN_MASK_WRITE|INSN_MASK_SPECIAL) +#define INSN_INTTRIG (6 | INSN_MASK_WRITE|INSN_MASK_SPECIAL) + +/* trigger flags */ +/* These flags are used in comedi_trig structures */ + +#define TRIG_BOGUS 0x0001 /* do the motions */ +#define TRIG_DITHER 0x0002 /* enable dithering */ +#define TRIG_DEGLITCH 0x0004 /* enable deglitching */ +/*#define TRIG_RT 0x0008 */ /* perform op in real time */ +#define TRIG_CONFIG 0x0010 /* perform configuration, not triggering */ +#define TRIG_WAKE_EOS 0x0020 /* wake up on end-of-scan events */ +/*#define TRIG_WRITE 0x0040*/ /* write to bidirectional devices */ + +/* command flags */ +/* These flags are used in comedi_cmd structures */ + +#define CMDF_PRIORITY 0x00000008 /* try to use a real-time interrupt while performing command */ + +#define TRIG_RT CMDF_PRIORITY /* compatibility definition */ + +#define CMDF_WRITE 0x00000040 +#define TRIG_WRITE CMDF_WRITE /* compatibility definition */ + +#define CMDF_RAWDATA 0x00000080 + +#define COMEDI_EV_START 0x00040000 +#define COMEDI_EV_SCAN_BEGIN 0x00080000 +#define COMEDI_EV_CONVERT 0x00100000 +#define COMEDI_EV_SCAN_END 0x00200000 +#define COMEDI_EV_STOP 0x00400000 + +#define TRIG_ROUND_MASK 0x00030000 +#define TRIG_ROUND_NEAREST 0x00000000 +#define TRIG_ROUND_DOWN 0x00010000 +#define TRIG_ROUND_UP 0x00020000 +#define TRIG_ROUND_UP_NEXT 0x00030000 + +/* trigger sources */ + +#define TRIG_ANY 0xffffffff +#define TRIG_INVALID 0x00000000 + +#define TRIG_NONE 0x00000001 /* never trigger */ +#define TRIG_NOW 0x00000002 /* trigger now + N ns */ +#define TRIG_FOLLOW 0x00000004 /* trigger on next lower level trig */ +#define TRIG_TIME 0x00000008 /* trigger at time N ns */ +#define TRIG_TIMER 0x00000010 /* trigger at rate N ns */ +#define TRIG_COUNT 0x00000020 /* trigger when count reaches N */ +#define TRIG_EXT 0x00000040 /* trigger on external signal N */ +#define TRIG_INT 0x00000080 /* trigger on comedi-internal signal N */ +#define TRIG_OTHER 0x00000100 /* driver defined */ + +/* subdevice flags */ + +#define SDF_BUSY 0x0001 /* device is busy */ +#define SDF_BUSY_OWNER 0x0002 /* device is busy with your job */ +#define SDF_LOCKED 0x0004 /* subdevice is locked */ +#define SDF_LOCK_OWNER 0x0008 /* you own lock */ +#define SDF_MAXDATA 0x0010 /* maxdata depends on channel */ +#define SDF_FLAGS 0x0020 /* flags depend on channel */ +#define SDF_RANGETYPE 0x0040 /* range type depends on channel */ +#define SDF_MODE0 0x0080 /* can do mode 0 */ +#define SDF_MODE1 0x0100 /* can do mode 1 */ +#define SDF_MODE2 0x0200 /* can do mode 2 */ +#define SDF_MODE3 0x0400 /* can do mode 3 */ +#define SDF_MODE4 0x0800 /* can do mode 4 */ +#define SDF_CMD 0x1000 /* can do commands (deprecated) */ +#define SDF_SOFT_CALIBRATED 0x2000 /* subdevice uses software calibration */ +#define SDF_CMD_WRITE 0x4000 /* can do output commands */ +#define SDF_CMD_READ 0x8000 /* can do input commands */ + +#define SDF_READABLE 0x00010000 /* subdevice can be read (e.g. analog input) */ +#define SDF_WRITABLE 0x00020000 /* subdevice can be written (e.g. analog output) */ +#define SDF_WRITEABLE SDF_WRITABLE /* spelling error in API */ +#define SDF_INTERNAL 0x00040000 /* subdevice does not have externally visible lines */ +#define SDF_RT 0x00080000 /* DEPRECATED: subdevice is RT capable */ +#define SDF_GROUND 0x00100000 /* can do aref=ground */ +#define SDF_COMMON 0x00200000 /* can do aref=common */ +#define SDF_DIFF 0x00400000 /* can do aref=diff */ +#define SDF_OTHER 0x00800000 /* can do aref=other */ +#define SDF_DITHER 0x01000000 /* can do dithering */ +#define SDF_DEGLITCH 0x02000000 /* can do deglitching */ +#define SDF_MMAP 0x04000000 /* can do mmap() */ +#define SDF_RUNNING 0x08000000 /* subdevice is acquiring data */ +#define SDF_LSAMPL 0x10000000 /* subdevice uses 32-bit samples */ +#define SDF_PACKED 0x20000000 /* subdevice can do packed DIO */ +/* re recyle these flags for PWM */ +#define SDF_PWM_COUNTER SDF_MODE0 /* PWM can automatically switch off */ +#define SDF_PWM_HBRIDGE SDF_MODE1 /* PWM is signed (H-bridge) */ + + + +/* subdevice types */ + +enum comedi_subdevice_type { + COMEDI_SUBD_UNUSED, /* unused by driver */ + COMEDI_SUBD_AI, /* analog input */ + COMEDI_SUBD_AO, /* analog output */ + COMEDI_SUBD_DI, /* digital input */ + COMEDI_SUBD_DO, /* digital output */ + COMEDI_SUBD_DIO, /* digital input/output */ + COMEDI_SUBD_COUNTER, /* counter */ + COMEDI_SUBD_TIMER, /* timer */ + COMEDI_SUBD_MEMORY, /* memory, EEPROM, DPRAM */ + COMEDI_SUBD_CALIB, /* calibration DACs */ + COMEDI_SUBD_PROC, /* processor, DSP */ + COMEDI_SUBD_SERIAL, /* serial IO */ + COMEDI_SUBD_PWM /* PWM */ +}; + +/* configuration instructions */ + +enum configuration_ids { + INSN_CONFIG_DIO_INPUT = 0, + INSN_CONFIG_DIO_OUTPUT = 1, + INSN_CONFIG_DIO_OPENDRAIN = 2, + INSN_CONFIG_ANALOG_TRIG = 16, +/* INSN_CONFIG_WAVEFORM = 17, */ +/* INSN_CONFIG_TRIG = 18, */ +/* INSN_CONFIG_COUNTER = 19, */ + INSN_CONFIG_ALT_SOURCE = 20, + INSN_CONFIG_DIGITAL_TRIG = 21, + INSN_CONFIG_BLOCK_SIZE = 22, + INSN_CONFIG_TIMER_1 = 23, + INSN_CONFIG_FILTER = 24, + INSN_CONFIG_CHANGE_NOTIFY = 25, + + /*ALPHA*/ INSN_CONFIG_SERIAL_CLOCK = 26, + INSN_CONFIG_BIDIRECTIONAL_DATA = 27, + INSN_CONFIG_DIO_QUERY = 28, + INSN_CONFIG_PWM_OUTPUT = 29, + INSN_CONFIG_GET_PWM_OUTPUT = 30, + INSN_CONFIG_ARM = 31, + INSN_CONFIG_DISARM = 32, + INSN_CONFIG_GET_COUNTER_STATUS = 33, + INSN_CONFIG_RESET = 34, + INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR = 1001, /* Use CTR as single pulsegenerator */ + INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR = 1002, /* Use CTR as pulsetraingenerator */ + INSN_CONFIG_GPCT_QUADRATURE_ENCODER = 1003, /* Use the counter as encoder */ + INSN_CONFIG_SET_GATE_SRC = 2001, /* Set gate source */ + INSN_CONFIG_GET_GATE_SRC = 2002, /* Get gate source */ + INSN_CONFIG_SET_CLOCK_SRC = 2003, /* Set master clock source */ + INSN_CONFIG_GET_CLOCK_SRC = 2004, /* Get master clock source */ + INSN_CONFIG_SET_OTHER_SRC = 2005, /* Set other source */ +/* INSN_CONFIG_GET_OTHER_SRC = 2006,*/ /* Get other source */ + INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE, /* Get size in bytes of + subdevice's on-board fifos + used during streaming + input/output */ + INSN_CONFIG_SET_COUNTER_MODE = 4097, + INSN_CONFIG_8254_SET_MODE = INSN_CONFIG_SET_COUNTER_MODE, /* deprecated */ + INSN_CONFIG_8254_READ_STATUS = 4098, + INSN_CONFIG_SET_ROUTING = 4099, + INSN_CONFIG_GET_ROUTING = 4109, +/* PWM */ + INSN_CONFIG_PWM_SET_PERIOD = 5000, /* sets frequency */ + INSN_CONFIG_PWM_GET_PERIOD = 5001, /* gets frequency */ + INSN_CONFIG_GET_PWM_STATUS = 5002, /* is it running? */ + INSN_CONFIG_PWM_SET_H_BRIDGE = 5003, /* sets H bridge: duty cycle and sign bit for a relay at the same time*/ + INSN_CONFIG_PWM_GET_H_BRIDGE = 5004 /* gets H bridge data: duty cycle and the sign bit */ +}; + +enum comedi_io_direction { + COMEDI_INPUT = 0, + COMEDI_OUTPUT = 1, + COMEDI_OPENDRAIN = 2 +}; + +enum comedi_support_level { + COMEDI_UNKNOWN_SUPPORT = 0, + COMEDI_SUPPORTED, + COMEDI_UNSUPPORTED +}; + +/* ioctls */ + +#define CIO 'd' +#define COMEDI_DEVCONFIG _IOW(CIO, 0, comedi_devconfig) +#define COMEDI_DEVINFO _IOR(CIO, 1, comedi_devinfo) +#define COMEDI_SUBDINFO _IOR(CIO, 2, comedi_subdinfo) +#define COMEDI_CHANINFO _IOR(CIO, 3, comedi_chaninfo) +#define COMEDI_TRIG _IOWR(CIO, 4, comedi_trig) +#define COMEDI_LOCK _IO(CIO, 5) +#define COMEDI_UNLOCK _IO(CIO, 6) +#define COMEDI_CANCEL _IO(CIO, 7) +#define COMEDI_RANGEINFO _IOR(CIO, 8, comedi_rangeinfo) +#define COMEDI_CMD _IOR(CIO, 9, comedi_cmd) +#define COMEDI_CMDTEST _IOR(CIO, 10, comedi_cmd) +#define COMEDI_INSNLIST _IOR(CIO, 11, comedi_insnlist) +#define COMEDI_INSN _IOR(CIO, 12, comedi_insn) +#define COMEDI_BUFCONFIG _IOR(CIO, 13, comedi_bufconfig) +#define COMEDI_BUFINFO _IOWR(CIO, 14, comedi_bufinfo) +#define COMEDI_POLL _IO(CIO, 15) + +/* structures */ + +typedef struct comedi_trig_struct comedi_trig; +typedef struct comedi_cmd_struct comedi_cmd; +typedef struct comedi_insn_struct comedi_insn; +typedef struct comedi_insnlist_struct comedi_insnlist; +typedef struct comedi_chaninfo_struct comedi_chaninfo; +typedef struct comedi_subdinfo_struct comedi_subdinfo; +typedef struct comedi_devinfo_struct comedi_devinfo; +typedef struct comedi_devconfig_struct comedi_devconfig; +typedef struct comedi_rangeinfo_struct comedi_rangeinfo; +typedef struct comedi_krange_struct comedi_krange; +typedef struct comedi_bufconfig_struct comedi_bufconfig; +typedef struct comedi_bufinfo_struct comedi_bufinfo; + +struct comedi_trig_struct { + unsigned int subdev; /* subdevice */ + unsigned int mode; /* mode */ + unsigned int flags; + unsigned int n_chan; /* number of channels */ + unsigned int *chanlist; /* channel/range list */ + sampl_t *data; /* data list, size depends on subd flags */ + unsigned int n; /* number of scans */ + unsigned int trigsrc; + unsigned int trigvar; + unsigned int trigvar1; + unsigned int data_len; + unsigned int unused[3]; +}; + +struct comedi_insn_struct { + unsigned int insn; + unsigned int n; + lsampl_t *data; + unsigned int subdev; + unsigned int chanspec; + unsigned int unused[3]; +}; + +struct comedi_insnlist_struct { + unsigned int n_insns; + comedi_insn *insns; +}; + +struct comedi_cmd_struct { + unsigned int subdev; + unsigned int flags; + + unsigned int start_src; + unsigned int start_arg; + + unsigned int scan_begin_src; + unsigned int scan_begin_arg; + + unsigned int convert_src; + unsigned int convert_arg; + + unsigned int scan_end_src; + unsigned int scan_end_arg; + + unsigned int stop_src; + unsigned int stop_arg; + + unsigned int *chanlist; /* channel/range list */ + unsigned int chanlist_len; + + sampl_t *data; /* data list, size depends on subd flags */ + unsigned int data_len; +}; + +struct comedi_chaninfo_struct { + unsigned int subdev; + lsampl_t *maxdata_list; + unsigned int *flaglist; + unsigned int *rangelist; + unsigned int unused[4]; +}; + +struct comedi_rangeinfo_struct { + unsigned int range_type; + void *range_ptr; +}; + +struct comedi_krange_struct { + int min; /* fixed point, multiply by 1e-6 */ + int max; /* fixed point, multiply by 1e-6 */ + unsigned int flags; +}; + + +struct comedi_subdinfo_struct { + unsigned int type; + unsigned int n_chan; + unsigned int subd_flags; + unsigned int timer_type; + unsigned int len_chanlist; + lsampl_t maxdata; + unsigned int flags; /* channel flags */ + unsigned int range_type; /* lookup in kernel */ + unsigned int settling_time_0; + unsigned insn_bits_support; /* see support_level enum for values*/ + unsigned int unused[8]; +}; + +struct comedi_devinfo_struct { + unsigned int version_code; + unsigned int n_subdevs; + char driver_name[COMEDI_NAMELEN]; + char board_name[COMEDI_NAMELEN]; + int read_subdevice; + int write_subdevice; + int unused[30]; +}; + +struct comedi_devconfig_struct { + char board_name[COMEDI_NAMELEN]; + int options[COMEDI_NDEVCONFOPTS]; +}; + +struct comedi_bufconfig_struct { + unsigned int subdevice; + unsigned int flags; + + unsigned int maximum_size; + unsigned int size; + + unsigned int unused[4]; +}; + +struct comedi_bufinfo_struct { + unsigned int subdevice; + unsigned int bytes_read; + + unsigned int buf_write_ptr; + unsigned int buf_read_ptr; + unsigned int buf_write_count; + unsigned int buf_read_count; + + unsigned int bytes_written; + + unsigned int unused[4]; +}; + +/* range stuff */ + +#define __RANGE(a, b) ((((a)&0xffff)<<16)|((b)&0xffff)) + +#define RANGE_OFFSET(a) (((a)>>16)&0xffff) +#define RANGE_LENGTH(b) ((b)&0xffff) + +#define RF_UNIT(flags) ((flags)&0xff) +#define RF_EXTERNAL (1<<8) + +#define UNIT_volt 0 +#define UNIT_mA 1 +#define UNIT_none 2 + +#define COMEDI_MIN_SPEED ((unsigned int)0xffffffff) + +/* callback stuff */ +/* only relevant to kernel modules. */ + +#define COMEDI_CB_EOS 1 /* end of scan */ +#define COMEDI_CB_EOA 2 /* end of acquisition */ +#define COMEDI_CB_BLOCK 4 /* DEPRECATED: convenient block size */ +#define COMEDI_CB_EOBUF 8 /* DEPRECATED: end of buffer */ +#define COMEDI_CB_ERROR 16 /* card error during acquisition */ +#define COMEDI_CB_OVERFLOW 32 /* buffer overflow/underflow */ + +/**********************************************************/ +/* everything after this line is ALPHA */ +/**********************************************************/ + +/* + 8254 specific configuration. + + It supports two config commands: + + 0 ID: INSN_CONFIG_SET_COUNTER_MODE + 1 8254 Mode + I8254_MODE0, I8254_MODE1, ..., I8254_MODE5 + OR'ed with: + I8254_BCD, I8254_BINARY + + 0 ID: INSN_CONFIG_8254_READ_STATUS + 1 <-- Status byte returned here. + B7 = Output + B6 = NULL Count + B5 - B0 Current mode. + +*/ + +enum i8254_mode { + I8254_MODE0 = (0 << 1), /* Interrupt on terminal count */ + I8254_MODE1 = (1 << 1), /* Hardware retriggerable one-shot */ + I8254_MODE2 = (2 << 1), /* Rate generator */ + I8254_MODE3 = (3 << 1), /* Square wave mode */ + I8254_MODE4 = (4 << 1), /* Software triggered strobe */ + I8254_MODE5 = (5 << 1), /* Hardware triggered strobe (retriggerable) */ + I8254_BCD = 1, /* use binary-coded decimal instead of binary (pretty useless) */ + I8254_BINARY = 0 +}; + +static inline unsigned NI_USUAL_PFI_SELECT(unsigned pfi_channel) +{ + if (pfi_channel < 10) + return 0x1 + pfi_channel; + else + return 0xb + pfi_channel; +} +static inline unsigned NI_USUAL_RTSI_SELECT(unsigned rtsi_channel) +{ + if (rtsi_channel < 7) + return 0xb + rtsi_channel; + else + return 0x1b; +} +/* mode bits for NI general-purpose counters, set with + * INSN_CONFIG_SET_COUNTER_MODE */ +#define NI_GPCT_COUNTING_MODE_SHIFT 16 +#define NI_GPCT_INDEX_PHASE_BITSHIFT 20 +#define NI_GPCT_COUNTING_DIRECTION_SHIFT 24 +enum ni_gpct_mode_bits { + NI_GPCT_GATE_ON_BOTH_EDGES_BIT = 0x4, + NI_GPCT_EDGE_GATE_MODE_MASK = 0x18, + NI_GPCT_EDGE_GATE_STARTS_STOPS_BITS = 0x0, + NI_GPCT_EDGE_GATE_STOPS_STARTS_BITS = 0x8, + NI_GPCT_EDGE_GATE_STARTS_BITS = 0x10, + NI_GPCT_EDGE_GATE_NO_STARTS_NO_STOPS_BITS = 0x18, + NI_GPCT_STOP_MODE_MASK = 0x60, + NI_GPCT_STOP_ON_GATE_BITS = 0x00, + NI_GPCT_STOP_ON_GATE_OR_TC_BITS = 0x20, + NI_GPCT_STOP_ON_GATE_OR_SECOND_TC_BITS = 0x40, + NI_GPCT_LOAD_B_SELECT_BIT = 0x80, + NI_GPCT_OUTPUT_MODE_MASK = 0x300, + NI_GPCT_OUTPUT_TC_PULSE_BITS = 0x100, + NI_GPCT_OUTPUT_TC_TOGGLE_BITS = 0x200, + NI_GPCT_OUTPUT_TC_OR_GATE_TOGGLE_BITS = 0x300, + NI_GPCT_HARDWARE_DISARM_MASK = 0xc00, + NI_GPCT_NO_HARDWARE_DISARM_BITS = 0x000, + NI_GPCT_DISARM_AT_TC_BITS = 0x400, + NI_GPCT_DISARM_AT_GATE_BITS = 0x800, + NI_GPCT_DISARM_AT_TC_OR_GATE_BITS = 0xc00, + NI_GPCT_LOADING_ON_TC_BIT = 0x1000, + NI_GPCT_LOADING_ON_GATE_BIT = 0x4000, + NI_GPCT_COUNTING_MODE_MASK = 0x7 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_NORMAL_BITS = + 0x0 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_QUADRATURE_X1_BITS = + 0x1 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_QUADRATURE_X2_BITS = + 0x2 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_QUADRATURE_X4_BITS = + 0x3 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_TWO_PULSE_BITS = + 0x4 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_COUNTING_MODE_SYNC_SOURCE_BITS = + 0x6 << NI_GPCT_COUNTING_MODE_SHIFT, + NI_GPCT_INDEX_PHASE_MASK = 0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_PHASE_LOW_A_LOW_B_BITS = + 0x0 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_PHASE_LOW_A_HIGH_B_BITS = + 0x1 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_PHASE_HIGH_A_LOW_B_BITS = + 0x2 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_PHASE_HIGH_A_HIGH_B_BITS = + 0x3 << NI_GPCT_INDEX_PHASE_BITSHIFT, + NI_GPCT_INDEX_ENABLE_BIT = 0x400000, + NI_GPCT_COUNTING_DIRECTION_MASK = + 0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_COUNTING_DIRECTION_DOWN_BITS = + 0x00 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_COUNTING_DIRECTION_UP_BITS = + 0x1 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_COUNTING_DIRECTION_HW_UP_DOWN_BITS = + 0x2 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_COUNTING_DIRECTION_HW_GATE_BITS = + 0x3 << NI_GPCT_COUNTING_DIRECTION_SHIFT, + NI_GPCT_RELOAD_SOURCE_MASK = 0xc000000, + NI_GPCT_RELOAD_SOURCE_FIXED_BITS = 0x0, + NI_GPCT_RELOAD_SOURCE_SWITCHING_BITS = 0x4000000, + NI_GPCT_RELOAD_SOURCE_GATE_SELECT_BITS = 0x8000000, + NI_GPCT_OR_GATE_BIT = 0x10000000, + NI_GPCT_INVERT_OUTPUT_BIT = 0x20000000 +}; + +/* Bits for setting a clock source with + * INSN_CONFIG_SET_CLOCK_SRC when using NI general-purpose counters. */ +enum ni_gpct_clock_source_bits { + NI_GPCT_CLOCK_SRC_SELECT_MASK = 0x3f, + NI_GPCT_TIMEBASE_1_CLOCK_SRC_BITS = 0x0, + NI_GPCT_TIMEBASE_2_CLOCK_SRC_BITS = 0x1, + NI_GPCT_TIMEBASE_3_CLOCK_SRC_BITS = 0x2, + NI_GPCT_LOGIC_LOW_CLOCK_SRC_BITS = 0x3, + NI_GPCT_NEXT_GATE_CLOCK_SRC_BITS = 0x4, + NI_GPCT_NEXT_TC_CLOCK_SRC_BITS = 0x5, + NI_GPCT_SOURCE_PIN_i_CLOCK_SRC_BITS = 0x6, /* NI 660x-specific */ + NI_GPCT_PXI10_CLOCK_SRC_BITS = 0x7, + NI_GPCT_PXI_STAR_TRIGGER_CLOCK_SRC_BITS = 0x8, + NI_GPCT_ANALOG_TRIGGER_OUT_CLOCK_SRC_BITS = 0x9, + NI_GPCT_PRESCALE_MODE_CLOCK_SRC_MASK = 0x30000000, + NI_GPCT_NO_PRESCALE_CLOCK_SRC_BITS = 0x0, + NI_GPCT_PRESCALE_X2_CLOCK_SRC_BITS = 0x10000000, /* divide source by 2 */ + NI_GPCT_PRESCALE_X8_CLOCK_SRC_BITS = 0x20000000, /* divide source by 8 */ + NI_GPCT_INVERT_CLOCK_SRC_BIT = 0x80000000 +}; +static inline unsigned NI_GPCT_SOURCE_PIN_CLOCK_SRC_BITS(unsigned n) +{ + /* NI 660x-specific */ + return 0x10 + n; +} +static inline unsigned NI_GPCT_RTSI_CLOCK_SRC_BITS(unsigned n) +{ + return 0x18 + n; +} +static inline unsigned NI_GPCT_PFI_CLOCK_SRC_BITS(unsigned n) +{ + /* no pfi on NI 660x */ + return 0x20 + n; +} + +/* Possibilities for setting a gate source with +INSN_CONFIG_SET_GATE_SRC when using NI general-purpose counters. +May be bitwise-or'd with CR_EDGE or CR_INVERT. */ +enum ni_gpct_gate_select { + /* m-series gates */ + NI_GPCT_TIMESTAMP_MUX_GATE_SELECT = 0x0, + NI_GPCT_AI_START2_GATE_SELECT = 0x12, + NI_GPCT_PXI_STAR_TRIGGER_GATE_SELECT = 0x13, + NI_GPCT_NEXT_OUT_GATE_SELECT = 0x14, + NI_GPCT_AI_START1_GATE_SELECT = 0x1c, + NI_GPCT_NEXT_SOURCE_GATE_SELECT = 0x1d, + NI_GPCT_ANALOG_TRIGGER_OUT_GATE_SELECT = 0x1e, + NI_GPCT_LOGIC_LOW_GATE_SELECT = 0x1f, + /* more gates for 660x */ + NI_GPCT_SOURCE_PIN_i_GATE_SELECT = 0x100, + NI_GPCT_GATE_PIN_i_GATE_SELECT = 0x101, + /* more gates for 660x "second gate" */ + NI_GPCT_UP_DOWN_PIN_i_GATE_SELECT = 0x201, + NI_GPCT_SELECTED_GATE_GATE_SELECT = 0x21e, + /* m-series "second gate" sources are unknown, + we should add them here with an offset of 0x300 when known. */ + NI_GPCT_DISABLED_GATE_SELECT = 0x8000, +}; +static inline unsigned NI_GPCT_GATE_PIN_GATE_SELECT(unsigned n) +{ + return 0x102 + n; +} +static inline unsigned NI_GPCT_RTSI_GATE_SELECT(unsigned n) +{ + return NI_USUAL_RTSI_SELECT(n); +} +static inline unsigned NI_GPCT_PFI_GATE_SELECT(unsigned n) +{ + return NI_USUAL_PFI_SELECT(n); +} +static inline unsigned NI_GPCT_UP_DOWN_PIN_GATE_SELECT(unsigned n) +{ + return 0x202 + n; +} + +/* Possibilities for setting a source with +INSN_CONFIG_SET_OTHER_SRC when using NI general-purpose counters. */ +enum ni_gpct_other_index { + NI_GPCT_SOURCE_ENCODER_A, + NI_GPCT_SOURCE_ENCODER_B, + NI_GPCT_SOURCE_ENCODER_Z +}; +enum ni_gpct_other_select { + /* m-series gates */ + /* Still unknown, probably only need NI_GPCT_PFI_OTHER_SELECT */ + NI_GPCT_DISABLED_OTHER_SELECT = 0x8000, +}; +static inline unsigned NI_GPCT_PFI_OTHER_SELECT(unsigned n) +{ + return NI_USUAL_PFI_SELECT(n); +} + +/* start sources for ni general-purpose counters for use with +INSN_CONFIG_ARM */ +enum ni_gpct_arm_source { + NI_GPCT_ARM_IMMEDIATE = 0x0, + NI_GPCT_ARM_PAIRED_IMMEDIATE = 0x1, /* Start both the counter and + the adjacent paired counter + simultaneously */ + /* NI doesn't document bits for selecting hardware arm triggers. If + * the NI_GPCT_ARM_UNKNOWN bit is set, we will pass the least + * significant bits (3 bits for 660x or 5 bits for m-series) through to + * the hardware. This will at least allow someone to figure out what + * the bits do later. */ + NI_GPCT_ARM_UNKNOWN = 0x1000, +}; + +/* digital filtering options for ni 660x for use with INSN_CONFIG_FILTER. */ +enum ni_gpct_filter_select { + NI_GPCT_FILTER_OFF = 0x0, + NI_GPCT_FILTER_TIMEBASE_3_SYNC = 0x1, + NI_GPCT_FILTER_100x_TIMEBASE_1 = 0x2, + NI_GPCT_FILTER_20x_TIMEBASE_1 = 0x3, + NI_GPCT_FILTER_10x_TIMEBASE_1 = 0x4, + NI_GPCT_FILTER_2x_TIMEBASE_1 = 0x5, + NI_GPCT_FILTER_2x_TIMEBASE_3 = 0x6 +}; + +/* PFI digital filtering options for ni m-series for use with + * INSN_CONFIG_FILTER. */ +enum ni_pfi_filter_select { + NI_PFI_FILTER_OFF = 0x0, + NI_PFI_FILTER_125ns = 0x1, + NI_PFI_FILTER_6425ns = 0x2, + NI_PFI_FILTER_2550us = 0x3 +}; + +/* master clock sources for ni mio boards and INSN_CONFIG_SET_CLOCK_SRC */ +enum ni_mio_clock_source { + NI_MIO_INTERNAL_CLOCK = 0, + NI_MIO_RTSI_CLOCK = 1, /* doesn't work for m-series, use + NI_MIO_PLL_RTSI_CLOCK() */ + /* the NI_MIO_PLL_* sources are m-series only */ + NI_MIO_PLL_PXI_STAR_TRIGGER_CLOCK = 2, + NI_MIO_PLL_PXI10_CLOCK = 3, + NI_MIO_PLL_RTSI0_CLOCK = 4 +}; +static inline unsigned NI_MIO_PLL_RTSI_CLOCK(unsigned rtsi_channel) +{ + return NI_MIO_PLL_RTSI0_CLOCK + rtsi_channel; +} + +/* Signals which can be routed to an NI RTSI pin with INSN_CONFIG_SET_ROUTING. + The numbers assigned are not arbitrary, they correspond to the bits required + to program the board. */ +enum ni_rtsi_routing { + NI_RTSI_OUTPUT_ADR_START1 = 0, + NI_RTSI_OUTPUT_ADR_START2 = 1, + NI_RTSI_OUTPUT_SCLKG = 2, + NI_RTSI_OUTPUT_DACUPDN = 3, + NI_RTSI_OUTPUT_DA_START1 = 4, + NI_RTSI_OUTPUT_G_SRC0 = 5, + NI_RTSI_OUTPUT_G_GATE0 = 6, + NI_RTSI_OUTPUT_RGOUT0 = 7, + NI_RTSI_OUTPUT_RTSI_BRD_0 = 8, + NI_RTSI_OUTPUT_RTSI_OSC = 12 /* pre-m-series always have RTSI clock + on line 7 */ +}; +static inline unsigned NI_RTSI_OUTPUT_RTSI_BRD(unsigned n) +{ + return NI_RTSI_OUTPUT_RTSI_BRD_0 + n; +} + +/* Signals which can be routed to an NI PFI pin on an m-series board with + * INSN_CONFIG_SET_ROUTING. These numbers are also returned by + * INSN_CONFIG_GET_ROUTING on pre-m-series boards, even though their routing + * cannot be changed. The numbers assigned are not arbitrary, they correspond + * to the bits required to program the board. */ +enum ni_pfi_routing { + NI_PFI_OUTPUT_PFI_DEFAULT = 0, + NI_PFI_OUTPUT_AI_START1 = 1, + NI_PFI_OUTPUT_AI_START2 = 2, + NI_PFI_OUTPUT_AI_CONVERT = 3, + NI_PFI_OUTPUT_G_SRC1 = 4, + NI_PFI_OUTPUT_G_GATE1 = 5, + NI_PFI_OUTPUT_AO_UPDATE_N = 6, + NI_PFI_OUTPUT_AO_START1 = 7, + NI_PFI_OUTPUT_AI_START_PULSE = 8, + NI_PFI_OUTPUT_G_SRC0 = 9, + NI_PFI_OUTPUT_G_GATE0 = 10, + NI_PFI_OUTPUT_EXT_STROBE = 11, + NI_PFI_OUTPUT_AI_EXT_MUX_CLK = 12, + NI_PFI_OUTPUT_GOUT0 = 13, + NI_PFI_OUTPUT_GOUT1 = 14, + NI_PFI_OUTPUT_FREQ_OUT = 15, + NI_PFI_OUTPUT_PFI_DO = 16, + NI_PFI_OUTPUT_I_ATRIG = 17, + NI_PFI_OUTPUT_RTSI0 = 18, + NI_PFI_OUTPUT_PXI_STAR_TRIGGER_IN = 26, + NI_PFI_OUTPUT_SCXI_TRIG1 = 27, + NI_PFI_OUTPUT_DIO_CHANGE_DETECT_RTSI = 28, + NI_PFI_OUTPUT_CDI_SAMPLE = 29, + NI_PFI_OUTPUT_CDO_UPDATE = 30 +}; +static inline unsigned NI_PFI_OUTPUT_RTSI(unsigned rtsi_channel) +{ + return NI_PFI_OUTPUT_RTSI0 + rtsi_channel; +} + +/* Signals which can be routed to output on a NI PFI pin on a 660x board + with INSN_CONFIG_SET_ROUTING. The numbers assigned are + not arbitrary, they correspond to the bits required + to program the board. Lines 0 to 7 can only be set to + NI_660X_PFI_OUTPUT_DIO. Lines 32 to 39 can only be set to + NI_660X_PFI_OUTPUT_COUNTER. */ +enum ni_660x_pfi_routing { + NI_660X_PFI_OUTPUT_COUNTER = 1, /* counter */ + NI_660X_PFI_OUTPUT_DIO = 2, /* static digital output */ +}; + +/* NI External Trigger lines. These values are not arbitrary, but are related + * to the bits required to program the board (offset by 1 for historical + * reasons). */ +static inline unsigned NI_EXT_PFI(unsigned pfi_channel) +{ + return NI_USUAL_PFI_SELECT(pfi_channel) - 1; +} +static inline unsigned NI_EXT_RTSI(unsigned rtsi_channel) +{ + return NI_USUAL_RTSI_SELECT(rtsi_channel) - 1; +} + +/* status bits for INSN_CONFIG_GET_COUNTER_STATUS */ +enum comedi_counter_status_flags { + COMEDI_COUNTER_ARMED = 0x1, + COMEDI_COUNTER_COUNTING = 0x2, + COMEDI_COUNTER_TERMINAL_COUNT = 0x4, +}; + +/* Clock sources for CDIO subdevice on NI m-series boards. Used as the + * scan_begin_arg for a comedi_command. These sources may also be bitwise-or'd + * with CR_INVERT to change polarity. */ +enum ni_m_series_cdio_scan_begin_src { + NI_CDIO_SCAN_BEGIN_SRC_GROUND = 0, + NI_CDIO_SCAN_BEGIN_SRC_AI_START = 18, + NI_CDIO_SCAN_BEGIN_SRC_AI_CONVERT = 19, + NI_CDIO_SCAN_BEGIN_SRC_PXI_STAR_TRIGGER = 20, + NI_CDIO_SCAN_BEGIN_SRC_G0_OUT = 28, + NI_CDIO_SCAN_BEGIN_SRC_G1_OUT = 29, + NI_CDIO_SCAN_BEGIN_SRC_ANALOG_TRIGGER = 30, + NI_CDIO_SCAN_BEGIN_SRC_AO_UPDATE = 31, + NI_CDIO_SCAN_BEGIN_SRC_FREQ_OUT = 32, + NI_CDIO_SCAN_BEGIN_SRC_DIO_CHANGE_DETECT_IRQ = 33 +}; +static inline unsigned NI_CDIO_SCAN_BEGIN_SRC_PFI(unsigned pfi_channel) +{ + return NI_USUAL_PFI_SELECT(pfi_channel); +} +static inline unsigned NI_CDIO_SCAN_BEGIN_SRC_RTSI(unsigned rtsi_channel) +{ + return NI_USUAL_RTSI_SELECT(rtsi_channel); +} + +/* scan_begin_src for scan_begin_arg==TRIG_EXT with analog output command on NI + * boards. These scan begin sources can also be bitwise-or'd with CR_INVERT to + * change polarity. */ +static inline unsigned NI_AO_SCAN_BEGIN_SRC_PFI(unsigned pfi_channel) +{ + return NI_USUAL_PFI_SELECT(pfi_channel); +} +static inline unsigned NI_AO_SCAN_BEGIN_SRC_RTSI(unsigned rtsi_channel) +{ + return NI_USUAL_RTSI_SELECT(rtsi_channel); +} + +/* Bits for setting a clock source with + * INSN_CONFIG_SET_CLOCK_SRC when using NI frequency output subdevice. */ +enum ni_freq_out_clock_source_bits { + NI_FREQ_OUT_TIMEBASE_1_DIV_2_CLOCK_SRC, /* 10 MHz */ + NI_FREQ_OUT_TIMEBASE_2_CLOCK_SRC /* 100 KHz */ +}; + +/* Values for setting a clock source with INSN_CONFIG_SET_CLOCK_SRC for + * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */ + enum amplc_dio_clock_source { + AMPLC_DIO_CLK_CLKN, /* per channel external clock + input/output pin (pin is only an + input when clock source set to this + value, otherwise it is an output) */ + AMPLC_DIO_CLK_10MHZ, /* 10 MHz internal clock */ + AMPLC_DIO_CLK_1MHZ, /* 1 MHz internal clock */ + AMPLC_DIO_CLK_100KHZ, /* 100 kHz internal clock */ + AMPLC_DIO_CLK_10KHZ, /* 10 kHz internal clock */ + AMPLC_DIO_CLK_1KHZ, /* 1 kHz internal clock */ + AMPLC_DIO_CLK_OUTNM1, /* output of preceding counter channel + (for channel 0, preceding counter + channel is channel 2 on preceding + counter subdevice, for first counter + subdevice, preceding counter + subdevice is the last counter + subdevice) */ + AMPLC_DIO_CLK_EXT /* per chip external input pin */ + }; + +/* Values for setting a gate source with INSN_CONFIG_SET_GATE_SRC for + * 8254 counter subdevices on Amplicon DIO boards (amplc_dio200 driver). */ + enum amplc_dio_gate_source { + AMPLC_DIO_GAT_VCC, /* internal high logic level */ + AMPLC_DIO_GAT_GND, /* internal low logic level */ + AMPLC_DIO_GAT_GATN, /* per channel external gate input */ + AMPLC_DIO_GAT_NOUTNM2, /* negated output of counter channel + minus 2 (for channels 0 or 1, + channel minus 2 is channel 1 or 2 on + the preceding counter subdevice, for + the first counter subdevice the + preceding counter subdevice is the + last counter subdevice) */ + AMPLC_DIO_GAT_RESERVED4, + AMPLC_DIO_GAT_RESERVED5, + AMPLC_DIO_GAT_RESERVED6, + AMPLC_DIO_GAT_RESERVED7 + }; + +#ifdef __cplusplus +} +#endif + +#endif /* _COMEDI_H */ diff --git a/drivers/staging/comedi/comedi_compat32.c b/drivers/staging/comedi/comedi_compat32.c new file mode 100644 index 00000000000..7d0116bcb9f --- /dev/null +++ b/drivers/staging/comedi/comedi_compat32.c @@ -0,0 +1,597 @@ +/* + comedi/comedi_compat32.c + 32-bit ioctl compatibility for 64-bit comedi kernel module. + + Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk> + Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/> + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2007 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. + +*/ + +#define __NO_VERSION__ +#include "comedi.h" +#include <linux/smp_lock.h> +#include <asm/uaccess.h> + +#include "comedi_compat32.h" + +#ifdef CONFIG_COMPAT + +#ifndef HAVE_COMPAT_IOCTL +#include <linux/ioctl32.h> /* for (un)register_ioctl32_conversion */ +#endif + +#define COMEDI32_CHANINFO _IOR(CIO,3,comedi32_chaninfo) +#define COMEDI32_RANGEINFO _IOR(CIO,8,comedi32_rangeinfo) +/* N.B. COMEDI32_CMD and COMEDI_CMD ought to use _IOWR, not _IOR. + * It's too late to change it now, but it only affects the command number. */ +#define COMEDI32_CMD _IOR(CIO,9,comedi32_cmd) +/* N.B. COMEDI32_CMDTEST and COMEDI_CMDTEST ought to use _IOWR, not _IOR. + * It's too late to change it now, but it only affects the command number. */ +#define COMEDI32_CMDTEST _IOR(CIO,10,comedi32_cmd) +#define COMEDI32_INSNLIST _IOR(CIO,11,comedi32_insnlist) +#define COMEDI32_INSN _IOR(CIO,12,comedi32_insn) + +typedef struct comedi32_chaninfo_struct { + unsigned int subdev; + compat_uptr_t maxdata_list; /* 32-bit 'lsampl_t *' */ + compat_uptr_t flaglist; /* 32-bit 'unsigned int *' */ + compat_uptr_t rangelist; /* 32-bit 'unsigned int *' */ + unsigned int unused[4]; +} comedi32_chaninfo; + +typedef struct comedi32_rangeinfo_struct { + unsigned int range_type; + compat_uptr_t range_ptr; /* 32-bit 'void *' */ +} comedi32_rangeinfo; + +typedef struct comedi32_cmd_struct { + unsigned int subdev; + unsigned int flags; + unsigned int start_src; + unsigned int start_arg; + unsigned int scan_begin_src; + unsigned int scan_begin_arg; + unsigned int convert_src; + unsigned int convert_arg; + unsigned int scan_end_src; + unsigned int scan_end_arg; + unsigned int stop_src; + unsigned int stop_arg; + compat_uptr_t chanlist; /* 32-bit 'unsigned int *' */ + unsigned int chanlist_len; + compat_uptr_t data; /* 32-bit 'sampl_t *' */ + unsigned int data_len; +} comedi32_cmd; + +typedef struct comedi32_insn_struct { + unsigned int insn; + unsigned int n; + compat_uptr_t data; /* 32-bit 'lsampl_t *' */ + unsigned int subdev; + unsigned int chanspec; + unsigned int unused[3]; +} comedi32_insn; + +typedef struct comedi32_insnlist_struct { + unsigned int n_insns; + compat_uptr_t insns; /* 32-bit 'comedi_insn *' */ +} comedi32_insnlist; + +/* Handle translated ioctl. */ +static int translated_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + if (!file->f_op) { + return -ENOTTY; + } +#ifdef HAVE_UNLOCKED_IOCTL + if (file->f_op->unlocked_ioctl) { + int rc = (int)(*file->f_op->unlocked_ioctl)(file, cmd, arg); + if (rc == -ENOIOCTLCMD) { + rc = -ENOTTY; + } + return rc; + } +#endif + if (file->f_op->ioctl) { + int rc; + lock_kernel(); + rc = (*file->f_op->ioctl)(file->f_dentry->d_inode, + file, cmd, arg); + unlock_kernel(); + return rc; + } + return -ENOTTY; +} + +/* Handle 32-bit COMEDI_CHANINFO ioctl. */ +static int compat_chaninfo(struct file *file, unsigned long arg) +{ + comedi_chaninfo __user *chaninfo; + comedi32_chaninfo __user *chaninfo32; + int err; + union { + unsigned int uint; + compat_uptr_t uptr; + } temp; + + chaninfo32 = compat_ptr(arg); + chaninfo = compat_alloc_user_space(sizeof(*chaninfo)); + + /* Copy chaninfo structure. Ignore unused members. */ + if (!access_ok(VERIFY_READ, chaninfo32, sizeof(*chaninfo32)) + || !access_ok(VERIFY_WRITE, chaninfo, + sizeof(*chaninfo))) { + return -EFAULT; + } + err = 0; + err |= __get_user(temp.uint, &chaninfo32->subdev); + err |= __put_user(temp.uint, &chaninfo->subdev); + err |= __get_user(temp.uptr, &chaninfo32->maxdata_list); + err |= __put_user(compat_ptr(temp.uptr), &chaninfo->maxdata_list); + err |= __get_user(temp.uptr, &chaninfo32->flaglist); + err |= __put_user(compat_ptr(temp.uptr), &chaninfo->flaglist); + err |= __get_user(temp.uptr, &chaninfo32->rangelist); + err |= __put_user(compat_ptr(temp.uptr), &chaninfo->rangelist); + if (err) { + return -EFAULT; + } + + return translated_ioctl(file, COMEDI_CHANINFO, (unsigned long)chaninfo); +} + +/* Handle 32-bit COMEDI_RANGEINFO ioctl. */ +static int compat_rangeinfo(struct file *file, unsigned long arg) +{ + comedi_rangeinfo __user *rangeinfo; + comedi32_rangeinfo __user *rangeinfo32; + int err; + union { + unsigned int uint; + compat_uptr_t uptr; + } temp; + + rangeinfo32 = compat_ptr(arg); + rangeinfo = compat_alloc_user_space(sizeof(*rangeinfo)); + + /* Copy rangeinfo structure. */ + if (!access_ok(VERIFY_READ, rangeinfo32, sizeof(*rangeinfo32)) + || !access_ok(VERIFY_WRITE, rangeinfo, + sizeof(*rangeinfo))) { + return -EFAULT; + } + err = 0; + err |= __get_user(temp.uint, &rangeinfo32->range_type); + err |= __put_user(temp.uint, &rangeinfo->range_type); + err |= __get_user(temp.uptr, &rangeinfo32->range_ptr); + err |= __put_user(compat_ptr(temp.uptr), &rangeinfo->range_ptr); + if (err) { + return -EFAULT; + } + + return translated_ioctl(file, COMEDI_RANGEINFO, + (unsigned long)rangeinfo); +} + +/* Copy 32-bit cmd structure to native cmd structure. */ +static int get_compat_cmd(comedi_cmd __user *cmd, + comedi32_cmd __user *cmd32) +{ + int err; + union { + unsigned int uint; + compat_uptr_t uptr; + } temp; + + /* Copy cmd structure. */ + if (!access_ok(VERIFY_READ, cmd32, sizeof(*cmd32)) + || !access_ok(VERIFY_WRITE, cmd, sizeof(*cmd))) { + return -EFAULT; + } + err = 0; + err |= __get_user(temp.uint, &cmd32->subdev); + err |= __put_user(temp.uint, &cmd->subdev); + err |= __get_user(temp.uint, &cmd32->flags); + err |= __put_user(temp.uint, &cmd->flags); + err |= __get_user(temp.uint, &cmd32->start_src); + err |= __put_user(temp.uint, &cmd->start_src); + err |= __get_user(temp.uint, &cmd32->start_arg); + err |= __put_user(temp.uint, &cmd->start_arg); + err |= __get_user(temp.uint, &cmd32->scan_begin_src); + err |= __put_user(temp.uint, &cmd->scan_begin_src); + err |= __get_user(temp.uint, &cmd32->scan_begin_arg); + err |= __put_user(temp.uint, &cmd->scan_begin_arg); + err |= __get_user(temp.uint, &cmd32->convert_src); + err |= __put_user(temp.uint, &cmd->convert_src); + err |= __get_user(temp.uint, &cmd32->convert_arg); + err |= __put_user(temp.uint, &cmd->convert_arg); + err |= __get_user(temp.uint, &cmd32->scan_end_src); + err |= __put_user(temp.uint, &cmd->scan_end_src); + err |= __get_user(temp.uint, &cmd32->scan_end_arg); + err |= __put_user(temp.uint, &cmd->scan_end_arg); + err |= __get_user(temp.uint, &cmd32->stop_src); + err |= __put_user(temp.uint, &cmd->stop_src); + err |= __get_user(temp.uint, &cmd32->stop_arg); + err |= __put_user(temp.uint, &cmd->stop_arg); + err |= __get_user(temp.uptr, &cmd32->chanlist); + err |= __put_user(compat_ptr(temp.uptr), &cmd->chanlist); + err |= __get_user(temp.uint, &cmd32->chanlist_len); + err |= __put_user(temp.uint, &cmd->chanlist_len); + err |= __get_user(temp.uptr, &cmd32->data); + err |= __put_user(compat_ptr(temp.uptr), &cmd->data); + err |= __get_user(temp.uint, &cmd32->data_len); + err |= __put_user(temp.uint, &cmd->data_len); + return err ? -EFAULT : 0; +} + +/* Copy native cmd structure to 32-bit cmd structure. */ +static int put_compat_cmd(comedi32_cmd __user *cmd32, comedi_cmd __user *cmd) +{ + int err; + unsigned int temp; + + /* Copy back most of cmd structure. */ + /* Assume the pointer values are already valid. */ + /* (Could use ptr_to_compat() to set them, but that wasn't implemented + * until kernel version 2.6.11.) */ + if (!access_ok(VERIFY_READ, cmd, sizeof(*cmd)) + || !access_ok(VERIFY_WRITE, cmd32, sizeof(*cmd32))) { + return -EFAULT; + } + err = 0; + err |= __get_user(temp, &cmd->subdev); + err |= __put_user(temp, &cmd32->subdev); + err |= __get_user(temp, &cmd->flags); + err |= __put_user(temp, &cmd32->flags); + err |= __get_user(temp, &cmd->start_src); + err |= __put_user(temp, &cmd32->start_src); + err |= __get_user(temp, &cmd->start_arg); + err |= __put_user(temp, &cmd32->start_arg); + err |= __get_user(temp, &cmd->scan_begin_src); + err |= __put_user(temp, &cmd32->scan_begin_src); + err |= __get_user(temp, &cmd->scan_begin_arg); + err |= __put_user(temp, &cmd32->scan_begin_arg); + err |= __get_user(temp, &cmd->convert_src); + err |= __put_user(temp, &cmd32->convert_src); + err |= __get_user(temp, &cmd->convert_arg); + err |= __put_user(temp, &cmd32->convert_arg); + err |= __get_user(temp, &cmd->scan_end_src); + err |= __put_user(temp, &cmd32->scan_end_src); + err |= __get_user(temp, &cmd->scan_end_arg); + err |= __put_user(temp, &cmd32->scan_end_arg); + err |= __get_user(temp, &cmd->stop_src); + err |= __put_user(temp, &cmd32->stop_src); + err |= __get_user(temp, &cmd->stop_arg); + err |= __put_user(temp, &cmd32->stop_arg); + /* Assume chanlist pointer is unchanged. */ + err |= __get_user(temp, &cmd->chanlist_len); + err |= __put_user(temp, &cmd32->chanlist_len); + /* Assume data pointer is unchanged. */ + err |= __get_user(temp, &cmd->data_len); + err |= __put_user(temp, &cmd32->data_len); + return err ? -EFAULT : 0; +} + +/* Handle 32-bit COMEDI_CMD ioctl. */ +static int compat_cmd(struct file *file, unsigned long arg) +{ + comedi_cmd __user *cmd; + comedi32_cmd __user *cmd32; + int rc; + + cmd32 = compat_ptr(arg); + cmd = compat_alloc_user_space(sizeof(*cmd)); + + rc = get_compat_cmd(cmd, cmd32); + if (rc) { + return rc; + } + + return translated_ioctl(file, COMEDI_CMD, (unsigned long)cmd); +} + +/* Handle 32-bit COMEDI_CMDTEST ioctl. */ +static int compat_cmdtest(struct file *file, unsigned long arg) +{ + comedi_cmd __user *cmd; + comedi32_cmd __user *cmd32; + int rc, err; + + cmd32 = compat_ptr(arg); + cmd = compat_alloc_user_space(sizeof(*cmd)); + + rc = get_compat_cmd(cmd, cmd32); + if (rc) { + return rc; + } + + rc = translated_ioctl(file, COMEDI_CMDTEST, (unsigned long)cmd); + if (rc < 0) { + return rc; + } + + err = put_compat_cmd(cmd32, cmd); + if (err) { + rc = err; + } + return rc; +} + +/* Copy 32-bit insn structure to native insn structure. */ +static int get_compat_insn(comedi_insn __user *insn, + comedi32_insn __user *insn32) +{ + int err; + union { + unsigned int uint; + compat_uptr_t uptr; + } temp; + + /* Copy insn structure. Ignore the unused members. */ + err = 0; + if (!access_ok(VERIFY_READ, insn32, sizeof(*insn32)) + || !access_ok(VERIFY_WRITE, insn, sizeof(*insn))) { + return -EFAULT; + } + err |= __get_user(temp.uint, &insn32->insn); + err |= __put_user(temp.uint, &insn->insn); + err |= __get_user(temp.uint, &insn32->n); + err |= __put_user(temp.uint, &insn->n); + err |= __get_user(temp.uptr, &insn32->data); + err |= __put_user(compat_ptr(temp.uptr), &insn->data); + err |= __get_user(temp.uint, &insn32->subdev); + err |= __put_user(temp.uint, &insn->subdev); + err |= __get_user(temp.uint, &insn32->chanspec); + err |= __put_user(temp.uint, &insn->chanspec); + return err ? -EFAULT : 0; +} + +/* Handle 32-bit COMEDI_INSNLIST ioctl. */ +static int compat_insnlist(struct file *file, unsigned long arg) +{ + struct combined_insnlist { + comedi_insnlist insnlist; + comedi_insn insn[1]; + } __user *s; + comedi32_insnlist __user *insnlist32; + comedi32_insn __user *insn32; + compat_uptr_t uptr; + unsigned int n_insns, n; + int err, rc; + + insnlist32 = compat_ptr(arg); + + /* Get 32-bit insnlist structure. */ + if (!access_ok(VERIFY_READ, insnlist32, sizeof(*insnlist32))) { + return -EFAULT; + } + err = 0; + err |= __get_user(n_insns, &insnlist32->n_insns); + err |= __get_user(uptr, &insnlist32->insns); + insn32 = compat_ptr(uptr); + if (err) { + return -EFAULT; + } + + /* Allocate user memory to copy insnlist and insns into. */ + s = compat_alloc_user_space(offsetof(struct combined_insnlist, + insn[n_insns])); + + /* Set native insnlist structure. */ + if (!access_ok(VERIFY_WRITE, &s->insnlist, sizeof(s->insnlist))) { + return -EFAULT; + } + err |= __put_user(n_insns, &s->insnlist.n_insns); + err |= __put_user(&s->insn[0], &s->insnlist.insns); + if (err) { + return -EFAULT; + } + + /* Copy insn structures. */ + for (n = 0; n < n_insns; n++) { + rc = get_compat_insn(&s->insn[n], &insn32[n]); + if (rc) { + return rc; + } + } + + return translated_ioctl(file, COMEDI_INSNLIST, + (unsigned long)&s->insnlist); +} + +/* Handle 32-bit COMEDI_INSN ioctl. */ +static int compat_insn(struct file *file, unsigned long arg) +{ + comedi_insn __user *insn; + comedi32_insn __user *insn32; + int rc; + + insn32 = compat_ptr(arg); + insn = compat_alloc_user_space(sizeof(*insn)); + + rc = get_compat_insn(insn, insn32); + if (rc) { + return rc; + } + + return translated_ioctl(file, COMEDI_INSN, (unsigned long)insn); +} + +/* Process untranslated ioctl. */ +/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */ +static inline int raw_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + int rc; + + switch (cmd) { + case COMEDI_DEVCONFIG: + case COMEDI_DEVINFO: + case COMEDI_SUBDINFO: + case COMEDI_BUFCONFIG: + case COMEDI_BUFINFO: + /* Just need to translate the pointer argument. */ + arg = (unsigned long)compat_ptr(arg); + rc = translated_ioctl(file, cmd, arg); + break; + case COMEDI_LOCK: + case COMEDI_UNLOCK: + case COMEDI_CANCEL: + case COMEDI_POLL: + /* No translation needed. */ + rc = translated_ioctl(file, cmd, arg); + break; + case COMEDI32_CHANINFO: + rc = compat_chaninfo(file, arg); + break; + case COMEDI32_RANGEINFO: + rc = compat_rangeinfo(file, arg); + break; + case COMEDI32_CMD: + rc = compat_cmd(file, arg); + break; + case COMEDI32_CMDTEST: + rc = compat_cmdtest(file, arg); + break; + case COMEDI32_INSNLIST: + rc = compat_insnlist(file, arg); + break; + case COMEDI32_INSN: + rc = compat_insn(file, arg); + break; + default: + rc = -ENOIOCTLCMD; + break; + } + return rc; +} + +#ifdef HAVE_COMPAT_IOCTL /* defined in <linux/fs.h> 2.6.11 onwards */ + +/* compat_ioctl file operation. */ +/* Returns -ENOIOCTLCMD for unrecognised ioctl codes. */ +long comedi_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + return raw_ioctl(file, cmd, arg); +} + +#else /* HAVE_COMPAT_IOCTL */ + +/* + * Brain-dead ioctl compatibility for 2.6.10 and earlier. + * + * It's brain-dead because cmd numbers need to be unique system-wide! + * The comedi driver could end up attempting to execute ioctls for non-Comedi + * devices because it registered the system-wide cmd code first. Similarly, + * another driver could end up attempting to execute ioctls for a Comedi + * device because it registered the cmd code first. Chaos ensues. + */ + +/* Handler for all 32-bit ioctl codes registered by this driver. */ +static int mapped_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg, + struct file *file) +{ + int rc; + + /* Make sure we are dealing with a Comedi device. */ + if (imajor(file->f_dentry->d_inode) != COMEDI_MAJOR) { + return -ENOTTY; + } + rc = raw_ioctl(file, cmd, arg); + /* Do not return -ENOIOCTLCMD. */ + if (rc == -ENOIOCTLCMD) { + rc = -ENOTTY; + } + return rc; +} + +struct ioctl32_map { + unsigned int cmd; + int (*handler)(unsigned int, unsigned int, unsigned long, + struct file *); + int registered; +}; + +static struct ioctl32_map comedi_ioctl32_map[] = { + { COMEDI_DEVCONFIG, mapped_ioctl, 0 }, + { COMEDI_DEVINFO, mapped_ioctl, 0 }, + { COMEDI_SUBDINFO, mapped_ioctl, 0 }, + { COMEDI_BUFCONFIG, mapped_ioctl, 0 }, + { COMEDI_BUFINFO, mapped_ioctl, 0 }, + { COMEDI_LOCK, mapped_ioctl, 0 }, + { COMEDI_UNLOCK, mapped_ioctl, 0 }, + { COMEDI_CANCEL, mapped_ioctl, 0 }, + { COMEDI_POLL, mapped_ioctl, 0 }, + { COMEDI32_CHANINFO, mapped_ioctl, 0 }, + { COMEDI32_RANGEINFO, mapped_ioctl, 0 }, + { COMEDI32_CMD, mapped_ioctl, 0 }, + { COMEDI32_CMDTEST, mapped_ioctl, 0 }, + { COMEDI32_INSNLIST, mapped_ioctl, 0 }, + { COMEDI32_INSN, mapped_ioctl, 0 }, +}; + +#define NUM_IOCTL32_MAPS ARRAY_SIZE(comedi_ioctl32_map) + +/* Register system-wide 32-bit ioctl handlers. */ +void comedi_register_ioctl32(void) +{ + int n, rc; + + for (n = 0; n < NUM_IOCTL32_MAPS; n++) { + rc = register_ioctl32_conversion(comedi_ioctl32_map[n].cmd, + comedi_ioctl32_map[n].handler); + if (rc) { + printk(KERN_WARNING + "comedi: failed to register 32-bit " + "compatible ioctl handler for 0x%X - " + "expect bad things to happen!\n", + comedi_ioctl32_map[n].cmd); + } + comedi_ioctl32_map[n].registered = !rc; + } +} + +/* Unregister system-wide 32-bit ioctl translations. */ +void comedi_unregister_ioctl32(void) +{ + int n, rc; + + for (n = 0; n < NUM_IOCTL32_MAPS; n++) { + if (comedi_ioctl32_map[n].registered) { + rc = unregister_ioctl32_conversion( + comedi_ioctl32_map[n].cmd, + comedi_ioctl32_map[n].handler); + if (rc) { + printk(KERN_ERR + "comedi: failed to unregister 32-bit " + "compatible ioctl handler for 0x%X - " + "expect kernel Oops!\n", + comedi_ioctl32_map[n].cmd); + } else { + comedi_ioctl32_map[n].registered = 0; + } + } + } +} + +#endif /* HAVE_COMPAT_IOCTL */ + +#endif /* CONFIG_COMPAT */ diff --git a/drivers/staging/comedi/comedi_compat32.h b/drivers/staging/comedi/comedi_compat32.h new file mode 100644 index 00000000000..0ca01642c16 --- /dev/null +++ b/drivers/staging/comedi/comedi_compat32.h @@ -0,0 +1,58 @@ +/* + comedi/comedi_compat32.h + 32-bit ioctl compatibility for 64-bit comedi kernel module. + + Author: Ian Abbott, MEV Ltd. <abbotti@mev.co.uk> + Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/> + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2007 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. + +*/ + +#ifndef _COMEDI_COMPAT32_H +#define _COMEDI_COMPAT32_H + +#include <linux/compat.h> +#include <linux/fs.h> /* For HAVE_COMPAT_IOCTL and HAVE_UNLOCKED_IOCTL */ + +#ifdef CONFIG_COMPAT + +#ifdef HAVE_COMPAT_IOCTL + +extern long comedi_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg); +#define comedi_register_ioctl32() do {} while (0) +#define comedi_unregister_ioctl32() do {} while (0) + +#else /* HAVE_COMPAT_IOCTL */ + +#define comedi_compat_ioctl 0 /* NULL */ +extern void comedi_register_ioctl32(void); +extern void comedi_unregister_ioctl32(void); + +#endif /* HAVE_COMPAT_IOCTL */ + +#else /* CONFIG_COMPAT */ + +#define comedi_compat_ioctl 0 /* NULL */ +#define comedi_register_ioctl32() do {} while (0) +#define comedi_unregister_ioctl32() do {} while (0) + +#endif /* CONFIG_COMPAT */ + +#endif /* _COMEDI_COMPAT32_H */ diff --git a/drivers/staging/comedi/comedi_fops.c b/drivers/staging/comedi/comedi_fops.c new file mode 100644 index 00000000000..018c964396d --- /dev/null +++ b/drivers/staging/comedi/comedi_fops.c @@ -0,0 +1,2244 @@ +/* + comedi/comedi_fops.c + comedi kernel module + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#undef DEBUG + +#define __NO_VERSION__ +#include "comedi_fops.h" +#include "comedi_compat32.h" + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/fcntl.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/kmod.h> +#include <linux/poll.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/vmalloc.h> +#include <linux/fs.h> +#include "comedidev.h" +#include <linux/cdev.h> + +#include <linux/io.h> +#include <linux/uaccess.h> + +/* #include "kvmem.h" */ + +MODULE_AUTHOR("http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi core module"); +MODULE_LICENSE("GPL"); + +#ifdef CONFIG_COMEDI_DEBUG +int comedi_debug; +module_param(comedi_debug, int, 0644); +#endif + +static DEFINE_SPINLOCK(comedi_file_info_table_lock); +static struct comedi_device_file_info + *comedi_file_info_table[COMEDI_NUM_MINORS]; + +static int do_devconfig_ioctl(comedi_device *dev, comedi_devconfig *arg); +static int do_bufconfig_ioctl(comedi_device *dev, void *arg); +static int do_devinfo_ioctl(comedi_device *dev, comedi_devinfo *arg, + struct file *file); +static int do_subdinfo_ioctl(comedi_device *dev, comedi_subdinfo *arg, + void *file); +static int do_chaninfo_ioctl(comedi_device *dev, comedi_chaninfo *arg); +static int do_bufinfo_ioctl(comedi_device *dev, void *arg); +static int do_cmd_ioctl(comedi_device *dev, void *arg, void *file); +static int do_lock_ioctl(comedi_device *dev, unsigned int arg, void *file); +static int do_unlock_ioctl(comedi_device *dev, unsigned int arg, void *file); +static int do_cancel_ioctl(comedi_device *dev, unsigned int arg, void *file); +static int do_cmdtest_ioctl(comedi_device *dev, void *arg, void *file); +static int do_insnlist_ioctl(comedi_device *dev, void *arg, void *file); +static int do_insn_ioctl(comedi_device *dev, void *arg, void *file); +static int do_poll_ioctl(comedi_device *dev, unsigned int subd, void *file); + +extern void do_become_nonbusy(comedi_device *dev, comedi_subdevice *s); +static int do_cancel(comedi_device *dev, comedi_subdevice *s); + +static int comedi_fasync(int fd, struct file *file, int on); + +static int is_device_busy(comedi_device *dev); + +#ifdef HAVE_UNLOCKED_IOCTL +static long comedi_unlocked_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +#else +static int comedi_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +#endif +{ + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + comedi_device *dev = dev_file_info->device; + int rc; + + mutex_lock(&dev->mutex); + + /* Device config is special, because it must work on + * an unconfigured device. */ + if (cmd == COMEDI_DEVCONFIG) { + rc = do_devconfig_ioctl(dev, (void *)arg); + goto done; + } + + if (!dev->attached) { + DPRINTK("no driver configured on /dev/comedi%i\n", dev->minor); + rc = -ENODEV; + goto done; + } + + switch (cmd) { + case COMEDI_BUFCONFIG: + rc = do_bufconfig_ioctl(dev, (void *)arg); + break; + case COMEDI_DEVINFO: + rc = do_devinfo_ioctl(dev, (void *)arg, file); + break; + case COMEDI_SUBDINFO: + rc = do_subdinfo_ioctl(dev, (void *)arg, file); + break; + case COMEDI_CHANINFO: + rc = do_chaninfo_ioctl(dev, (void *)arg); + break; + case COMEDI_RANGEINFO: + rc = do_rangeinfo_ioctl(dev, (void *)arg); + break; + case COMEDI_BUFINFO: + rc = do_bufinfo_ioctl(dev, (void *)arg); + break; + case COMEDI_LOCK: + rc = do_lock_ioctl(dev, arg, file); + break; + case COMEDI_UNLOCK: + rc = do_unlock_ioctl(dev, arg, file); + break; + case COMEDI_CANCEL: + rc = do_cancel_ioctl(dev, arg, file); + break; + case COMEDI_CMD: + rc = do_cmd_ioctl(dev, (void *)arg, file); + break; + case COMEDI_CMDTEST: + rc = do_cmdtest_ioctl(dev, (void *)arg, file); + break; + case COMEDI_INSNLIST: + rc = do_insnlist_ioctl(dev, (void *)arg, file); + break; + case COMEDI_INSN: + rc = do_insn_ioctl(dev, (void *)arg, file); + break; + case COMEDI_POLL: + rc = do_poll_ioctl(dev, arg, file); + break; + default: + rc = -ENOTTY; + break; + } + +done: + mutex_unlock(&dev->mutex); + return rc; +} + +/* + COMEDI_DEVCONFIG + device config ioctl + + arg: + pointer to devconfig structure + + reads: + devconfig structure at arg + + writes: + none +*/ +static int do_devconfig_ioctl(comedi_device *dev, comedi_devconfig *arg) +{ + comedi_devconfig it; + int ret; + unsigned char *aux_data = NULL; + int aux_len; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (arg == NULL) { + if (is_device_busy(dev)) + return -EBUSY; + if (dev->attached) { + struct module *driver_module = dev->driver->module; + comedi_device_detach(dev); + module_put(driver_module); + } + return 0; + } + + if (copy_from_user(&it, arg, sizeof(comedi_devconfig))) + return -EFAULT; + + it.board_name[COMEDI_NAMELEN - 1] = 0; + + if (comedi_aux_data(it.options, 0) && + it.options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { + int bit_shift; + aux_len = it.options[COMEDI_DEVCONF_AUX_DATA_LENGTH]; + if (aux_len < 0) + return -EFAULT; + + aux_data = vmalloc(aux_len); + if (!aux_data) + return -ENOMEM; + + if (copy_from_user(aux_data, + comedi_aux_data(it.options, 0), aux_len)) { + vfree(aux_data); + return -EFAULT; + } + it.options[COMEDI_DEVCONF_AUX_DATA_LO] = + (unsigned long)aux_data; + if (sizeof(void *) > sizeof(int)) { + bit_shift = sizeof(int) * 8; + it.options[COMEDI_DEVCONF_AUX_DATA_HI] = + ((unsigned long)aux_data) >> bit_shift; + } else + it.options[COMEDI_DEVCONF_AUX_DATA_HI] = 0; + } + + ret = comedi_device_attach(dev, &it); + if (ret == 0) { + if (!try_module_get(dev->driver->module)) { + comedi_device_detach(dev); + return -ENOSYS; + } + } + + if (aux_data) + vfree(aux_data); + + return ret; +} + +/* + COMEDI_BUFCONFIG + buffer configuration ioctl + + arg: + pointer to bufconfig structure + + reads: + bufconfig at arg + + writes: + modified bufconfig at arg + +*/ +static int do_bufconfig_ioctl(comedi_device *dev, void *arg) +{ + comedi_bufconfig bc; + comedi_async *async; + comedi_subdevice *s; + int ret = 0; + + if (copy_from_user(&bc, arg, sizeof(comedi_bufconfig))) + return -EFAULT; + + if (bc.subdevice >= dev->n_subdevices || bc.subdevice < 0) + return -EINVAL; + + s = dev->subdevices + bc.subdevice; + async = s->async; + + if (!async) { + DPRINTK("subdevice does not have async capability\n"); + bc.size = 0; + bc.maximum_size = 0; + goto copyback; + } + + if (bc.maximum_size) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + async->max_bufsize = bc.maximum_size; + } + + if (bc.size) { + if (bc.size > async->max_bufsize) + return -EPERM; + + if (s->busy) { + DPRINTK("subdevice is busy, cannot resize buffer\n"); + return -EBUSY; + } + if (async->mmap_count) { + DPRINTK("subdevice is mmapped, cannot resize buffer\n"); + return -EBUSY; + } + + if (!async->prealloc_buf) + return -EINVAL; + + /* make sure buffer is an integral number of pages + * (we round up) */ + bc.size = (bc.size + PAGE_SIZE - 1) & PAGE_MASK; + + ret = comedi_buf_alloc(dev, s, bc.size); + if (ret < 0) + return ret; + + if (s->buf_change) { + ret = s->buf_change(dev, s, bc.size); + if (ret < 0) + return ret; + } + + DPRINTK("comedi%i subd %d buffer resized to %i bytes\n", + dev->minor, bc.subdevice, async->prealloc_bufsz); + } + + bc.size = async->prealloc_bufsz; + bc.maximum_size = async->max_bufsize; + +copyback: + if (copy_to_user(arg, &bc, sizeof(comedi_bufconfig))) + return -EFAULT; + + return 0; +} + +/* + COMEDI_DEVINFO + device info ioctl + + arg: + pointer to devinfo structure + + reads: + none + + writes: + devinfo structure + +*/ +static int do_devinfo_ioctl(comedi_device *dev, comedi_devinfo *arg, + struct file *file) +{ + comedi_devinfo devinfo; + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + comedi_subdevice *read_subdev = + comedi_get_read_subdevice(dev_file_info); + comedi_subdevice *write_subdev = + comedi_get_write_subdevice(dev_file_info); + + memset(&devinfo, 0, sizeof(devinfo)); + + /* fill devinfo structure */ + devinfo.version_code = COMEDI_VERSION_CODE; + devinfo.n_subdevs = dev->n_subdevices; + memcpy(devinfo.driver_name, dev->driver->driver_name, COMEDI_NAMELEN); + memcpy(devinfo.board_name, dev->board_name, COMEDI_NAMELEN); + + if (read_subdev) + devinfo.read_subdevice = read_subdev - dev->subdevices; + else + devinfo.read_subdevice = -1; + + if (write_subdev) + devinfo.write_subdevice = write_subdev - dev->subdevices; + else + devinfo.write_subdevice = -1; + + if (copy_to_user(arg, &devinfo, sizeof(comedi_devinfo))) + return -EFAULT; + + return 0; +} + +/* + COMEDI_SUBDINFO + subdevice info ioctl + + arg: + pointer to array of subdevice info structures + + reads: + none + + writes: + array of subdevice info structures at arg + +*/ +static int do_subdinfo_ioctl(comedi_device *dev, comedi_subdinfo *arg, + void *file) +{ + int ret, i; + comedi_subdinfo *tmp, *us; + comedi_subdevice *s; + + tmp = kcalloc(dev->n_subdevices, sizeof(comedi_subdinfo), GFP_KERNEL); + if (!tmp) + return -ENOMEM; + + /* fill subdinfo structs */ + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + us = tmp + i; + + us->type = s->type; + us->n_chan = s->n_chan; + us->subd_flags = s->subdev_flags; + if (comedi_get_subdevice_runflags(s) & SRF_RUNNING) + us->subd_flags |= SDF_RUNNING; +#define TIMER_nanosec 5 /* backwards compatibility */ + us->timer_type = TIMER_nanosec; + us->len_chanlist = s->len_chanlist; + us->maxdata = s->maxdata; + if (s->range_table) { + us->range_type = + (i << 24) | (0 << 16) | (s->range_table->length); + } else { + us->range_type = 0; /* XXX */ + } + us->flags = s->flags; + + if (s->busy) + us->subd_flags |= SDF_BUSY; + if (s->busy == file) + us->subd_flags |= SDF_BUSY_OWNER; + if (s->lock) + us->subd_flags |= SDF_LOCKED; + if (s->lock == file) + us->subd_flags |= SDF_LOCK_OWNER; + if (!s->maxdata && s->maxdata_list) + us->subd_flags |= SDF_MAXDATA; + if (s->flaglist) + us->subd_flags |= SDF_FLAGS; + if (s->range_table_list) + us->subd_flags |= SDF_RANGETYPE; + if (s->do_cmd) + us->subd_flags |= SDF_CMD; + + if (s->insn_bits != &insn_inval) + us->insn_bits_support = COMEDI_SUPPORTED; + else + us->insn_bits_support = COMEDI_UNSUPPORTED; + + us->settling_time_0 = s->settling_time_0; + } + + ret = copy_to_user(arg, tmp, + dev->n_subdevices * sizeof(comedi_subdinfo)); + + kfree(tmp); + + return ret ? -EFAULT : 0; +} + +/* + COMEDI_CHANINFO + subdevice info ioctl + + arg: + pointer to chaninfo structure + + reads: + chaninfo structure at arg + + writes: + arrays at elements of chaninfo structure + +*/ +static int do_chaninfo_ioctl(comedi_device *dev, comedi_chaninfo *arg) +{ + comedi_subdevice *s; + comedi_chaninfo it; + + if (copy_from_user(&it, arg, sizeof(comedi_chaninfo))) + return -EFAULT; + + if (it.subdev >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + it.subdev; + + if (it.maxdata_list) { + if (s->maxdata || !s->maxdata_list) + return -EINVAL; + if (copy_to_user(it.maxdata_list, s->maxdata_list, + s->n_chan * sizeof(lsampl_t))) + return -EFAULT; + } + + if (it.flaglist) { + if (!s->flaglist) + return -EINVAL; + if (copy_to_user(it.flaglist, s->flaglist, + s->n_chan * sizeof(unsigned int))) + return -EFAULT; + } + + if (it.rangelist) { + int i; + + if (!s->range_table_list) + return -EINVAL; + for (i = 0; i < s->n_chan; i++) { + int x; + + x = (dev->minor << 28) | (it.subdev << 24) | (i << 16) | + (s->range_table_list[i]->length); + put_user(x, it.rangelist + i); + } +#if 0 + if (copy_to_user(it.rangelist, s->range_type_list, + s->n_chan*sizeof(unsigned int))) + return -EFAULT; +#endif + } + + return 0; +} + + /* + COMEDI_BUFINFO + buffer information ioctl + + arg: + pointer to bufinfo structure + + reads: + bufinfo at arg + + writes: + modified bufinfo at arg + + */ +static int do_bufinfo_ioctl(comedi_device *dev, void *arg) +{ + comedi_bufinfo bi; + comedi_subdevice *s; + comedi_async *async; + + if (copy_from_user(&bi, arg, sizeof(comedi_bufinfo))) + return -EFAULT; + + if (bi.subdevice >= dev->n_subdevices || bi.subdevice < 0) + return -EINVAL; + + s = dev->subdevices + bi.subdevice; + async = s->async; + + if (!async) { + DPRINTK("subdevice does not have async capability\n"); + bi.buf_write_ptr = 0; + bi.buf_read_ptr = 0; + bi.buf_write_count = 0; + bi.buf_read_count = 0; + goto copyback; + } + + if (bi.bytes_read && (s->subdev_flags & SDF_CMD_READ)) { + bi.bytes_read = comedi_buf_read_alloc(async, bi.bytes_read); + comedi_buf_read_free(async, bi.bytes_read); + + if (!(comedi_get_subdevice_runflags(s) & (SRF_ERROR | + SRF_RUNNING)) + && async->buf_write_count == async->buf_read_count) { + do_become_nonbusy(dev, s); + } + } + + if (bi.bytes_written && (s->subdev_flags & SDF_CMD_WRITE)) { + bi.bytes_written = + comedi_buf_write_alloc(async, bi.bytes_written); + comedi_buf_write_free(async, bi.bytes_written); + } + + bi.buf_write_count = async->buf_write_count; + bi.buf_write_ptr = async->buf_write_ptr; + bi.buf_read_count = async->buf_read_count; + bi.buf_read_ptr = async->buf_read_ptr; + +copyback: + if (copy_to_user(arg, &bi, sizeof(comedi_bufinfo))) + return -EFAULT; + + return 0; +} + +static int parse_insn(comedi_device *dev, comedi_insn *insn, lsampl_t *data, + void *file); +/* + * COMEDI_INSNLIST + * synchronous instructions + * + * arg: + * pointer to sync cmd structure + * + * reads: + * sync cmd struct at arg + * instruction list + * data (for writes) + * + * writes: + * data (for reads) + */ +/* arbitrary limits */ +#define MAX_SAMPLES 256 +static int do_insnlist_ioctl(comedi_device *dev, void *arg, void *file) +{ + comedi_insnlist insnlist; + comedi_insn *insns = NULL; + lsampl_t *data = NULL; + int i = 0; + int ret = 0; + + if (copy_from_user(&insnlist, arg, sizeof(comedi_insnlist))) + return -EFAULT; + + data = kmalloc(sizeof(lsampl_t) * MAX_SAMPLES, GFP_KERNEL); + if (!data) { + DPRINTK("kmalloc failed\n"); + ret = -ENOMEM; + goto error; + } + + insns = kmalloc(sizeof(comedi_insn) * insnlist.n_insns, GFP_KERNEL); + if (!insns) { + DPRINTK("kmalloc failed\n"); + ret = -ENOMEM; + goto error; + } + + if (copy_from_user(insns, insnlist.insns, + sizeof(comedi_insn) * insnlist.n_insns)) { + DPRINTK("copy_from_user failed\n"); + ret = -EFAULT; + goto error; + } + + for (i = 0; i < insnlist.n_insns; i++) { + if (insns[i].n > MAX_SAMPLES) { + DPRINTK("number of samples too large\n"); + ret = -EINVAL; + goto error; + } + if (insns[i].insn & INSN_MASK_WRITE) { + if (copy_from_user(data, insns[i].data, + insns[i].n * sizeof(lsampl_t))) { + DPRINTK("copy_from_user failed\n"); + ret = -EFAULT; + goto error; + } + } + ret = parse_insn(dev, insns + i, data, file); + if (ret < 0) + goto error; + if (insns[i].insn & INSN_MASK_READ) { + if (copy_to_user(insns[i].data, data, + insns[i].n * sizeof(lsampl_t))) { + DPRINTK("copy_to_user failed\n"); + ret = -EFAULT; + goto error; + } + } + if (need_resched()) + schedule(); + } + +error: + kfree(insns); + kfree(data); + + if (ret < 0) + return ret; + return i; +} + +static int check_insn_config_length(comedi_insn *insn, lsampl_t *data) +{ + if (insn->n < 1) + return -EINVAL; + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + case INSN_CONFIG_DIO_INPUT: + case INSN_CONFIG_DISARM: + case INSN_CONFIG_RESET: + if (insn->n == 1) + return 0; + break; + case INSN_CONFIG_ARM: + case INSN_CONFIG_DIO_QUERY: + case INSN_CONFIG_BLOCK_SIZE: + case INSN_CONFIG_FILTER: + case INSN_CONFIG_SERIAL_CLOCK: + case INSN_CONFIG_BIDIRECTIONAL_DATA: + case INSN_CONFIG_ALT_SOURCE: + case INSN_CONFIG_SET_COUNTER_MODE: + case INSN_CONFIG_8254_READ_STATUS: + case INSN_CONFIG_SET_ROUTING: + case INSN_CONFIG_GET_ROUTING: + case INSN_CONFIG_GET_PWM_STATUS: + case INSN_CONFIG_PWM_SET_PERIOD: + case INSN_CONFIG_PWM_GET_PERIOD: + if (insn->n == 2) + return 0; + break; + case INSN_CONFIG_SET_GATE_SRC: + case INSN_CONFIG_GET_GATE_SRC: + case INSN_CONFIG_SET_CLOCK_SRC: + case INSN_CONFIG_GET_CLOCK_SRC: + case INSN_CONFIG_SET_OTHER_SRC: + case INSN_CONFIG_GET_COUNTER_STATUS: + case INSN_CONFIG_PWM_SET_H_BRIDGE: + case INSN_CONFIG_PWM_GET_H_BRIDGE: + case INSN_CONFIG_GET_HARDWARE_BUFFER_SIZE: + if (insn->n == 3) + return 0; + break; + case INSN_CONFIG_PWM_OUTPUT: + case INSN_CONFIG_ANALOG_TRIG: + if (insn->n == 5) + return 0; + break; + /* by default we allow the insn since we don't have checks for + * all possible cases yet */ + default: + rt_printk("comedi: no check for data length of config insn id " + "%i is implemented.\n" + " Add a check to %s in %s.\n" + " Assuming n=%i is correct.\n", data[0], __func__, + __FILE__, insn->n); + return 0; + break; + } + return -EINVAL; +} + +static int parse_insn(comedi_device *dev, comedi_insn *insn, lsampl_t *data, + void *file) +{ + comedi_subdevice *s; + int ret = 0; + int i; + + if (insn->insn & INSN_MASK_SPECIAL) { + /* a non-subdevice instruction */ + + switch (insn->insn) { + case INSN_GTOD: + { + struct timeval tv; + + if (insn->n != 2) { + ret = -EINVAL; + break; + } + + do_gettimeofday(&tv); + data[0] = tv.tv_sec; + data[1] = tv.tv_usec; + ret = 2; + + break; + } + case INSN_WAIT: + if (insn->n != 1 || data[0] >= 100000) { + ret = -EINVAL; + break; + } + udelay(data[0] / 1000); + ret = 1; + break; + case INSN_INTTRIG: + if (insn->n != 1) { + ret = -EINVAL; + break; + } + if (insn->subdev >= dev->n_subdevices) { + DPRINTK("%d not usable subdevice\n", + insn->subdev); + ret = -EINVAL; + break; + } + s = dev->subdevices + insn->subdev; + if (!s->async) { + DPRINTK("no async\n"); + ret = -EINVAL; + break; + } + if (!s->async->inttrig) { + DPRINTK("no inttrig\n"); + ret = -EAGAIN; + break; + } + ret = s->async->inttrig(dev, s, insn->data[0]); + if (ret >= 0) + ret = 1; + break; + default: + DPRINTK("invalid insn\n"); + ret = -EINVAL; + break; + } + } else { + /* a subdevice instruction */ + lsampl_t maxdata; + + if (insn->subdev >= dev->n_subdevices) { + DPRINTK("subdevice %d out of range\n", insn->subdev); + ret = -EINVAL; + goto out; + } + s = dev->subdevices + insn->subdev; + + if (s->type == COMEDI_SUBD_UNUSED) { + DPRINTK("%d not usable subdevice\n", insn->subdev); + ret = -EIO; + goto out; + } + + /* are we locked? (ioctl lock) */ + if (s->lock && s->lock != file) { + DPRINTK("device locked\n"); + ret = -EACCES; + goto out; + } + + ret = check_chanlist(s, 1, &insn->chanspec); + if (ret < 0) { + ret = -EINVAL; + DPRINTK("bad chanspec\n"); + goto out; + } + + if (s->busy) { + ret = -EBUSY; + goto out; + } + /* This looks arbitrary. It is. */ + s->busy = &parse_insn; + switch (insn->insn) { + case INSN_READ: + ret = s->insn_read(dev, s, insn, data); + break; + case INSN_WRITE: + maxdata = s->maxdata_list + ? s->maxdata_list[CR_CHAN(insn->chanspec)] + : s->maxdata; + for (i = 0; i < insn->n; ++i) { + if (data[i] > maxdata) { + ret = -EINVAL; + DPRINTK("bad data value(s)\n"); + break; + } + } + if (ret == 0) + ret = s->insn_write(dev, s, insn, data); + break; + case INSN_BITS: + if (insn->n != 2) { + ret = -EINVAL; + break; + } + ret = s->insn_bits(dev, s, insn, data); + break; + case INSN_CONFIG: + ret = check_insn_config_length(insn, data); + if (ret) + break; + ret = s->insn_config(dev, s, insn, data); + break; + default: + ret = -EINVAL; + break; + } + + s->busy = NULL; + } + +out: + return ret; +} + +/* + * COMEDI_INSN + * synchronous instructions + * + * arg: + * pointer to insn + * + * reads: + * comedi_insn struct at arg + * data (for writes) + * + * writes: + * data (for reads) + */ +static int do_insn_ioctl(comedi_device *dev, void *arg, void *file) +{ + comedi_insn insn; + lsampl_t *data = NULL; + int ret = 0; + + data = kmalloc(sizeof(lsampl_t) * MAX_SAMPLES, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto error; + } + + if (copy_from_user(&insn, arg, sizeof(comedi_insn))) { + ret = -EFAULT; + goto error; + } + + /* This is where the behavior of insn and insnlist deviate. */ + if (insn.n > MAX_SAMPLES) + insn.n = MAX_SAMPLES; + if (insn.insn & INSN_MASK_WRITE) { + if (copy_from_user(data, insn.data, insn.n * sizeof(lsampl_t))) { + ret = -EFAULT; + goto error; + } + } + ret = parse_insn(dev, &insn, data, file); + if (ret < 0) + goto error; + if (insn.insn & INSN_MASK_READ) { + if (copy_to_user(insn.data, data, insn.n * sizeof(lsampl_t))) { + ret = -EFAULT; + goto error; + } + } + ret = insn.n; + +error: + kfree(data); + + return ret; +} + +/* + COMEDI_CMD + command ioctl + + arg: + pointer to cmd structure + + reads: + cmd structure at arg + channel/range list + + writes: + modified cmd structure at arg + +*/ +static int do_cmd_ioctl(comedi_device *dev, void *arg, void *file) +{ + comedi_cmd user_cmd; + comedi_subdevice *s; + comedi_async *async; + int ret = 0; + unsigned int *chanlist_saver = NULL; + + if (copy_from_user(&user_cmd, arg, sizeof(comedi_cmd))) { + DPRINTK("bad cmd address\n"); + return -EFAULT; + } + /* save user's chanlist pointer so it can be restored later */ + chanlist_saver = user_cmd.chanlist; + + if (user_cmd.subdev >= dev->n_subdevices) { + DPRINTK("%d no such subdevice\n", user_cmd.subdev); + return -ENODEV; + } + + s = dev->subdevices + user_cmd.subdev; + async = s->async; + + if (s->type == COMEDI_SUBD_UNUSED) { + DPRINTK("%d not valid subdevice\n", user_cmd.subdev); + return -EIO; + } + + if (!s->do_cmd || !s->do_cmdtest || !s->async) { + DPRINTK("subdevice %i does not support commands\n", + user_cmd.subdev); + return -EIO; + } + + /* are we locked? (ioctl lock) */ + if (s->lock && s->lock != file) { + DPRINTK("subdevice locked\n"); + return -EACCES; + } + + /* are we busy? */ + if (s->busy) { + DPRINTK("subdevice busy\n"); + return -EBUSY; + } + s->busy = file; + + /* make sure channel/gain list isn't too long */ + if (user_cmd.chanlist_len > s->len_chanlist) { + DPRINTK("channel/gain list too long %u > %d\n", + user_cmd.chanlist_len, s->len_chanlist); + ret = -EINVAL; + goto cleanup; + } + + /* make sure channel/gain list isn't too short */ + if (user_cmd.chanlist_len < 1) { + DPRINTK("channel/gain list too short %u < 1\n", + user_cmd.chanlist_len); + ret = -EINVAL; + goto cleanup; + } + + kfree(async->cmd.chanlist); + async->cmd = user_cmd; + async->cmd.data = NULL; + /* load channel/gain list */ + async->cmd.chanlist = + kmalloc(async->cmd.chanlist_len * sizeof(int), GFP_KERNEL); + if (!async->cmd.chanlist) { + DPRINTK("allocation failed\n"); + ret = -ENOMEM; + goto cleanup; + } + + if (copy_from_user(async->cmd.chanlist, user_cmd.chanlist, + async->cmd.chanlist_len * sizeof(int))) { + DPRINTK("fault reading chanlist\n"); + ret = -EFAULT; + goto cleanup; + } + + /* make sure each element in channel/gain list is valid */ + ret = check_chanlist(s, async->cmd.chanlist_len, async->cmd.chanlist); + if (ret < 0) { + DPRINTK("bad chanlist\n"); + goto cleanup; + } + + ret = s->do_cmdtest(dev, s, &async->cmd); + + if (async->cmd.flags & TRIG_BOGUS || ret) { + DPRINTK("test returned %d\n", ret); + user_cmd = async->cmd; + /* restore chanlist pointer before copying back */ + user_cmd.chanlist = chanlist_saver; + user_cmd.data = NULL; + if (copy_to_user(arg, &user_cmd, sizeof(comedi_cmd))) { + DPRINTK("fault writing cmd\n"); + ret = -EFAULT; + goto cleanup; + } + ret = -EAGAIN; + goto cleanup; + } + + if (!async->prealloc_bufsz) { + ret = -ENOMEM; + DPRINTK("no buffer (?)\n"); + goto cleanup; + } + + comedi_reset_async_buf(async); + + async->cb_mask = + COMEDI_CB_EOA | COMEDI_CB_BLOCK | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW; + if (async->cmd.flags & TRIG_WAKE_EOS) + async->cb_mask |= COMEDI_CB_EOS; + + comedi_set_subdevice_runflags(s, ~0, SRF_USER | SRF_RUNNING); + +#ifdef CONFIG_COMEDI_RT + if (async->cmd.flags & TRIG_RT) { + if (comedi_switch_to_rt(dev) == 0) + comedi_set_subdevice_runflags(s, SRF_RT, SRF_RT); + } +#endif + + ret = s->do_cmd(dev, s); + if (ret == 0) + return 0; + +cleanup: + do_become_nonbusy(dev, s); + + return ret; +} + +/* + COMEDI_CMDTEST + command testing ioctl + + arg: + pointer to cmd structure + + reads: + cmd structure at arg + channel/range list + + writes: + modified cmd structure at arg + +*/ +static int do_cmdtest_ioctl(comedi_device *dev, void *arg, void *file) +{ + comedi_cmd user_cmd; + comedi_subdevice *s; + int ret = 0; + unsigned int *chanlist = NULL; + unsigned int *chanlist_saver = NULL; + + if (copy_from_user(&user_cmd, arg, sizeof(comedi_cmd))) { + DPRINTK("bad cmd address\n"); + return -EFAULT; + } + /* save user's chanlist pointer so it can be restored later */ + chanlist_saver = user_cmd.chanlist; + + if (user_cmd.subdev >= dev->n_subdevices) { + DPRINTK("%d no such subdevice\n", user_cmd.subdev); + return -ENODEV; + } + + s = dev->subdevices + user_cmd.subdev; + if (s->type == COMEDI_SUBD_UNUSED) { + DPRINTK("%d not valid subdevice\n", user_cmd.subdev); + return -EIO; + } + + if (!s->do_cmd || !s->do_cmdtest) { + DPRINTK("subdevice %i does not support commands\n", + user_cmd.subdev); + return -EIO; + } + + /* make sure channel/gain list isn't too long */ + if (user_cmd.chanlist_len > s->len_chanlist) { + DPRINTK("channel/gain list too long %d > %d\n", + user_cmd.chanlist_len, s->len_chanlist); + ret = -EINVAL; + goto cleanup; + } + + /* load channel/gain list */ + if (user_cmd.chanlist) { + chanlist = + kmalloc(user_cmd.chanlist_len * sizeof(int), GFP_KERNEL); + if (!chanlist) { + DPRINTK("allocation failed\n"); + ret = -ENOMEM; + goto cleanup; + } + + if (copy_from_user(chanlist, user_cmd.chanlist, + user_cmd.chanlist_len * sizeof(int))) { + DPRINTK("fault reading chanlist\n"); + ret = -EFAULT; + goto cleanup; + } + + /* make sure each element in channel/gain list is valid */ + ret = check_chanlist(s, user_cmd.chanlist_len, chanlist); + if (ret < 0) { + DPRINTK("bad chanlist\n"); + goto cleanup; + } + + user_cmd.chanlist = chanlist; + } + + ret = s->do_cmdtest(dev, s, &user_cmd); + + /* restore chanlist pointer before copying back */ + user_cmd.chanlist = chanlist_saver; + + if (copy_to_user(arg, &user_cmd, sizeof(comedi_cmd))) { + DPRINTK("bad cmd address\n"); + ret = -EFAULT; + goto cleanup; + } +cleanup: + kfree(chanlist); + + return ret; +} + +/* + COMEDI_LOCK + lock subdevice + + arg: + subdevice number + + reads: + none + + writes: + none + +*/ + +static int do_lock_ioctl(comedi_device *dev, unsigned int arg, void *file) +{ + int ret = 0; + unsigned long flags; + comedi_subdevice *s; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + arg; + + comedi_spin_lock_irqsave(&s->spin_lock, flags); + if (s->busy || s->lock) + ret = -EBUSY; + else + s->lock = file; + comedi_spin_unlock_irqrestore(&s->spin_lock, flags); + + if (ret < 0) + return ret; + +#if 0 + if (s->lock_f) + ret = s->lock_f(dev, s); +#endif + + return ret; +} + +/* + COMEDI_UNLOCK + unlock subdevice + + arg: + subdevice number + + reads: + none + + writes: + none + + This function isn't protected by the semaphore, since + we already own the lock. +*/ +static int do_unlock_ioctl(comedi_device *dev, unsigned int arg, void *file) +{ + comedi_subdevice *s; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + arg; + + if (s->busy) + return -EBUSY; + + if (s->lock && s->lock != file) + return -EACCES; + + if (s->lock == file) { +#if 0 + if (s->unlock) + s->unlock(dev, s); +#endif + + s->lock = NULL; + } + + return 0; +} + +/* + COMEDI_CANCEL + cancel acquisition ioctl + + arg: + subdevice number + + reads: + nothing + + writes: + nothing + +*/ +static int do_cancel_ioctl(comedi_device *dev, unsigned int arg, void *file) +{ + comedi_subdevice *s; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + arg; + if (s->async == NULL) + return -EINVAL; + + if (s->lock && s->lock != file) + return -EACCES; + + if (!s->busy) + return 0; + + if (s->busy != file) + return -EBUSY; + + return do_cancel(dev, s); +} + +/* + COMEDI_POLL ioctl + instructs driver to synchronize buffers + + arg: + subdevice number + + reads: + nothing + + writes: + nothing + +*/ +static int do_poll_ioctl(comedi_device *dev, unsigned int arg, void *file) +{ + comedi_subdevice *s; + + if (arg >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + arg; + + if (s->lock && s->lock != file) + return -EACCES; + + if (!s->busy) + return 0; + + if (s->busy != file) + return -EBUSY; + + if (s->poll) + return s->poll(dev, s); + + return -EINVAL; +} + +static int do_cancel(comedi_device *dev, comedi_subdevice *s) +{ + int ret = 0; + + if ((comedi_get_subdevice_runflags(s) & SRF_RUNNING) && s->cancel) + ret = s->cancel(dev, s); + + do_become_nonbusy(dev, s); + + return ret; +} + +void comedi_unmap(struct vm_area_struct *area) +{ + comedi_async *async; + comedi_device *dev; + + async = area->vm_private_data; + dev = async->subdevice->device; + + mutex_lock(&dev->mutex); + async->mmap_count--; + mutex_unlock(&dev->mutex); +} + +static struct vm_operations_struct comedi_vm_ops = { + .close = comedi_unmap, +}; + +static int comedi_mmap(struct file *file, struct vm_area_struct *vma) +{ + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + comedi_device *dev = dev_file_info->device; + comedi_async *async = NULL; + unsigned long start = vma->vm_start; + unsigned long size; + int n_pages; + int i; + int retval; + comedi_subdevice *s; + + mutex_lock(&dev->mutex); + if (!dev->attached) { + DPRINTK("no driver configured on comedi%i\n", dev->minor); + retval = -ENODEV; + goto done; + } + if (vma->vm_flags & VM_WRITE) + s = comedi_get_write_subdevice(dev_file_info); + else + s = comedi_get_read_subdevice(dev_file_info); + + if (s == NULL) { + retval = -EINVAL; + goto done; + } + async = s->async; + if (async == NULL) { + retval = -EINVAL; + goto done; + } + + if (vma->vm_pgoff != 0) { + DPRINTK("comedi: mmap() offset must be 0.\n"); + retval = -EINVAL; + goto done; + } + + size = vma->vm_end - vma->vm_start; + if (size > async->prealloc_bufsz) { + retval = -EFAULT; + goto done; + } + if (size & (~PAGE_MASK)) { + retval = -EFAULT; + goto done; + } + + n_pages = size >> PAGE_SHIFT; + for (i = 0; i < n_pages; ++i) { + if (remap_pfn_range(vma, start, + page_to_pfn(virt_to_page(async-> + buf_page_list[i]. + virt_addr)), + PAGE_SIZE, PAGE_SHARED)) { + retval = -EAGAIN; + goto done; + } + start += PAGE_SIZE; + } + + vma->vm_ops = &comedi_vm_ops; + vma->vm_private_data = async; + + async->mmap_count++; + + retval = 0; +done: + mutex_unlock(&dev->mutex); + return retval; +} + +static unsigned int comedi_poll(struct file *file, poll_table *wait) +{ + unsigned int mask = 0; + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + comedi_device *dev = dev_file_info->device; + comedi_subdevice *read_subdev; + comedi_subdevice *write_subdev; + + mutex_lock(&dev->mutex); + if (!dev->attached) { + DPRINTK("no driver configured on comedi%i\n", dev->minor); + mutex_unlock(&dev->mutex); + return 0; + } + + mask = 0; + read_subdev = comedi_get_read_subdevice(dev_file_info); + if (read_subdev) { + poll_wait(file, &read_subdev->async->wait_head, wait); + if (!read_subdev->busy + || comedi_buf_read_n_available(read_subdev->async) > 0 + || !(comedi_get_subdevice_runflags(read_subdev) & + SRF_RUNNING)) { + mask |= POLLIN | POLLRDNORM; + } + } + write_subdev = comedi_get_write_subdevice(dev_file_info); + if (write_subdev) { + poll_wait(file, &write_subdev->async->wait_head, wait); + comedi_buf_write_alloc(write_subdev->async, + write_subdev->async->prealloc_bufsz); + if (!write_subdev->busy + || !(comedi_get_subdevice_runflags(write_subdev) & + SRF_RUNNING) + || comedi_buf_write_n_allocated(write_subdev->async) >= + bytes_per_sample(write_subdev->async->subdevice)) { + mask |= POLLOUT | POLLWRNORM; + } + } + + mutex_unlock(&dev->mutex); + return mask; +} + +static ssize_t comedi_write(struct file *file, const char *buf, size_t nbytes, + loff_t *offset) +{ + comedi_subdevice *s; + comedi_async *async; + int n, m, count = 0, retval = 0; + DECLARE_WAITQUEUE(wait, current); + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + comedi_device *dev = dev_file_info->device; + + if (!dev->attached) { + DPRINTK("no driver configured on comedi%i\n", dev->minor); + retval = -ENODEV; + goto done; + } + + s = comedi_get_write_subdevice(dev_file_info); + if (s == NULL) { + retval = -EIO; + goto done; + } + async = s->async; + + if (!nbytes) { + retval = 0; + goto done; + } + if (!s->busy) { + retval = 0; + goto done; + } + if (s->busy != file) { + retval = -EACCES; + goto done; + } + add_wait_queue(&async->wait_head, &wait); + while (nbytes > 0 && !retval) { + set_current_state(TASK_INTERRUPTIBLE); + + n = nbytes; + + m = n; + if (async->buf_write_ptr + m > async->prealloc_bufsz) + m = async->prealloc_bufsz - async->buf_write_ptr; + comedi_buf_write_alloc(async, async->prealloc_bufsz); + if (m > comedi_buf_write_n_allocated(async)) + m = comedi_buf_write_n_allocated(async); + if (m < n) + n = m; + + if (n == 0) { + if (!(comedi_get_subdevice_runflags(s) & SRF_RUNNING)) { + if (comedi_get_subdevice_runflags(s) & + SRF_ERROR) { + retval = -EPIPE; + } else { + retval = 0; + } + do_become_nonbusy(dev, s); + break; + } + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + if (!s->busy) + break; + if (s->busy != file) { + retval = -EACCES; + break; + } + continue; + } + + m = copy_from_user(async->prealloc_buf + async->buf_write_ptr, + buf, n); + if (m) { + n -= m; + retval = -EFAULT; + } + comedi_buf_write_free(async, n); + + count += n; + nbytes -= n; + + buf += n; + break; /* makes device work like a pipe */ + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&async->wait_head, &wait); + +done: + return count ? count : retval; +} + +static ssize_t comedi_read(struct file *file, char *buf, size_t nbytes, + loff_t *offset) +{ + comedi_subdevice *s; + comedi_async *async; + int n, m, count = 0, retval = 0; + DECLARE_WAITQUEUE(wait, current); + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + comedi_device *dev = dev_file_info->device; + + if (!dev->attached) { + DPRINTK("no driver configured on comedi%i\n", dev->minor); + retval = -ENODEV; + goto done; + } + + s = comedi_get_read_subdevice(dev_file_info); + if (s == NULL) { + retval = -EIO; + goto done; + } + async = s->async; + if (!nbytes) { + retval = 0; + goto done; + } + if (!s->busy) { + retval = 0; + goto done; + } + if (s->busy != file) { + retval = -EACCES; + goto done; + } + + add_wait_queue(&async->wait_head, &wait); + while (nbytes > 0 && !retval) { + set_current_state(TASK_INTERRUPTIBLE); + + n = nbytes; + + m = comedi_buf_read_n_available(async); + /* printk("%d available\n",m); */ + if (async->buf_read_ptr + m > async->prealloc_bufsz) + m = async->prealloc_bufsz - async->buf_read_ptr; + /* printk("%d contiguous\n",m); */ + if (m < n) + n = m; + + if (n == 0) { + if (!(comedi_get_subdevice_runflags(s) & SRF_RUNNING)) { + do_become_nonbusy(dev, s); + if (comedi_get_subdevice_runflags(s) & + SRF_ERROR) { + retval = -EPIPE; + } else { + retval = 0; + } + break; + } + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + break; + } + if (signal_pending(current)) { + retval = -ERESTARTSYS; + break; + } + schedule(); + if (!s->busy) { + retval = 0; + break; + } + if (s->busy != file) { + retval = -EACCES; + break; + } + continue; + } + m = copy_to_user(buf, async->prealloc_buf + + async->buf_read_ptr, n); + if (m) { + n -= m; + retval = -EFAULT; + } + + comedi_buf_read_alloc(async, n); + comedi_buf_read_free(async, n); + + count += n; + nbytes -= n; + + buf += n; + break; /* makes device work like a pipe */ + } + if (!(comedi_get_subdevice_runflags(s) & (SRF_ERROR | SRF_RUNNING)) && + async->buf_read_count - async->buf_write_count == 0) { + do_become_nonbusy(dev, s); + } + set_current_state(TASK_RUNNING); + remove_wait_queue(&async->wait_head, &wait); + +done: + return count ? count : retval; +} + +/* + This function restores a subdevice to an idle state. + */ +void do_become_nonbusy(comedi_device *dev, comedi_subdevice *s) +{ + comedi_async *async = s->async; + + comedi_set_subdevice_runflags(s, SRF_RUNNING, 0); +#ifdef CONFIG_COMEDI_RT + if (comedi_get_subdevice_runflags(s) & SRF_RT) { + comedi_switch_to_non_rt(dev); + comedi_set_subdevice_runflags(s, SRF_RT, 0); + } +#endif + if (async) { + comedi_reset_async_buf(async); + async->inttrig = NULL; + } else { + printk(KERN_ERR + "BUG: (?) do_become_nonbusy called with async=0\n"); + } + + s->busy = NULL; +} + +static int comedi_open(struct inode *inode, struct file *file) +{ + char mod[32]; + const unsigned minor = iminor(inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + comedi_device *dev = dev_file_info->device; + if (dev == NULL) { + DPRINTK("invalid minor number\n"); + return -ENODEV; + } + + /* This is slightly hacky, but we want module autoloading + * to work for root. + * case: user opens device, attached -> ok + * case: user opens device, unattached, in_request_module=0 -> autoload + * case: user opens device, unattached, in_request_module=1 -> fail + * case: root opens device, attached -> ok + * case: root opens device, unattached, in_request_module=1 -> ok + * (typically called from modprobe) + * case: root opens device, unattached, in_request_module=0 -> autoload + * + * The last could be changed to "-> ok", which would deny root + * autoloading. + */ + mutex_lock(&dev->mutex); + if (dev->attached) + goto ok; + if (!capable(CAP_SYS_MODULE) && dev->in_request_module) { + DPRINTK("in request module\n"); + mutex_unlock(&dev->mutex); + return -ENODEV; + } + if (capable(CAP_SYS_MODULE) && dev->in_request_module) + goto ok; + + dev->in_request_module = 1; + + sprintf(mod, "char-major-%i-%i", COMEDI_MAJOR, dev->minor); +#ifdef CONFIG_KMOD + mutex_unlock(&dev->mutex); + request_module(mod); + mutex_lock(&dev->mutex); +#endif + + dev->in_request_module = 0; + + if (!dev->attached && !capable(CAP_SYS_MODULE)) { + DPRINTK("not attached and not CAP_SYS_MODULE\n"); + mutex_unlock(&dev->mutex); + return -ENODEV; + } +ok: + __module_get(THIS_MODULE); + + if (dev->attached) { + if (!try_module_get(dev->driver->module)) { + module_put(THIS_MODULE); + mutex_unlock(&dev->mutex); + return -ENOSYS; + } + } + + if (dev->attached && dev->use_count == 0 && dev->open) + dev->open(dev); + + dev->use_count++; + + mutex_unlock(&dev->mutex); + + return 0; +} + +static int comedi_close(struct inode *inode, struct file *file) +{ + const unsigned minor = iminor(inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + comedi_device *dev = dev_file_info->device; + comedi_subdevice *s = NULL; + int i; + + mutex_lock(&dev->mutex); + + if (dev->subdevices) { + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + + if (s->busy == file) + do_cancel(dev, s); + if (s->lock == file) + s->lock = NULL; + } + } + if (dev->attached && dev->use_count == 1 && dev->close) + dev->close(dev); + + module_put(THIS_MODULE); + if (dev->attached) + module_put(dev->driver->module); + + dev->use_count--; + + mutex_unlock(&dev->mutex); + + if (file->f_flags & FASYNC) + comedi_fasync(-1, file, 0); + + return 0; +} + +static int comedi_fasync(int fd, struct file *file, int on) +{ + const unsigned minor = iminor(file->f_dentry->d_inode); + struct comedi_device_file_info *dev_file_info = + comedi_get_device_file_info(minor); + + comedi_device *dev = dev_file_info->device; + + return fasync_helper(fd, file, on, &dev->async_queue); +} + +const struct file_operations comedi_fops = { + .owner = THIS_MODULE, +#ifdef HAVE_UNLOCKED_IOCTL + .unlocked_ioctl = comedi_unlocked_ioctl, +#else + .ioctl = comedi_ioctl, +#endif +#ifdef HAVE_COMPAT_IOCTL + .compat_ioctl = comedi_compat_ioctl, +#endif + .open = comedi_open, + .release = comedi_close, + .read = comedi_read, + .write = comedi_write, + .mmap = comedi_mmap, + .poll = comedi_poll, + .fasync = comedi_fasync, +}; + +struct class *comedi_class; +static struct cdev comedi_cdev; + +static void comedi_cleanup_legacy_minors(void) +{ + unsigned i; + + for (i = 0; i < COMEDI_NUM_LEGACY_MINORS; i++) + comedi_free_board_minor(i); +} + +static int __init comedi_init(void) +{ + int i; + int retval; + + printk(KERN_INFO "comedi: version " COMEDI_RELEASE + " - http://www.comedi.org\n"); + + memset(comedi_file_info_table, 0, + sizeof(struct comedi_device_file_info *) * COMEDI_NUM_MINORS); + + retval = register_chrdev_region(MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS, "comedi"); + if (retval) + return -EIO; + cdev_init(&comedi_cdev, &comedi_fops); + comedi_cdev.owner = THIS_MODULE; + kobject_set_name(&comedi_cdev.kobj, "comedi"); + if (cdev_add(&comedi_cdev, MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS)) { + unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS); + return -EIO; + } + comedi_class = class_create(THIS_MODULE, "comedi"); + if (IS_ERR(comedi_class)) { + printk("comedi: failed to create class"); + cdev_del(&comedi_cdev); + unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS); + return PTR_ERR(comedi_class); + } + + /* XXX requires /proc interface */ + comedi_proc_init(); + + /* create devices files for legacy/manual use */ + for (i = 0; i < COMEDI_NUM_LEGACY_MINORS; i++) { + int minor; + minor = comedi_alloc_board_minor(NULL); + if (minor < 0) { + comedi_cleanup_legacy_minors(); + cdev_del(&comedi_cdev); + unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), + COMEDI_NUM_MINORS); + return minor; + } + } + + comedi_rt_init(); + + comedi_register_ioctl32(); + + return 0; +} + +static void __exit comedi_cleanup(void) +{ + int i; + + comedi_cleanup_legacy_minors(); + for (i = 0; i < COMEDI_NUM_MINORS; ++i) + BUG_ON(comedi_file_info_table[i]); + + + class_destroy(comedi_class); + cdev_del(&comedi_cdev); + unregister_chrdev_region(MKDEV(COMEDI_MAJOR, 0), COMEDI_NUM_MINORS); + + comedi_proc_cleanup(); + + comedi_rt_cleanup(); + + comedi_unregister_ioctl32(); +} + +module_init(comedi_init); +module_exit(comedi_cleanup); + +void comedi_error(const comedi_device *dev, const char *s) +{ + rt_printk("comedi%d: %s: %s\n", dev->minor, dev->driver->driver_name, + s); +} + +void comedi_event(comedi_device *dev, comedi_subdevice *s) +{ + comedi_async *async = s->async; + unsigned runflags = 0; + unsigned runflags_mask = 0; + + /* DPRINTK("comedi_event 0x%x\n",mask); */ + + if ((comedi_get_subdevice_runflags(s) & SRF_RUNNING) == 0) + return; + + if (s->async-> + events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) { + runflags_mask |= SRF_RUNNING; + } + /* remember if an error event has occured, so an error + * can be returned the next time the user does a read() */ + if (s->async->events & (COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) { + runflags_mask |= SRF_ERROR; + runflags |= SRF_ERROR; + } + if (runflags_mask) { + /*sets SRF_ERROR and SRF_RUNNING together atomically */ + comedi_set_subdevice_runflags(s, runflags_mask, runflags); + } + + if (async->cb_mask & s->async->events) { + if (comedi_get_subdevice_runflags(s) & SRF_USER) { + + if (dev->rt) { +#ifdef CONFIG_COMEDI_RT + /* pend wake up */ + comedi_rt_pend_wakeup(&async->wait_head); +#else + printk + ("BUG: comedi_event() code unreachable\n"); +#endif + } else { + wake_up_interruptible(&async->wait_head); + if (s->subdev_flags & SDF_CMD_READ) { + kill_fasync(&dev->async_queue, SIGIO, + POLL_IN); + } + if (s->subdev_flags & SDF_CMD_WRITE) { + kill_fasync(&dev->async_queue, SIGIO, + POLL_OUT); + } + } + } else { + if (async->cb_func) + async->cb_func(s->async->events, async->cb_arg); + /* XXX bug here. If subdevice A is rt, and + * subdevice B tries to callback to a normal + * linux kernel function, it will be at the + * wrong priority. Since this isn't very + * common, I'm not going to worry about it. */ + } + } + s->async->events = 0; +} + +void comedi_set_subdevice_runflags(comedi_subdevice *s, unsigned mask, + unsigned bits) +{ + unsigned long flags; + + comedi_spin_lock_irqsave(&s->spin_lock, flags); + s->runflags &= ~mask; + s->runflags |= (bits & mask); + comedi_spin_unlock_irqrestore(&s->spin_lock, flags); +} + +unsigned comedi_get_subdevice_runflags(comedi_subdevice *s) +{ + unsigned long flags; + unsigned runflags; + + comedi_spin_lock_irqsave(&s->spin_lock, flags); + runflags = s->runflags; + comedi_spin_unlock_irqrestore(&s->spin_lock, flags); + return runflags; +} + +static int is_device_busy(comedi_device *dev) +{ + comedi_subdevice *s; + int i; + + if (!dev->attached) + return 0; + + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + if (s->busy) + return 1; + if (s->async && s->async->mmap_count) + return 1; + } + + return 0; +} + +void comedi_device_init(comedi_device *dev) +{ + memset(dev, 0, sizeof(comedi_device)); + spin_lock_init(&dev->spinlock); + mutex_init(&dev->mutex); + dev->minor = -1; +} + +void comedi_device_cleanup(comedi_device *dev) +{ + if (dev == NULL) + return; + mutex_lock(&dev->mutex); + comedi_device_detach(dev); + mutex_unlock(&dev->mutex); + mutex_destroy(&dev->mutex); +} + +int comedi_alloc_board_minor(struct device *hardware_device) +{ + unsigned long flags; + struct comedi_device_file_info *info; + device_create_result_type *csdev; + unsigned i; + + info = kzalloc(sizeof(struct comedi_device_file_info), GFP_KERNEL); + if (info == NULL) + return -ENOMEM; + info->device = kzalloc(sizeof(comedi_device), GFP_KERNEL); + if (info->device == NULL) { + kfree(info); + return -ENOMEM; + } + comedi_device_init(info->device); + comedi_spin_lock_irqsave(&comedi_file_info_table_lock, flags); + for (i = 0; i < COMEDI_NUM_BOARD_MINORS; ++i) { + if (comedi_file_info_table[i] == NULL) { + comedi_file_info_table[i] = info; + break; + } + } + comedi_spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + if (i == COMEDI_NUM_BOARD_MINORS) { + comedi_device_cleanup(info->device); + kfree(info->device); + kfree(info); + rt_printk + ("comedi: error: ran out of minor numbers for board device files.\n"); + return -EBUSY; + } + info->device->minor = i; + csdev = COMEDI_DEVICE_CREATE(comedi_class, NULL, + MKDEV(COMEDI_MAJOR, i), NULL, + hardware_device, "comedi%i", i); + if (!IS_ERR(csdev)) + info->device->class_dev = csdev; + + return i; +} + +void comedi_free_board_minor(unsigned minor) +{ + unsigned long flags; + struct comedi_device_file_info *info; + + BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS); + comedi_spin_lock_irqsave(&comedi_file_info_table_lock, flags); + info = comedi_file_info_table[minor]; + comedi_file_info_table[minor] = NULL; + comedi_spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + + if (info) { + comedi_device *dev = info->device; + if (dev) { + if (dev->class_dev) { + device_destroy(comedi_class, + MKDEV(COMEDI_MAJOR, dev->minor)); + } + comedi_device_cleanup(dev); + kfree(dev); + } + kfree(info); + } +} + +int comedi_alloc_subdevice_minor(comedi_device *dev, comedi_subdevice *s) +{ + unsigned long flags; + struct comedi_device_file_info *info; + device_create_result_type *csdev; + unsigned i; + + info = kmalloc(sizeof(struct comedi_device_file_info), GFP_KERNEL); + if (info == NULL) + return -ENOMEM; + info->device = dev; + info->read_subdevice = s; + info->write_subdevice = s; + comedi_spin_lock_irqsave(&comedi_file_info_table_lock, flags); + for (i = COMEDI_FIRST_SUBDEVICE_MINOR; i < COMEDI_NUM_BOARD_MINORS; ++i) { + if (comedi_file_info_table[i] == NULL) { + comedi_file_info_table[i] = info; + break; + } + } + comedi_spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + if (i == COMEDI_NUM_MINORS) { + kfree(info); + rt_printk + ("comedi: error: ran out of minor numbers for board device files.\n"); + return -EBUSY; + } + s->minor = i; + csdev = COMEDI_DEVICE_CREATE(comedi_class, dev->class_dev, + MKDEV(COMEDI_MAJOR, i), NULL, NULL, + "comedi%i_subd%i", dev->minor, + (int)(s - dev->subdevices)); + if (!IS_ERR(csdev)) + s->class_dev = csdev; + + return i; +} + +void comedi_free_subdevice_minor(comedi_subdevice *s) +{ + unsigned long flags; + struct comedi_device_file_info *info; + + if (s == NULL) + return; + if (s->minor < 0) + return; + + BUG_ON(s->minor >= COMEDI_NUM_MINORS); + BUG_ON(s->minor < COMEDI_FIRST_SUBDEVICE_MINOR); + + comedi_spin_lock_irqsave(&comedi_file_info_table_lock, flags); + info = comedi_file_info_table[s->minor]; + comedi_file_info_table[s->minor] = NULL; + comedi_spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + + if (s->class_dev) { + device_destroy(comedi_class, MKDEV(COMEDI_MAJOR, s->minor)); + s->class_dev = NULL; + } + kfree(info); +} + +struct comedi_device_file_info *comedi_get_device_file_info(unsigned minor) +{ + unsigned long flags; + struct comedi_device_file_info *info; + + BUG_ON(minor >= COMEDI_NUM_MINORS); + comedi_spin_lock_irqsave(&comedi_file_info_table_lock, flags); + info = comedi_file_info_table[minor]; + comedi_spin_unlock_irqrestore(&comedi_file_info_table_lock, flags); + return info; +} diff --git a/drivers/staging/comedi/comedi_fops.h b/drivers/staging/comedi/comedi_fops.h new file mode 100644 index 00000000000..63f8df558e8 --- /dev/null +++ b/drivers/staging/comedi/comedi_fops.h @@ -0,0 +1,8 @@ + +#ifndef _COMEDI_FOPS_H +#define _COMEDI_FOPS_H + +extern struct class *comedi_class; +extern const struct file_operations comedi_fops; + +#endif /* _COMEDI_FOPS_H */ diff --git a/drivers/staging/comedi/comedi_ksyms.c b/drivers/staging/comedi/comedi_ksyms.c new file mode 100644 index 00000000000..90d57282efb --- /dev/null +++ b/drivers/staging/comedi/comedi_ksyms.c @@ -0,0 +1,77 @@ +/* + module/exp_ioctl.c + exported comedi functions + + 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. + +*/ + +#define __NO_VERSION__ +#ifndef EXPORT_SYMTAB +#define EXPORT_SYMTAB +#endif + +#include "comedidev.h" + +/* for drivers */ +EXPORT_SYMBOL(comedi_driver_register); +EXPORT_SYMBOL(comedi_driver_unregister); +//EXPORT_SYMBOL(comedi_bufcheck); +//EXPORT_SYMBOL(comedi_done); +//EXPORT_SYMBOL(comedi_error_done); +EXPORT_SYMBOL(comedi_error); +//EXPORT_SYMBOL(comedi_eobuf); +//EXPORT_SYMBOL(comedi_eos); +EXPORT_SYMBOL(comedi_event); +EXPORT_SYMBOL(comedi_get_subdevice_runflags); +EXPORT_SYMBOL(comedi_set_subdevice_runflags); +EXPORT_SYMBOL(range_bipolar10); +EXPORT_SYMBOL(range_bipolar5); +EXPORT_SYMBOL(range_bipolar2_5); +EXPORT_SYMBOL(range_unipolar10); +EXPORT_SYMBOL(range_unipolar5); +EXPORT_SYMBOL(range_unknown); +#ifdef CONFIG_COMEDI_RT +EXPORT_SYMBOL(comedi_free_irq); +EXPORT_SYMBOL(comedi_request_irq); +EXPORT_SYMBOL(comedi_switch_to_rt); +EXPORT_SYMBOL(comedi_switch_to_non_rt); +EXPORT_SYMBOL(rt_pend_call); +#endif +#ifdef CONFIG_COMEDI_DEBUG +EXPORT_SYMBOL(comedi_debug); +#endif +EXPORT_SYMBOL_GPL(comedi_alloc_board_minor); +EXPORT_SYMBOL_GPL(comedi_free_board_minor); +EXPORT_SYMBOL_GPL(comedi_pci_auto_config); +EXPORT_SYMBOL_GPL(comedi_pci_auto_unconfig); + +/* for kcomedilib */ +EXPORT_SYMBOL(check_chanlist); +EXPORT_SYMBOL_GPL(comedi_get_device_file_info); + +EXPORT_SYMBOL(comedi_buf_put); +EXPORT_SYMBOL(comedi_buf_get); +EXPORT_SYMBOL(comedi_buf_read_n_available); +EXPORT_SYMBOL(comedi_buf_write_free); +EXPORT_SYMBOL(comedi_buf_write_alloc); +EXPORT_SYMBOL(comedi_buf_read_free); +EXPORT_SYMBOL(comedi_buf_read_alloc); +EXPORT_SYMBOL(comedi_buf_memcpy_to); +EXPORT_SYMBOL(comedi_buf_memcpy_from); +EXPORT_SYMBOL(comedi_reset_async_buf); diff --git a/drivers/staging/comedi/comedi_rt.h b/drivers/staging/comedi/comedi_rt.h new file mode 100644 index 00000000000..61852bf5adc --- /dev/null +++ b/drivers/staging/comedi/comedi_rt.h @@ -0,0 +1,150 @@ +/* + module/comedi_rt.h + header file for real-time structures, variables, and constants + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _COMEDI_RT_H +#define _COMEDI_RT_H + +#ifndef _COMEDIDEV_H +#error comedi_rt.h should only be included by comedidev.h +#endif + +#include <linux/version.h> +#include <linux/kdev_t.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/delay.h> + +#ifdef CONFIG_COMEDI_RT + +#ifdef CONFIG_COMEDI_RTAI +#include <rtai.h> +#include <rtai_sched.h> +#include <rtai_version.h> +#endif +#ifdef CONFIG_COMEDI_RTL +#include <rtl_core.h> +#include <rtl_time.h> +/* #ifdef RTLINUX_VERSION_CODE */ +#include <rtl_sync.h> +/* #endif */ +#define rt_printk rtl_printf +#endif +#ifdef CONFIG_COMEDI_FUSION +#define rt_printk(format, args...) printk(format , ## args) +#endif /* CONFIG_COMEDI_FUSION */ +#ifdef CONFIG_PRIORITY_IRQ +#define rt_printk printk +#endif + +int comedi_request_irq(unsigned int irq, irqreturn_t(*handler) (int, + void *PT_REGS_ARG), unsigned long flags, const char *device, + comedi_device *dev_id); +void comedi_free_irq(unsigned int irq, comedi_device *dev_id); +void comedi_rt_init(void); +void comedi_rt_cleanup(void); +int comedi_switch_to_rt(comedi_device *dev); +void comedi_switch_to_non_rt(comedi_device *dev); +void comedi_rt_pend_wakeup(wait_queue_head_t *q); +extern int rt_pend_call(void (*func) (int arg1, void *arg2), int arg1, + void *arg2); + +#else + +#define comedi_request_irq(a, b, c, d, e) request_irq(a, b, c, d, e) +#define comedi_free_irq(a, b) free_irq(a, b) +#define comedi_rt_init() do {} while (0) +#define comedi_rt_cleanup() do {} while (0) +#define comedi_switch_to_rt(a) (-1) +#define comedi_switch_to_non_rt(a) do {} while (0) +#define comedi_rt_pend_wakeup(a) do {} while (0) + +#define rt_printk(format, args...) printk(format, ##args) + +#endif + +/* Define a spin_lock_irqsave function that will work with rt or without. + * Use inline functions instead of just macros to enforce some type checking. + */ +#define comedi_spin_lock_irqsave(lock_ptr, flags) \ + (flags = __comedi_spin_lock_irqsave(lock_ptr)) + +static inline unsigned long __comedi_spin_lock_irqsave(spinlock_t *lock_ptr) +{ + unsigned long flags; + +#if defined(CONFIG_COMEDI_RTAI) + flags = rt_spin_lock_irqsave(lock_ptr); + +#elif defined(CONFIG_COMEDI_RTL) + rtl_spin_lock_irqsave(lock_ptr, flags); + +#elif defined(CONFIG_COMEDI_RTL_V1) + rtl_spin_lock_irqsave(lock_ptr, flags); + +#elif defined(CONFIG_COMEDI_FUSION) + rthal_spin_lock_irqsave(lock_ptr, flags); +#else + spin_lock_irqsave(lock_ptr, flags); + +#endif + + return flags; +} + +static inline void comedi_spin_unlock_irqrestore(spinlock_t *lock_ptr, + unsigned long flags) +{ + +#if defined(CONFIG_COMEDI_RTAI) + rt_spin_unlock_irqrestore(flags, lock_ptr); + +#elif defined(CONFIG_COMEDI_RTL) + rtl_spin_unlock_irqrestore(lock_ptr, flags); + +#elif defined(CONFIG_COMEDI_RTL_V1) + rtl_spin_unlock_irqrestore(lock_ptr, flags); +#elif defined(CONFIG_COMEDI_FUSION) + rthal_spin_unlock_irqrestore(lock_ptr, flags); +#else + spin_unlock_irqrestore(lock_ptr, flags); + +#endif + +} + +/* define a RT safe udelay */ +static inline void comedi_udelay(unsigned int usec) +{ +#if defined(CONFIG_COMEDI_RTAI) + static const int nanosec_per_usec = 1000; + rt_busy_sleep(usec * nanosec_per_usec); +#elif defined(CONFIG_COMEDI_RTL) + static const int nanosec_per_usec = 1000; + rtl_delay(usec * nanosec_per_usec); +#else + udelay(usec); +#endif +} + +#endif diff --git a/drivers/staging/comedi/comedidev.h b/drivers/staging/comedi/comedidev.h new file mode 100644 index 00000000000..3735355d3c5 --- /dev/null +++ b/drivers/staging/comedi/comedidev.h @@ -0,0 +1,537 @@ +/* + include/linux/comedidev.h + header file for kernel-only structures, variables, and constants + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _COMEDIDEV_H +#define _COMEDIDEV_H + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/version.h> +#include <linux/kdev_t.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/spinlock.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/mm.h> +#include <linux/init.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include "interrupt.h" +#include <linux/dma-mapping.h> +#include <linux/uaccess.h> +#include <linux/io.h> + +#include "comedi.h" + +#define DPRINTK(format, args...) do { \ + if (comedi_debug) \ + printk(KERN_DEBUG "comedi: " format , ## args); \ +} while (0) + +#define COMEDI_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c)) +#define COMEDI_VERSION_CODE COMEDI_VERSION(COMEDI_MAJORVERSION, COMEDI_MINORVERSION, COMEDI_MICROVERSION) +#define COMEDI_RELEASE VERSION + +#define COMEDI_INITCLEANUP_NOMODULE(x) \ + static int __init x ## _init_module(void) \ + {return comedi_driver_register(&(x));} \ + static void __exit x ## _cleanup_module(void) \ + {comedi_driver_unregister(&(x));} \ + module_init(x ## _init_module); \ + module_exit(x ## _cleanup_module); \ + +#define COMEDI_MODULE_MACROS \ + MODULE_AUTHOR("Comedi http://www.comedi.org"); \ + MODULE_DESCRIPTION("Comedi low-level driver"); \ + MODULE_LICENSE("GPL"); \ + +#define COMEDI_INITCLEANUP(x) \ + COMEDI_MODULE_MACROS \ + COMEDI_INITCLEANUP_NOMODULE(x) + +#define COMEDI_PCI_INITCLEANUP_NOMODULE(comedi_driver, pci_id_table) \ + static int __devinit comedi_driver ## _pci_probe(struct pci_dev *dev, \ + const struct pci_device_id *ent) \ + { \ + return comedi_pci_auto_config(dev, comedi_driver.driver_name); \ + } \ + static void __devexit comedi_driver ## _pci_remove(struct pci_dev *dev) \ + { \ + comedi_pci_auto_unconfig(dev); \ + } \ + static struct pci_driver comedi_driver ## _pci_driver = \ + { \ + .id_table = pci_id_table, \ + .probe = &comedi_driver ## _pci_probe, \ + .remove = __devexit_p(&comedi_driver ## _pci_remove) \ + }; \ + static int __init comedi_driver ## _init_module(void) \ + { \ + int retval; \ + retval = comedi_driver_register(&comedi_driver); \ + if (retval < 0) \ + return retval; \ + comedi_driver ## _pci_driver.name = (char *)comedi_driver.driver_name; \ + return pci_register_driver(&comedi_driver ## _pci_driver); \ + } \ + static void __exit comedi_driver ## _cleanup_module(void) \ + { \ + pci_unregister_driver(&comedi_driver ## _pci_driver); \ + comedi_driver_unregister(&comedi_driver); \ + } \ + module_init(comedi_driver ## _init_module); \ + module_exit(comedi_driver ## _cleanup_module); + +#define COMEDI_PCI_INITCLEANUP(comedi_driver, pci_id_table) \ + COMEDI_MODULE_MACROS \ + COMEDI_PCI_INITCLEANUP_NOMODULE(comedi_driver, pci_id_table) + +#define PCI_VENDOR_ID_INOVA 0x104c +#define PCI_VENDOR_ID_NATINST 0x1093 +#define PCI_VENDOR_ID_DATX 0x1116 +#define PCI_VENDOR_ID_COMPUTERBOARDS 0x1307 +#define PCI_VENDOR_ID_ADVANTECH 0x13fe +#define PCI_VENDOR_ID_RTD 0x1435 +#define PCI_VENDOR_ID_AMPLICON 0x14dc +#define PCI_VENDOR_ID_ADLINK 0x144a +#define PCI_VENDOR_ID_ICP 0x104c +#define PCI_VENDOR_ID_CONTEC 0x1221 +#define PCI_VENDOR_ID_MEILHAUS 0x1402 + +#define COMEDI_NUM_MINORS 0x100 +#define COMEDI_NUM_LEGACY_MINORS 0x10 +#define COMEDI_NUM_BOARD_MINORS 0x30 +#define COMEDI_FIRST_SUBDEVICE_MINOR COMEDI_NUM_BOARD_MINORS + +typedef struct comedi_device_struct comedi_device; +typedef struct comedi_subdevice_struct comedi_subdevice; +typedef struct comedi_async_struct comedi_async; +typedef struct comedi_driver_struct comedi_driver; +typedef struct comedi_lrange_struct comedi_lrange; + +typedef struct device device_create_result_type; + +#define COMEDI_DEVICE_CREATE(cs, parent, devt, drvdata, device, fmt...) \ + device_create(cs, ((parent) ? (parent) : (device)), devt, drvdata, fmt) + +struct comedi_subdevice_struct { + comedi_device *device; + int type; + int n_chan; + volatile int subdev_flags; + int len_chanlist; /* maximum length of channel/gain list */ + + void *private; + + comedi_async *async; + + void *lock; + void *busy; + unsigned runflags; + spinlock_t spin_lock; + + int io_bits; + + lsampl_t maxdata; /* if maxdata==0, use list */ + const lsampl_t *maxdata_list; /* list is channel specific */ + + unsigned int flags; + const unsigned int *flaglist; + + unsigned int settling_time_0; + + const comedi_lrange *range_table; + const comedi_lrange *const *range_table_list; + + unsigned int *chanlist; /* driver-owned chanlist (not used) */ + + int (*insn_read) (comedi_device *, comedi_subdevice *, comedi_insn *, + lsampl_t *); + int (*insn_write) (comedi_device *, comedi_subdevice *, comedi_insn *, + lsampl_t *); + int (*insn_bits) (comedi_device *, comedi_subdevice *, comedi_insn *, + lsampl_t *); + int (*insn_config) (comedi_device *, comedi_subdevice *, comedi_insn *, + lsampl_t *); + + int (*do_cmd) (comedi_device *, comedi_subdevice *); + int (*do_cmdtest) (comedi_device *, comedi_subdevice *, comedi_cmd *); + int (*poll) (comedi_device *, comedi_subdevice *); + int (*cancel) (comedi_device *, comedi_subdevice *); + /* int (*do_lock)(comedi_device *,comedi_subdevice *); */ + /* int (*do_unlock)(comedi_device *,comedi_subdevice *); */ + + /* called when the buffer changes */ + int (*buf_change) (comedi_device *dev, comedi_subdevice *s, + unsigned long new_size); + + void (*munge) (comedi_device *dev, comedi_subdevice *s, void *data, + unsigned int num_bytes, unsigned int start_chan_index); + enum dma_data_direction async_dma_dir; + + unsigned int state; + + device_create_result_type *class_dev; + int minor; +}; + +struct comedi_buf_page { + void *virt_addr; + dma_addr_t dma_addr; +}; + +struct comedi_async_struct { + comedi_subdevice *subdevice; + + void *prealloc_buf; /* pre-allocated buffer */ + unsigned int prealloc_bufsz; /* buffer size, in bytes */ + struct comedi_buf_page *buf_page_list; /* virtual and dma address of each page */ + unsigned n_buf_pages; /* num elements in buf_page_list */ + + unsigned int max_bufsize; /* maximum buffer size, bytes */ + unsigned int mmap_count; /* current number of mmaps of prealloc_buf */ + + unsigned int buf_write_count; /* byte count for writer (write completed) */ + unsigned int buf_write_alloc_count; /* byte count for writer (allocated for writing) */ + unsigned int buf_read_count; /* byte count for reader (read completed) */ + unsigned int buf_read_alloc_count; /* byte count for reader (allocated for reading) */ + + unsigned int buf_write_ptr; /* buffer marker for writer */ + unsigned int buf_read_ptr; /* buffer marker for reader */ + + unsigned int cur_chan; /* useless channel marker for interrupt */ + /* number of bytes that have been received for current scan */ + unsigned int scan_progress; + /* keeps track of where we are in chanlist as for munging */ + unsigned int munge_chan; + /* number of bytes that have been munged */ + unsigned int munge_count; + /* buffer marker for munging */ + unsigned int munge_ptr; + + unsigned int events; /* events that have occurred */ + + comedi_cmd cmd; + + wait_queue_head_t wait_head; + + /* callback stuff */ + unsigned int cb_mask; + int (*cb_func) (unsigned int flags, void *); + void *cb_arg; + + int (*inttrig) (comedi_device *dev, comedi_subdevice *s, + unsigned int x); +}; + +struct comedi_driver_struct { + struct comedi_driver_struct *next; + + const char *driver_name; + struct module *module; + int (*attach) (comedi_device *, comedi_devconfig *); + int (*detach) (comedi_device *); + + /* number of elements in board_name and board_id arrays */ + unsigned int num_names; + const char *const *board_name; + /* offset in bytes from one board name pointer to the next */ + int offset; +}; + +struct comedi_device_struct { + int use_count; + comedi_driver *driver; + void *private; + + device_create_result_type *class_dev; + int minor; + /* hw_dev is passed to dma_alloc_coherent when allocating async buffers + * for subdevices that have async_dma_dir set to something other than + * DMA_NONE */ + struct device *hw_dev; + + const char *board_name; + const void *board_ptr; + int attached; + int rt; + spinlock_t spinlock; + struct mutex mutex; + int in_request_module; + + int n_subdevices; + comedi_subdevice *subdevices; + + /* dumb */ + unsigned long iobase; + unsigned int irq; + + comedi_subdevice *read_subdev; + comedi_subdevice *write_subdev; + + struct fasync_struct *async_queue; + + void (*open) (comedi_device *dev); + void (*close) (comedi_device *dev); +}; + +struct comedi_device_file_info { + comedi_device *device; + comedi_subdevice *read_subdevice; + comedi_subdevice *write_subdevice; +}; + +#ifdef CONFIG_COMEDI_DEBUG +extern int comedi_debug; +#else +static const int comedi_debug; +#endif + +/* + * function prototypes + */ + +void comedi_event(comedi_device *dev, comedi_subdevice *s); +void comedi_error(const comedi_device *dev, const char *s); + +/* we can expand the number of bits used to encode devices/subdevices into + the minor number soon, after more distros support > 8 bit minor numbers + (like after Debian Etch gets released) */ +enum comedi_minor_bits { + COMEDI_DEVICE_MINOR_MASK = 0xf, + COMEDI_SUBDEVICE_MINOR_MASK = 0xf0 +}; +static const unsigned COMEDI_SUBDEVICE_MINOR_SHIFT = 4; +static const unsigned COMEDI_SUBDEVICE_MINOR_OFFSET = 1; + +struct comedi_device_file_info *comedi_get_device_file_info(unsigned minor); + +static inline comedi_subdevice *comedi_get_read_subdevice( + const struct comedi_device_file_info *info) +{ + if (info->read_subdevice) + return info->read_subdevice; + if (info->device == NULL) + return NULL; + return info->device->read_subdev; +} + +static inline comedi_subdevice *comedi_get_write_subdevice( + const struct comedi_device_file_info *info) +{ + if (info->write_subdevice) + return info->write_subdevice; + if (info->device == NULL) + return NULL; + return info->device->write_subdev; +} + +void comedi_device_detach(comedi_device *dev); +int comedi_device_attach(comedi_device *dev, comedi_devconfig *it); +int comedi_driver_register(comedi_driver *); +int comedi_driver_unregister(comedi_driver *); + +void init_polling(void); +void cleanup_polling(void); +void start_polling(comedi_device *); +void stop_polling(comedi_device *); + +int comedi_buf_alloc(comedi_device *dev, comedi_subdevice *s, unsigned long + new_size); + +#ifdef CONFIG_PROC_FS +void comedi_proc_init(void); +void comedi_proc_cleanup(void); +#else +static inline void comedi_proc_init(void) +{ +} +static inline void comedi_proc_cleanup(void) +{ +} +#endif + +/* subdevice runflags */ +enum subdevice_runflags { + SRF_USER = 0x00000001, + SRF_RT = 0x00000002, + /* indicates an COMEDI_CB_ERROR event has occurred since the last + * command was started */ + SRF_ERROR = 0x00000004, + SRF_RUNNING = 0x08000000 +}; + +/* + various internal comedi functions + */ + +int do_rangeinfo_ioctl(comedi_device *dev, comedi_rangeinfo *arg); +int check_chanlist(comedi_subdevice *s, int n, unsigned int *chanlist); +void comedi_set_subdevice_runflags(comedi_subdevice *s, unsigned mask, + unsigned bits); +unsigned comedi_get_subdevice_runflags(comedi_subdevice *s); +int insn_inval(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data); + +/* range stuff */ + +#define RANGE(a, b) {(a)*1e6, (b)*1e6, 0} +#define RANGE_ext(a, b) {(a)*1e6, (b)*1e6, RF_EXTERNAL} +#define RANGE_mA(a, b) {(a)*1e6, (b)*1e6, UNIT_mA} +#define RANGE_unitless(a, b) {(a)*1e6, (b)*1e6, 0} /* XXX */ +#define BIP_RANGE(a) {-(a)*1e6, (a)*1e6, 0} +#define UNI_RANGE(a) {0, (a)*1e6, 0} + +extern const comedi_lrange range_bipolar10; +extern const comedi_lrange range_bipolar5; +extern const comedi_lrange range_bipolar2_5; +extern const comedi_lrange range_unipolar10; +extern const comedi_lrange range_unipolar5; +extern const comedi_lrange range_unknown; + +#define range_digital range_unipolar5 + +#if __GNUC__ >= 3 +#define GCC_ZERO_LENGTH_ARRAY +#else +#define GCC_ZERO_LENGTH_ARRAY 0 +#endif + +struct comedi_lrange_struct { + int length; + comedi_krange range[GCC_ZERO_LENGTH_ARRAY]; +}; + +/* some silly little inline functions */ + +static inline int alloc_subdevices(comedi_device *dev, + unsigned int num_subdevices) +{ + unsigned i; + + dev->n_subdevices = num_subdevices; + dev->subdevices = + kcalloc(num_subdevices, sizeof(comedi_subdevice), GFP_KERNEL); + if (!dev->subdevices) + return -ENOMEM; + for (i = 0; i < num_subdevices; ++i) { + dev->subdevices[i].device = dev; + dev->subdevices[i].async_dma_dir = DMA_NONE; + spin_lock_init(&dev->subdevices[i].spin_lock); + dev->subdevices[i].minor = -1; + } + return 0; +} + +static inline int alloc_private(comedi_device *dev, int size) +{ + dev->private = kzalloc(size, GFP_KERNEL); + if (!dev->private) + return -ENOMEM; + return 0; +} + +static inline unsigned int bytes_per_sample(const comedi_subdevice *subd) +{ + if (subd->subdev_flags & SDF_LSAMPL) + return sizeof(lsampl_t); + else + return sizeof(sampl_t); +} + +/* must be used in attach to set dev->hw_dev if you wish to dma directly +into comedi's buffer */ +static inline void comedi_set_hw_dev(comedi_device *dev, struct device *hw_dev) +{ + if (dev->hw_dev) + put_device(dev->hw_dev); + + dev->hw_dev = hw_dev; + if (dev->hw_dev) { + dev->hw_dev = get_device(dev->hw_dev); + BUG_ON(dev->hw_dev == NULL); + } +} + +int comedi_buf_put(comedi_async *async, sampl_t x); +int comedi_buf_get(comedi_async *async, sampl_t *x); + +unsigned int comedi_buf_write_n_available(comedi_async *async); +unsigned int comedi_buf_write_alloc(comedi_async *async, unsigned int nbytes); +unsigned int comedi_buf_write_alloc_strict(comedi_async *async, + unsigned int nbytes); +unsigned comedi_buf_write_free(comedi_async *async, unsigned int nbytes); +unsigned comedi_buf_read_alloc(comedi_async *async, unsigned nbytes); +unsigned comedi_buf_read_free(comedi_async *async, unsigned int nbytes); +unsigned int comedi_buf_read_n_available(comedi_async *async); +void comedi_buf_memcpy_to(comedi_async *async, unsigned int offset, + const void *source, unsigned int num_bytes); +void comedi_buf_memcpy_from(comedi_async *async, unsigned int offset, + void *destination, unsigned int num_bytes); +static inline unsigned comedi_buf_write_n_allocated(comedi_async *async) +{ + return async->buf_write_alloc_count - async->buf_write_count; +} +static inline unsigned comedi_buf_read_n_allocated(comedi_async *async) +{ + return async->buf_read_alloc_count - async->buf_read_count; +} + +void comedi_reset_async_buf(comedi_async *async); + +static inline void *comedi_aux_data(int options[], int n) +{ + unsigned long address; + unsigned long addressLow; + int bit_shift; + if (sizeof(int) >= sizeof(void *)) + address = options[COMEDI_DEVCONF_AUX_DATA_LO]; + else { + address = options[COMEDI_DEVCONF_AUX_DATA_HI]; + bit_shift = sizeof(int) * 8; + address <<= bit_shift; + addressLow = options[COMEDI_DEVCONF_AUX_DATA_LO]; + addressLow &= (1UL << bit_shift) - 1; + address |= addressLow; + } + if (n >= 1) + address += options[COMEDI_DEVCONF_AUX_DATA0_LENGTH]; + if (n >= 2) + address += options[COMEDI_DEVCONF_AUX_DATA1_LENGTH]; + if (n >= 3) + address += options[COMEDI_DEVCONF_AUX_DATA2_LENGTH]; + BUG_ON(n > 3); + return (void *)address; +} + +int comedi_alloc_board_minor(struct device *hardware_device); +void comedi_free_board_minor(unsigned minor); +int comedi_alloc_subdevice_minor(comedi_device *dev, comedi_subdevice *s); +void comedi_free_subdevice_minor(comedi_subdevice *s); +int comedi_pci_auto_config(struct pci_dev *pcidev, const char *board_name); +void comedi_pci_auto_unconfig(struct pci_dev *pcidev); + +#include "comedi_rt.h" + +#endif /* _COMEDIDEV_H */ diff --git a/drivers/staging/comedi/comedilib.h b/drivers/staging/comedi/comedilib.h new file mode 100644 index 00000000000..fc5fc015726 --- /dev/null +++ b/drivers/staging/comedi/comedilib.h @@ -0,0 +1,192 @@ +/* + linux/include/comedilib.h + header file for kcomedilib + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998-2001 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. + +*/ + +#ifndef _LINUX_COMEDILIB_H +#define _LINUX_COMEDILIB_H + +#include "comedi.h" + +/* Kernel internal stuff. Needed by real-time modules and such. */ + +#ifndef __KERNEL__ +#error linux/comedilib.h should not be included by non-kernel-space code +#endif + +/* exported functions */ + +#ifndef KCOMEDILIB_DEPRECATED + +typedef void comedi_t; + +/* these functions may not be called at real-time priority */ + +comedi_t *comedi_open(const char *path); +int comedi_close(comedi_t *dev); + +/* these functions may be called at any priority, but may fail at + real-time priority */ + +int comedi_lock(comedi_t *dev, unsigned int subdev); +int comedi_unlock(comedi_t *dev, unsigned int subdev); + +/* these functions may be called at any priority, but you must hold + the lock for the subdevice */ + +int comedi_loglevel(int loglevel); +void comedi_perror(const char *s); +char *comedi_strerror(int errnum); +int comedi_errno(void); +int comedi_fileno(comedi_t *dev); + +int comedi_cancel(comedi_t *dev, unsigned int subdev); +int comedi_register_callback(comedi_t *dev, unsigned int subdev, + unsigned int mask, int (*cb) (unsigned int, void *), void *arg); + +int comedi_command(comedi_t *dev, comedi_cmd *cmd); +int comedi_command_test(comedi_t *dev, comedi_cmd *cmd); +int comedi_trigger(comedi_t *dev, unsigned int subdev, comedi_trig *it); +int __comedi_trigger(comedi_t *dev, unsigned int subdev, comedi_trig *it); +int comedi_data_write(comedi_t *dev, unsigned int subdev, unsigned int chan, + unsigned int range, unsigned int aref, lsampl_t data); +int comedi_data_read(comedi_t *dev, unsigned int subdev, unsigned int chan, + unsigned int range, unsigned int aref, lsampl_t *data); +int comedi_data_read_hint(comedi_t *dev, unsigned int subdev, + unsigned int chan, unsigned int range, unsigned int aref); +int comedi_data_read_delayed(comedi_t *dev, unsigned int subdev, + unsigned int chan, unsigned int range, unsigned int aref, + lsampl_t *data, unsigned int nano_sec); +int comedi_dio_config(comedi_t *dev, unsigned int subdev, unsigned int chan, + unsigned int io); +int comedi_dio_read(comedi_t *dev, unsigned int subdev, unsigned int chan, + unsigned int *val); +int comedi_dio_write(comedi_t *dev, unsigned int subdev, unsigned int chan, + unsigned int val); +int comedi_dio_bitfield(comedi_t *dev, unsigned int subdev, unsigned int mask, + unsigned int *bits); +int comedi_get_n_subdevices(comedi_t *dev); +int comedi_get_version_code(comedi_t *dev); +const char *comedi_get_driver_name(comedi_t *dev); +const char *comedi_get_board_name(comedi_t *dev); +int comedi_get_subdevice_type(comedi_t *dev, unsigned int subdevice); +int comedi_find_subdevice_by_type(comedi_t *dev, int type, unsigned int subd); +int comedi_get_n_channels(comedi_t *dev, unsigned int subdevice); +lsampl_t comedi_get_maxdata(comedi_t *dev, unsigned int subdevice, unsigned + int chan); +int comedi_get_n_ranges(comedi_t *dev, unsigned int subdevice, unsigned int + chan); +int comedi_do_insn(comedi_t *dev, comedi_insn *insn); +int comedi_poll(comedi_t *dev, unsigned int subdev); + +/* DEPRECATED functions */ +int comedi_get_rangetype(comedi_t *dev, unsigned int subdevice, + unsigned int chan); + +/* ALPHA functions */ +unsigned int comedi_get_subdevice_flags(comedi_t *dev, unsigned int subdevice); +int comedi_get_len_chanlist(comedi_t *dev, unsigned int subdevice); +int comedi_get_krange(comedi_t *dev, unsigned int subdevice, unsigned int + chan, unsigned int range, comedi_krange *krange); +unsigned int comedi_get_buf_head_pos(comedi_t *dev, unsigned int subdevice); +int comedi_set_user_int_count(comedi_t *dev, unsigned int subdevice, + unsigned int buf_user_count); +int comedi_map(comedi_t *dev, unsigned int subdev, void *ptr); +int comedi_unmap(comedi_t *dev, unsigned int subdev); +int comedi_get_buffer_size(comedi_t *dev, unsigned int subdev); +int comedi_mark_buffer_read(comedi_t *dev, unsigned int subdevice, + unsigned int num_bytes); +int comedi_mark_buffer_written(comedi_t *d, unsigned int subdevice, + unsigned int num_bytes); +int comedi_get_buffer_contents(comedi_t *dev, unsigned int subdevice); +int comedi_get_buffer_offset(comedi_t *dev, unsigned int subdevice); + +#else + +/* these functions may not be called at real-time priority */ + +int comedi_open(unsigned int minor); +void comedi_close(unsigned int minor); + +/* these functions may be called at any priority, but may fail at + real-time priority */ + +int comedi_lock(unsigned int minor, unsigned int subdev); +int comedi_unlock(unsigned int minor, unsigned int subdev); + +/* these functions may be called at any priority, but you must hold + the lock for the subdevice */ + +int comedi_cancel(unsigned int minor, unsigned int subdev); +int comedi_register_callback(unsigned int minor, unsigned int subdev, + unsigned int mask, int (*cb) (unsigned int, void *), void *arg); + +int comedi_command(unsigned int minor, comedi_cmd *cmd); +int comedi_command_test(unsigned int minor, comedi_cmd *cmd); +int comedi_trigger(unsigned int minor, unsigned int subdev, comedi_trig *it); +int __comedi_trigger(unsigned int minor, unsigned int subdev, comedi_trig *it); +int comedi_data_write(unsigned int dev, unsigned int subdev, unsigned int chan, + unsigned int range, unsigned int aref, lsampl_t data); +int comedi_data_read(unsigned int dev, unsigned int subdev, unsigned int chan, + unsigned int range, unsigned int aref, lsampl_t *data); +int comedi_dio_config(unsigned int dev, unsigned int subdev, unsigned int chan, + unsigned int io); +int comedi_dio_read(unsigned int dev, unsigned int subdev, unsigned int chan, + unsigned int *val); +int comedi_dio_write(unsigned int dev, unsigned int subdev, unsigned int chan, + unsigned int val); +int comedi_dio_bitfield(unsigned int dev, unsigned int subdev, + unsigned int mask, unsigned int *bits); +int comedi_get_n_subdevices(unsigned int dev); +int comedi_get_version_code(unsigned int dev); +char *comedi_get_driver_name(unsigned int dev); +char *comedi_get_board_name(unsigned int minor); +int comedi_get_subdevice_type(unsigned int minor, unsigned int subdevice); +int comedi_find_subdevice_by_type(unsigned int minor, int type, + unsigned int subd); +int comedi_get_n_channels(unsigned int minor, unsigned int subdevice); +lsampl_t comedi_get_maxdata(unsigned int minor, unsigned int subdevice, unsigned + int chan); +int comedi_get_n_ranges(unsigned int minor, unsigned int subdevice, unsigned int + chan); +int comedi_do_insn(unsigned int minor, comedi_insn *insn); +int comedi_poll(unsigned int minor, unsigned int subdev); + +/* DEPRECATED functions */ +int comedi_get_rangetype(unsigned int minor, unsigned int subdevice, + unsigned int chan); + +/* ALPHA functions */ +unsigned int comedi_get_subdevice_flags(unsigned int minor, unsigned int + subdevice); +int comedi_get_len_chanlist(unsigned int minor, unsigned int subdevice); +int comedi_get_krange(unsigned int minor, unsigned int subdevice, unsigned int + chan, unsigned int range, comedi_krange *krange); +unsigned int comedi_get_buf_head_pos(unsigned int minor, unsigned int + subdevice); +int comedi_set_user_int_count(unsigned int minor, unsigned int subdevice, + unsigned int buf_user_count); +int comedi_map(unsigned int minor, unsigned int subdev, void **ptr); +int comedi_unmap(unsigned int minor, unsigned int subdev); + +#endif + +#endif diff --git a/drivers/staging/comedi/drivers.c b/drivers/staging/comedi/drivers.c new file mode 100644 index 00000000000..06372b227bb --- /dev/null +++ b/drivers/staging/comedi/drivers.c @@ -0,0 +1,846 @@ +/* + module/drivers.c + functions for manipulating drivers + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#define _GNU_SOURCE + +#define __NO_VERSION__ +#include "comedi_fops.h" +#include <linux/device.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/fcntl.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include "comedidev.h" +#include "wrapper.h" +#include <linux/highmem.h> /* for SuSE brokenness */ +#include <linux/vmalloc.h> +#include <linux/cdev.h> +#include <linux/dma-mapping.h> + +#include <asm/io.h> +#include <asm/system.h> + +static int postconfig(comedi_device * dev); +static int insn_rw_emulate_bits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static void *comedi_recognize(comedi_driver * driv, const char *name); +static void comedi_report_boards(comedi_driver * driv); +static int poll_invalid(comedi_device * dev, comedi_subdevice * s); +int comedi_buf_alloc(comedi_device * dev, comedi_subdevice * s, + unsigned long new_size); + +comedi_driver *comedi_drivers; + +int comedi_modprobe(int minor) +{ + return -EINVAL; +} + +static void cleanup_device(comedi_device * dev) +{ + int i; + comedi_subdevice *s; + + if (dev->subdevices) { + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + comedi_free_subdevice_minor(s); + if (s->async) { + comedi_buf_alloc(dev, s, 0); + kfree(s->async); + } + } + kfree(dev->subdevices); + dev->subdevices = NULL; + dev->n_subdevices = 0; + } + if (dev->private) { + kfree(dev->private); + dev->private = NULL; + } + dev->driver = 0; + dev->board_name = NULL; + dev->board_ptr = NULL; + dev->iobase = 0; + dev->irq = 0; + dev->read_subdev = NULL; + dev->write_subdev = NULL; + dev->open = NULL; + dev->close = NULL; + comedi_set_hw_dev(dev, NULL); +} + +static void __comedi_device_detach(comedi_device * dev) +{ + dev->attached = 0; + if (dev->driver) { + dev->driver->detach(dev); + } else { + printk("BUG: dev->driver=NULL in comedi_device_detach()\n"); + } + cleanup_device(dev); +} + +void comedi_device_detach(comedi_device * dev) +{ + if (!dev->attached) + return; + __comedi_device_detach(dev); +} + +int comedi_device_attach(comedi_device * dev, comedi_devconfig * it) +{ + comedi_driver *driv; + int ret; + + if (dev->attached) + return -EBUSY; + + for (driv = comedi_drivers; driv; driv = driv->next) { + if (!try_module_get(driv->module)) { + printk("comedi: failed to increment module count, skipping\n"); + continue; + } + if (driv->num_names) { + dev->board_ptr = comedi_recognize(driv, it->board_name); + if (dev->board_ptr == NULL) { + module_put(driv->module); + continue; + } + } else { + if (strcmp(driv->driver_name, it->board_name)) { + module_put(driv->module); + continue; + } + } + //initialize dev->driver here so comedi_error() can be called from attach + dev->driver = driv; + ret = driv->attach(dev, it); + if (ret < 0) { + module_put(dev->driver->module); + __comedi_device_detach(dev); + return ret; + } + goto attached; + } + + // recognize has failed if we get here + // report valid board names before returning error + for (driv = comedi_drivers; driv; driv = driv->next) { + if (!try_module_get(driv->module)) { + printk("comedi: failed to increment module count\n"); + continue; + } + comedi_report_boards(driv); + module_put(driv->module); + } + return -EIO; + +attached: + /* do a little post-config cleanup */ + ret = postconfig(dev); + module_put(dev->driver->module); + if (ret < 0) { + __comedi_device_detach(dev); + return ret; + } + + if (!dev->board_name) { + printk("BUG: dev->board_name=<%p>\n", dev->board_name); + dev->board_name = "BUG"; + } + smp_wmb(); + dev->attached = 1; + + return 0; +} + +int comedi_driver_register(comedi_driver * driver) +{ + driver->next = comedi_drivers; + comedi_drivers = driver; + + return 0; +} + +int comedi_driver_unregister(comedi_driver * driver) +{ + comedi_driver *prev; + int i; + + /* check for devices using this driver */ + for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) { + struct comedi_device_file_info *dev_file_info = comedi_get_device_file_info(i); + comedi_device *dev; + + if(dev_file_info == NULL) continue; + dev = dev_file_info->device; + + mutex_lock(&dev->mutex); + if (dev->attached && dev->driver == driver) { + if (dev->use_count) + printk("BUG! detaching device with use_count=%d\n", dev->use_count); + comedi_device_detach(dev); + } + mutex_unlock(&dev->mutex); + } + + if (comedi_drivers == driver) { + comedi_drivers = driver->next; + return 0; + } + + for (prev = comedi_drivers; prev->next; prev = prev->next) { + if (prev->next == driver) { + prev->next = driver->next; + return 0; + } + } + return -EINVAL; +} + +static int postconfig(comedi_device * dev) +{ + int i; + comedi_subdevice *s; + comedi_async *async = NULL; + int ret; + + for (i = 0; i < dev->n_subdevices; i++) { + s = dev->subdevices + i; + + if (s->type == COMEDI_SUBD_UNUSED) + continue; + + if (s->len_chanlist == 0) + s->len_chanlist = 1; + + if (s->do_cmd) { + BUG_ON((s->subdev_flags & (SDF_CMD_READ | + SDF_CMD_WRITE)) == 0); + BUG_ON(!s->do_cmdtest); + + async = kzalloc(sizeof(comedi_async), GFP_KERNEL); + if (async == NULL) { + printk("failed to allocate async struct\n"); + return -ENOMEM; + } + init_waitqueue_head(&async->wait_head); + async->subdevice = s; + s->async = async; + +#define DEFAULT_BUF_MAXSIZE (64*1024) +#define DEFAULT_BUF_SIZE (64*1024) + + async->max_bufsize = DEFAULT_BUF_MAXSIZE; + + async->prealloc_buf = NULL; + async->prealloc_bufsz = 0; + if (comedi_buf_alloc(dev, s, DEFAULT_BUF_SIZE) < 0) { + printk("Buffer allocation failed\n"); + return -ENOMEM; + } + if (s->buf_change) { + ret = s->buf_change(dev, s, DEFAULT_BUF_SIZE); + if (ret < 0) + return ret; + } + comedi_alloc_subdevice_minor(dev, s); + } + + if (!s->range_table && !s->range_table_list) + s->range_table = &range_unknown; + + if (!s->insn_read && s->insn_bits) + s->insn_read = insn_rw_emulate_bits; + if (!s->insn_write && s->insn_bits) + s->insn_write = insn_rw_emulate_bits; + + if (!s->insn_read) + s->insn_read = insn_inval; + if (!s->insn_write) + s->insn_write = insn_inval; + if (!s->insn_bits) + s->insn_bits = insn_inval; + if (!s->insn_config) + s->insn_config = insn_inval; + + if (!s->poll) + s->poll = poll_invalid; + } + + return 0; +} + +// generic recognize function for drivers that register their supported board names +void *comedi_recognize(comedi_driver * driv, const char *name) +{ + unsigned i; + const char *const *name_ptr = driv->board_name; + for (i = 0; i < driv->num_names; i++) { + if (strcmp(*name_ptr, name) == 0) + return (void *)name_ptr; + name_ptr = + (const char *const *)((const char *)name_ptr + + driv->offset); + } + + return NULL; +} + +void comedi_report_boards(comedi_driver * driv) +{ + unsigned int i; + const char *const *name_ptr; + + printk("comedi: valid board names for %s driver are:\n", + driv->driver_name); + + name_ptr = driv->board_name; + for (i = 0; i < driv->num_names; i++) { + printk(" %s\n", *name_ptr); + name_ptr = (const char **)((char *)name_ptr + driv->offset); + } + + if (driv->num_names == 0) + printk(" %s\n", driv->driver_name); +} + +static int poll_invalid(comedi_device * dev, comedi_subdevice * s) +{ + return -EINVAL; +} + +int insn_inval(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + return -EINVAL; +} + +static int insn_rw_emulate_bits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + comedi_insn new_insn; + int ret; + static const unsigned channels_per_bitfield = 32; + + unsigned chan = CR_CHAN(insn->chanspec); + const unsigned base_bitfield_channel = + (chan < channels_per_bitfield) ? 0 : chan; + lsampl_t new_data[2]; + memset(new_data, 0, sizeof(new_data)); + memset(&new_insn, 0, sizeof(new_insn)); + new_insn.insn = INSN_BITS; + new_insn.chanspec = base_bitfield_channel; + new_insn.n = 2; + new_insn.data = new_data; + new_insn.subdev = insn->subdev; + + if (insn->insn == INSN_WRITE) { + if (!(s->subdev_flags & SDF_WRITABLE)) + return -EINVAL; + new_data[0] = 1 << (chan - base_bitfield_channel); /* mask */ + new_data[1] = data[0] ? (1 << (chan - base_bitfield_channel)) : 0; /* bits */ + } + + ret = s->insn_bits(dev, s, &new_insn, new_data); + if (ret < 0) + return ret; + + if (insn->insn == INSN_READ) { + data[0] = (new_data[1] >> (chan - base_bitfield_channel)) & 1; + } + + return 1; +} + +static inline unsigned long uvirt_to_kva(pgd_t * pgd, unsigned long adr) +{ + unsigned long ret = 0UL; + pmd_t *pmd; + pte_t *ptep, pte; + pud_t *pud; + + if (!pgd_none(*pgd)) { + pud = pud_offset(pgd, adr); + pmd = pmd_offset(pud, adr); + if (!pmd_none(*pmd)) { + ptep = pte_offset_kernel(pmd, adr); + pte = *ptep; + if (pte_present(pte)) { + ret = (unsigned long) + page_address(pte_page(pte)); + ret |= (adr & (PAGE_SIZE - 1)); + } + } + } + return ret; +} + +static inline unsigned long kvirt_to_kva(unsigned long adr) +{ + unsigned long va, kva; + + va = adr; + kva = uvirt_to_kva(pgd_offset_k(va), va); + + return kva; +} + +int comedi_buf_alloc(comedi_device * dev, comedi_subdevice * s, + unsigned long new_size) +{ + comedi_async *async = s->async; + + /* Round up new_size to multiple of PAGE_SIZE */ + new_size = (new_size + PAGE_SIZE - 1) & PAGE_MASK; + + /* if no change is required, do nothing */ + if (async->prealloc_buf && async->prealloc_bufsz == new_size) { + return 0; + } + // deallocate old buffer + if (async->prealloc_buf) { + vunmap(async->prealloc_buf); + async->prealloc_buf = NULL; + async->prealloc_bufsz = 0; + } + if (async->buf_page_list) { + unsigned i; + for (i = 0; i < async->n_buf_pages; ++i) { + if (async->buf_page_list[i].virt_addr) { + mem_map_unreserve(virt_to_page(async-> + buf_page_list[i].virt_addr)); + if (s->async_dma_dir != DMA_NONE) { + dma_free_coherent(dev->hw_dev, + PAGE_SIZE, + async->buf_page_list[i]. + virt_addr, + async->buf_page_list[i]. + dma_addr); + } else { + free_page((unsigned long)async-> + buf_page_list[i].virt_addr); + } + } + } + vfree(async->buf_page_list); + async->buf_page_list = NULL; + async->n_buf_pages = 0; + } + // allocate new buffer + if (new_size) { + unsigned i = 0; + unsigned n_pages = new_size >> PAGE_SHIFT; + struct page **pages = NULL; + + async->buf_page_list = + vmalloc(sizeof(struct comedi_buf_page) * n_pages); + if (async->buf_page_list) { + memset(async->buf_page_list, 0, + sizeof(struct comedi_buf_page) * n_pages); + pages = vmalloc(sizeof(struct page *) * n_pages); + } + if (pages) { + for (i = 0; i < n_pages; i++) { + if (s->async_dma_dir != DMA_NONE) { + async->buf_page_list[i].virt_addr = + dma_alloc_coherent(dev->hw_dev, + PAGE_SIZE, + &async->buf_page_list[i]. + dma_addr, + GFP_KERNEL | __GFP_COMP); + } else { + async->buf_page_list[i].virt_addr = + (void *) + get_zeroed_page(GFP_KERNEL); + } + if (async->buf_page_list[i].virt_addr == NULL) { + break; + } + mem_map_reserve(virt_to_page(async-> + buf_page_list[i].virt_addr)); + pages[i] = + virt_to_page(async->buf_page_list[i]. + virt_addr); + } + } + if (i == n_pages) { + async->prealloc_buf = + vmap(pages, n_pages, VM_MAP, + PAGE_KERNEL_NOCACHE); + } + if (pages) { + vfree(pages); + } + if (async->prealloc_buf == NULL) { + /* Some allocation failed above. */ + if (async->buf_page_list) { + for (i = 0; i < n_pages; i++) { + if (async->buf_page_list[i].virt_addr == + NULL) { + break; + } + mem_map_unreserve(virt_to_page(async-> + buf_page_list[i]. + virt_addr)); + if (s->async_dma_dir != DMA_NONE) { + dma_free_coherent(dev->hw_dev, + PAGE_SIZE, + async->buf_page_list[i]. + virt_addr, + async->buf_page_list[i]. + dma_addr); + } else { + free_page((unsigned long)async-> + buf_page_list[i]. + virt_addr); + } + } + vfree(async->buf_page_list); + async->buf_page_list = NULL; + } + return -ENOMEM; + } + async->n_buf_pages = n_pages; + } + async->prealloc_bufsz = new_size; + + return 0; +} + +/* munging is applied to data by core as it passes between user + * and kernel space */ +unsigned int comedi_buf_munge(comedi_async * async, unsigned int num_bytes) +{ + comedi_subdevice *s = async->subdevice; + unsigned int count = 0; + const unsigned num_sample_bytes = bytes_per_sample(s); + + if (s->munge == NULL || (async->cmd.flags & CMDF_RAWDATA)) { + async->munge_count += num_bytes; + if ((int)(async->munge_count - async->buf_write_count) > 0) + BUG(); + return num_bytes; + } + /* don't munge partial samples */ + num_bytes -= num_bytes % num_sample_bytes; + while (count < num_bytes) { + int block_size; + + block_size = num_bytes - count; + if (block_size < 0) { + rt_printk("%s: %s: bug! block_size is negative\n", + __FILE__, __FUNCTION__); + break; + } + if ((int)(async->munge_ptr + block_size - + async->prealloc_bufsz) > 0) + block_size = async->prealloc_bufsz - async->munge_ptr; + + s->munge(s->device, s, async->prealloc_buf + async->munge_ptr, + block_size, async->munge_chan); + + smp_wmb(); //barrier insures data is munged in buffer before munge_count is incremented + + async->munge_chan += block_size / num_sample_bytes; + async->munge_chan %= async->cmd.chanlist_len; + async->munge_count += block_size; + async->munge_ptr += block_size; + async->munge_ptr %= async->prealloc_bufsz; + count += block_size; + } + if ((int)(async->munge_count - async->buf_write_count) > 0) + BUG(); + return count; +} + +unsigned int comedi_buf_write_n_available(comedi_async * async) +{ + unsigned int free_end; + unsigned int nbytes; + + if (async == NULL) + return 0; + + free_end = async->buf_read_count + async->prealloc_bufsz; + nbytes = free_end - async->buf_write_alloc_count; + nbytes -= nbytes % bytes_per_sample(async->subdevice); + /* barrier insures the read of buf_read_count in this + query occurs before any following writes to the buffer which + might be based on the return value from this query. + */ + smp_mb(); + return nbytes; +} + +/* allocates chunk for the writer from free buffer space */ +unsigned int comedi_buf_write_alloc(comedi_async * async, unsigned int nbytes) +{ + unsigned int free_end = async->buf_read_count + async->prealloc_bufsz; + + if ((int)(async->buf_write_alloc_count + nbytes - free_end) > 0) { + nbytes = free_end - async->buf_write_alloc_count; + } + async->buf_write_alloc_count += nbytes; + /* barrier insures the read of buf_read_count above occurs before + we write data to the write-alloc'ed buffer space */ + smp_mb(); + return nbytes; +} + +/* allocates nothing unless it can completely fulfill the request */ +unsigned int comedi_buf_write_alloc_strict(comedi_async * async, + unsigned int nbytes) +{ + unsigned int free_end = async->buf_read_count + async->prealloc_bufsz; + + if ((int)(async->buf_write_alloc_count + nbytes - free_end) > 0) { + nbytes = 0; + } + async->buf_write_alloc_count += nbytes; + /* barrier insures the read of buf_read_count above occurs before + we write data to the write-alloc'ed buffer space */ + smp_mb(); + return nbytes; +} + +/* transfers a chunk from writer to filled buffer space */ +unsigned comedi_buf_write_free(comedi_async * async, unsigned int nbytes) +{ + if ((int)(async->buf_write_count + nbytes - + async->buf_write_alloc_count) > 0) { + rt_printk + ("comedi: attempted to write-free more bytes than have been write-allocated.\n"); + nbytes = async->buf_write_alloc_count - async->buf_write_count; + } + async->buf_write_count += nbytes; + async->buf_write_ptr += nbytes; + comedi_buf_munge(async, async->buf_write_count - async->munge_count); + if (async->buf_write_ptr >= async->prealloc_bufsz) { + async->buf_write_ptr %= async->prealloc_bufsz; + } + return nbytes; +} + +/* allocates a chunk for the reader from filled (and munged) buffer space */ +unsigned comedi_buf_read_alloc(comedi_async * async, unsigned nbytes) +{ + if ((int)(async->buf_read_alloc_count + nbytes - async->munge_count) > + 0) { + nbytes = async->munge_count - async->buf_read_alloc_count; + } + async->buf_read_alloc_count += nbytes; + /* barrier insures read of munge_count occurs before we actually read + data out of buffer */ + smp_rmb(); + return nbytes; +} + +/* transfers control of a chunk from reader to free buffer space */ +unsigned comedi_buf_read_free(comedi_async * async, unsigned int nbytes) +{ + // barrier insures data has been read out of buffer before read count is incremented + smp_mb(); + if ((int)(async->buf_read_count + nbytes - + async->buf_read_alloc_count) > 0) { + rt_printk + ("comedi: attempted to read-free more bytes than have been read-allocated.\n"); + nbytes = async->buf_read_alloc_count - async->buf_read_count; + } + async->buf_read_count += nbytes; + async->buf_read_ptr += nbytes; + async->buf_read_ptr %= async->prealloc_bufsz; + return nbytes; +} + +void comedi_buf_memcpy_to(comedi_async * async, unsigned int offset, + const void *data, unsigned int num_bytes) +{ + unsigned int write_ptr = async->buf_write_ptr + offset; + + if (write_ptr >= async->prealloc_bufsz) + write_ptr %= async->prealloc_bufsz; + + while (num_bytes) { + unsigned int block_size; + + if (write_ptr + num_bytes > async->prealloc_bufsz) + block_size = async->prealloc_bufsz - write_ptr; + else + block_size = num_bytes; + + memcpy(async->prealloc_buf + write_ptr, data, block_size); + + data += block_size; + num_bytes -= block_size; + + write_ptr = 0; + } +} + +void comedi_buf_memcpy_from(comedi_async * async, unsigned int offset, + void *dest, unsigned int nbytes) +{ + void *src; + unsigned int read_ptr = async->buf_read_ptr + offset; + + if (read_ptr >= async->prealloc_bufsz) + read_ptr %= async->prealloc_bufsz; + + while (nbytes) { + unsigned int block_size; + + src = async->prealloc_buf + read_ptr; + + if (nbytes >= async->prealloc_bufsz - read_ptr) + block_size = async->prealloc_bufsz - read_ptr; + else + block_size = nbytes; + + memcpy(dest, src, block_size); + nbytes -= block_size; + dest += block_size; + read_ptr = 0; + } +} + +unsigned int comedi_buf_read_n_available(comedi_async * async) +{ + unsigned num_bytes; + + if (async == NULL) + return 0; + num_bytes = async->munge_count - async->buf_read_count; + /* barrier insures the read of munge_count in this + query occurs before any following reads of the buffer which + might be based on the return value from this query. + */ + smp_rmb(); + return num_bytes; +} + +int comedi_buf_get(comedi_async * async, sampl_t * x) +{ + unsigned int n = comedi_buf_read_n_available(async); + + if (n < sizeof(sampl_t)) + return 0; + comedi_buf_read_alloc(async, sizeof(sampl_t)); + *x = *(sampl_t *) (async->prealloc_buf + async->buf_read_ptr); + comedi_buf_read_free(async, sizeof(sampl_t)); + return 1; +} + +int comedi_buf_put(comedi_async * async, sampl_t x) +{ + unsigned int n = comedi_buf_write_alloc_strict(async, sizeof(sampl_t)); + + if (n < sizeof(sampl_t)) { + async->events |= COMEDI_CB_ERROR; + return 0; + } + *(sampl_t *) (async->prealloc_buf + async->buf_write_ptr) = x; + comedi_buf_write_free(async, sizeof(sampl_t)); + return 1; +} + +void comedi_reset_async_buf(comedi_async * async) +{ + async->buf_write_alloc_count = 0; + async->buf_write_count = 0; + async->buf_read_alloc_count = 0; + async->buf_read_count = 0; + + async->buf_write_ptr = 0; + async->buf_read_ptr = 0; + + async->cur_chan = 0; + async->scan_progress = 0; + async->munge_chan = 0; + async->munge_count = 0; + async->munge_ptr = 0; + + async->events = 0; +} + +int comedi_auto_config(struct device *hardware_device, const char *board_name, const int *options, unsigned num_options) +{ + comedi_devconfig it; + int minor; + struct comedi_device_file_info *dev_file_info; + int retval; + + minor = comedi_alloc_board_minor(hardware_device); + if(minor < 0) return minor; + dev_set_drvdata(hardware_device, (void*)(unsigned long)minor); + + dev_file_info = comedi_get_device_file_info(minor); + + memset(&it, 0, sizeof(it)); + strncpy(it.board_name, board_name, COMEDI_NAMELEN); + it.board_name[COMEDI_NAMELEN - 1] = '\0'; + BUG_ON(num_options > COMEDI_NDEVCONFOPTS); + memcpy(it.options, options, num_options * sizeof(int)); + + mutex_lock(&dev_file_info->device->mutex); + retval = comedi_device_attach(dev_file_info->device, &it); + mutex_unlock(&dev_file_info->device->mutex); + if(retval < 0) + { + comedi_free_board_minor(minor); + } + return retval; +} + +void comedi_auto_unconfig(struct device *hardware_device) +{ + unsigned long minor = (unsigned long)dev_get_drvdata(hardware_device); + + BUG_ON(minor >= COMEDI_NUM_BOARD_MINORS); + + comedi_free_board_minor(minor); +} + +int comedi_pci_auto_config(struct pci_dev *pcidev, const char *board_name) +{ + int options[2]; + + // pci bus + options[0] = pcidev->bus->number; + // pci slot + options[1] = PCI_SLOT(pcidev->devfn); + + return comedi_auto_config(&pcidev->dev, board_name, options, sizeof(options) / sizeof(options[0])); +} + +void comedi_pci_auto_unconfig(struct pci_dev *pcidev) +{ + comedi_auto_unconfig(&pcidev->dev); +} diff --git a/drivers/staging/comedi/drivers/Makefile b/drivers/staging/comedi/drivers/Makefile new file mode 100644 index 00000000000..eb7a615e085 --- /dev/null +++ b/drivers/staging/comedi/drivers/Makefile @@ -0,0 +1,21 @@ +# Makefile for individual comedi drivers +# + +# Comedi "helper" modules +obj-$(CONFIG_COMEDI) += comedi_fc.o +obj-$(CONFIG_COMEDI) += comedi_bond.o +obj-$(CONFIG_COMEDI) += comedi_test.o +obj-$(CONFIG_COMEDI) += comedi_parport.o + +# Comedi PCI drivers +obj-$(CONFIG_COMEDI_PCI_DRIVERS) += mite.o +obj-$(CONFIG_COMEDI_PCI_DRIVERS) += icp_multi.o +obj-$(CONFIG_COMEDI_PCI_DRIVERS) += me_daq.o +obj-$(CONFIG_COMEDI_PCI_DRIVERS) += me4000.o +obj-$(CONFIG_COMEDI_PCI_DRIVERS) += rtd520.o +obj-$(CONFIG_COMEDI_PCI_DRIVERS) += s626.o + +# Comedi USB drivers +obj-$(CONFIG_COMEDI_USB_DRIVERS) += usbdux.o +obj-$(CONFIG_COMEDI_USB_DRIVERS) += usbduxfast.o +obj-$(CONFIG_COMEDI_USB_DRIVERS) += dt9812.o diff --git a/drivers/staging/comedi/drivers/comedi_bond.c b/drivers/staging/comedi/drivers/comedi_bond.c new file mode 100644 index 00000000000..9e5496f4f1a --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_bond.c @@ -0,0 +1,535 @@ +/* + comedi/drivers/comedi_bond.c + A Comedi driver to 'bond' or merge multiple drivers and devices as one. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef <ds@schleef.org> + Copyright (C) 2005 Calin A. Culianu <calin@ajvar.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: comedi_bond +Description: A driver to 'bond' (merge) multiple subdevices from multiple + devices together as one. +Devices: +Author: ds +Updated: Mon, 10 Oct 00:18:25 -0500 +Status: works + +This driver allows you to 'bond' (merge) multiple comedi subdevices +(coming from possibly difference boards and/or drivers) together. For +example, if you had a board with 2 different DIO subdevices, and +another with 1 DIO subdevice, you could 'bond' them with this driver +so that they look like one big fat DIO subdevice. This makes writing +applications slightly easier as you don't have to worry about managing +different subdevices in the application -- you just worry about +indexing one linear array of channel id's. + +Right now only DIO subdevices are supported as that's the personal itch +I am scratching with this driver. If you want to add support for AI and AO +subdevs, go right on ahead and do so! + +Commands aren't supported -- although it would be cool if they were. + +Configuration Options: + List of comedi-minors to bond. All subdevices of the same type + within each minor will be concatenated together in the order given here. +*/ + +/* + * The previous block comment is used to automatically generate + * documentation in Comedi and Comedilib. The fields: + * + * Driver: the name of the driver + * Description: a short phrase describing the driver. Don't list boards. + * Devices: a full list of the boards that attempt to be supported by + * the driver. Format is "(manufacturer) board name [comedi name]", + * where comedi_name is the name that is used to configure the board. + * See the comment near board_name: in the comedi_driver structure + * below. If (manufacturer) or [comedi name] is missing, the previous + * value is used. + * Author: you + * Updated: date when the _documentation_ was last updated. Use 'date -R' + * to get a value for this. + * Status: a one-word description of the status. Valid values are: + * works - driver works correctly on most boards supported, and + * passes comedi_test. + * unknown - unknown. Usually put there by ds. + * experimental - may not work in any particular release. Author + * probably wants assistance testing it. + * bitrotten - driver has not been update in a long time, probably + * doesn't work, and probably is missing support for significant + * Comedi interface features. + * untested - author probably wrote it "blind", and is believed to + * work, but no confirmation. + * + * These headers should be followed by a blank line, and any comments + * you wish to say about the driver. The comment area is the place + * to put any known bugs, limitations, unsupported features, supported + * command triggers, whether or not commands are supported on particular + * subdevices, etc. + * + * Somewhere in the comment should be information about configuration + * options that are used with comedi_config. + */ + +#include "../comedilib.h" +#include "../comedidev.h" +#include <linux/string.h> + +/* The maxiumum number of channels per subdevice. */ +#define MAX_CHANS 256 + +#define MODULE_NAME "comedi_bond" +#ifdef MODULE_LICENSE +MODULE_LICENSE("GPL"); +#endif +#ifndef STR +# define STR1(x) #x +# define STR(x) STR1(x) +#endif + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "If true, print extra cryptic debugging output useful" + "only to developers."); + +#define LOG_MSG(x...) printk(KERN_INFO MODULE_NAME": "x) +#define DEBUG(x...) \ + do { \ + if (debug) \ + printk(KERN_DEBUG MODULE_NAME": DEBUG: "x); \ + } while (0) +#define WARNING(x...) printk(KERN_WARNING MODULE_NAME ": WARNING: "x) +#define ERROR(x...) printk(KERN_ERR MODULE_NAME ": INTERNAL ERROR: "x) +MODULE_AUTHOR("Calin A. Culianu"); +MODULE_DESCRIPTION(MODULE_NAME "A driver for COMEDI to bond multiple COMEDI " + "devices together as one. In the words of John Lennon: " + "'And the world will live as one...'"); + +/* + * Board descriptions for two imaginary boards. Describing the + * boards in this way is optional, and completely driver-dependent. + * Some drivers use arrays such as this, other do not. + */ +struct BondingBoard { + const char *name; +}; + +static const struct BondingBoard bondingBoards[] = { + { + .name = MODULE_NAME, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const struct BondingBoard *)dev->board_ptr) + +struct BondedDevice { + comedi_t *dev; + unsigned minor; + unsigned subdev; + unsigned subdev_type; + unsigned nchans; + unsigned chanid_offset; /* The offset into our unified linear + channel-id's of chanid 0 on this + subdevice. */ +}; + +/* this structure is for data unique to this hardware driver. If + several hardware drivers keep similar information in this structure, + feel free to suggest moving the variable to the comedi_device struct. */ +struct Private { +# define MAX_BOARD_NAME 256 + char name[MAX_BOARD_NAME]; + struct BondedDevice **devs; + unsigned ndevs; + struct BondedDevice *chanIdDevMap[MAX_CHANS]; + unsigned nchans; +}; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((struct Private *)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 bonding_attach(comedi_device *dev, comedi_devconfig *it); +static int bonding_detach(comedi_device *dev); +/** Build Private array of all devices.. */ +static int doDevConfig(comedi_device *dev, comedi_devconfig *it); +static void doDevUnconfig(comedi_device *dev); +/* Ugly implementation of realloc that always copies memory around -- I'm lazy, + * what can I say? I like to do wasteful memcopies.. :) */ +static void *Realloc(const void *ptr, size_t len, size_t old_len); + +static comedi_driver driver_bonding = { + .driver_name = MODULE_NAME, + .module = THIS_MODULE, + .attach = bonding_attach, + .detach = bonding_detach, + /* It is not necessary to implement the following members if you are + * writing a driver for a ISA PnP or PCI card */ + /* Most drivers will support multiple types of boards by + * having an array of board structures. These were defined + * in skel_boards[] above. Note that the element 'name' + * was first in the structure -- Comedi uses this fact to + * extract the name of the board without knowing any details + * about the structure except for its length. + * When a device is attached (by comedi_config), the name + * of the device is given to Comedi, and Comedi tries to + * match it by going through the list of board names. If + * there is a match, the address of the pointer is put + * into dev->board_ptr and driver->attach() is called. + * + * Note that these are not necessary if you can determine + * the type of board in software. ISA PnP, PCI, and PCMCIA + * devices are such boards. + */ + .board_name = &bondingBoards[0].name, + .offset = sizeof(struct BondingBoard), + .num_names = sizeof(bondingBoards) / sizeof(struct BondingBoard), +}; + +static int bonding_dio_insn_bits(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data); +static int bonding_dio_insn_config(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int bonding_attach(comedi_device *dev, comedi_devconfig *it) +{ + comedi_subdevice *s; + + LOG_MSG("comedi%d\n", dev->minor); + + /* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(struct Private)) < 0) + return -ENOMEM; + + /* + * Setup our bonding from config params.. sets up our Private struct.. + */ + if (!doDevConfig(dev, it)) + return -EINVAL; + + /* + * Initialize dev->board_name. Note that we can use the "thisboard" + * macro now, since we just initialized it in the last line. + */ + dev->board_name = devpriv->name; + + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 1) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = devpriv->nchans; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = bonding_dio_insn_bits; + s->insn_config = bonding_dio_insn_config; + + LOG_MSG("attached with %u DIO channels coming from %u different " + "subdevices all bonded together. " + "John Lennon would be proud!\n", + devpriv->nchans, devpriv->ndevs); + + return 1; +} + +/* + * _detach is called to deconfigure a device. It should deallocate + * resources. + * This function is also called when _attach() fails, so it should be + * careful not to release resources that were not necessarily + * allocated by _attach(). dev->private and dev->subdevices are + * deallocated automatically by the core. + */ +static int bonding_detach(comedi_device *dev) +{ + LOG_MSG("comedi%d: remove\n", dev->minor); + doDevUnconfig(dev); + return 0; +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write */ +static int bonding_dio_insn_bits(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ +#define LSAMPL_BITS (sizeof(lsampl_t)*8) + unsigned nchans = LSAMPL_BITS, num_done = 0, i; + if (insn->n != 2) + return -EINVAL; + + if (devpriv->nchans < nchans) + nchans = devpriv->nchans; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + for (i = 0; num_done < nchans && i < devpriv->ndevs; ++i) { + struct BondedDevice *bdev = devpriv->devs[i]; + /* Grab the channel mask and data of only the bits corresponding + to this subdevice.. need to shift them to zero position of + course. */ + /* Bits corresponding to this subdev. */ + lsampl_t subdevMask = ((1 << bdev->nchans) - 1); + lsampl_t writeMask, dataBits; + + /* Argh, we have >= LSAMPL_BITS chans.. take all bits */ + if (bdev->nchans >= LSAMPL_BITS) + subdevMask = (lsampl_t) (-1); + + writeMask = (data[0] >> num_done) & subdevMask; + dataBits = (data[1] >> num_done) & subdevMask; + + /* Read/Write the new digital lines */ + if (comedi_dio_bitfield(bdev->dev, bdev->subdev, writeMask, + &dataBits) != 2) + return -EINVAL; + + /* Make room for the new bits in data[1], the return value */ + data[1] &= ~(subdevMask << num_done); + /* Put the bits in the return value */ + data[1] |= (dataBits & subdevMask) << num_done; + /* Save the new bits to the saved state.. */ + s->state = data[1]; + + num_done += bdev->nchans; + } + + return insn->n; +} + +static int bonding_dio_insn_config(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int chan = CR_CHAN(insn->chanspec), ret, io_bits = s->io_bits; + unsigned int io; + struct BondedDevice *bdev; + + if (chan < 0 || chan >= devpriv->nchans) + return -EINVAL; + bdev = devpriv->chanIdDevMap[chan]; + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + io = COMEDI_OUTPUT; /* is this really necessary? */ + io_bits |= 1 << chan; + break; + case INSN_CONFIG_DIO_INPUT: + io = COMEDI_INPUT; /* is this really necessary? */ + io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + break; + } + /* 'real' channel id for this subdev.. */ + chan -= bdev->chanid_offset; + ret = comedi_dio_config(bdev->dev, bdev->subdev, chan, io); + if (ret != 1) + return -EINVAL; + /* Finally, save the new io_bits values since we didn't get + an error above. */ + s->io_bits = io_bits; + return insn->n; +} + +static void *Realloc(const void *oldmem, size_t newlen, size_t oldlen) +{ + void *newmem = kmalloc(newlen, GFP_KERNEL); + + if (newmem && oldmem) + memcpy(newmem, oldmem, min(oldlen, newlen)); + kfree(oldmem); + return newmem; +} + +static int doDevConfig(comedi_device *dev, comedi_devconfig *it) +{ + int i; + comedi_t *devs_opened[COMEDI_NUM_BOARD_MINORS]; + + memset(devs_opened, 0, sizeof(devs_opened)); + devpriv->name[0] = 0;; + /* Loop through all comedi devices specified on the command-line, + building our device list */ + for (i = 0; i < COMEDI_NDEVCONFOPTS && (!i || it->options[i]); ++i) { + char file[] = "/dev/comediXXXXXX"; + int minor = it->options[i]; + comedi_t *d; + int sdev = -1, nchans, tmp; + struct BondedDevice *bdev = NULL; + + if (minor < 0 || minor > COMEDI_NUM_BOARD_MINORS) { + ERROR("Minor %d is invalid!\n", minor); + return 0; + } + if (minor == dev->minor) { + ERROR("Cannot bond this driver to itself!\n"); + return 0; + } + if (devs_opened[minor]) { + ERROR("Minor %d specified more than once!\n", minor); + return 0; + } + + snprintf(file, sizeof(file), "/dev/comedi%u", minor); + file[sizeof(file) - 1] = 0; + + d = devs_opened[minor] = comedi_open(file); + + if (!d) { + ERROR("Minor %u could not be opened\n", minor); + return 0; + } + + /* Do DIO, as that's all we support now.. */ + while ((sdev = comedi_find_subdevice_by_type(d, COMEDI_SUBD_DIO, + sdev + 1)) > -1) { + nchans = comedi_get_n_channels(d, sdev); + if (nchans <= 0) { + ERROR("comedi_get_n_channels() returned %d " + "on minor %u subdev %d!\n", + nchans, minor, sdev); + return 0; + } + bdev = kmalloc(sizeof(*bdev), GFP_KERNEL); + if (!bdev) { + ERROR("Out of memory.\n"); + return 0; + } + bdev->dev = d; + bdev->minor = minor; + bdev->subdev = sdev; + bdev->subdev_type = COMEDI_SUBD_DIO; + bdev->nchans = nchans; + bdev->chanid_offset = devpriv->nchans; + + /* map channel id's to BondedDevice * pointer.. */ + while (nchans--) + devpriv->chanIdDevMap[devpriv->nchans++] = bdev; + + /* Now put bdev pointer at end of devpriv->devs array + * list.. */ + + /* ergh.. ugly.. we need to realloc :( */ + tmp = devpriv->ndevs * sizeof(bdev); + devpriv->devs = + Realloc(devpriv->devs, + ++devpriv->ndevs * sizeof(bdev), tmp); + if (!devpriv->devs) { + ERROR("Could not allocate memory. " + "Out of memory?"); + return 0; + } + + devpriv->devs[devpriv->ndevs - 1] = bdev; + { + /** Append dev:subdev to devpriv->name */ + char buf[20]; + int left = + MAX_BOARD_NAME - strlen(devpriv->name) - + 1; + snprintf(buf, sizeof(buf), "%d:%d ", dev->minor, + bdev->subdev); + buf[sizeof(buf) - 1] = 0; + strncat(devpriv->name, buf, left); + } + + } + } + + if (!devpriv->nchans) { + ERROR("No channels found!\n"); + return 0; + } + + return 1; +} + +static void doDevUnconfig(comedi_device *dev) +{ + unsigned long devs_closed = 0; + + if (devpriv) { + while (devpriv->ndevs-- && devpriv->devs) { + struct BondedDevice *bdev; + + bdev = devpriv->devs[devpriv->ndevs]; + if (!bdev) + continue; + if (!(devs_closed & (0x1 << bdev->minor))) { + comedi_close(bdev->dev); + devs_closed |= (0x1 << bdev->minor); + } + kfree(bdev); + } + kfree(devpriv->devs); + devpriv->devs = NULL; + kfree(devpriv); + dev->private = NULL; + } +} + +static int __init init(void) +{ + return comedi_driver_register(&driver_bonding); +} + +static void __exit cleanup(void) +{ + comedi_driver_unregister(&driver_bonding); +} + +module_init(init); +module_exit(cleanup); diff --git a/drivers/staging/comedi/drivers/comedi_fc.c b/drivers/staging/comedi/drivers/comedi_fc.c new file mode 100644 index 00000000000..cd74dbe5417 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_fc.c @@ -0,0 +1,118 @@ +/* + comedi/drivers/comedi_fc.c + + This is a place for code driver writers wish to share between + two or more drivers. fc is short + for frank-common. + + Author: Frank Mori Hess <fmhess@users.sourceforge.net> + Copyright (C) 2002 Frank Mori Hess + + 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. + +************************************************************************/ + +#include "../comedidev.h" + +#include "comedi_fc.h" + +static void increment_scan_progress(comedi_subdevice *subd, + unsigned int num_bytes) +{ + comedi_async *async = subd->async; + unsigned int scan_length = cfc_bytes_per_scan(subd); + + async->scan_progress += num_bytes; + if (async->scan_progress >= scan_length) { + async->scan_progress %= scan_length; + async->events |= COMEDI_CB_EOS; + } +} + +/* Writes an array of data points to comedi's buffer */ +unsigned int cfc_write_array_to_buffer(comedi_subdevice *subd, void *data, + unsigned int num_bytes) +{ + comedi_async *async = subd->async; + unsigned int retval; + + if (num_bytes == 0) + return 0; + + retval = comedi_buf_write_alloc(async, num_bytes); + if (retval != num_bytes) { + rt_printk("comedi: buffer overrun\n"); + async->events |= COMEDI_CB_OVERFLOW; + return 0; + } + + comedi_buf_memcpy_to(async, 0, data, num_bytes); + comedi_buf_write_free(async, num_bytes); + increment_scan_progress(subd, num_bytes); + async->events |= COMEDI_CB_BLOCK; + + return num_bytes; +} +EXPORT_SYMBOL(cfc_write_array_to_buffer); + +unsigned int cfc_read_array_from_buffer(comedi_subdevice *subd, void *data, + unsigned int num_bytes) +{ + comedi_async *async = subd->async; + + if (num_bytes == 0) + return 0; + + num_bytes = comedi_buf_read_alloc(async, num_bytes); + comedi_buf_memcpy_from(async, 0, data, num_bytes); + comedi_buf_read_free(async, num_bytes); + increment_scan_progress(subd, num_bytes); + async->events |= COMEDI_CB_BLOCK; + + return num_bytes; +} +EXPORT_SYMBOL(cfc_read_array_from_buffer); + +unsigned int cfc_handle_events(comedi_device *dev, comedi_subdevice *subd) +{ + unsigned int events = subd->async->events; + + if (events == 0) + return events; + + if (events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW)) + subd->cancel(dev, subd); + + comedi_event(dev, subd); + + return events; +} +EXPORT_SYMBOL(cfc_handle_events); + +MODULE_AUTHOR("Frank Mori Hess <fmhess@users.sourceforge.net>"); +MODULE_DESCRIPTION("Shared functions for Comedi low-level drivers"); +MODULE_LICENSE("GPL"); + +static int __init comedi_fc_init_module(void) +{ + return 0; +} + +static void __exit comedi_fc_cleanup_module(void) +{ +} + +module_init(comedi_fc_init_module); +module_exit(comedi_fc_cleanup_module); diff --git a/drivers/staging/comedi/drivers/comedi_fc.h b/drivers/staging/comedi/drivers/comedi_fc.h new file mode 100644 index 00000000000..6952fe20f27 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_fc.h @@ -0,0 +1,76 @@ +/* + comedi_fc.h + + This is a place for code driver writers wish to share between + two or more drivers. These functions are meant to be used only + by drivers, they are NOT part of the kcomedilib API! + + Author: Frank Mori Hess <fmhess@users.sourceforge.net> + Copyright (C) 2002 Frank Mori Hess + + 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. + +************************************************************************/ + +#ifndef _COMEDI_FC_H +#define _COMEDI_FC_H + +#include "../comedidev.h" + +/* Writes an array of data points to comedi's buffer */ +extern unsigned int cfc_write_array_to_buffer(comedi_subdevice *subd, + void *data, + unsigned int num_bytes); + +static inline unsigned int cfc_write_to_buffer(comedi_subdevice *subd, + sampl_t data) +{ + return cfc_write_array_to_buffer(subd, &data, sizeof(data)); +}; + +static inline unsigned int cfc_write_long_to_buffer(comedi_subdevice *subd, + lsampl_t data) +{ + return cfc_write_array_to_buffer(subd, &data, sizeof(data)); +}; + +extern unsigned int cfc_read_array_from_buffer(comedi_subdevice *subd, + void *data, + unsigned int num_bytes); + +extern unsigned int cfc_handle_events(comedi_device *dev, + comedi_subdevice *subd); + +static inline unsigned int cfc_bytes_per_scan(comedi_subdevice *subd) +{ + int num_samples; + int bits_per_sample; + + switch (subd->type) { + case COMEDI_SUBD_DI: + case COMEDI_SUBD_DO: + case COMEDI_SUBD_DIO: + bits_per_sample = 8 * bytes_per_sample(subd); + num_samples = (subd->async->cmd.chanlist_len + + bits_per_sample - 1) / bits_per_sample; + break; + default: + num_samples = subd->async->cmd.chanlist_len; + break; + } + return num_samples * bytes_per_sample(subd); +} + +#endif /* _COMEDI_FC_H */ diff --git a/drivers/staging/comedi/drivers/comedi_parport.c b/drivers/staging/comedi/drivers/comedi_parport.c new file mode 100644 index 00000000000..ba838fffa29 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_parport.c @@ -0,0 +1,390 @@ +/* + comedi/drivers/comedi_parport.c + hardware driver for standard parallel port + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998,2001 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: comedi_parport +Description: Standard PC parallel port +Author: ds +Status: works in immediate mode +Devices: [standard] parallel port (comedi_parport) +Updated: Tue, 30 Apr 2002 21:11:45 -0700 + +A cheap and easy way to get a few more digital I/O lines. Steal +additional parallel ports from old computers or your neighbors' +computers. + +Option list: + 0: I/O port base for the parallel port. + 1: IRQ + +Parallel Port Lines: + +pin subdev chan aka +--- ------ ---- --- +1 2 0 strobe +2 0 0 data 0 +3 0 1 data 1 +4 0 2 data 2 +5 0 3 data 3 +6 0 4 data 4 +7 0 5 data 5 +8 0 6 data 6 +9 0 7 data 7 +10 1 3 acknowledge +11 1 4 busy +12 1 2 output +13 1 1 printer selected +14 2 1 auto LF +15 1 0 error +16 2 2 init +17 2 3 select printer +18-25 ground + +Notes: + +Subdevices 0 is digital I/O, subdevice 1 is digital input, and +subdevice 2 is digital output. Unlike other Comedi devices, +subdevice 0 defaults to output. + +Pins 13 and 14 are inverted once by Comedi and once by the +hardware, thus cancelling the effect. + +Pin 1 is a strobe, thus acts like one. There's no way in software +to change this, at least on a standard parallel port. + +Subdevice 3 pretends to be a digital input subdevice, but it always +returns 0 when read. However, if you run a command with +scan_begin_src=TRIG_EXT, it uses pin 10 as a external triggering +pin, which can be used to wake up tasks. +*/ +/* + see http://www.beyondlogic.org/ for information. + or http://www.linux-magazin.de/ausgabe/1999/10/IO/io.html + */ + +#include "../comedidev.h" +#include <linux/ioport.h> + +#define PARPORT_SIZE 3 + +#define PARPORT_A 0 +#define PARPORT_B 1 +#define PARPORT_C 2 + +static int parport_attach(comedi_device *dev, comedi_devconfig *it); +static int parport_detach(comedi_device *dev); +static comedi_driver driver_parport = { + .driver_name = "comedi_parport", + .module = THIS_MODULE, + .attach = parport_attach, + .detach = parport_detach, +}; + +COMEDI_INITCLEANUP(driver_parport); + +struct parport_private { + unsigned int a_data; + unsigned int c_data; + int enable_irq; +}; +#define devpriv ((struct parport_private *)(dev->private)) + +static int parport_insn_a(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + if (data[0]) { + devpriv->a_data &= ~data[0]; + devpriv->a_data |= (data[0] & data[1]); + + outb(devpriv->a_data, dev->iobase + PARPORT_A); + } + + data[1] = inb(dev->iobase + PARPORT_A); + + return 2; +} + +static int parport_insn_config_a(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + if (data[0]) { + s->io_bits = 0xff; + devpriv->c_data &= ~(1 << 5); + } else { + s->io_bits = 0; + devpriv->c_data |= (1 << 5); + } + outb(devpriv->c_data, dev->iobase + PARPORT_C); + + return 1; +} + +static int parport_insn_b(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + if (data[0]) { + /* should writes be ignored? */ + /* anyone??? */ + } + + data[1] = (inb(dev->iobase + PARPORT_B) >> 3); + + return 2; +} + +static int parport_insn_c(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + data[0] &= 0x0f; + if (data[0]) { + devpriv->c_data &= ~data[0]; + devpriv->c_data |= (data[0] & data[1]); + + outb(devpriv->c_data, dev->iobase + PARPORT_C); + } + + data[1] = devpriv->c_data & 0xf; + + return 2; +} + +static int parport_intr_insn(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + if (insn->n < 1) + return -EINVAL; + + data[1] = 0; + return 2; +} + +static int parport_intr_cmdtest(comedi_device *dev, comedi_subdevice *s, + comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1 */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_FOLLOW; + 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: ignored */ + + if (err) + return 2; + + /* step 3: */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + if (cmd->scan_end_arg != 1) { + cmd->scan_end_arg = 1; + err++; + } + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + + if (err) + return 3; + + /* step 4: ignored */ + + if (err) + return 4; + + return 0; +} + +static int parport_intr_cmd(comedi_device *dev, comedi_subdevice *s) +{ + devpriv->c_data |= 0x10; + outb(devpriv->c_data, dev->iobase + PARPORT_C); + + devpriv->enable_irq = 1; + + return 0; +} + +static int parport_intr_cancel(comedi_device *dev, comedi_subdevice *s) +{ + printk(KERN_DEBUG "parport_intr_cancel()\n"); + + devpriv->c_data &= ~0x10; + outb(devpriv->c_data, dev->iobase + PARPORT_C); + + devpriv->enable_irq = 0; + + return 0; +} + +static irqreturn_t parport_interrupt(int irq, void *d PT_REGS_ARG) +{ + comedi_device *dev = d; + comedi_subdevice *s = dev->subdevices + 3; + + if (!devpriv->enable_irq) { + printk(KERN_ERR "comedi_parport: bogus irq, ignored\n"); + return IRQ_NONE; + } + + comedi_buf_put(s->async, 0); + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + + comedi_event(dev, s); + return IRQ_HANDLED; +} + +static int parport_attach(comedi_device *dev, comedi_devconfig *it) +{ + int ret; + unsigned int irq; + unsigned long iobase; + comedi_subdevice *s; + + iobase = it->options[0]; + printk(KERN_INFO "comedi%d: parport: 0x%04lx ", dev->minor, iobase); + if (!request_region(iobase, PARPORT_SIZE, "parport (comedi)")) { + printk("I/O port conflict\n"); + return -EIO; + } + dev->iobase = iobase; + + irq = it->options[1]; + if (irq) { + printk(" irq=%u", irq); + ret = comedi_request_irq(irq, parport_interrupt, 0, + "comedi_parport", dev); + if (ret < 0) { + printk(" irq not available\n"); + return -EINVAL; + } + dev->irq = irq; + } + dev->board_name = "parport"; + + ret = alloc_subdevices(dev, 4); + if (ret < 0) + return ret; + ret = alloc_private(dev, sizeof(struct parport_private)); + if (ret < 0) + return ret; + + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_insn_a; + s->insn_config = parport_insn_config_a; + + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 5; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_insn_b; + + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 4; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_insn_c; + + s = dev->subdevices + 3; + if (irq) { + dev->read_subdev = s; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE | SDF_CMD_READ; + s->n_chan = 1; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = parport_intr_insn; + s->do_cmdtest = parport_intr_cmdtest; + s->do_cmd = parport_intr_cmd; + s->cancel = parport_intr_cancel; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + devpriv->a_data = 0; + outb(devpriv->a_data, dev->iobase + PARPORT_A); + devpriv->c_data = 0; + outb(devpriv->c_data, dev->iobase + PARPORT_C); + + printk("\n"); + return 1; +} + +static int parport_detach(comedi_device *dev) +{ + printk("comedi%d: parport: remove\n", dev->minor); + + if (dev->iobase) + release_region(dev->iobase, PARPORT_SIZE); + + if (dev->irq) + comedi_free_irq(dev->irq, dev); + + return 0; +} diff --git a/drivers/staging/comedi/drivers/comedi_pci.h b/drivers/staging/comedi/drivers/comedi_pci.h new file mode 100644 index 00000000000..c14a036a053 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_pci.h @@ -0,0 +1,60 @@ +/* + comedi/drivers/comedi_pci.h + Various PCI functions for drivers. + + Copyright (C) 2007 MEV Ltd. <http://www.mev.co.uk/> + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#ifndef _COMEDI_PCI_H_ +#define _COMEDI_PCI_H_ + +#include <linux/pci.h> + +/* + * Enable the PCI device and request the regions. + */ +static inline int comedi_pci_enable(struct pci_dev *pdev, const char *res_name) +{ + int rc; + + rc = pci_enable_device(pdev); + if (rc < 0) + return rc; + + rc = pci_request_regions(pdev, res_name); + if (rc < 0) + pci_disable_device(pdev); + + return rc; +} + +/* + * Release the regions and disable the PCI device. + * + * This must be matched with a previous successful call to comedi_pci_enable(). + */ +static inline void comedi_pci_disable(struct pci_dev *pdev) +{ + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +#endif diff --git a/drivers/staging/comedi/drivers/comedi_test.c b/drivers/staging/comedi/drivers/comedi_test.c new file mode 100644 index 00000000000..4b4c37d0748 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_test.c @@ -0,0 +1,527 @@ +/* + comedi/drivers/comedi_test.c + + Generates fake waveform signals that can be read through + the command interface. It does _not_ read from any board; + it just generates deterministic waveforms. + Useful for various testing purposes. + + Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de> + Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net> + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +************************************************************************/ +/* +Driver: comedi_test +Description: generates fake waveforms +Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess + <fmhess@users.sourceforge.net>, ds +Devices: +Status: works +Updated: Sat, 16 Mar 2002 17:34:48 -0800 + +This driver is mainly for testing purposes, but can also be used to +generate sample waveforms on systems that don't have data acquisition +hardware. + +Configuration options: + [0] - Amplitude in microvolts for fake waveforms (default 1 volt) + [1] - Period in microseconds for fake waveforms (default 0.1 sec) + +Generates a sawtooth wave on channel 0, square wave on channel 1, additional +waveforms could be added to other channels (currently they return flatline +zero volts). + +*/ + +#include "../comedidev.h" + +#include <asm/div64.h> + +#include "comedi_fc.h" + +/* Board descriptions */ +struct waveform_board { + const char *name; + int ai_chans; + int ai_bits; + int have_dio; +}; + +#define N_CHANS 8 + +static const struct waveform_board waveform_boards[] = { + { + .name = "comedi_test", + .ai_chans = N_CHANS, + .ai_bits = 16, + .have_dio = 0, + }, +}; + +#define thisboard ((const struct waveform_board *)dev->board_ptr) + +/* Data unique to this driver */ +struct waveform_private { + struct timer_list timer; + struct timeval last; /* time at which last timer interrupt occured */ + unsigned int uvolt_amplitude; /* waveform amplitude in microvolts */ + unsigned long usec_period; /* waveform period in microseconds */ + unsigned long usec_current; /* current time (modulo waveform period) */ + unsigned long usec_remainder; /* usec since last scan; */ + unsigned long ai_count; /* number of conversions remaining */ + unsigned int scan_period; /* scan period in usec */ + unsigned int convert_period; /* conversion period in usec */ + unsigned timer_running:1; + lsampl_t ao_loopbacks[N_CHANS]; +}; +#define devpriv ((struct waveform_private *)dev->private) + +static int waveform_attach(comedi_device *dev, comedi_devconfig *it); +static int waveform_detach(comedi_device *dev); +static comedi_driver driver_waveform = { + .driver_name = "comedi_test", + .module = THIS_MODULE, + .attach = waveform_attach, + .detach = waveform_detach, + .board_name = &waveform_boards[0].name, + .offset = sizeof(struct waveform_board), + .num_names = sizeof(waveform_boards) / sizeof(struct waveform_board), +}; + +COMEDI_INITCLEANUP(driver_waveform); + +static int waveform_ai_cmdtest(comedi_device *dev, comedi_subdevice *s, + comedi_cmd *cmd); +static int waveform_ai_cmd(comedi_device *dev, comedi_subdevice *s); +static int waveform_ai_cancel(comedi_device *dev, comedi_subdevice *s); +static int waveform_ai_insn_read(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data); +static int waveform_ao_insn_write(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data); +static sampl_t fake_sawtooth(comedi_device *dev, unsigned int range, + unsigned long current_time); +static sampl_t fake_squarewave(comedi_device *dev, unsigned int range, + unsigned long current_time); +static sampl_t fake_flatline(comedi_device *dev, unsigned int range, + unsigned long current_time); +static sampl_t fake_waveform(comedi_device *dev, unsigned int channel, + unsigned int range, unsigned long current_time); + +/* 1000 nanosec in a microsec */ +static const int nano_per_micro = 1000; + +/* fake analog input ranges */ +static const comedi_lrange waveform_ai_ranges = { + 2, + { + BIP_RANGE(10), + BIP_RANGE(5), + } +}; + +/* + This is the background routine used to generate arbitrary data. + It should run in the background; therefore it is scheduled by + a timer mechanism. +*/ +static void waveform_ai_interrupt(unsigned long arg) +{ + comedi_device *dev = (comedi_device *) arg; + comedi_async *async = dev->read_subdev->async; + comedi_cmd *cmd = &async->cmd; + unsigned int i, j; + /* all times in microsec */ + unsigned long elapsed_time; + unsigned int num_scans; + struct timeval now; + + do_gettimeofday(&now); + + elapsed_time = + 1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec - + devpriv->last.tv_usec; + devpriv->last = now; + num_scans = + (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period; + devpriv->usec_remainder = + (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period; + async->events = 0; + + for (i = 0; i < num_scans; i++) { + for (j = 0; j < cmd->chanlist_len; j++) { + cfc_write_to_buffer(dev->read_subdev, + fake_waveform(dev, CR_CHAN(cmd->chanlist[j]), + CR_RANGE(cmd->chanlist[j]), + devpriv->usec_current + + i * devpriv->scan_period + + j * devpriv->convert_period)); + } + devpriv->ai_count++; + if (cmd->stop_src == TRIG_COUNT + && devpriv->ai_count >= cmd->stop_arg) { + async->events |= COMEDI_CB_EOA; + break; + } + } + + devpriv->usec_current += elapsed_time; + devpriv->usec_current %= devpriv->usec_period; + + if ((async->events & COMEDI_CB_EOA) == 0 && devpriv->timer_running) + mod_timer(&devpriv->timer, jiffies + 1); + else + del_timer(&devpriv->timer); + + comedi_event(dev, dev->read_subdev); +} + +static int waveform_attach(comedi_device *dev, comedi_devconfig *it) +{ + comedi_subdevice *s; + int amplitude = it->options[0]; + int period = it->options[1]; + int i; + + dev->board_name = thisboard->name; + + if (alloc_private(dev, sizeof(struct waveform_private)) < 0) + return -ENOMEM; + + /* set default amplitude and period */ + if (amplitude <= 0) + amplitude = 1000000; /* 1 volt */ + if (period <= 0) + period = 100000; /* 0.1 sec */ + + devpriv->uvolt_amplitude = amplitude; + devpriv->usec_period = period; + + dev->n_subdevices = 2; + if (alloc_subdevices(dev, dev->n_subdevices) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + dev->read_subdev = s; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + s->n_chan = thisboard->ai_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &waveform_ai_ranges; + s->len_chanlist = s->n_chan * 2; + s->insn_read = waveform_ai_insn_read; + s->do_cmd = waveform_ai_cmd; + s->do_cmdtest = waveform_ai_cmdtest; + s->cancel = waveform_ai_cancel; + + s = dev->subdevices + 1; + dev->write_subdev = s; + /* analog output subdevice (loopback) */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_GROUND; + s->n_chan = thisboard->ai_chans; + s->maxdata = (1 << thisboard->ai_bits) - 1; + s->range_table = &waveform_ai_ranges; + s->len_chanlist = s->n_chan * 2; + s->insn_write = waveform_ao_insn_write; + s->do_cmd = NULL; + s->do_cmdtest = NULL; + s->cancel = NULL; + + /* Our default loopback value is just a 0V flatline */ + for (i = 0; i < s->n_chan; i++) + devpriv->ao_loopbacks[i] = s->maxdata / 2; + + init_timer(&(devpriv->timer)); + devpriv->timer.function = waveform_ai_interrupt; + devpriv->timer.data = (unsigned long)dev; + + printk(KERN_INFO "comedi%d: comedi_test: " + "%i microvolt, %li microsecond waveform attached\n", dev->minor, + devpriv->uvolt_amplitude, devpriv->usec_period); + return 1; +} + +static int waveform_detach(comedi_device *dev) +{ + printk("comedi%d: comedi_test: remove\n", dev->minor); + + if (dev->private) + waveform_ai_cancel(dev, dev->read_subdev); + + return 0; +} + +static int waveform_ai_cmdtest(comedi_device *dev, comedi_subdevice *s, + comedi_cmd *cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW | TRIG_TIMER; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique and mutually compatible + */ + + if (cmd->convert_src != TRIG_NOW && cmd->convert_src != TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + if (cmd->convert_src == TRIG_NOW) { + if (cmd->convert_arg != 0) { + cmd->convert_arg = 0; + err++; + } + } + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < nano_per_micro) { + cmd->scan_begin_arg = nano_per_micro; + err++; + } + if (cmd->convert_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->chanlist_len) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->chanlist_len; + err++; + } + } + /* + * XXX these checks are generic and should go in core if not there + * already + */ + 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 (cmd->stop_src == TRIG_COUNT) { + if (!cmd->stop_arg) { + cmd->stop_arg = 1; + err++; + } + } else { /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + /* round to nearest microsec */ + cmd->scan_begin_arg = + nano_per_micro * ((tmp + + (nano_per_micro / 2)) / nano_per_micro); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + /* round to nearest microsec */ + cmd->convert_arg = + nano_per_micro * ((tmp + + (nano_per_micro / 2)) / nano_per_micro); + if (tmp != cmd->convert_arg) + err++; + } + + if (err) + return 4; + + return 0; +} + +static int waveform_ai_cmd(comedi_device *dev, comedi_subdevice *s) +{ + comedi_cmd *cmd = &s->async->cmd; + + if (cmd->flags & TRIG_RT) { + comedi_error(dev, + "commands at RT priority not supported in this driver"); + return -1; + } + + devpriv->timer_running = 1; + devpriv->ai_count = 0; + devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro; + + if (cmd->convert_src == TRIG_NOW) + devpriv->convert_period = 0; + else if (cmd->convert_src == TRIG_TIMER) + devpriv->convert_period = cmd->convert_arg / nano_per_micro; + else { + comedi_error(dev, "bug setting conversion period"); + return -1; + } + + do_gettimeofday(&devpriv->last); + devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period; + devpriv->usec_remainder = 0; + + devpriv->timer.expires = jiffies + 1; + add_timer(&devpriv->timer); + return 0; +} + +static int waveform_ai_cancel(comedi_device *dev, comedi_subdevice *s) +{ + devpriv->timer_running = 0; + del_timer(&devpriv->timer); + return 0; +} + +static sampl_t fake_sawtooth(comedi_device *dev, unsigned int range_index, + unsigned long current_time) +{ + comedi_subdevice *s = dev->read_subdev; + unsigned int offset = s->maxdata / 2; + u64 value; + const comedi_krange *krange = &s->range_table->range[range_index]; + u64 binary_amplitude; + + binary_amplitude = s->maxdata; + binary_amplitude *= devpriv->uvolt_amplitude; + do_div(binary_amplitude, krange->max - krange->min); + + current_time %= devpriv->usec_period; + value = current_time; + value *= binary_amplitude * 2; + do_div(value, devpriv->usec_period); + value -= binary_amplitude; /* get rid of sawtooth's dc offset */ + + return offset + value; +} +static sampl_t fake_squarewave(comedi_device *dev, unsigned int range_index, + unsigned long current_time) +{ + comedi_subdevice *s = dev->read_subdev; + unsigned int offset = s->maxdata / 2; + u64 value; + const comedi_krange *krange = &s->range_table->range[range_index]; + current_time %= devpriv->usec_period; + + value = s->maxdata; + value *= devpriv->uvolt_amplitude; + do_div(value, krange->max - krange->min); + + if (current_time < devpriv->usec_period / 2) + value *= -1; + + return offset + value; +} + +static sampl_t fake_flatline(comedi_device *dev, unsigned int range_index, + unsigned long current_time) +{ + return dev->read_subdev->maxdata / 2; +} + +/* generates a different waveform depending on what channel is read */ +static sampl_t fake_waveform(comedi_device *dev, unsigned int channel, + unsigned int range, unsigned long current_time) +{ + enum { + SAWTOOTH_CHAN, + SQUARE_CHAN, + }; + switch (channel) { + case SAWTOOTH_CHAN: + return fake_sawtooth(dev, range, current_time); + break; + case SQUARE_CHAN: + return fake_squarewave(dev, range, current_time); + break; + default: + break; + } + + return fake_flatline(dev, range, current_time); +} + +static int waveform_ai_insn_read(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int i, chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + data[i] = devpriv->ao_loopbacks[chan]; + + return insn->n; +} + +static int waveform_ao_insn_write(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int i, chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) + devpriv->ao_loopbacks[chan] = data[i]; + + return insn->n; +} diff --git a/drivers/staging/comedi/drivers/dt9812.c b/drivers/staging/comedi/drivers/dt9812.c new file mode 100644 index 00000000000..f2d2173d721 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt9812.c @@ -0,0 +1,1162 @@ +/* + * comedi/drivers/dt9812.c + * COMEDI driver for DataTranslation DT9812 USB module + * + * Copyright (C) 2005 Anders Blomdell <anders.blomdell@control.lth.se> + * + * COMEDI - Linux Control and Measurement Device Interface + * + * 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: dt9812 +Description: Data Translation DT9812 USB module +Author: anders.blomdell@control.lth.se (Anders Blomdell) +Status: in development +Devices: [Data Translation] DT9812 (dt9812) +Updated: Sun Nov 20 20:18:34 EST 2005 + +This driver works, but bulk transfers not implemented. Might be a starting point +for someone else. I found out too late that USB has too high latencies (>1 ms) +for my needs. +*/ + +/* + * Nota Bene: + * 1. All writes to command pipe has to be 32 bytes (ISP1181B SHRTP=0 ?) + * 2. The DDK source (as of sep 2005) is in error regarding the + * input MUX bits (example code says P4, but firmware schematics + * says P1). + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/uaccess.h> +#include <linux/usb.h> + +#include "../comedidev.h" + +#define DT9812_DIAGS_BOARD_INFO_ADDR 0xFBFF +#define DT9812_MAX_WRITE_CMD_PIPE_SIZE 32 +#define DT9812_MAX_READ_CMD_PIPE_SIZE 32 + +/* + * See Silican Laboratories C8051F020/1/2/3 manual + */ +#define F020_SFR_P4 0x84 +#define F020_SFR_P1 0x90 +#define F020_SFR_P2 0xa0 +#define F020_SFR_P3 0xb0 +#define F020_SFR_AMX0CF 0xba +#define F020_SFR_AMX0SL 0xbb +#define F020_SFR_ADC0CF 0xbc +#define F020_SFR_ADC0L 0xbe +#define F020_SFR_ADC0H 0xbf +#define F020_SFR_DAC0L 0xd2 +#define F020_SFR_DAC0H 0xd3 +#define F020_SFR_DAC0CN 0xd4 +#define F020_SFR_DAC1L 0xd5 +#define F020_SFR_DAC1H 0xd6 +#define F020_SFR_DAC1CN 0xd7 +#define F020_SFR_ADC0CN 0xe8 + +#define F020_MASK_ADC0CF_AMP0GN0 0x01 +#define F020_MASK_ADC0CF_AMP0GN1 0x02 +#define F020_MASK_ADC0CF_AMP0GN2 0x04 + +#define F020_MASK_ADC0CN_AD0EN 0x80 +#define F020_MASK_ADC0CN_AD0INT 0x20 +#define F020_MASK_ADC0CN_AD0BUSY 0x10 + +#define F020_MASK_DACxCN_DACxEN 0x80 + +enum { + /* A/D D/A DI DO CT */ + DT9812_DEVID_DT9812_10, /* 8 2 8 8 1 +/- 10V */ + DT9812_DEVID_DT9812_2PT5,/* 8 2 8 8 1 0-2.44V */ +#if 0 + DT9812_DEVID_DT9813, /* 16 2 4 4 1 +/- 10V */ + DT9812_DEVID_DT9814 /* 24 2 0 0 1 +/- 10V */ +#endif +}; + +enum dt9812_gain { + DT9812_GAIN_0PT25 = 1, + DT9812_GAIN_0PT5 = 2, + DT9812_GAIN_1 = 4, + DT9812_GAIN_2 = 8, + DT9812_GAIN_4 = 16, + DT9812_GAIN_8 = 32, + DT9812_GAIN_16 = 64, +}; + +enum { + DT9812_LEAST_USB_FIRMWARE_CMD_CODE = 0, + /* Write Flash memory */ + DT9812_W_FLASH_DATA = 0, + /* Read Flash memory misc config info */ + DT9812_R_FLASH_DATA = 1, + + /* + * Register read/write commands for processor + */ + + /* Read a single byte of USB memory */ + DT9812_R_SINGLE_BYTE_REG = 2, + /* Write a single byte of USB memory */ + DT9812_W_SINGLE_BYTE_REG = 3, + /* Multiple Reads of USB memory */ + DT9812_R_MULTI_BYTE_REG = 4, + /* Multiple Writes of USB memory */ + DT9812_W_MULTI_BYTE_REG = 5, + /* Read, (AND) with mask, OR value, then write (single) */ + DT9812_RMW_SINGLE_BYTE_REG = 6, + /* Read, (AND) with mask, OR value, then write (multiple) */ + DT9812_RMW_MULTI_BYTE_REG = 7, + + /* + * Register read/write commands for SMBus + */ + + /* Read a single byte of SMBus */ + DT9812_R_SINGLE_BYTE_SMBUS = 8, + /* Write a single byte of SMBus */ + DT9812_W_SINGLE_BYTE_SMBUS = 9, + /* Multiple Reads of SMBus */ + DT9812_R_MULTI_BYTE_SMBUS = 10, + /* Multiple Writes of SMBus */ + DT9812_W_MULTI_BYTE_SMBUS = 11, + + /* + * Register read/write commands for a device + */ + + /* Read a single byte of a device */ + DT9812_R_SINGLE_BYTE_DEV = 12, + /* Write a single byte of a device */ + DT9812_W_SINGLE_BYTE_DEV = 13, + /* Multiple Reads of a device */ + DT9812_R_MULTI_BYTE_DEV = 14, + /* Multiple Writes of a device */ + DT9812_W_MULTI_BYTE_DEV = 15, + + /* Not sure if we'll need this */ + DT9812_W_DAC_THRESHOLD = 16, + + /* Set interrupt on change mask */ + DT9812_W_INT_ON_CHANGE_MASK = 17, + + /* Write (or Clear) the CGL for the ADC */ + DT9812_W_CGL = 18, + /* Multiple Reads of USB memory */ + DT9812_R_MULTI_BYTE_USBMEM = 19, + /* Multiple Writes to USB memory */ + DT9812_W_MULTI_BYTE_USBMEM = 20, + + /* Issue a start command to a given subsystem */ + DT9812_START_SUBSYSTEM = 21, + /* Issue a stop command to a given subsystem */ + DT9812_STOP_SUBSYSTEM = 22, + + /* calibrate the board using CAL_POT_CMD */ + DT9812_CALIBRATE_POT = 23, + /* set the DAC FIFO size */ + DT9812_W_DAC_FIFO_SIZE = 24, + /* Write or Clear the CGL for the DAC */ + DT9812_W_CGL_DAC = 25, + /* Read a single value from a subsystem */ + DT9812_R_SINGLE_VALUE_CMD = 26, + /* Write a single value to a subsystem */ + DT9812_W_SINGLE_VALUE_CMD = 27, + /* Valid DT9812_USB_FIRMWARE_CMD_CODE's will be less than this number */ + DT9812_MAX_USB_FIRMWARE_CMD_CODE, +}; + +struct dt9812_flash_data { + u16 numbytes; + u16 address; +}; + +#define DT9812_MAX_NUM_MULTI_BYTE_RDS \ + ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / sizeof(u8)) + +struct dt9812_read_multi { + u8 count; + u8 address[DT9812_MAX_NUM_MULTI_BYTE_RDS]; +}; + +struct dt9812_write_byte { + u8 address; + u8 value; +}; + +#define DT9812_MAX_NUM_MULTI_BYTE_WRTS \ + ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / \ + sizeof(struct dt9812_write_byte)) + +struct dt9812_write_multi { + u8 count; + struct dt9812_write_byte write[DT9812_MAX_NUM_MULTI_BYTE_WRTS]; +}; + +struct dt9812_rmw_byte { + u8 address; + u8 and_mask; + u8 or_value; +}; + +#define DT9812_MAX_NUM_MULTI_BYTE_RMWS \ + ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / sizeof(struct dt9812_rmw_byte)) + +struct dt9812_rmw_multi { + u8 count; + struct dt9812_rmw_byte rmw[DT9812_MAX_NUM_MULTI_BYTE_RMWS]; +}; + +struct dt9812_usb_cmd { + u32 cmd; + union { + struct dt9812_flash_data flash_data_info; + struct dt9812_read_multi read_multi_info; + struct dt9812_write_multi write_multi_info; + struct dt9812_rmw_multi rmw_multi_info; + } u; +#if 0 + WRITE_BYTE_INFO WriteByteInfo; + READ_BYTE_INFO ReadByteInfo; + WRITE_MULTI_INFO WriteMultiInfo; + READ_MULTI_INFO ReadMultiInfo; + RMW_BYTE_INFO RMWByteInfo; + RMW_MULTI_INFO RMWMultiInfo; + DAC_THRESHOLD_INFO DacThresholdInfo; + INT_ON_CHANGE_MASK_INFO IntOnChangeMaskInfo; + CGL_INFO CglInfo; + SUBSYSTEM_INFO SubsystemInfo; + CAL_POT_CMD CalPotCmd; + WRITE_DEV_BYTE_INFO WriteDevByteInfo; + READ_DEV_BYTE_INFO ReadDevByteInfo; + WRITE_DEV_MULTI_INFO WriteDevMultiInfo; + READ_DEV_MULTI_INFO ReadDevMultiInfo; + READ_SINGLE_VALUE_INFO ReadSingleValueInfo; + WRITE_SINGLE_VALUE_INFO WriteSingleValueInfo; +#endif +}; + +#define DT9812_NUM_SLOTS 16 + +static DECLARE_MUTEX(dt9812_mutex); + +static struct usb_device_id dt9812_table[] = { + {USB_DEVICE(0x0867, 0x9812)}, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, dt9812_table); + +struct usb_dt9812 { + struct slot_dt9812 *slot; + struct usb_device *udev; + struct usb_interface *interface; + u16 vendor; + u16 product; + u16 device; + u32 serial; + struct { + __u8 addr; + size_t size; + } message_pipe, command_write, command_read, write_stream, read_stream; + struct kref kref; + u16 analog_out_shadow[2]; + u8 digital_out_shadow; +}; + +struct comedi_dt9812 { + struct slot_dt9812 *slot; + u32 serial; +}; + +struct slot_dt9812 { + struct semaphore mutex; + u32 serial; + struct usb_dt9812 *usb; + struct comedi_dt9812 *comedi; +}; + +static const comedi_lrange dt9812_10_ain_range = { 1, { + BIP_RANGE(10), + } +}; + +static const comedi_lrange dt9812_2pt5_ain_range = { 1, { + UNI_RANGE(2.5), + } +}; + +static const comedi_lrange dt9812_10_aout_range = { 1, { + BIP_RANGE(10), + } +}; + +static const comedi_lrange dt9812_2pt5_aout_range = { 1, { + UNI_RANGE(2.5), + } +}; + +static struct slot_dt9812 dt9812[DT9812_NUM_SLOTS]; + +/* Useful shorthand access to private data */ +#define devpriv ((struct comedi_dt9812 *)dev->private) + +static inline struct usb_dt9812 *to_dt9812_dev(struct kref *d) +{ + return container_of(d, struct usb_dt9812, kref); +} + +static void dt9812_delete(struct kref *kref) +{ + struct usb_dt9812 *dev = to_dt9812_dev(kref); + + usb_put_dev(dev->udev); + kfree(dev); +} + +static int dt9812_read_info(struct usb_dt9812 *dev, int offset, void *buf, + size_t buf_size) +{ + struct dt9812_usb_cmd cmd; + int count, retval; + + cmd.cmd = cpu_to_le32(DT9812_R_FLASH_DATA); + cmd.u.flash_data_info.address = + cpu_to_le16(DT9812_DIAGS_BOARD_INFO_ADDR + offset); + cmd.u.flash_data_info.numbytes = cpu_to_le16(buf_size); + + /* DT9812 only responds to 32 byte writes!! */ + count = 32; + retval = usb_bulk_msg(dev->udev, + usb_sndbulkpipe(dev->udev, + dev->command_write.addr), + &cmd, 32, &count, HZ * 1); + if (retval) + return retval; + retval = usb_bulk_msg(dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->command_read.addr), + buf, buf_size, &count, HZ * 1); + return retval; +} + +static int dt9812_read_multiple_registers(struct usb_dt9812 *dev, int reg_count, + u8 *address, u8 *value) +{ + struct dt9812_usb_cmd cmd; + int i, count, retval; + + cmd.cmd = cpu_to_le32(DT9812_R_MULTI_BYTE_REG); + cmd.u.read_multi_info.count = reg_count; + for (i = 0; i < reg_count; i++) + cmd.u.read_multi_info.address[i] = address[i]; + + /* DT9812 only responds to 32 byte writes!! */ + count = 32; + retval = usb_bulk_msg(dev->udev, + usb_sndbulkpipe(dev->udev, + dev->command_write.addr), + &cmd, 32, &count, HZ * 1); + if (retval) + return retval; + retval = usb_bulk_msg(dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->command_read.addr), + value, reg_count, &count, HZ * 1); + return retval; +} + +static int dt9812_write_multiple_registers(struct usb_dt9812 *dev, + int reg_count, u8 *address, + u8 *value) +{ + struct dt9812_usb_cmd cmd; + int i, count, retval; + + cmd.cmd = cpu_to_le32(DT9812_W_MULTI_BYTE_REG); + cmd.u.read_multi_info.count = reg_count; + for (i = 0; i < reg_count; i++) { + cmd.u.write_multi_info.write[i].address = address[i]; + cmd.u.write_multi_info.write[i].value = value[i]; + } + /* DT9812 only responds to 32 byte writes!! */ + retval = usb_bulk_msg(dev->udev, + usb_sndbulkpipe(dev->udev, + dev->command_write.addr), + &cmd, 32, &count, HZ * 1); + return retval; +} + +static int dt9812_rmw_multiple_registers(struct usb_dt9812 *dev, int reg_count, + struct dt9812_rmw_byte *rmw) +{ + struct dt9812_usb_cmd cmd; + int i, count, retval; + + cmd.cmd = cpu_to_le32(DT9812_RMW_MULTI_BYTE_REG); + cmd.u.rmw_multi_info.count = reg_count; + for (i = 0; i < reg_count; i++) + cmd.u.rmw_multi_info.rmw[i] = rmw[i]; + + /* DT9812 only responds to 32 byte writes!! */ + retval = usb_bulk_msg(dev->udev, + usb_sndbulkpipe(dev->udev, + dev->command_write.addr), + &cmd, 32, &count, HZ * 1); + return retval; +} + +static int dt9812_digital_in(struct slot_dt9812 *slot, u8 *bits) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + u8 reg[2] = { F020_SFR_P3, F020_SFR_P1 }; + u8 value[2]; + + result = dt9812_read_multiple_registers(slot->usb, 2, reg, + value); + if (result == 0) { + /* + * bits 0-6 in F020_SFR_P3 are bits 0-6 in the digital + * input port bit 3 in F020_SFR_P1 is bit 7 in the + * digital input port + */ + *bits = (value[0] & 0x7f) | ((value[1] & 0x08) << 4); + /* printk("%2.2x, %2.2x -> %2.2x\n", + value[0], value[1], *bits); */ + } + } + up(&slot->mutex); + + return result; +} + +static int dt9812_digital_out(struct slot_dt9812 *slot, u8 bits) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + u8 reg[1]; + u8 value[1]; + + reg[0] = F020_SFR_P2; + value[0] = bits; + result = dt9812_write_multiple_registers(slot->usb, 1, reg, + value); + slot->usb->digital_out_shadow = bits; + } + up(&slot->mutex); + return result; +} + +static int dt9812_digital_out_shadow(struct slot_dt9812 *slot, u8 *bits) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + *bits = slot->usb->digital_out_shadow; + result = 0; + } + up(&slot->mutex); + return result; +} + +static void dt9812_configure_mux(struct usb_dt9812 *dev, + struct dt9812_rmw_byte *rmw, int channel) +{ + if (dev->device == DT9812_DEVID_DT9812_10) { + /* In the DT9812/10V MUX is selected by P1.5-7 */ + rmw->address = F020_SFR_P1; + rmw->and_mask = 0xe0; + rmw->or_value = channel << 5; + } else { + /* In the DT9812/2.5V, internal mux is selected by bits 0:2 */ + rmw->address = F020_SFR_AMX0SL; + rmw->and_mask = 0xff; + rmw->or_value = channel & 0x07; + } +} + +static void dt9812_configure_gain(struct usb_dt9812 *dev, + struct dt9812_rmw_byte *rmw, + enum dt9812_gain gain) +{ + if (dev->device == DT9812_DEVID_DT9812_10) { + /* In the DT9812/10V, there is an external gain of 0.5 */ + gain <<= 1; + } + + rmw->address = F020_SFR_ADC0CF; + rmw->and_mask = F020_MASK_ADC0CF_AMP0GN2 | + F020_MASK_ADC0CF_AMP0GN1 | + F020_MASK_ADC0CF_AMP0GN0; + switch (gain) { + /* + * 000 -> Gain = 1 + * 001 -> Gain = 2 + * 010 -> Gain = 4 + * 011 -> Gain = 8 + * 10x -> Gain = 16 + * 11x -> Gain = 0.5 + */ + case DT9812_GAIN_0PT5: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN2 || + F020_MASK_ADC0CF_AMP0GN1; + break; + case DT9812_GAIN_1: + rmw->or_value = 0x00; + break; + case DT9812_GAIN_2: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN0; + break; + case DT9812_GAIN_4: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN1; + break; + case DT9812_GAIN_8: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN1 || + F020_MASK_ADC0CF_AMP0GN0; + break; + case DT9812_GAIN_16: + rmw->or_value = F020_MASK_ADC0CF_AMP0GN2; + break; + default: + err("Illegal gain %d\n", gain); + + } +} + +static int dt9812_analog_in(struct slot_dt9812 *slot, int channel, u16 *value, + enum dt9812_gain gain) +{ + struct dt9812_rmw_byte rmw[3]; + u8 reg[3] = { + F020_SFR_ADC0CN, + F020_SFR_ADC0H, + F020_SFR_ADC0L + }; + u8 val[3]; + int result = -ENODEV; + + down(&slot->mutex); + if (!slot->usb) + goto exit; + + /* 1 select the gain */ + dt9812_configure_gain(slot->usb, &rmw[0], gain); + + /* 2 set the MUX to select the channel */ + dt9812_configure_mux(slot->usb, &rmw[1], channel); + + /* 3 start conversion */ + rmw[2].address = F020_SFR_ADC0CN; + rmw[2].and_mask = 0xff; + rmw[2].or_value = F020_MASK_ADC0CN_AD0EN | F020_MASK_ADC0CN_AD0BUSY; + + result = dt9812_rmw_multiple_registers(slot->usb, 3, rmw); + if (result) + goto exit; + + /* read the status and ADC */ + result = dt9812_read_multiple_registers(slot->usb, 3, reg, val); + if (result) + goto exit; + /* + * An ADC conversion takes 16 SAR clocks cycles, i.e. about 9us. + * Therefore, between the instant that AD0BUSY was set via + * dt9812_rmw_multiple_registers and the read of AD0BUSY via + * dt9812_read_multiple_registers, the conversion should be complete + * since these two operations require two USB transactions each taking + * at least a millisecond to complete. However, lets make sure that + * conversion is finished. + */ + if ((val[0] & (F020_MASK_ADC0CN_AD0INT | F020_MASK_ADC0CN_AD0BUSY)) == + F020_MASK_ADC0CN_AD0INT) { + switch (slot->usb->device) { + case DT9812_DEVID_DT9812_10: + /* + * For DT9812-10V the personality module set the + * encoding to 2's complement. Hence, convert it before + * returning it + */ + *value = ((val[1] << 8) | val[2]) + 0x800; + break; + case DT9812_DEVID_DT9812_2PT5: + *value = (val[1] << 8) | val[2]; + break; + } + } + +exit: + up(&slot->mutex); + return result; +} + +static int dt9812_analog_out_shadow(struct slot_dt9812 *slot, int channel, + u16 *value) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + *value = slot->usb->analog_out_shadow[channel]; + result = 0; + } + up(&slot->mutex); + + return result; +} + +static int dt9812_analog_out(struct slot_dt9812 *slot, int channel, u16 value) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + struct dt9812_rmw_byte rmw[3]; + + switch (channel) { + case 0: + /* 1. Set DAC mode */ + rmw[0].address = F020_SFR_DAC0CN; + rmw[0].and_mask = 0xff; + rmw[0].or_value = F020_MASK_DACxCN_DACxEN; + + /* 2 load low byte of DAC value first */ + rmw[1].address = F020_SFR_DAC0L; + rmw[1].and_mask = 0xff; + rmw[1].or_value = value & 0xff; + + /* 3 load high byte of DAC value next to latch the + 12-bit value */ + rmw[2].address = F020_SFR_DAC0H; + rmw[2].and_mask = 0xff; + rmw[2].or_value = (value >> 8) & 0xf; + break; + + case 1: + /* 1. Set DAC mode */ + rmw[0].address = F020_SFR_DAC1CN; + rmw[0].and_mask = 0xff; + rmw[0].or_value = F020_MASK_DACxCN_DACxEN; + + /* 2 load low byte of DAC value first */ + rmw[1].address = F020_SFR_DAC1L; + rmw[1].and_mask = 0xff; + rmw[1].or_value = value & 0xff; + + /* 3 load high byte of DAC value next to latch the + 12-bit value */ + rmw[2].address = F020_SFR_DAC1H; + rmw[2].and_mask = 0xff; + rmw[2].or_value = (value >> 8) & 0xf; + break; + } + result = dt9812_rmw_multiple_registers(slot->usb, 3, rmw); + slot->usb->analog_out_shadow[channel] = value; + } + up(&slot->mutex); + + return result; +} + +/* + * USB framework functions + */ + +static int dt9812_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int retval = -ENOMEM; + struct usb_dt9812 *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int i; + u8 fw; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "Out of memory\n"); + goto error; + } + kref_init(&dev->kref); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* Check endpoints */ + iface_desc = interface->cur_altsetting; + + if (iface_desc->desc.bNumEndpoints != 5) { + err("Wrong number of endpints."); + retval = -ENODEV; + goto error; + } + + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + int direction = -1; + endpoint = &iface_desc->endpoint[i].desc; + switch (i) { + case 0: + direction = USB_DIR_IN; + dev->message_pipe.addr = endpoint->bEndpointAddress; + dev->message_pipe.size = + le16_to_cpu(endpoint->wMaxPacketSize); + + break; + case 1: + direction = USB_DIR_OUT; + dev->command_write.addr = endpoint->bEndpointAddress; + dev->command_write.size = + le16_to_cpu(endpoint->wMaxPacketSize); + break; + case 2: + direction = USB_DIR_IN; + dev->command_read.addr = endpoint->bEndpointAddress; + dev->command_read.size = + le16_to_cpu(endpoint->wMaxPacketSize); + break; + case 3: + direction = USB_DIR_OUT; + dev->write_stream.addr = endpoint->bEndpointAddress; + dev->write_stream.size = + le16_to_cpu(endpoint->wMaxPacketSize); + break; + case 4: + direction = USB_DIR_IN; + dev->read_stream.addr = endpoint->bEndpointAddress; + dev->read_stream.size = + le16_to_cpu(endpoint->wMaxPacketSize); + break; + } + if ((endpoint->bEndpointAddress & USB_DIR_IN) != direction) { + dev_err(&interface->dev, + "Endpoint has wrong direction.\n"); + retval = -ENODEV; + goto error; + } + } + if (dt9812_read_info(dev, 0, &fw, sizeof(fw)) != 0) { + /* + * Seems like a configuration reset is necessary if driver is + * reloaded while device is attached + */ + usb_reset_configuration(dev->udev); + for (i = 0; i < 10; i++) { + retval = dt9812_read_info(dev, 1, &fw, sizeof(fw)); + if (retval == 0) { + dev_info(&interface->dev, + "usb_reset_configuration succeded " + "after %d iterations\n", i); + break; + } + } + } + + if (dt9812_read_info(dev, 1, &dev->vendor, sizeof(dev->vendor)) != 0) { + err("Failed to read vendor."); + retval = -ENODEV; + goto error; + } + if (dt9812_read_info(dev, 3, &dev->product, + sizeof(dev->product)) != 0) { + err("Failed to read product."); + retval = -ENODEV; + goto error; + } + if (dt9812_read_info(dev, 5, &dev->device, sizeof(dev->device)) != 0) { + err("Failed to read device."); + retval = -ENODEV; + goto error; + } + if (dt9812_read_info(dev, 7, &dev->serial, sizeof(dev->serial)) != 0) { + err("Failed to read serial."); + retval = -ENODEV; + goto error; + } + + dev->vendor = le16_to_cpu(dev->vendor); + dev->product = le16_to_cpu(dev->product); + dev->device = le16_to_cpu(dev->device); + dev->serial = le32_to_cpu(dev->serial); + switch (dev->device) { + case DT9812_DEVID_DT9812_10: + dev->analog_out_shadow[0] = 0x0800; + dev->analog_out_shadow[1] = 0x800; + break; + case DT9812_DEVID_DT9812_2PT5: + dev->analog_out_shadow[0] = 0x0000; + dev->analog_out_shadow[1] = 0x0000; + break; + } + dev->digital_out_shadow = 0; + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "USB DT9812 (%4.4x.%4.4x.%4.4x) #0x%8.8x\n", + dev->vendor, dev->product, dev->device, dev->serial); + + down(&dt9812_mutex); + { + /* Find a slot for the USB device */ + struct slot_dt9812 *first = NULL; + struct slot_dt9812 *best = NULL; + + for (i = 0; i < DT9812_NUM_SLOTS; i++) { + if (!first && !dt9812[i].usb && dt9812[i].serial == 0) + first = &dt9812[i]; + if (!best && dt9812[i].serial == dev->serial) + best = &dt9812[i]; + } + + if (!best) + best = first; + + if (best) { + down(&best->mutex); + best->usb = dev; + dev->slot = best; + up(&best->mutex); + } + } + up(&dt9812_mutex); + + return 0; + +error: + if (dev) + kref_put(&dev->kref, dt9812_delete); + return retval; +} + +static void dt9812_disconnect(struct usb_interface *interface) +{ + struct usb_dt9812 *dev; + int minor = interface->minor; + + down(&dt9812_mutex); + dev = usb_get_intfdata(interface); + if (dev->slot) { + down(&dev->slot->mutex); + dev->slot->usb = NULL; + up(&dev->slot->mutex); + dev->slot = NULL; + } + usb_set_intfdata(interface, NULL); + up(&dt9812_mutex); + + /* queue final destruction */ + kref_put(&dev->kref, dt9812_delete); + + dev_info(&interface->dev, "USB Dt9812 #%d now disconnected\n", minor); +} + +static struct usb_driver dt9812_usb_driver = { + .name = "dt9812", + .probe = dt9812_probe, + .disconnect = dt9812_disconnect, + .id_table = dt9812_table, +}; + +/* + * Comedi functions + */ + +static void dt9812_comedi_open(comedi_device *dev) +{ + down(&devpriv->slot->mutex); + if (devpriv->slot->usb) { + /* We have an attached device, fill in current range info */ + comedi_subdevice *s; + + s = &dev->subdevices[0]; + s->n_chan = 8; + s->maxdata = 1; + + s = &dev->subdevices[1]; + s->n_chan = 8; + s->maxdata = 1; + + s = &dev->subdevices[2]; + s->n_chan = 8; + switch (devpriv->slot->usb->device) { + case 0:{ + s->maxdata = 4095; + s->range_table = &dt9812_10_ain_range; + } + break; + case 1:{ + s->maxdata = 4095; + s->range_table = &dt9812_2pt5_ain_range; + } + break; + } + + s = &dev->subdevices[3]; + s->n_chan = 2; + switch (devpriv->slot->usb->device) { + case 0:{ + s->maxdata = 4095; + s->range_table = &dt9812_10_aout_range; + } + break; + case 1:{ + s->maxdata = 4095; + s->range_table = &dt9812_2pt5_aout_range; + } + break; + } + } + up(&devpriv->slot->mutex); +} + +static int dt9812_di_rinsn(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int n; + u8 bits = 0; + + dt9812_digital_in(devpriv->slot, &bits); + for (n = 0; n < insn->n; n++) + data[n] = ((1 << insn->chanspec) & bits) != 0; + return n; +} + +static int dt9812_do_winsn(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int n; + u8 bits = 0; + + dt9812_digital_out_shadow(devpriv->slot, &bits); + for (n = 0; n < insn->n; n++) { + u8 mask = 1 << insn->chanspec; + + bits &= ~mask; + if (data[n]) + bits |= mask; + } + dt9812_digital_out(devpriv->slot, bits); + return n; +} + +static int dt9812_ai_rinsn(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int n; + + for (n = 0; n < insn->n; n++) { + u16 value = 0; + + dt9812_analog_in(devpriv->slot, insn->chanspec, &value, + DT9812_GAIN_1); + data[n] = value; + } + return n; +} + +static int dt9812_ao_rinsn(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int n; + u16 value; + + for (n = 0; n < insn->n; n++) { + value = 0; + dt9812_analog_out_shadow(devpriv->slot, insn->chanspec, &value); + data[n] = value; + } + return n; +} + +static int dt9812_ao_winsn(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int n; + + for (n = 0; n < insn->n; n++) + dt9812_analog_out(devpriv->slot, insn->chanspec, data[n]); + return n; +} + +static int dt9812_attach(comedi_device *dev, comedi_devconfig *it) +{ + int i; + comedi_subdevice *s; + + dev->board_name = "dt9812"; + + if (alloc_private(dev, sizeof(struct comedi_dt9812)) < 0) + return -ENOMEM; + + /* + * Special open routine, since USB unit may be unattached at + * comedi_config time, hence range can not be determined + */ + dev->open = dt9812_comedi_open; + + devpriv->serial = it->options[0]; + + /* Allocate subdevices */ + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + /* digital input subdevice */ + s = dev->subdevices + 0; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_read = &dt9812_di_rinsn; + + /* digital output subdevice */ + s = dev->subdevices + 1; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_write = &dt9812_do_winsn; + + /* analog input subdevice */ + s = dev->subdevices + 2; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_GROUND; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = NULL; + s->insn_read = &dt9812_ai_rinsn; + + /* analog output subdevice */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE; + s->n_chan = 0; + s->maxdata = 1; + s->range_table = NULL; + s->insn_write = &dt9812_ao_winsn; + s->insn_read = &dt9812_ao_rinsn; + + printk(KERN_INFO "comedi%d: successfully attached to dt9812.\n", + dev->minor); + + down(&dt9812_mutex); + /* Find a slot for the comedi device */ + { + struct slot_dt9812 *first = NULL; + struct slot_dt9812 *best = NULL; + for (i = 0; i < DT9812_NUM_SLOTS; i++) { + if (!first && !dt9812[i].comedi) { + /* First free slot from comedi side */ + first = &dt9812[i]; + } + if (!best && + dt9812[i].usb && + dt9812[i].usb->serial == devpriv->serial) { + /* We have an attaced device with matching ID */ + best = &dt9812[i]; + } + } + if (!best) + best = first; + if (best) { + down(&best->mutex); + best->comedi = devpriv; + best->serial = devpriv->serial; + devpriv->slot = best; + up(&best->mutex); + } + } + up(&dt9812_mutex); + + return 0; +} + +static int dt9812_detach(comedi_device *dev) +{ + return 0; +} + +static comedi_driver dt9812_comedi_driver = { + .module = THIS_MODULE, + .driver_name = "dt9812", + .attach = dt9812_attach, + .detach = dt9812_detach, +}; + +static int __init usb_dt9812_init(void) +{ + int result, i; + + /* Initialize all driver slots */ + for (i = 0; i < DT9812_NUM_SLOTS; i++) { + init_MUTEX(&dt9812[i].mutex); + dt9812[i].serial = 0; + dt9812[i].usb = NULL; + dt9812[i].comedi = NULL; + } + dt9812[12].serial = 0x0; + + /* register with the USB subsystem */ + result = usb_register(&dt9812_usb_driver); + if (result) { + printk(KERN_ERR KBUILD_MODNAME + ": usb_register failed. Error number %d\n", result); + return result; + } + /* register with comedi */ + result = comedi_driver_register(&dt9812_comedi_driver); + if (result) { + usb_deregister(&dt9812_usb_driver); + err("comedi_driver_register failed. Error number %d", result); + } + + return result; +} + +static void __exit usb_dt9812_exit(void) +{ + /* unregister with comedi */ + comedi_driver_unregister(&dt9812_comedi_driver); + + /* deregister this driver with the USB subsystem */ + usb_deregister(&dt9812_usb_driver); +} + +module_init(usb_dt9812_init); +module_exit(usb_dt9812_exit); + +MODULE_AUTHOR("Anders Blomdell <anders.blomdell@control.lth.se>"); +MODULE_DESCRIPTION("Comedi DT9812 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/icp_multi.c b/drivers/staging/comedi/drivers/icp_multi.c new file mode 100644 index 00000000000..59144d7cb0b --- /dev/null +++ b/drivers/staging/comedi/drivers/icp_multi.c @@ -0,0 +1,1085 @@ +/* + comedi/drivers/icp_multi.c + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2002 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: icp_multi +Description: Inova ICP_MULTI +Author: Anne Smorthit <anne.smorthit@sfwte.ch> +Devices: [Inova] ICP_MULTI (icp_multi) +Status: works + +The driver works for analog input and output and digital input and output. +It does not work with interrupts or with the counters. Currently no support +for DMA. + +It has 16 single-ended or 8 differential Analogue Input channels with 12-bit +resolution. Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA. Input +ranges can be individually programmed for each channel. Voltage or current +measurement is selected by jumper. + +There are 4 x 12-bit Analogue Outputs. Ranges : 5V, 10V, +/-5V, +/-10V + +16 x Digital Inputs, 24V + +8 x Digital Outputs, 24V, 1A + +4 x 16-bit counters + +Options: + [0] - PCI bus number - if bus number and slot number are 0, + then driver search for first unused card + [1] - PCI slot number +*/ + +#include "../comedidev.h" + +#include <linux/delay.h> +#include <linux/pci.h> + +#include "icp_multi.h" + +#define DEVICE_ID 0x8000 /* Device ID */ + +#define ICP_MULTI_EXTDEBUG + +// Hardware types of the cards +#define TYPE_ICP_MULTI 0 + +#define IORANGE_ICP_MULTI 32 + +#define ICP_MULTI_ADC_CSR 0 /* R/W: ADC command/status register */ +#define ICP_MULTI_AI 2 /* R: Analogue input data */ +#define ICP_MULTI_DAC_CSR 4 /* R/W: DAC command/status register */ +#define ICP_MULTI_AO 6 /* R/W: Analogue output data */ +#define ICP_MULTI_DI 8 /* R/W: Digital inouts */ +#define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */ +#define ICP_MULTI_INT_EN 0x0C /* R/W: Interrupt enable register */ +#define ICP_MULTI_INT_STAT 0x0E /* R/W: Interrupt status register */ +#define ICP_MULTI_CNTR0 0x10 /* R/W: Counter 0 */ +#define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */ +#define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */ +#define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */ + +#define ICP_MULTI_SIZE 0x20 /* 32 bytes */ + +// Define bits from ADC command/status register +#define ADC_ST 0x0001 /* Start ADC */ +#define ADC_BSY 0x0001 /* ADC busy */ +#define ADC_BI 0x0010 /* Bipolar input range 1 = bipolar */ +#define ADC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ +#define ADC_DI 0x0040 /* Differential input mode 1 = differential */ + +// Define bits from DAC command/status register +#define DAC_ST 0x0001 /* Start DAC */ +#define DAC_BSY 0x0001 /* DAC busy */ +#define DAC_BI 0x0010 /* Bipolar input range 1 = bipolar */ +#define DAC_RA 0x0020 /* Input range 0 = 5V, 1 = 10V */ + +// Define bits from interrupt enable/status registers +#define ADC_READY 0x0001 /* A/d conversion ready interrupt */ +#define DAC_READY 0x0002 /* D/a conversion ready interrupt */ +#define DOUT_ERROR 0x0004 /* Digital output error interrupt */ +#define DIN_STATUS 0x0008 /* Digital input status change interrupt */ +#define CIE0 0x0010 /* Counter 0 overrun interrupt */ +#define CIE1 0x0020 /* Counter 1 overrun interrupt */ +#define CIE2 0x0040 /* Counter 2 overrun interrupt */ +#define CIE3 0x0080 /* Counter 3 overrun interrupt */ + +// Useful definitions +#define Status_IRQ 0x00ff // All interrupts + +// Define analogue range +static const comedi_lrange range_analog = { 4, { + UNI_RANGE(5), + UNI_RANGE(10), + BIP_RANGE(5), + BIP_RANGE(10) + } +}; + +static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 }; + +/* +============================================================================== + Forward declarations +============================================================================== +*/ +static int icp_multi_attach(comedi_device * dev, comedi_devconfig * it); +static int icp_multi_detach(comedi_device * dev); + +/* +============================================================================== + Data & Structure declarations +============================================================================== +*/ +static unsigned short pci_list_builded = 0; /*>0 list of card is known */ + +typedef struct { + const char *name; // driver name + int device_id; + int iorange; // I/O range len + char have_irq; // 1=card support IRQ + char cardtype; // 0=ICP Multi + int n_aichan; // num of A/D chans + int n_aichand; // num of A/D chans in diff mode + int n_aochan; // num of D/A chans + int n_dichan; // num of DI chans + int n_dochan; // num of DO chans + int n_ctrs; // num of counters + int ai_maxdata; // resolution of A/D + int ao_maxdata; // resolution of D/A + const comedi_lrange *rangelist_ai; // rangelist for A/D + const char *rangecode; // range codes for programming + const comedi_lrange *rangelist_ao; // rangelist for D/A +} boardtype; + +static const boardtype boardtypes[] = { + {"icp_multi", // Driver name + DEVICE_ID, // PCI device ID + IORANGE_ICP_MULTI, // I/O range length + 1, // 1=Card supports interrupts + TYPE_ICP_MULTI, // Card type = ICP MULTI + 16, // Num of A/D channels + 8, // Num of A/D channels in diff mode + 4, // Num of D/A channels + 16, // Num of digital inputs + 8, // Num of digital outputs + 4, // Num of counters + 0x0fff, // Resolution of A/D + 0x0fff, // Resolution of D/A + &range_analog, // Rangelist for A/D + range_codes_analog, // Range codes for programming + &range_analog}, // Rangelist for D/A +}; + +#define n_boardtypes (sizeof(boardtypes)/sizeof(boardtype)) + +static comedi_driver driver_icp_multi = { + driver_name:"icp_multi", + module:THIS_MODULE, + attach:icp_multi_attach, + detach:icp_multi_detach, + num_names:n_boardtypes, + board_name:&boardtypes[0].name, + offset:sizeof(boardtype), +}; + +COMEDI_INITCLEANUP(driver_icp_multi); + +typedef struct { + struct pcilst_struct *card; // pointer to card + char valid; // card is usable + void *io_addr; // Pointer to mapped io address + resource_size_t phys_iobase; // Physical io address + unsigned int AdcCmdStatus; // ADC Command/Status register + unsigned int DacCmdStatus; // DAC Command/Status register + unsigned int IntEnable; // Interrupt Enable register + unsigned int IntStatus; // Interrupt Status register + unsigned int act_chanlist[32]; // list of scaned channel + unsigned char act_chanlist_len; // len of scanlist + unsigned char act_chanlist_pos; // actual position in MUX list + unsigned int *ai_chanlist; // actaul chanlist + sampl_t *ai_data; // data buffer + sampl_t ao_data[4]; // data output buffer + sampl_t di_data; // Digital input data + unsigned int do_data; // Remember digital output data +} icp_multi_private; + +#define devpriv ((icp_multi_private *)dev->private) +#define this_board ((const boardtype *)dev->board_ptr) + +/* +============================================================================== + More forward declarations +============================================================================== +*/ + +#if 0 +static int check_channel_list(comedi_device * dev, comedi_subdevice * s, + unsigned int *chanlist, unsigned int n_chan); +#endif +static void setup_channel_list(comedi_device * dev, comedi_subdevice * s, + unsigned int *chanlist, unsigned int n_chan); +static int icp_multi_reset(comedi_device * dev); + +/* +============================================================================== + Functions +============================================================================== +*/ + +/* +============================================================================== + + Name: icp_multi_insn_read_ai + + Description: + This function reads a single analogue input. + + Parameters: + comedi_device *dev Pointer to current device structure + comedi_subdevice *s Pointer to current subdevice structure + comedi_insn *insn Pointer to current comedi instruction + lsampl_t *data Pointer to analogue input data + + Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_read_ai(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int n, timeout; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: BGN: icp_multi_insn_read_ai(...)\n"); +#endif + // Disable A/D conversion ready interrupt + devpriv->IntEnable &= ~ADC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + // Clear interrupt status + devpriv->IntStatus |= ADC_READY; + writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); + + // Set up appropriate channel, mode and range data, for specified channel + setup_channel_list(dev, s, &insn->chanspec, 1); + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp_multi A ST=%4x IO=%p\n", + readw(devpriv->io_addr + ICP_MULTI_ADC_CSR), + devpriv->io_addr + ICP_MULTI_ADC_CSR); +#endif + + for (n = 0; n < insn->n; n++) { + // Set start ADC bit + devpriv->AdcCmdStatus |= ADC_ST; + writew(devpriv->AdcCmdStatus, + devpriv->io_addr + ICP_MULTI_ADC_CSR); + devpriv->AdcCmdStatus &= ~ADC_ST; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi B n=%d ST=%4x\n", n, + readw(devpriv->io_addr + ICP_MULTI_ADC_CSR)); +#endif + + comedi_udelay(1); + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi C n=%d ST=%4x\n", n, + readw(devpriv->io_addr + ICP_MULTI_ADC_CSR)); +#endif + + // Wait for conversion to complete, or get fed up waiting + timeout = 100; + while (timeout--) { + if (!(readw(devpriv->io_addr + + ICP_MULTI_ADC_CSR) & ADC_BSY)) + goto conv_finish; + +#ifdef ICP_MULTI_EXTDEBUG + if (!(timeout % 10)) + printk("icp multi D n=%d tm=%d ST=%4x\n", n, + timeout, + readw(devpriv->io_addr + + ICP_MULTI_ADC_CSR)); +#endif + + comedi_udelay(1); + } + + // If we reach here, a timeout has occurred + comedi_error(dev, "A/D insn timeout"); + + // Disable interrupt + devpriv->IntEnable &= ~ADC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + // Clear interrupt status + devpriv->IntStatus |= ADC_READY; + writew(devpriv->IntStatus, + devpriv->io_addr + ICP_MULTI_INT_STAT); + + // Clear data received + data[n] = 0; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: END: icp_multi_insn_read_ai(...) n=%d\n", n); +#endif + return -ETIME; + + conv_finish: + data[n] = + (readw(devpriv->io_addr + ICP_MULTI_AI) >> 4) & 0x0fff; + } + + // Disable interrupt + devpriv->IntEnable &= ~ADC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + // Clear interrupt status + devpriv->IntStatus |= ADC_READY; + writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: END: icp_multi_insn_read_ai(...) n=%d\n", n); +#endif + return n; +} + +/* +============================================================================== + + Name: icp_multi_insn_write_ao + + Description: + This function writes a single analogue output. + + Parameters: + comedi_device *dev Pointer to current device structure + comedi_subdevice *s Pointer to current subdevice structure + comedi_insn *insn Pointer to current comedi instruction + lsampl_t *data Pointer to analogue output data + + Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_write_ao(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int n, chan, range, timeout; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: BGN: icp_multi_insn_write_ao(...)\n"); +#endif + // Disable D/A conversion ready interrupt + devpriv->IntEnable &= ~DAC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + // Clear interrupt status + devpriv->IntStatus |= DAC_READY; + writew(devpriv->IntStatus, devpriv->io_addr + ICP_MULTI_INT_STAT); + + // Get channel number and range + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + + // Set up range and channel data + // Bit 4 = 1 : Bipolar + // Bit 5 = 0 : 5V + // Bit 5 = 1 : 10V + // Bits 8-9 : Channel number + devpriv->DacCmdStatus &= 0xfccf; + devpriv->DacCmdStatus |= this_board->rangecode[range]; + devpriv->DacCmdStatus |= (chan << 8); + + writew(devpriv->DacCmdStatus, devpriv->io_addr + ICP_MULTI_DAC_CSR); + + for (n = 0; n < insn->n; n++) { + // Wait for analogue output data register to be ready for new data, or get fed up waiting + timeout = 100; + while (timeout--) { + if (!(readw(devpriv->io_addr + + ICP_MULTI_DAC_CSR) & DAC_BSY)) + goto dac_ready; + +#ifdef ICP_MULTI_EXTDEBUG + if (!(timeout % 10)) + printk("icp multi A n=%d tm=%d ST=%4x\n", n, + timeout, + readw(devpriv->io_addr + + ICP_MULTI_DAC_CSR)); +#endif + + comedi_udelay(1); + } + + // If we reach here, a timeout has occurred + comedi_error(dev, "D/A insn timeout"); + + // Disable interrupt + devpriv->IntEnable &= ~DAC_READY; + writew(devpriv->IntEnable, devpriv->io_addr + ICP_MULTI_INT_EN); + + // Clear interrupt status + devpriv->IntStatus |= DAC_READY; + writew(devpriv->IntStatus, + devpriv->io_addr + ICP_MULTI_INT_STAT); + + // Clear data received + devpriv->ao_data[chan] = 0; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: END: icp_multi_insn_write_ao(...) n=%d\n", n); +#endif + return -ETIME; + + dac_ready: + // Write data to analogue output data register + writew(data[n], devpriv->io_addr + ICP_MULTI_AO); + + // Set DAC_ST bit to write the data to selected channel + devpriv->DacCmdStatus |= DAC_ST; + writew(devpriv->DacCmdStatus, + devpriv->io_addr + ICP_MULTI_DAC_CSR); + devpriv->DacCmdStatus &= ~DAC_ST; + + // Save analogue output data + devpriv->ao_data[chan] = data[n]; + } + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: END: icp_multi_insn_write_ao(...) n=%d\n", n); +#endif + return n; +} + +/* +============================================================================== + + Name: icp_multi_insn_read_ao + + Description: + This function reads a single analogue output. + + Parameters: + comedi_device *dev Pointer to current device structure + comedi_subdevice *s Pointer to current subdevice structure + comedi_insn *insn Pointer to current comedi instruction + lsampl_t *data Pointer to analogue output data + + Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_read_ao(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int n, chan; + + // Get channel number + chan = CR_CHAN(insn->chanspec); + + // Read analogue outputs + for (n = 0; n < insn->n; n++) + data[n] = devpriv->ao_data[chan]; + + return n; +} + +/* +============================================================================== + + Name: icp_multi_insn_bits_di + + Description: + This function reads the digital inputs. + + Parameters: + comedi_device *dev Pointer to current device structure + comedi_subdevice *s Pointer to current subdevice structure + comedi_insn *insn Pointer to current comedi instruction + lsampl_t *data Pointer to analogue output data + + Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_bits_di(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); + + return 2; +} + +/* +============================================================================== + + Name: icp_multi_insn_bits_do + + Description: + This function writes the appropriate digital outputs. + + Parameters: + comedi_device *dev Pointer to current device structure + comedi_subdevice *s Pointer to current subdevice structure + comedi_insn *insn Pointer to current comedi instruction + lsampl_t *data Pointer to analogue output data + + Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_bits_do(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: BGN: icp_multi_insn_bits_do(...)\n"); +#endif + + if (data[0]) { + s->state &= ~data[0]; + s->state |= (data[0] & data[1]); + + printk("Digital outputs = %4x \n", s->state); + + writew(s->state, devpriv->io_addr + ICP_MULTI_DO); + } + + data[1] = readw(devpriv->io_addr + ICP_MULTI_DI); + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: END: icp_multi_insn_bits_do(...)\n"); +#endif + return 2; +} + +/* +============================================================================== + + Name: icp_multi_insn_read_ctr + + Description: + This function reads the specified counter. + + Parameters: + comedi_device *dev Pointer to current device structure + comedi_subdevice *s Pointer to current subdevice structure + comedi_insn *insn Pointer to current comedi instruction + lsampl_t *data Pointer to counter data + + Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_read_ctr(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + return 0; +} + +/* +============================================================================== + + Name: icp_multi_insn_write_ctr + + Description: + This function write to the specified counter. + + Parameters: + comedi_device *dev Pointer to current device structure + comedi_subdevice *s Pointer to current subdevice structure + comedi_insn *insn Pointer to current comedi instruction + lsampl_t *data Pointer to counter data + + Returns:int Nmuber of instructions executed + +============================================================================== +*/ +static int icp_multi_insn_write_ctr(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + return 0; +} + +/* +============================================================================== + + Name: interrupt_service_icp_multi + + Description: + This function is the interrupt service routine for all + interrupts generated by the icp multi board. + + Parameters: + int irq + void *d Pointer to current device + +============================================================================== +*/ +static irqreturn_t interrupt_service_icp_multi(int irq, void *d PT_REGS_ARG) +{ + comedi_device *dev = d; + int int_no; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: BGN: interrupt_service_icp_multi(%d,...)\n", + irq); +#endif + + // Is this interrupt from our board? + int_no = readw(devpriv->io_addr + ICP_MULTI_INT_STAT) & Status_IRQ; + if (!int_no) + // No, exit + return IRQ_NONE; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: interrupt_service_icp_multi() ST: %4x\n", + readw(devpriv->io_addr + ICP_MULTI_INT_STAT)); +#endif + + // Determine which interrupt is active & handle it + switch (int_no) { + case ADC_READY: + break; + case DAC_READY: + break; + case DOUT_ERROR: + break; + case DIN_STATUS: + break; + case CIE0: + break; + case CIE1: + break; + case CIE2: + break; + case CIE3: + break; + default: + break; + + } + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: END: interrupt_service_icp_multi(...)\n"); +#endif + return IRQ_HANDLED; +} + +#if 0 +/* +============================================================================== + + Name: check_channel_list + + Description: + This function checks if the channel list, provided by user + is built correctly + + Parameters: + comedi_device *dev Pointer to current sevice structure + comedi_subdevice *s Pointer to current subdevice structure + unsigned int *chanlist Pointer to packed channel list + unsigned int n_chan Number of channels to scan + + Returns:int 0 = failure + 1 = success + +============================================================================== +*/ +static int check_channel_list(comedi_device * dev, comedi_subdevice * s, + unsigned int *chanlist, unsigned int n_chan) +{ + unsigned int i; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: check_channel_list(...,%d)\n", n_chan); +#endif + // Check that we at least have one channel to check + if (n_chan < 1) { + comedi_error(dev, "range/channel list is empty!"); + return 0; + } + // Check all channels + for (i = 0; i < n_chan; i++) { + // Check that channel number is < maximum + if (CR_AREF(chanlist[i]) == AREF_DIFF) { + if (CR_CHAN(chanlist[i]) > this_board->n_aichand) { + comedi_error(dev, + "Incorrect differential ai channel number"); + return 0; + } + } else { + if (CR_CHAN(chanlist[i]) > this_board->n_aichan) { + comedi_error(dev, + "Incorrect ai channel number"); + return 0; + } + } + } + return 1; +} +#endif + +/* +============================================================================== + + Name: setup_channel_list + + Description: + This function sets the appropriate channel selection, + differential input mode and range bits in the ADC Command/ + Status register. + + Parameters: + comedi_device *dev Pointer to current sevice structure + comedi_subdevice *s Pointer to current subdevice structure + unsigned int *chanlist Pointer to packed channel list + unsigned int n_chan Number of channels to scan + + Returns:Void + +============================================================================== +*/ +static void setup_channel_list(comedi_device * dev, comedi_subdevice * s, + unsigned int *chanlist, unsigned int n_chan) +{ + unsigned int i, range, chanprog; + unsigned int diff; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: setup_channel_list(...,%d)\n", n_chan); +#endif + devpriv->act_chanlist_len = n_chan; + devpriv->act_chanlist_pos = 0; + + for (i = 0; i < n_chan; i++) { + // Get channel + chanprog = CR_CHAN(chanlist[i]); + + // Determine if it is a differential channel (Bit 15 = 1) + if (CR_AREF(chanlist[i]) == AREF_DIFF) { + diff = 1; + chanprog &= 0x0007; + } else { + diff = 0; + chanprog &= 0x000f; + } + + // Clear channel, range and input mode bits in A/D command/status register + devpriv->AdcCmdStatus &= 0xf00f; + + // Set channel number and differential mode status bit + if (diff) { + // Set channel number, bits 9-11 & mode, bit 6 + devpriv->AdcCmdStatus |= (chanprog << 9); + devpriv->AdcCmdStatus |= ADC_DI; + } else + // Set channel number, bits 8-11 + devpriv->AdcCmdStatus |= (chanprog << 8); + + // Get range for current channel + range = this_board->rangecode[CR_RANGE(chanlist[i])]; + // Set range. bits 4-5 + devpriv->AdcCmdStatus |= range; + + /* Output channel, range, mode to ICP Multi */ + writew(devpriv->AdcCmdStatus, + devpriv->io_addr + ICP_MULTI_ADC_CSR); + +#ifdef ICP_MULTI_EXTDEBUG + printk("GS: %2d. [%4x]=%4x %4x\n", i, chanprog, range, + devpriv->act_chanlist[i]); +#endif + } + +} + +/* +============================================================================== + + Name: icp_multi_reset + + Description: + This function resets the icp multi device to a 'safe' state + + Parameters: + comedi_device *dev Pointer to current sevice structure + + Returns:int 0 = success + +============================================================================== +*/ +static int icp_multi_reset(comedi_device * dev) +{ + unsigned int i; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp_multi EDBG: BGN: icp_multi_reset(...)\n"); +#endif + // Clear INT enables and requests + writew(0, devpriv->io_addr + ICP_MULTI_INT_EN); + writew(0x00ff, devpriv->io_addr + ICP_MULTI_INT_STAT); + + if (this_board->n_aochan) + // Set DACs to 0..5V range and 0V output + for (i = 0; i < this_board->n_aochan; i++) { + devpriv->DacCmdStatus &= 0xfcce; + + // Set channel number + devpriv->DacCmdStatus |= (i << 8); + + // Output 0V + writew(0, devpriv->io_addr + ICP_MULTI_AO); + + // Set start conversion bit + devpriv->DacCmdStatus |= DAC_ST; + + // Output to command / status register + writew(devpriv->DacCmdStatus, + devpriv->io_addr + ICP_MULTI_DAC_CSR); + + // Delay to allow DAC time to recover + comedi_udelay(1); + } + // Digital outputs to 0 + writew(0, devpriv->io_addr + ICP_MULTI_DO); + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: END: icp_multi_reset(...)\n"); +#endif + return 0; +} + +/* +============================================================================== + + Name: icp_multi_attach + + Description: + This function sets up all the appropriate data for the current + device. + + Parameters: + comedi_device *dev Pointer to current device structure + comedi_devconfig *it Pointer to current device configuration + + Returns:int 0 = success + +============================================================================== +*/ +static int icp_multi_attach(comedi_device * dev, comedi_devconfig * it) +{ + comedi_subdevice *s; + int ret, subdev, n_subdevices; + unsigned int irq; + struct pcilst_struct *card = NULL; + resource_size_t io_addr[5], iobase; + unsigned char pci_bus, pci_slot, pci_func; + + printk("icp_multi EDBG: BGN: icp_multi_attach(...)\n"); + + // Alocate private data storage space + if ((ret = alloc_private(dev, sizeof(icp_multi_private))) < 0) + return ret; + + // Initialise list of PCI cards in system, if not already done so + if (pci_list_builded++ == 0) { + pci_card_list_init(PCI_VENDOR_ID_ICP, +#ifdef ICP_MULTI_EXTDEBUG + 1 +#else + 0 +#endif + ); + } + + printk("Anne's comedi%d: icp_multi: board=%s", dev->minor, + this_board->name); + + if ((card = select_and_alloc_pci_card(PCI_VENDOR_ID_ICP, + this_board->device_id, it->options[0], + it->options[1])) == NULL) + return -EIO; + + devpriv->card = card; + + if ((pci_card_data(card, &pci_bus, &pci_slot, &pci_func, &io_addr[0], + &irq)) < 0) { + printk(" - Can't get configuration data!\n"); + return -EIO; + } + + iobase = io_addr[2]; + devpriv->phys_iobase = iobase; + + printk(", b:s:f=%d:%d:%d, io=0x%8llx \n", pci_bus, pci_slot, pci_func, + (unsigned long long)iobase); + + devpriv->io_addr = ioremap(iobase, ICP_MULTI_SIZE); + + if (devpriv->io_addr == NULL) { + printk("ioremap failed.\n"); + return -ENOMEM; + } +#ifdef ICP_MULTI_EXTDEBUG + printk("0x%08llx mapped to %p, ", (unsigned long long)iobase, + devpriv->io_addr); +#endif + + dev->board_name = this_board->name; + + n_subdevices = 0; + if (this_board->n_aichan) + n_subdevices++; + if (this_board->n_aochan) + n_subdevices++; + if (this_board->n_dichan) + n_subdevices++; + if (this_board->n_dochan) + n_subdevices++; + if (this_board->n_ctrs) + n_subdevices++; + + if ((ret = alloc_subdevices(dev, n_subdevices)) < 0) { + return ret; + } + + icp_multi_reset(dev); + + if (this_board->have_irq) { + if (irq) { + if (comedi_request_irq(irq, interrupt_service_icp_multi, + IRQF_SHARED, "Inova Icp Multi", dev)) { + printk(", unable to allocate IRQ %u, DISABLING IT", irq); + irq = 0; /* Can't use IRQ */ + } else + printk(", irq=%u", irq); + } else + printk(", IRQ disabled"); + } else + irq = 0; + + dev->irq = irq; + + printk(".\n"); + + subdev = 0; + + if (this_board->n_aichan) { + s = dev->subdevices + subdev; + dev->read_subdev = s; + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND; + if (this_board->n_aichand) + s->subdev_flags |= SDF_DIFF; + s->n_chan = this_board->n_aichan; + s->maxdata = this_board->ai_maxdata; + s->len_chanlist = this_board->n_aichan; + s->range_table = this_board->rangelist_ai; + s->insn_read = icp_multi_insn_read_ai; + subdev++; + } + + if (this_board->n_aochan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_aochan; + s->maxdata = this_board->ao_maxdata; + s->len_chanlist = this_board->n_aochan; + s->range_table = this_board->rangelist_ao; + s->insn_write = icp_multi_insn_write_ao; + s->insn_read = icp_multi_insn_read_ao; + subdev++; + } + + if (this_board->n_dichan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DI; + s->subdev_flags = SDF_READABLE; + s->n_chan = this_board->n_dichan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dichan; + s->range_table = &range_digital; + s->io_bits = 0; + s->insn_bits = icp_multi_insn_bits_di; + subdev++; + } + + if (this_board->n_dochan) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_DO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = this_board->n_dochan; + s->maxdata = 1; + s->len_chanlist = this_board->n_dochan; + s->range_table = &range_digital; + s->io_bits = (1 << this_board->n_dochan) - 1; + s->state = 0; + s->insn_bits = icp_multi_insn_bits_do; + subdev++; + } + + if (this_board->n_ctrs) { + s = dev->subdevices + subdev; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; + s->n_chan = this_board->n_ctrs; + s->maxdata = 0xffff; + s->len_chanlist = this_board->n_ctrs; + s->state = 0; + s->insn_read = icp_multi_insn_read_ctr; + s->insn_write = icp_multi_insn_write_ctr; + subdev++; + } + + devpriv->valid = 1; + +#ifdef ICP_MULTI_EXTDEBUG + printk("icp multi EDBG: END: icp_multi_attach(...)\n"); +#endif + + return 0; +} + +/* +============================================================================== + + Name: icp_multi_detach + + Description: + This function releases all the resources used by the current + device. + + Parameters: + comedi_device *dev Pointer to current device structure + + Returns:int 0 = success + +============================================================================== +*/ +static int icp_multi_detach(comedi_device * dev) +{ + + if (dev->private) + if (devpriv->valid) + icp_multi_reset(dev); + + if (dev->irq) + comedi_free_irq(dev->irq, dev); + + if (dev->private && devpriv->io_addr) + iounmap(devpriv->io_addr); + + if (dev->private && devpriv->card) + pci_card_free(devpriv->card); + + if (--pci_list_builded == 0) { + pci_card_list_cleanup(PCI_VENDOR_ID_ICP); + } + + return 0; +} diff --git a/drivers/staging/comedi/drivers/icp_multi.h b/drivers/staging/comedi/drivers/icp_multi.h new file mode 100644 index 00000000000..6df4a8d15ff --- /dev/null +++ b/drivers/staging/comedi/drivers/icp_multi.h @@ -0,0 +1,278 @@ +/* + comedi/drivers/icp_multi.h + + Stuff for ICP Multi + + Author: Anne Smorthit <anne.smorthit@sfwte.ch> + +*/ + +#ifndef _ICP_MULTI_H_ +#define _ICP_MULTI_H_ + +#include "../comedidev.h" +#include "comedi_pci.h" + +/****************************************************************************/ + +struct pcilst_struct { + struct pcilst_struct *next; + int used; + struct pci_dev *pcidev; + unsigned short vendor; + unsigned short device; + unsigned char pci_bus; + unsigned char pci_slot; + unsigned char pci_func; + resource_size_t io_addr[5]; + unsigned int irq; +}; + +struct pcilst_struct *inova_devices; // ptr to root list of all Inova devices + +/****************************************************************************/ + +static void pci_card_list_init(unsigned short pci_vendor, char display); +static void pci_card_list_cleanup(unsigned short pci_vendor); +static struct pcilst_struct *find_free_pci_card_by_device(unsigned short + vendor_id, unsigned short device_id); +static int find_free_pci_card_by_position(unsigned short vendor_id, + unsigned short device_id, unsigned short pci_bus, + unsigned short pci_slot, struct pcilst_struct **card); +static struct pcilst_struct *select_and_alloc_pci_card(unsigned short vendor_id, + unsigned short device_id, unsigned short pci_bus, + unsigned short pci_slot); + +static int pci_card_alloc(struct pcilst_struct *amcc); +static int pci_card_free(struct pcilst_struct *amcc); +static void pci_card_list_display(void); +static int pci_card_data(struct pcilst_struct *amcc, + unsigned char *pci_bus, unsigned char *pci_slot, + unsigned char *pci_func, resource_size_t * io_addr, unsigned int *irq); + +/****************************************************************************/ + +/* build list of Inova cards in this system */ +static void pci_card_list_init(unsigned short pci_vendor, char display) +{ + struct pci_dev *pcidev; + struct pcilst_struct *inova, *last; + int i; + + inova_devices = NULL; + last = NULL; + + 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)) { + if (pcidev->vendor == pci_vendor) { + inova = kmalloc(sizeof(*inova), GFP_KERNEL); + if (!inova) { + printk("icp_multi: pci_card_list_init: allocation failed\n"); + pci_dev_put(pcidev); + break; + } + memset(inova, 0, sizeof(*inova)); + + inova->pcidev = pci_dev_get(pcidev); + if (last) { + last->next = inova; + } else { + inova_devices = inova; + } + last = inova; + + inova->vendor = pcidev->vendor; + inova->device = pcidev->device; + inova->pci_bus = pcidev->bus->number; + inova->pci_slot = PCI_SLOT(pcidev->devfn); + inova->pci_func = PCI_FUNC(pcidev->devfn); + /* Note: resources may be invalid if PCI device + * not enabled, but they are corrected in + * pci_card_alloc. */ + for (i = 0; i < 5; i++) + inova->io_addr[i] = + pci_resource_start(pcidev, i); + inova->irq = pcidev->irq; + } + } + + if (display) + pci_card_list_display(); +} + +/****************************************************************************/ +/* free up list of amcc cards in this system */ +static void pci_card_list_cleanup(unsigned short pci_vendor) +{ + struct pcilst_struct *inova, *next; + + for (inova = inova_devices; inova; inova = next) { + next = inova->next; + pci_dev_put(inova->pcidev); + kfree(inova); + } + + inova_devices = NULL; +} + +/****************************************************************************/ +/* find first unused card with this device_id */ +static struct pcilst_struct *find_free_pci_card_by_device(unsigned short + vendor_id, unsigned short device_id) +{ + struct pcilst_struct *inova, *next; + + for (inova = inova_devices; inova; inova = next) { + next = inova->next; + if ((!inova->used) && (inova->device == device_id) + && (inova->vendor == vendor_id)) + return inova; + + } + + return NULL; +} + +/****************************************************************************/ +/* find card on requested position */ +static int find_free_pci_card_by_position(unsigned short vendor_id, + unsigned short device_id, unsigned short pci_bus, + unsigned short pci_slot, struct pcilst_struct **card) +{ + struct pcilst_struct *inova, *next; + + *card = NULL; + for (inova = inova_devices; inova; inova = next) { + next = inova->next; + if ((inova->vendor == vendor_id) && (inova->device == device_id) + && (inova->pci_bus == pci_bus) + && (inova->pci_slot == pci_slot)) { + if (!(inova->used)) { + *card = inova; + return 0; // ok, card is found + } else { + return 2; // card exist but is used + } + } + } + + return 1; // no card found +} + +/****************************************************************************/ +/* mark card as used */ +static int pci_card_alloc(struct pcilst_struct *inova) +{ + int i; + + if (!inova) { + rt_printk(" - BUG!! inova is NULL!\n"); + return -1; + } + + if (inova->used) + return 1; + if (comedi_pci_enable(inova->pcidev, "icp_multi")) { + rt_printk(" - Can't enable PCI device and request regions!\n"); + return -1; + } + /* Resources will be accurate now. */ + for (i = 0; i < 5; i++) + inova->io_addr[i] = pci_resource_start(inova->pcidev, i); + inova->irq = inova->pcidev->irq; + inova->used = 1; + return 0; +} + +/****************************************************************************/ +/* mark card as free */ +static int pci_card_free(struct pcilst_struct *inova) +{ + if (!inova) + return -1; + + if (!inova->used) + return 1; + inova->used = 0; + comedi_pci_disable(inova->pcidev); + return 0; +} + +/****************************************************************************/ +/* display list of found cards */ +static void pci_card_list_display(void) +{ + struct pcilst_struct *inova, *next; + + printk("Anne's List of pci cards\n"); + printk("bus:slot:func vendor device io_inova io_daq irq used\n"); + + for (inova = inova_devices; inova; inova = next) { + next = inova->next; + printk("%2d %2d %2d 0x%4x 0x%4x 0x%8llx 0x%8llx %2u %2d\n", inova->pci_bus, inova->pci_slot, inova->pci_func, inova->vendor, inova->device, (unsigned long long)inova->io_addr[0], (unsigned long long)inova->io_addr[2], inova->irq, inova->used); + + } +} + +/****************************************************************************/ +/* return all card information for driver */ +static int pci_card_data(struct pcilst_struct *inova, + unsigned char *pci_bus, unsigned char *pci_slot, + unsigned char *pci_func, resource_size_t * io_addr, unsigned int *irq) +{ + int i; + + if (!inova) + return -1; + *pci_bus = inova->pci_bus; + *pci_slot = inova->pci_slot; + *pci_func = inova->pci_func; + for (i = 0; i < 5; i++) + io_addr[i] = inova->io_addr[i]; + *irq = inova->irq; + return 0; +} + +/****************************************************************************/ +/* select and alloc card */ +static struct pcilst_struct *select_and_alloc_pci_card(unsigned short vendor_id, + unsigned short device_id, unsigned short pci_bus, + unsigned short pci_slot) +{ + struct pcilst_struct *card; + int err; + + if ((pci_bus < 1) & (pci_slot < 1)) { // use autodetection + if ((card = find_free_pci_card_by_device(vendor_id, + device_id)) == NULL) { + rt_printk(" - Unused card not found in system!\n"); + return NULL; + } + } else { + switch (find_free_pci_card_by_position(vendor_id, device_id, + pci_bus, pci_slot, &card)) { + case 1: + rt_printk + (" - Card not found on requested position b:s %d:%d!\n", + pci_bus, pci_slot); + return NULL; + case 2: + rt_printk + (" - Card on requested position is used b:s %d:%d!\n", + pci_bus, pci_slot); + return NULL; + } + } + + if ((err = pci_card_alloc(card)) != 0) { + if (err > 0) + rt_printk(" - Can't allocate card!\n"); + /* else: error already printed. */ + return NULL; + } + + return card; +} + +#endif diff --git a/drivers/staging/comedi/drivers/me4000.c b/drivers/staging/comedi/drivers/me4000.c new file mode 100644 index 00000000000..b432aa7d764 --- /dev/null +++ b/drivers/staging/comedi/drivers/me4000.c @@ -0,0 +1,2362 @@ +/* + comedi/drivers/me4000.c + Source code for the Meilhaus ME-4000 board family. + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + */ +/* +Driver: me4000 +Description: Meilhaus ME-4000 series boards +Devices: [Meilhaus] ME-4650 (me4000), ME-4670i, ME-4680, ME-4680i, ME-4680is +Author: gg (Guenter Gebhardt <g.gebhardt@meilhaus.com>) +Updated: Mon, 18 Mar 2002 15:34:01 -0800 +Status: broken (no support for loading firmware) + +Supports: + + - Analog Input + - Analog Output + - Digital I/O + - Counter + +Configuration Options: + + [0] - PCI bus number (optional) + [1] - PCI slot number (optional) + + If bus/slot is not specified, the first available PCI + device will be used. + +The firmware required by these boards is available in the +comedi_nonfree_firmware tarball available from +http://www.comedi.org. However, the driver's support for +loading the firmware through comedi_config is currently +broken. + + */ + +#include "../comedidev.h" + +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/spinlock.h> + +#include "comedi_pci.h" +#include "me4000.h" +#if 0 +/* file removed due to GPL incompatibility */ +#include "me4000_fw.h" +#endif + +/*============================================================================= + PCI device table. + This is used by modprobe to translate PCI IDs to drivers. + ===========================================================================*/ + +static DEFINE_PCI_DEVICE_TABLE(me4000_pci_table) = { + {PCI_VENDOR_ID_MEILHAUS, 0x4650, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + + {PCI_VENDOR_ID_MEILHAUS, 0x4660, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MEILHAUS, 0x4661, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MEILHAUS, 0x4662, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MEILHAUS, 0x4663, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + + {PCI_VENDOR_ID_MEILHAUS, 0x4670, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MEILHAUS, 0x4671, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MEILHAUS, 0x4672, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MEILHAUS, 0x4673, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + + {PCI_VENDOR_ID_MEILHAUS, 0x4680, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MEILHAUS, 0x4681, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MEILHAUS, 0x4682, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_MEILHAUS, 0x4683, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + + {0} +}; + +MODULE_DEVICE_TABLE(pci, me4000_pci_table); + +static const me4000_board_t me4000_boards[] = { + {"ME-4650", 0x4650, {0, 0}, {16, 0, 0, 0}, {4}, {0}}, + + {"ME-4660", 0x4660, {0, 0}, {32, 0, 16, 0}, {4}, {3}}, + {"ME-4660i", 0x4661, {0, 0}, {32, 0, 16, 0}, {4}, {3}}, + {"ME-4660s", 0x4662, {0, 0}, {32, 8, 16, 0}, {4}, {3}}, + {"ME-4660is", 0x4663, {0, 0}, {32, 8, 16, 0}, {4}, {3}}, + + {"ME-4670", 0x4670, {4, 0}, {32, 0, 16, 1}, {4}, {3}}, + {"ME-4670i", 0x4671, {4, 0}, {32, 0, 16, 1}, {4}, {3}}, + {"ME-4670s", 0x4672, {4, 0}, {32, 8, 16, 1}, {4}, {3}}, + {"ME-4670is", 0x4673, {4, 0}, {32, 8, 16, 1}, {4}, {3}}, + + {"ME-4680", 0x4680, {4, 4}, {32, 0, 16, 1}, {4}, {3}}, + {"ME-4680i", 0x4681, {4, 4}, {32, 0, 16, 1}, {4}, {3}}, + {"ME-4680s", 0x4682, {4, 4}, {32, 8, 16, 1}, {4}, {3}}, + {"ME-4680is", 0x4683, {4, 4}, {32, 8, 16, 1}, {4}, {3}}, + + {0}, +}; + +#define ME4000_BOARD_VERSIONS (sizeof(me4000_boards) / sizeof(me4000_board_t) - 1) + +/*----------------------------------------------------------------------------- + Comedi function prototypes + ---------------------------------------------------------------------------*/ +static int me4000_attach(comedi_device * dev, comedi_devconfig * it); +static int me4000_detach(comedi_device * dev); +static comedi_driver driver_me4000 = { + driver_name:"me4000", + module:THIS_MODULE, + attach:me4000_attach, + detach:me4000_detach, +}; + +/*----------------------------------------------------------------------------- + Meilhaus function prototypes + ---------------------------------------------------------------------------*/ +static int me4000_probe(comedi_device * dev, comedi_devconfig * it); +static int get_registers(comedi_device * dev, struct pci_dev *pci_dev_p); +static int init_board_info(comedi_device * dev, struct pci_dev *pci_dev_p); +static int init_ao_context(comedi_device * dev); +static int init_ai_context(comedi_device * dev); +static int init_dio_context(comedi_device * dev); +static int init_cnt_context(comedi_device * dev); +static int xilinx_download(comedi_device * dev); +static int reset_board(comedi_device * dev); + +static int me4000_dio_insn_bits(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); + +static int me4000_dio_insn_config(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); + +static int cnt_reset(comedi_device * dev, unsigned int channel); + +static int cnt_config(comedi_device * dev, + unsigned int channel, unsigned int mode); + +static int me4000_cnt_insn_config(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); + +static int me4000_cnt_insn_write(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); + +static int me4000_cnt_insn_read(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); + +static int me4000_ai_insn_read(comedi_device * dev, + comedi_subdevice * subdevice, comedi_insn * insn, lsampl_t * data); + +static int me4000_ai_cancel(comedi_device * dev, comedi_subdevice * s); + +static int ai_check_chanlist(comedi_device * dev, + comedi_subdevice * s, comedi_cmd * cmd); + +static int ai_round_cmd_args(comedi_device * dev, + comedi_subdevice * s, + comedi_cmd * cmd, + unsigned int *init_ticks, + unsigned int *scan_ticks, unsigned int *chan_ticks); + +static int ai_prepare(comedi_device * dev, + comedi_subdevice * s, + comedi_cmd * cmd, + unsigned int init_ticks, + unsigned int scan_ticks, unsigned int chan_ticks); + +static int ai_write_chanlist(comedi_device * dev, + comedi_subdevice * s, comedi_cmd * cmd); + +static irqreturn_t me4000_ai_isr(int irq, void *dev_id PT_REGS_ARG); + +static int me4000_ai_do_cmd_test(comedi_device * dev, + comedi_subdevice * s, comedi_cmd * cmd); + +static int me4000_ai_do_cmd(comedi_device * dev, comedi_subdevice * s); + +static int me4000_ao_insn_write(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); + +static int me4000_ao_insn_read(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data); + +/*----------------------------------------------------------------------------- + Meilhaus inline functions + ---------------------------------------------------------------------------*/ + +static inline void me4000_outb(comedi_device * dev, unsigned char value, + unsigned long port) +{ + PORT_PDEBUG("--> 0x%02X port 0x%04lX\n", value, port); + outb(value, port); +} + +static inline void me4000_outl(comedi_device * dev, unsigned long value, + unsigned long port) +{ + PORT_PDEBUG("--> 0x%08lX port 0x%04lX\n", value, port); + outl(value, port); +} + +static inline unsigned long me4000_inl(comedi_device * dev, unsigned long port) +{ + unsigned long value; + value = inl(port); + PORT_PDEBUG("<-- 0x%08lX port 0x%04lX\n", value, port); + return value; +} + +static inline unsigned char me4000_inb(comedi_device * dev, unsigned long port) +{ + unsigned char value; + value = inb(port); + PORT_PDEBUG("<-- 0x%08X port 0x%04lX\n", value, port); + return value; +} + +static const comedi_lrange me4000_ai_range = { + 4, + { + UNI_RANGE(2.5), + UNI_RANGE(10), + BIP_RANGE(2.5), + BIP_RANGE(10), + } +}; + +static const comedi_lrange me4000_ao_range = { + 1, + { + BIP_RANGE(10), + } +}; + +static int me4000_attach(comedi_device * dev, comedi_devconfig * it) +{ + comedi_subdevice *s; + int result; + + CALL_PDEBUG("In me4000_attach()\n"); + + result = me4000_probe(dev, it); + if (result) + return result; + + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. It relies on + * n_subdevices being set correctly. + */ + if (alloc_subdevices(dev, 4) < 0) + return -ENOMEM; + + /*========================================================================= + Analog input subdevice + ========================================================================*/ + + s = dev->subdevices + 0; + + if (thisboard->ai.count) { + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; + s->n_chan = thisboard->ai.count; + s->maxdata = 0xFFFF; // 16 bit ADC + s->len_chanlist = ME4000_AI_CHANNEL_LIST_COUNT; + s->range_table = &me4000_ai_range; + s->insn_read = me4000_ai_insn_read; + + if (info->irq > 0) { + if (comedi_request_irq(info->irq, me4000_ai_isr, + IRQF_SHARED, "ME-4000", dev)) { + printk("comedi%d: me4000: me4000_attach(): Unable to allocate irq\n", dev->minor); + } else { + dev->read_subdev = s; + s->subdev_flags |= SDF_CMD_READ; + s->cancel = me4000_ai_cancel; + s->do_cmdtest = me4000_ai_do_cmd_test; + s->do_cmd = me4000_ai_do_cmd; + } + } else { + printk(KERN_WARNING + "comedi%d: me4000: me4000_attach(): No interrupt available\n", + dev->minor); + } + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /*========================================================================= + Analog output subdevice + ========================================================================*/ + + s = dev->subdevices + 1; + + if (thisboard->ao.count) { + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITEABLE | SDF_COMMON | SDF_GROUND; + s->n_chan = thisboard->ao.count; + s->maxdata = 0xFFFF; // 16 bit DAC + s->range_table = &me4000_ao_range; + s->insn_write = me4000_ao_insn_write; + s->insn_read = me4000_ao_insn_read; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /*========================================================================= + Digital I/O subdevice + ========================================================================*/ + + s = dev->subdevices + 2; + + if (thisboard->dio.count) { + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = thisboard->dio.count * 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = me4000_dio_insn_bits; + s->insn_config = me4000_dio_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + /* + * Check for optoisolated ME-4000 version. If one the first + * port is a fixed output port and the second is a fixed input port. + */ + if (!me4000_inl(dev, info->dio_context.dir_reg)) { + s->io_bits |= 0xFF; + me4000_outl(dev, ME4000_DIO_CTRL_BIT_MODE_0, + info->dio_context.dir_reg); + } + + /*========================================================================= + Counter subdevice + ========================================================================*/ + + s = dev->subdevices + 3; + + if (thisboard->cnt.count) { + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = thisboard->cnt.count; + s->maxdata = 0xFFFF; // 16 bit counters + s->insn_read = me4000_cnt_insn_read; + s->insn_write = me4000_cnt_insn_write; + s->insn_config = me4000_cnt_insn_config; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + + return 0; +} + +static int me4000_probe(comedi_device * dev, comedi_devconfig * it) +{ + struct pci_dev *pci_device; + int result, i; + me4000_board_t *board; + + CALL_PDEBUG("In me4000_probe()\n"); + + /* Allocate private memory */ + if (alloc_private(dev, sizeof(me4000_info_t)) < 0) { + return -ENOMEM; + } + /* + * Probe the device to determine what device in the series it is. + */ + for (pci_device = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL); + pci_device != NULL; + pci_device = + pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pci_device)) { + if (pci_device->vendor == PCI_VENDOR_ID_MEILHAUS) { + for (i = 0; i < ME4000_BOARD_VERSIONS; i++) { + if (me4000_boards[i].device_id == + pci_device->device) { + /* Was a particular bus/slot requested? */ + if ((it->options[0] != 0) + || (it->options[1] != 0)) { + /* Are we on the wrong bus/slot? */ + if (pci_device->bus->number != + it->options[0] + || PCI_SLOT(pci_device-> + devfn) != + it->options[1]) { + continue; + } + } + dev->board_ptr = me4000_boards + i; + board = (me4000_board_t *) dev-> + board_ptr; + info->pci_dev_p = pci_device; + goto found; + } + } + } + } + + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): No supported board found (req. bus/slot : %d/%d)\n", + dev->minor, it->options[0], it->options[1]); + return -ENODEV; + + found: + + printk(KERN_INFO + "comedi%d: me4000: me4000_probe(): Found %s at PCI bus %d, slot %d\n", + dev->minor, me4000_boards[i].name, pci_device->bus->number, + PCI_SLOT(pci_device->devfn)); + + /* Set data in device structure */ + dev->board_name = board->name; + + /* Enable PCI device and request regions */ + result = comedi_pci_enable(pci_device, dev->board_name); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Cannot enable PCI device and request I/O regions\n", + dev->minor); + return result; + } + + /* Get the PCI base registers */ + result = get_registers(dev, pci_device); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Cannot get registers\n", + dev->minor); + return result; + } + /* Initialize board info */ + result = init_board_info(dev, pci_device); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Cannot init baord info\n", + dev->minor); + return result; + } + + /* Init analog output context */ + result = init_ao_context(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Cannot init ao context\n", + dev->minor); + return result; + } + + /* Init analog input context */ + result = init_ai_context(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Cannot init ai context\n", + dev->minor); + return result; + } + + /* Init digital I/O context */ + result = init_dio_context(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Cannot init dio context\n", + dev->minor); + return result; + } + + /* Init counter context */ + result = init_cnt_context(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Cannot init cnt context\n", + dev->minor); + return result; + } + + /* Download the xilinx firmware */ + result = xilinx_download(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Can't download firmware\n", + dev->minor); + return result; + } + + /* Make a hardware reset */ + result = reset_board(dev); + if (result) { + printk(KERN_ERR + "comedi%d: me4000: me4000_probe(): Can't reset board\n", + dev->minor); + return result; + } + + return 0; +} + +static int get_registers(comedi_device * dev, struct pci_dev *pci_dev_p) +{ + + CALL_PDEBUG("In get_registers()\n"); + + /*--------------------------- plx regbase ---------------------------------*/ + + info->plx_regbase = pci_resource_start(pci_dev_p, 1); + if (info->plx_regbase == 0) { + printk(KERN_ERR + "comedi%d: me4000: get_registers(): PCI base address 1 is not available\n", + dev->minor); + return -ENODEV; + } + info->plx_regbase_size = pci_resource_len(pci_dev_p, 1); + + /*--------------------------- me4000 regbase ------------------------------*/ + + info->me4000_regbase = pci_resource_start(pci_dev_p, 2); + if (info->me4000_regbase == 0) { + printk(KERN_ERR + "comedi%d: me4000: get_registers(): PCI base address 2 is not available\n", + dev->minor); + return -ENODEV; + } + info->me4000_regbase_size = pci_resource_len(pci_dev_p, 2); + + /*--------------------------- timer regbase ------------------------------*/ + + info->timer_regbase = pci_resource_start(pci_dev_p, 3); + if (info->timer_regbase == 0) { + printk(KERN_ERR + "comedi%d: me4000: get_registers(): PCI base address 3 is not available\n", + dev->minor); + return -ENODEV; + } + info->timer_regbase_size = pci_resource_len(pci_dev_p, 3); + + /*--------------------------- program regbase ------------------------------*/ + + info->program_regbase = pci_resource_start(pci_dev_p, 5); + if (info->program_regbase == 0) { + printk(KERN_ERR + "comedi%d: me4000: get_registers(): PCI base address 5 is not available\n", + dev->minor); + return -ENODEV; + } + info->program_regbase_size = pci_resource_len(pci_dev_p, 5); + + return 0; +} + +static int init_board_info(comedi_device * dev, struct pci_dev *pci_dev_p) +{ + int result; + + CALL_PDEBUG("In init_board_info()\n"); + + /* Init spin locks */ + //spin_lock_init(&info->preload_lock); + //spin_lock_init(&info->ai_ctrl_lock); + + /* Get the serial number */ + result = pci_read_config_dword(pci_dev_p, 0x2C, &info->serial_no); + if (result != PCIBIOS_SUCCESSFUL) { + return result; + } + + /* Get the hardware revision */ + result = pci_read_config_byte(pci_dev_p, 0x08, &info->hw_revision); + if (result != PCIBIOS_SUCCESSFUL) { + return result; + } + + /* Get the vendor id */ + info->vendor_id = pci_dev_p->vendor; + + /* Get the device id */ + info->device_id = pci_dev_p->device; + + /* Get the irq assigned to the board */ + info->irq = pci_dev_p->irq; + + return 0; +} + +static int init_ao_context(comedi_device * dev) +{ + int i; + + CALL_PDEBUG("In init_ao_context()\n"); + + for (i = 0; i < thisboard->ao.count; i++) { + //spin_lock_init(&info->ao_context[i].use_lock); + info->ao_context[i].irq = info->irq; + + switch (i) { + case 0: + info->ao_context[i].ctrl_reg = + info->me4000_regbase + ME4000_AO_00_CTRL_REG; + info->ao_context[i].status_reg = + info->me4000_regbase + ME4000_AO_00_STATUS_REG; + info->ao_context[i].fifo_reg = + info->me4000_regbase + ME4000_AO_00_FIFO_REG; + info->ao_context[i].single_reg = + info->me4000_regbase + ME4000_AO_00_SINGLE_REG; + info->ao_context[i].timer_reg = + info->me4000_regbase + ME4000_AO_00_TIMER_REG; + info->ao_context[i].irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ao_context[i].preload_reg = + info->me4000_regbase + ME4000_AO_LOADSETREG_XX; + break; + case 1: + info->ao_context[i].ctrl_reg = + info->me4000_regbase + ME4000_AO_01_CTRL_REG; + info->ao_context[i].status_reg = + info->me4000_regbase + ME4000_AO_01_STATUS_REG; + info->ao_context[i].fifo_reg = + info->me4000_regbase + ME4000_AO_01_FIFO_REG; + info->ao_context[i].single_reg = + info->me4000_regbase + ME4000_AO_01_SINGLE_REG; + info->ao_context[i].timer_reg = + info->me4000_regbase + ME4000_AO_01_TIMER_REG; + info->ao_context[i].irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ao_context[i].preload_reg = + info->me4000_regbase + ME4000_AO_LOADSETREG_XX; + break; + case 2: + info->ao_context[i].ctrl_reg = + info->me4000_regbase + ME4000_AO_02_CTRL_REG; + info->ao_context[i].status_reg = + info->me4000_regbase + ME4000_AO_02_STATUS_REG; + info->ao_context[i].fifo_reg = + info->me4000_regbase + ME4000_AO_02_FIFO_REG; + info->ao_context[i].single_reg = + info->me4000_regbase + ME4000_AO_02_SINGLE_REG; + info->ao_context[i].timer_reg = + info->me4000_regbase + ME4000_AO_02_TIMER_REG; + info->ao_context[i].irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ao_context[i].preload_reg = + info->me4000_regbase + ME4000_AO_LOADSETREG_XX; + break; + case 3: + info->ao_context[i].ctrl_reg = + info->me4000_regbase + ME4000_AO_03_CTRL_REG; + info->ao_context[i].status_reg = + info->me4000_regbase + ME4000_AO_03_STATUS_REG; + info->ao_context[i].fifo_reg = + info->me4000_regbase + ME4000_AO_03_FIFO_REG; + info->ao_context[i].single_reg = + info->me4000_regbase + ME4000_AO_03_SINGLE_REG; + info->ao_context[i].timer_reg = + info->me4000_regbase + ME4000_AO_03_TIMER_REG; + info->ao_context[i].irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ao_context[i].preload_reg = + info->me4000_regbase + ME4000_AO_LOADSETREG_XX; + break; + default: + break; + } + } + + return 0; +} + +static int init_ai_context(comedi_device * dev) +{ + + CALL_PDEBUG("In init_ai_context()\n"); + + info->ai_context.irq = info->irq; + + info->ai_context.ctrl_reg = info->me4000_regbase + ME4000_AI_CTRL_REG; + info->ai_context.status_reg = + info->me4000_regbase + ME4000_AI_STATUS_REG; + info->ai_context.channel_list_reg = + info->me4000_regbase + ME4000_AI_CHANNEL_LIST_REG; + info->ai_context.data_reg = info->me4000_regbase + ME4000_AI_DATA_REG; + info->ai_context.chan_timer_reg = + info->me4000_regbase + ME4000_AI_CHAN_TIMER_REG; + info->ai_context.chan_pre_timer_reg = + info->me4000_regbase + ME4000_AI_CHAN_PRE_TIMER_REG; + info->ai_context.scan_timer_low_reg = + info->me4000_regbase + ME4000_AI_SCAN_TIMER_LOW_REG; + info->ai_context.scan_timer_high_reg = + info->me4000_regbase + ME4000_AI_SCAN_TIMER_HIGH_REG; + info->ai_context.scan_pre_timer_low_reg = + info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_LOW_REG; + info->ai_context.scan_pre_timer_high_reg = + info->me4000_regbase + ME4000_AI_SCAN_PRE_TIMER_HIGH_REG; + info->ai_context.start_reg = info->me4000_regbase + ME4000_AI_START_REG; + info->ai_context.irq_status_reg = + info->me4000_regbase + ME4000_IRQ_STATUS_REG; + info->ai_context.sample_counter_reg = + info->me4000_regbase + ME4000_AI_SAMPLE_COUNTER_REG; + + return 0; +} + +static int init_dio_context(comedi_device * dev) +{ + + CALL_PDEBUG("In init_dio_context()\n"); + + info->dio_context.dir_reg = info->me4000_regbase + ME4000_DIO_DIR_REG; + info->dio_context.ctrl_reg = info->me4000_regbase + ME4000_DIO_CTRL_REG; + info->dio_context.port_0_reg = + info->me4000_regbase + ME4000_DIO_PORT_0_REG; + info->dio_context.port_1_reg = + info->me4000_regbase + ME4000_DIO_PORT_1_REG; + info->dio_context.port_2_reg = + info->me4000_regbase + ME4000_DIO_PORT_2_REG; + info->dio_context.port_3_reg = + info->me4000_regbase + ME4000_DIO_PORT_3_REG; + + return 0; +} + +static int init_cnt_context(comedi_device * dev) +{ + + CALL_PDEBUG("In init_cnt_context()\n"); + + info->cnt_context.ctrl_reg = info->timer_regbase + ME4000_CNT_CTRL_REG; + info->cnt_context.counter_0_reg = + info->timer_regbase + ME4000_CNT_COUNTER_0_REG; + info->cnt_context.counter_1_reg = + info->timer_regbase + ME4000_CNT_COUNTER_1_REG; + info->cnt_context.counter_2_reg = + info->timer_regbase + ME4000_CNT_COUNTER_2_REG; + + return 0; +} + +#define FIRMWARE_NOT_AVAILABLE 1 +#if FIRMWARE_NOT_AVAILABLE +extern unsigned char *xilinx_firm; +#endif + +static int xilinx_download(comedi_device * dev) +{ + u32 value = 0; + wait_queue_head_t queue; + int idx = 0; + int size = 0; + + CALL_PDEBUG("In xilinx_download()\n"); + + init_waitqueue_head(&queue); + + /* + * Set PLX local interrupt 2 polarity to high. + * Interrupt is thrown by init pin of xilinx. + */ + outl(0x10, info->plx_regbase + PLX_INTCSR); + + /* Set /CS and /WRITE of the Xilinx */ + value = inl(info->plx_regbase + PLX_ICR); + value |= 0x100; + outl(value, info->plx_regbase + PLX_ICR); + + /* Init Xilinx with CS1 */ + inb(info->program_regbase + 0xC8); + + /* Wait until /INIT pin is set */ + udelay(20); + if (!inl(info->plx_regbase + PLX_INTCSR) & 0x20) { + printk(KERN_ERR + "comedi%d: me4000: xilinx_download(): Can't init Xilinx\n", + dev->minor); + return -EIO; + } + + /* Reset /CS and /WRITE of the Xilinx */ + value = inl(info->plx_regbase + PLX_ICR); + value &= ~0x100; + outl(value, info->plx_regbase + PLX_ICR); + if (FIRMWARE_NOT_AVAILABLE) { + comedi_error(dev, + "xilinx firmware unavailable due to licensing, aborting"); + return -EIO; + } else { + /* Download Xilinx firmware */ + size = (xilinx_firm[0] << 24) + (xilinx_firm[1] << 16) + + (xilinx_firm[2] << 8) + xilinx_firm[3]; + udelay(10); + + for (idx = 0; idx < size; idx++) { + outb(xilinx_firm[16 + idx], info->program_regbase); + udelay(10); + + /* Check if BUSY flag is low */ + if (inl(info->plx_regbase + PLX_ICR) & 0x20) { + printk(KERN_ERR + "comedi%d: me4000: xilinx_download(): Xilinx is still busy (idx = %d)\n", + dev->minor, idx); + return -EIO; + } + } + } + + /* If done flag is high download was successful */ + if (inl(info->plx_regbase + PLX_ICR) & 0x4) { + } else { + printk(KERN_ERR + "comedi%d: me4000: xilinx_download(): DONE flag is not set\n", + dev->minor); + printk(KERN_ERR + "comedi%d: me4000: xilinx_download(): Download not succesful\n", + dev->minor); + return -EIO; + } + + /* Set /CS and /WRITE */ + value = inl(info->plx_regbase + PLX_ICR); + value |= 0x100; + outl(value, info->plx_regbase + PLX_ICR); + + return 0; +} + +static int reset_board(comedi_device * dev) +{ + unsigned long icr; + + CALL_PDEBUG("In reset_board()\n"); + + /* Make a hardware reset */ + icr = me4000_inl(dev, info->plx_regbase + PLX_ICR); + icr |= 0x40000000; + me4000_outl(dev, icr, info->plx_regbase + PLX_ICR); + icr &= ~0x40000000; + me4000_outl(dev, icr, info->plx_regbase + PLX_ICR); + + /* 0x8000 to the DACs means an output voltage of 0V */ + me4000_outl(dev, 0x8000, + info->me4000_regbase + ME4000_AO_00_SINGLE_REG); + me4000_outl(dev, 0x8000, + info->me4000_regbase + ME4000_AO_01_SINGLE_REG); + me4000_outl(dev, 0x8000, + info->me4000_regbase + ME4000_AO_02_SINGLE_REG); + me4000_outl(dev, 0x8000, + info->me4000_regbase + ME4000_AO_03_SINGLE_REG); + + /* Set both stop bits in the analog input control register */ + me4000_outl(dev, + ME4000_AI_CTRL_BIT_IMMEDIATE_STOP | ME4000_AI_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AI_CTRL_REG); + + /* Set both stop bits in the analog output control register */ + me4000_outl(dev, + ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AO_00_CTRL_REG); + me4000_outl(dev, + ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AO_01_CTRL_REG); + me4000_outl(dev, + ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AO_02_CTRL_REG); + me4000_outl(dev, + ME4000_AO_CTRL_BIT_IMMEDIATE_STOP | ME4000_AO_CTRL_BIT_STOP, + info->me4000_regbase + ME4000_AO_03_CTRL_REG); + + /* Enable interrupts on the PLX */ + me4000_outl(dev, 0x43, info->plx_regbase + PLX_INTCSR); + + /* Set the adustment register for AO demux */ + me4000_outl(dev, ME4000_AO_DEMUX_ADJUST_VALUE, + info->me4000_regbase + ME4000_AO_DEMUX_ADJUST_REG); + + /* Set digital I/O direction for port 0 to output on isolated versions */ + if (!(me4000_inl(dev, info->me4000_regbase + ME4000_DIO_DIR_REG) & 0x1)) { + me4000_outl(dev, 0x1, + info->me4000_regbase + ME4000_DIO_CTRL_REG); + } + + return 0; +} + +static int me4000_detach(comedi_device * dev) +{ + CALL_PDEBUG("In me4000_detach()\n"); + + if (info) { + if (info->pci_dev_p) { + reset_board(dev); + if (info->plx_regbase) { + comedi_pci_disable(info->pci_dev_p); + } + pci_dev_put(info->pci_dev_p); + } + } + + return 0; +} + +/*============================================================================= + Analog input section + ===========================================================================*/ + +static int me4000_ai_insn_read(comedi_device * dev, + comedi_subdevice * subdevice, comedi_insn * insn, lsampl_t * data) +{ + + int chan = CR_CHAN(insn->chanspec); + int rang = CR_RANGE(insn->chanspec); + int aref = CR_AREF(insn->chanspec); + + unsigned long entry = 0; + unsigned long tmp; + long lval; + + CALL_PDEBUG("In me4000_ai_insn_read()\n"); + + if (insn->n == 0) { + return 0; + } else if (insn->n > 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): Invalid instruction length %d\n", + dev->minor, insn->n); + return -EINVAL; + } + + switch (rang) { + case 0: + entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5; + break; + case 1: + entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10; + break; + case 2: + entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5; + break; + case 3: + entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): Invalid range specified\n", + dev->minor); + return -EINVAL; + } + + switch (aref) { + case AREF_GROUND: + case AREF_COMMON: + if (chan >= thisboard->ai.count) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): Analog input is not available\n", + dev->minor); + return -EINVAL; + } + entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED | chan; + break; + + case AREF_DIFF: + if (rang == 0 || rang == 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): Range must be bipolar when aref = diff\n", + dev->minor); + return -EINVAL; + } + + if (chan >= thisboard->ai.diff_count) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): Analog input is not available\n", + dev->minor); + return -EINVAL; + } + entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL | chan; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): Invalid aref specified\n", + dev->minor); + return -EINVAL; + } + + entry |= ME4000_AI_LIST_LAST_ENTRY; + + /* Clear channel list, data fifo and both stop bits */ + tmp = me4000_inl(dev, info->ai_context.ctrl_reg); + tmp &= ~(ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO | + ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP); + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Set the acquisition mode to single */ + tmp &= ~(ME4000_AI_CTRL_BIT_MODE_0 | ME4000_AI_CTRL_BIT_MODE_1 | + ME4000_AI_CTRL_BIT_MODE_2); + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Enable channel list and data fifo */ + tmp |= ME4000_AI_CTRL_BIT_CHANNEL_FIFO | ME4000_AI_CTRL_BIT_DATA_FIFO; + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Generate channel list entry */ + me4000_outl(dev, entry, info->ai_context.channel_list_reg); + + /* Set the timer to maximum sample rate */ + me4000_outl(dev, ME4000_AI_MIN_TICKS, info->ai_context.chan_timer_reg); + me4000_outl(dev, ME4000_AI_MIN_TICKS, + info->ai_context.chan_pre_timer_reg); + + /* Start conversion by dummy read */ + me4000_inl(dev, info->ai_context.start_reg); + + /* Wait until ready */ + udelay(10); + if (!(me4000_inl(dev, info->ai_context. + status_reg) & ME4000_AI_STATUS_BIT_EF_DATA)) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_insn_read(): Value not available after wait\n", + dev->minor); + return -EIO; + } + + /* Read value from data fifo */ + lval = me4000_inl(dev, info->ai_context.data_reg) & 0xFFFF; + data[0] = lval ^ 0x8000; + + return 1; +} + +static int me4000_ai_cancel(comedi_device * dev, comedi_subdevice * s) +{ + unsigned long tmp; + + CALL_PDEBUG("In me4000_ai_cancel()\n"); + + /* Stop any running conversion */ + tmp = me4000_inl(dev, info->ai_context.ctrl_reg); + tmp &= ~(ME4000_AI_CTRL_BIT_STOP | ME4000_AI_CTRL_BIT_IMMEDIATE_STOP); + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Clear the control register */ + me4000_outl(dev, 0x0, info->ai_context.ctrl_reg); + + return 0; +} + +static int ai_check_chanlist(comedi_device * dev, + comedi_subdevice * s, comedi_cmd * cmd) +{ + int aref; + int i; + + CALL_PDEBUG("In ai_check_chanlist()\n"); + + /* Check whether a channel list is available */ + if (!cmd->chanlist_len) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): No channel list available\n", + dev->minor); + return -EINVAL; + } + + /* Check the channel list size */ + if (cmd->chanlist_len > ME4000_AI_CHANNEL_LIST_COUNT) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): Channel list is to large\n", + dev->minor); + return -EINVAL; + } + + /* Check the pointer */ + if (!cmd->chanlist) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): NULL pointer to channel list\n", + dev->minor); + return -EFAULT; + } + + /* Check whether aref is equal for all entries */ + aref = CR_AREF(cmd->chanlist[0]); + for (i = 0; i < cmd->chanlist_len; i++) { + if (CR_AREF(cmd->chanlist[i]) != aref) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): Mode is not equal for all entries\n", + dev->minor); + return -EINVAL; + } + } + + /* Check whether channels are available for this ending */ + if (aref == SDF_DIFF) { + for (i = 0; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) >= + thisboard->ai.diff_count) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): Channel number to high\n", + dev->minor); + return -EINVAL; + } + } + } else { + for (i = 0; i < cmd->chanlist_len; i++) { + if (CR_CHAN(cmd->chanlist[i]) >= thisboard->ai.count) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): Channel number to high\n", + dev->minor); + return -EINVAL; + } + } + } + + /* Check if bipolar is set for all entries when in differential mode */ + if (aref == SDF_DIFF) { + for (i = 0; i < cmd->chanlist_len; i++) { + if (CR_RANGE(cmd->chanlist[i]) != 1 && + CR_RANGE(cmd->chanlist[i]) != 2) { + printk(KERN_ERR + "comedi%d: me4000: ai_check_chanlist(): Bipolar is not selected in differential mode\n", + dev->minor); + return -EINVAL; + } + } + } + + return 0; +} + +static int ai_round_cmd_args(comedi_device * dev, + comedi_subdevice * s, + comedi_cmd * cmd, + unsigned int *init_ticks, + unsigned int *scan_ticks, unsigned int *chan_ticks) +{ + + int rest; + + CALL_PDEBUG("In ai_round_cmd_args()\n"); + + *init_ticks = 0; + *scan_ticks = 0; + *chan_ticks = 0; + + PDEBUG("ai_round_cmd_arg(): start_arg = %d\n", cmd->start_arg); + PDEBUG("ai_round_cmd_arg(): scan_begin_arg = %d\n", + cmd->scan_begin_arg); + PDEBUG("ai_round_cmd_arg(): convert_arg = %d\n", cmd->convert_arg); + + if (cmd->start_arg) { + *init_ticks = (cmd->start_arg * 33) / 1000; + rest = (cmd->start_arg * 33) % 1000; + + if (cmd->flags & TRIG_ROUND_NEAREST) { + if (rest > 33) { + (*init_ticks)++; + } + } else if (cmd->flags & TRIG_ROUND_UP) { + if (rest) + (*init_ticks)++; + } + } + + if (cmd->scan_begin_arg) { + *scan_ticks = (cmd->scan_begin_arg * 33) / 1000; + rest = (cmd->scan_begin_arg * 33) % 1000; + + if (cmd->flags & TRIG_ROUND_NEAREST) { + if (rest > 33) { + (*scan_ticks)++; + } + } else if (cmd->flags & TRIG_ROUND_UP) { + if (rest) + (*scan_ticks)++; + } + } + + if (cmd->convert_arg) { + *chan_ticks = (cmd->convert_arg * 33) / 1000; + rest = (cmd->convert_arg * 33) % 1000; + + if (cmd->flags & TRIG_ROUND_NEAREST) { + if (rest > 33) { + (*chan_ticks)++; + } + } else if (cmd->flags & TRIG_ROUND_UP) { + if (rest) + (*chan_ticks)++; + } + } + + PDEBUG("ai_round_cmd_args(): init_ticks = %d\n", *init_ticks); + PDEBUG("ai_round_cmd_args(): scan_ticks = %d\n", *scan_ticks); + PDEBUG("ai_round_cmd_args(): chan_ticks = %d\n", *chan_ticks); + + return 0; +} + +static void ai_write_timer(comedi_device * dev, + unsigned int init_ticks, + unsigned int scan_ticks, unsigned int chan_ticks) +{ + + CALL_PDEBUG("In ai_write_timer()\n"); + + me4000_outl(dev, init_ticks - 1, + info->ai_context.scan_pre_timer_low_reg); + me4000_outl(dev, 0x0, info->ai_context.scan_pre_timer_high_reg); + + if (scan_ticks) { + me4000_outl(dev, scan_ticks - 1, + info->ai_context.scan_timer_low_reg); + me4000_outl(dev, 0x0, info->ai_context.scan_timer_high_reg); + } + + me4000_outl(dev, chan_ticks - 1, info->ai_context.chan_pre_timer_reg); + me4000_outl(dev, chan_ticks - 1, info->ai_context.chan_timer_reg); +} + +static int ai_prepare(comedi_device * dev, + comedi_subdevice * s, + comedi_cmd * cmd, + unsigned int init_ticks, + unsigned int scan_ticks, unsigned int chan_ticks) +{ + + unsigned long tmp = 0; + + CALL_PDEBUG("In ai_prepare()\n"); + + /* Write timer arguments */ + ai_write_timer(dev, init_ticks, scan_ticks, chan_ticks); + + /* Reset control register */ + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Start sources */ + if ((cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) || + (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER)) { + tmp = ME4000_AI_CTRL_BIT_MODE_1 | + ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO; + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_TIMER) { + tmp = ME4000_AI_CTRL_BIT_MODE_2 | + ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO; + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_EXT) { + tmp = ME4000_AI_CTRL_BIT_MODE_0 | + ME4000_AI_CTRL_BIT_MODE_1 | + ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO; + } else { + tmp = ME4000_AI_CTRL_BIT_MODE_0 | + ME4000_AI_CTRL_BIT_CHANNEL_FIFO | + ME4000_AI_CTRL_BIT_DATA_FIFO; + } + + /* Stop triggers */ + if (cmd->stop_src == TRIG_COUNT) { + me4000_outl(dev, cmd->chanlist_len * cmd->stop_arg, + info->ai_context.sample_counter_reg); + tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ; + } else if (cmd->stop_src == TRIG_NONE && + cmd->scan_end_src == TRIG_COUNT) { + me4000_outl(dev, cmd->scan_end_arg, + info->ai_context.sample_counter_reg); + tmp |= ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ; + } else { + tmp |= ME4000_AI_CTRL_BIT_HF_IRQ; + } + + /* Write the setup to the control register */ + me4000_outl(dev, tmp, info->ai_context.ctrl_reg); + + /* Write the channel list */ + ai_write_chanlist(dev, s, cmd); + + return 0; +} + +static int ai_write_chanlist(comedi_device * dev, + comedi_subdevice * s, comedi_cmd * cmd) +{ + unsigned int entry; + unsigned int chan; + unsigned int rang; + unsigned int aref; + int i; + + CALL_PDEBUG("In ai_write_chanlist()\n"); + + for (i = 0; i < cmd->chanlist_len; i++) { + chan = CR_CHAN(cmd->chanlist[i]); + rang = CR_RANGE(cmd->chanlist[i]); + aref = CR_AREF(cmd->chanlist[i]); + + entry = chan; + + if (rang == 0) { + entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_2_5; + } else if (rang == 1) { + entry |= ME4000_AI_LIST_RANGE_UNIPOLAR_10; + } else if (rang == 2) { + entry |= ME4000_AI_LIST_RANGE_BIPOLAR_2_5; + } else { + entry |= ME4000_AI_LIST_RANGE_BIPOLAR_10; + } + + if (aref == SDF_DIFF) { + entry |= ME4000_AI_LIST_INPUT_DIFFERENTIAL; + } else { + entry |= ME4000_AI_LIST_INPUT_SINGLE_ENDED; + } + + me4000_outl(dev, entry, info->ai_context.channel_list_reg); + } + + return 0; +} + +static int me4000_ai_do_cmd(comedi_device * dev, comedi_subdevice * s) +{ + int err; + unsigned int init_ticks = 0; + unsigned int scan_ticks = 0; + unsigned int chan_ticks = 0; + comedi_cmd *cmd = &s->async->cmd; + + CALL_PDEBUG("In me4000_ai_do_cmd()\n"); + + /* Reset the analog input */ + err = me4000_ai_cancel(dev, s); + if (err) + return err; + + /* Round the timer arguments */ + err = ai_round_cmd_args(dev, + s, cmd, &init_ticks, &scan_ticks, &chan_ticks); + if (err) + return err; + + /* Prepare the AI for acquisition */ + err = ai_prepare(dev, s, cmd, init_ticks, scan_ticks, chan_ticks); + if (err) + return err; + + /* Start acquistion by dummy read */ + me4000_inl(dev, info->ai_context.start_reg); + + return 0; +} + +/* + * me4000_ai_do_cmd_test(): + * + * The demo cmd.c in ./comedilib/demo specifies 6 return values: + * - success + * - invalid source + * - source conflict + * - invalid argument + * - argument conflict + * - invalid chanlist + * So I tried to adopt this scheme. + */ +static int me4000_ai_do_cmd_test(comedi_device * dev, + comedi_subdevice * s, comedi_cmd * cmd) +{ + + unsigned int init_ticks; + unsigned int chan_ticks; + unsigned int scan_ticks; + int err = 0; + + CALL_PDEBUG("In me4000_ai_do_cmd_test()\n"); + + PDEBUG("me4000_ai_do_cmd_test(): subdev = %d\n", cmd->subdev); + PDEBUG("me4000_ai_do_cmd_test(): flags = %08X\n", cmd->flags); + PDEBUG("me4000_ai_do_cmd_test(): start_src = %08X\n", + cmd->start_src); + PDEBUG("me4000_ai_do_cmd_test(): start_arg = %d\n", + cmd->start_arg); + PDEBUG("me4000_ai_do_cmd_test(): scan_begin_src = %08X\n", + cmd->scan_begin_src); + PDEBUG("me4000_ai_do_cmd_test(): scan_begin_arg = %d\n", + cmd->scan_begin_arg); + PDEBUG("me4000_ai_do_cmd_test(): convert_src = %08X\n", + cmd->convert_src); + PDEBUG("me4000_ai_do_cmd_test(): convert_arg = %d\n", + cmd->convert_arg); + PDEBUG("me4000_ai_do_cmd_test(): scan_end_src = %08X\n", + cmd->scan_end_src); + PDEBUG("me4000_ai_do_cmd_test(): scan_end_arg = %d\n", + cmd->scan_end_arg); + PDEBUG("me4000_ai_do_cmd_test(): stop_src = %08X\n", + cmd->stop_src); + PDEBUG("me4000_ai_do_cmd_test(): stop_arg = %d\n", cmd->stop_arg); + PDEBUG("me4000_ai_do_cmd_test(): chanlist = %d\n", + (unsigned int)cmd->chanlist); + PDEBUG("me4000_ai_do_cmd_test(): chanlist_len = %d\n", + cmd->chanlist_len); + + /* Only rounding flags are implemented */ + cmd->flags &= TRIG_ROUND_NEAREST | TRIG_ROUND_UP | TRIG_ROUND_DOWN; + + /* Round the timer arguments */ + ai_round_cmd_args(dev, s, cmd, &init_ticks, &scan_ticks, &chan_ticks); + + /* + * Stage 1. Check if the trigger sources are generally valid. + */ + switch (cmd->start_src) { + case TRIG_NOW: + case TRIG_EXT: + break; + case TRIG_ANY: + cmd->start_src &= TRIG_NOW | TRIG_EXT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start source\n", + dev->minor); + cmd->start_src = TRIG_NOW; + err++; + } + switch (cmd->scan_begin_src) { + case TRIG_FOLLOW: + case TRIG_TIMER: + case TRIG_EXT: + break; + case TRIG_ANY: + cmd->scan_begin_src &= TRIG_FOLLOW | TRIG_TIMER | TRIG_EXT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan begin source\n", + dev->minor); + cmd->scan_begin_src = TRIG_FOLLOW; + err++; + } + switch (cmd->convert_src) { + case TRIG_TIMER: + case TRIG_EXT: + break; + case TRIG_ANY: + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert source\n", + dev->minor); + cmd->convert_src = TRIG_TIMER; + err++; + } + switch (cmd->scan_end_src) { + case TRIG_NONE: + case TRIG_COUNT: + break; + case TRIG_ANY: + cmd->scan_end_src &= TRIG_NONE | TRIG_COUNT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end source\n", + dev->minor); + cmd->scan_end_src = TRIG_NONE; + err++; + } + switch (cmd->stop_src) { + case TRIG_NONE: + case TRIG_COUNT: + break; + case TRIG_ANY: + cmd->stop_src &= TRIG_NONE | TRIG_COUNT; + err++; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid stop source\n", + dev->minor); + cmd->stop_src = TRIG_NONE; + err++; + } + if (err) { + return 1; + } + + /* + * Stage 2. Check for trigger source conflicts. + */ + if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_TIMER) { + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_EXT) { + } else { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start trigger combination\n", + dev->minor); + cmd->start_src = TRIG_NOW; + cmd->scan_begin_src = TRIG_FOLLOW; + cmd->convert_src = TRIG_TIMER; + err++; + } + + if (cmd->stop_src == TRIG_NONE && cmd->scan_end_src == TRIG_NONE) { + } else if (cmd->stop_src == TRIG_COUNT && + cmd->scan_end_src == TRIG_NONE) { + } else if (cmd->stop_src == TRIG_NONE && + cmd->scan_end_src == TRIG_COUNT) { + } else if (cmd->stop_src == TRIG_COUNT && + cmd->scan_end_src == TRIG_COUNT) { + } else { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid stop trigger combination\n", + dev->minor); + cmd->stop_src = TRIG_NONE; + cmd->scan_end_src = TRIG_NONE; + err++; + } + if (err) { + return 2; + } + + /* + * Stage 3. Check if arguments are generally valid. + */ + if (cmd->chanlist_len < 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): No channel list\n", + dev->minor); + cmd->chanlist_len = 1; + err++; + } + if (init_ticks < 66) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Start arg to low\n", + dev->minor); + cmd->start_arg = 2000; + err++; + } + if (scan_ticks && scan_ticks < 67) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Scan begin arg to low\n", + dev->minor); + cmd->scan_begin_arg = 2031; + err++; + } + if (chan_ticks < 66) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Convert arg to low\n", + dev->minor); + cmd->convert_arg = 2000; + err++; + } + if (err) { + return 3; + } + + /* + * Stage 4. Check for argument conflicts. + */ + if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n", + dev->minor); + cmd->start_arg = 2000; // 66 ticks at least + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n", + dev->minor); + cmd->convert_arg = 2000; // 66 ticks at least + err++; + } + if (scan_ticks <= cmd->chanlist_len * chan_ticks) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end arg\n", + dev->minor); + cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31; // At least one tick more + err++; + } + } else if (cmd->start_src == TRIG_NOW && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n", + dev->minor); + cmd->start_arg = 2000; // 66 ticks at least + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n", + dev->minor); + cmd->convert_arg = 2000; // 66 ticks at least + err++; + } + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_TIMER && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n", + dev->minor); + cmd->start_arg = 2000; // 66 ticks at least + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n", + dev->minor); + cmd->convert_arg = 2000; // 66 ticks at least + err++; + } + if (scan_ticks <= cmd->chanlist_len * chan_ticks) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end arg\n", + dev->minor); + cmd->scan_end_arg = 2000 * cmd->chanlist_len + 31; // At least one tick more + err++; + } + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_FOLLOW && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n", + dev->minor); + cmd->start_arg = 2000; // 66 ticks at least + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n", + dev->minor); + cmd->convert_arg = 2000; // 66 ticks at least + err++; + } + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_TIMER) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n", + dev->minor); + cmd->start_arg = 2000; // 66 ticks at least + err++; + } + if (chan_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid convert arg\n", + dev->minor); + cmd->convert_arg = 2000; // 66 ticks at least + err++; + } + } else if (cmd->start_src == TRIG_EXT && + cmd->scan_begin_src == TRIG_EXT && + cmd->convert_src == TRIG_EXT) { + + /* Check timer arguments */ + if (init_ticks < ME4000_AI_MIN_TICKS) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid start arg\n", + dev->minor); + cmd->start_arg = 2000; // 66 ticks at least + err++; + } + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg == 0) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid stop arg\n", + dev->minor); + cmd->stop_arg = 1; + err++; + } + } + if (cmd->scan_end_src == TRIG_COUNT) { + if (cmd->scan_end_arg == 0) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_do_cmd_test(): Invalid scan end arg\n", + dev->minor); + cmd->scan_end_arg = 1; + err++; + } + } + if (err) { + return 4; + } + + /* + * Stage 5. Check the channel list. + */ + if (ai_check_chanlist(dev, s, cmd)) + return 5; + + return 0; +} + +static irqreturn_t me4000_ai_isr(int irq, void *dev_id PT_REGS_ARG) +{ + unsigned int tmp; + comedi_device *dev = dev_id; + comedi_subdevice *s = dev->subdevices; + me4000_ai_context_t *ai_context = &info->ai_context; + int i; + int c = 0; + long lval; + + ISR_PDEBUG("me4000_ai_isr() is executed\n"); + + if (!dev->attached) { + ISR_PDEBUG("me4000_ai_isr() premature interrupt\n"); + return IRQ_NONE; + } + + /* Reset all events */ + s->async->events = 0; + + /* Check if irq number is right */ + if (irq != ai_context->irq) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): Incorrect interrupt num: %d\n", + dev->minor, irq); + return IRQ_HANDLED; + } + + if (me4000_inl(dev, + ai_context-> + irq_status_reg) & ME4000_IRQ_STATUS_BIT_AI_HF) { + ISR_PDEBUG + ("me4000_ai_isr(): Fifo half full interrupt occured\n"); + + /* Read status register to find out what happened */ + tmp = me4000_inl(dev, ai_context->ctrl_reg); + + if (!(tmp & ME4000_AI_STATUS_BIT_FF_DATA) && + !(tmp & ME4000_AI_STATUS_BIT_HF_DATA) && + (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) { + ISR_PDEBUG("me4000_ai_isr(): Fifo full\n"); + c = ME4000_AI_FIFO_COUNT; + + /* FIFO overflow, so stop conversion and disable all interrupts */ + tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; + tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | + ME4000_AI_CTRL_BIT_SC_IRQ); + me4000_outl(dev, tmp, ai_context->ctrl_reg); + + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): FIFO overflow\n", + dev->minor); + } else if ((tmp & ME4000_AI_STATUS_BIT_FF_DATA) + && !(tmp & ME4000_AI_STATUS_BIT_HF_DATA) + && (tmp & ME4000_AI_STATUS_BIT_EF_DATA)) { + ISR_PDEBUG("me4000_ai_isr(): Fifo half full\n"); + + s->async->events |= COMEDI_CB_BLOCK; + + c = ME4000_AI_FIFO_COUNT / 2; + } else { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): Can't determine state of fifo\n", + dev->minor); + c = 0; + + /* Undefined state, so stop conversion and disable all interrupts */ + tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; + tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | + ME4000_AI_CTRL_BIT_SC_IRQ); + me4000_outl(dev, tmp, ai_context->ctrl_reg); + + s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_EOA; + + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): Undefined FIFO state\n", + dev->minor); + } + + ISR_PDEBUG("me4000_ai_isr(): Try to read %d values\n", c); + + for (i = 0; i < c; i++) { + /* Read value from data fifo */ + lval = inl(ai_context->data_reg) & 0xFFFF; + lval ^= 0x8000; + + if (!comedi_buf_put(s->async, lval)) { + /* Buffer overflow, so stop conversion and disable all interrupts */ + tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; + tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | + ME4000_AI_CTRL_BIT_SC_IRQ); + me4000_outl(dev, tmp, ai_context->ctrl_reg); + + s->async->events |= COMEDI_CB_OVERFLOW; + + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): Buffer overflow\n", + dev->minor); + + break; + } + } + + /* Work is done, so reset the interrupt */ + ISR_PDEBUG("me4000_ai_isr(): Reset fifo half full interrupt\n"); + tmp |= ME4000_AI_CTRL_BIT_HF_IRQ_RESET; + me4000_outl(dev, tmp, ai_context->ctrl_reg); + tmp &= ~ME4000_AI_CTRL_BIT_HF_IRQ_RESET; + me4000_outl(dev, tmp, ai_context->ctrl_reg); + } + + if (me4000_inl(dev, + ai_context-> + irq_status_reg) & ME4000_IRQ_STATUS_BIT_SC) { + ISR_PDEBUG + ("me4000_ai_isr(): Sample counter interrupt occured\n"); + + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOA; + + /* Acquisition is complete, so stop conversion and disable all interrupts */ + tmp = me4000_inl(dev, ai_context->ctrl_reg); + tmp |= ME4000_AI_CTRL_BIT_IMMEDIATE_STOP; + tmp &= ~(ME4000_AI_CTRL_BIT_HF_IRQ | ME4000_AI_CTRL_BIT_SC_IRQ); + me4000_outl(dev, tmp, ai_context->ctrl_reg); + + /* Poll data until fifo empty */ + while (inl(ai_context->ctrl_reg) & ME4000_AI_STATUS_BIT_EF_DATA) { + /* Read value from data fifo */ + lval = inl(ai_context->data_reg) & 0xFFFF; + lval ^= 0x8000; + + if (!comedi_buf_put(s->async, lval)) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ai_isr(): Buffer overflow\n", + dev->minor); + s->async->events |= COMEDI_CB_OVERFLOW; + break; + } + } + + /* Work is done, so reset the interrupt */ + ISR_PDEBUG + ("me4000_ai_isr(): Reset interrupt from sample counter\n"); + tmp |= ME4000_AI_CTRL_BIT_SC_IRQ_RESET; + me4000_outl(dev, tmp, ai_context->ctrl_reg); + tmp &= ~ME4000_AI_CTRL_BIT_SC_IRQ_RESET; + me4000_outl(dev, tmp, ai_context->ctrl_reg); + } + + ISR_PDEBUG("me4000_ai_isr(): Events = 0x%X\n", s->async->events); + + if (s->async->events) + comedi_event(dev, s); + + return IRQ_HANDLED; +} + +/*============================================================================= + Analog output section + ===========================================================================*/ + +static int me4000_ao_insn_write(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + + int chan = CR_CHAN(insn->chanspec); + int rang = CR_RANGE(insn->chanspec); + int aref = CR_AREF(insn->chanspec); + unsigned long tmp; + + CALL_PDEBUG("In me4000_ao_insn_write()\n"); + + if (insn->n == 0) { + return 0; + } else if (insn->n > 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ao_insn_write(): Invalid instruction length %d\n", + dev->minor, insn->n); + return -EINVAL; + } + + if (chan >= thisboard->ao.count) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ao_insn_write(): Invalid channel %d\n", + dev->minor, insn->n); + return -EINVAL; + } + + if (rang != 0) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ao_insn_write(): Invalid range %d\n", + dev->minor, insn->n); + return -EINVAL; + } + + if (aref != AREF_GROUND && aref != AREF_COMMON) { + printk(KERN_ERR + "comedi%d: me4000: me4000_ao_insn_write(): Invalid aref %d\n", + dev->minor, insn->n); + return -EINVAL; + } + + /* Stop any running conversion */ + tmp = me4000_inl(dev, info->ao_context[chan].ctrl_reg); + tmp |= ME4000_AO_CTRL_BIT_IMMEDIATE_STOP; + me4000_outl(dev, tmp, info->ao_context[chan].ctrl_reg); + + /* Clear control register and set to single mode */ + me4000_outl(dev, 0x0, info->ao_context[chan].ctrl_reg); + + /* Write data value */ + me4000_outl(dev, data[0], info->ao_context[chan].single_reg); + + /* Store in the mirror */ + info->ao_context[chan].mirror = data[0]; + + return 1; +} + +static int me4000_ao_insn_read(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + int chan = CR_CHAN(insn->chanspec); + + if (insn->n == 0) { + return 0; + } else if (insn->n > 1) { + printk("comedi%d: me4000: me4000_ao_insn_read(): Invalid instruction length\n", dev->minor); + return -EINVAL; + } + + data[0] = info->ao_context[chan].mirror; + + return 1; +} + +/*============================================================================= + Digital I/O section + ===========================================================================*/ + +static int me4000_dio_insn_bits(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + + CALL_PDEBUG("In me4000_dio_insn_bits()\n"); + + /* Length of data must be 2 (mask and new data, see below) */ + if (insn->n == 0) { + return 0; + } + if (insn->n != 2) { + printk("comedi%d: me4000: me4000_dio_insn_bits(): Invalid instruction length\n", dev->minor); + return -EINVAL; + } + + /* + * The insn data consists of a mask in data[0] and the new data + * in data[1]. The mask defines which bits we are concerning about. + * The new data must be anded with the mask. + * Each channel corresponds to a bit. + */ + if (data[0]) { + /* Check if requested ports are configured for output */ + if ((s->io_bits & data[0]) != data[0]) + return -EIO; + + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + + /* Write out the new digital output lines */ + me4000_outl(dev, (s->state >> 0) & 0xFF, + info->dio_context.port_0_reg); + me4000_outl(dev, (s->state >> 8) & 0xFF, + info->dio_context.port_1_reg); + me4000_outl(dev, (s->state >> 16) & 0xFF, + info->dio_context.port_2_reg); + me4000_outl(dev, (s->state >> 24) & 0xFF, + info->dio_context.port_3_reg); + } + + /* On return, data[1] contains the value of + the digital input and output lines. */ + data[1] = + ((me4000_inl(dev, info->dio_context.port_0_reg) & 0xFF) << 0) | + ((me4000_inl(dev, info->dio_context.port_1_reg) & 0xFF) << 8) | + ((me4000_inl(dev, info->dio_context.port_2_reg) & 0xFF) << 16) | + ((me4000_inl(dev, info->dio_context.port_3_reg) & 0xFF) << 24); + + return 2; +} + +static int me4000_dio_insn_config(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + unsigned long tmp; + int chan = CR_CHAN(insn->chanspec); + + CALL_PDEBUG("In me4000_dio_insn_config()\n"); + + if (data[0] == INSN_CONFIG_DIO_QUERY) { + data[1] = + (s-> + io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + } + + /* + * The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. + * On the ME-4000 it is only possible to switch port wise (8 bit) + */ + + tmp = me4000_inl(dev, info->dio_context.ctrl_reg); + + if (data[0] == COMEDI_OUTPUT) { + if (chan < 8) { + s->io_bits |= 0xFF; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 | + ME4000_DIO_CTRL_BIT_MODE_1); + tmp |= ME4000_DIO_CTRL_BIT_MODE_0; + } else if (chan < 16) { + /* + * Chech for optoisolated ME-4000 version. If one the first + * port is a fixed output port and the second is a fixed input port. + */ + if (!me4000_inl(dev, info->dio_context.dir_reg)) + return -ENODEV; + + s->io_bits |= 0xFF00; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 | + ME4000_DIO_CTRL_BIT_MODE_3); + tmp |= ME4000_DIO_CTRL_BIT_MODE_2; + } else if (chan < 24) { + s->io_bits |= 0xFF0000; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_4 | + ME4000_DIO_CTRL_BIT_MODE_5); + tmp |= ME4000_DIO_CTRL_BIT_MODE_4; + } else if (chan < 32) { + s->io_bits |= 0xFF000000; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_6 | + ME4000_DIO_CTRL_BIT_MODE_7); + tmp |= ME4000_DIO_CTRL_BIT_MODE_6; + } else { + return -EINVAL; + } + } else { + if (chan < 8) { + /* + * Chech for optoisolated ME-4000 version. If one the first + * port is a fixed output port and the second is a fixed input port. + */ + if (!me4000_inl(dev, info->dio_context.dir_reg)) + return -ENODEV; + + s->io_bits &= ~0xFF; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_0 | + ME4000_DIO_CTRL_BIT_MODE_1); + } else if (chan < 16) { + s->io_bits &= ~0xFF00; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_2 | + ME4000_DIO_CTRL_BIT_MODE_3); + } else if (chan < 24) { + s->io_bits &= ~0xFF0000; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_4 | + ME4000_DIO_CTRL_BIT_MODE_5); + } else if (chan < 32) { + s->io_bits &= ~0xFF000000; + tmp &= ~(ME4000_DIO_CTRL_BIT_MODE_6 | + ME4000_DIO_CTRL_BIT_MODE_7); + } else { + return -EINVAL; + } + } + + me4000_outl(dev, tmp, info->dio_context.ctrl_reg); + + return 1; +} + +/*============================================================================= + Counter section + ===========================================================================*/ + +static int cnt_reset(comedi_device * dev, unsigned int channel) +{ + + CALL_PDEBUG("In cnt_reset()\n"); + + switch (channel) { + case 0: + me4000_outb(dev, 0x30, info->cnt_context.ctrl_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_0_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_0_reg); + break; + case 1: + me4000_outb(dev, 0x70, info->cnt_context.ctrl_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_1_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_1_reg); + break; + case 2: + me4000_outb(dev, 0xB0, info->cnt_context.ctrl_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_2_reg); + me4000_outb(dev, 0x00, info->cnt_context.counter_2_reg); + break; + default: + printk(KERN_ERR + "comedi%d: me4000: cnt_reset(): Invalid channel\n", + dev->minor); + return -EINVAL; + } + + return 0; +} + +static int cnt_config(comedi_device * dev, unsigned int channel, + unsigned int mode) +{ + int tmp = 0; + + CALL_PDEBUG("In cnt_config()\n"); + + switch (channel) { + case 0: + tmp |= ME4000_CNT_COUNTER_0; + break; + case 1: + tmp |= ME4000_CNT_COUNTER_1; + break; + case 2: + tmp |= ME4000_CNT_COUNTER_2; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: cnt_config(): Invalid channel\n", + dev->minor); + return -EINVAL; + } + + switch (mode) { + case 0: + tmp |= ME4000_CNT_MODE_0; + break; + case 1: + tmp |= ME4000_CNT_MODE_1; + break; + case 2: + tmp |= ME4000_CNT_MODE_2; + break; + case 3: + tmp |= ME4000_CNT_MODE_3; + break; + case 4: + tmp |= ME4000_CNT_MODE_4; + break; + case 5: + tmp |= ME4000_CNT_MODE_5; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: cnt_config(): Invalid counter mode\n", + dev->minor); + return -EINVAL; + } + + /* Write the control word */ + tmp |= 0x30; + me4000_outb(dev, tmp, info->cnt_context.ctrl_reg); + + return 0; +} + +static int me4000_cnt_insn_config(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + + int err; + + CALL_PDEBUG("In me4000_cnt_insn_config()\n"); + + switch (data[0]) { + case GPCT_RESET: + if (insn->n != 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_config(): Invalid instruction length%d\n", + dev->minor, insn->n); + return -EINVAL; + } + + err = cnt_reset(dev, insn->chanspec); + if (err) + return err; + break; + case GPCT_SET_OPERATION: + if (insn->n != 2) { + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_config(): Invalid instruction length%d\n", + dev->minor, insn->n); + return -EINVAL; + } + + err = cnt_config(dev, insn->chanspec, data[1]); + if (err) + return err; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_config(): Invalid instruction\n", + dev->minor); + return -EINVAL; + } + + return 2; +} + +static int me4000_cnt_insn_read(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + + unsigned short tmp; + + CALL_PDEBUG("In me4000_cnt_insn_read()\n"); + + if (insn->n == 0) { + return 0; + } + if (insn->n > 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_read(): Invalid instruction length %d\n", + dev->minor, insn->n); + return -EINVAL; + } + + switch (insn->chanspec) { + case 0: + tmp = me4000_inb(dev, info->cnt_context.counter_0_reg); + data[0] = tmp; + tmp = me4000_inb(dev, info->cnt_context.counter_0_reg); + data[0] |= tmp << 8; + break; + case 1: + tmp = me4000_inb(dev, info->cnt_context.counter_1_reg); + data[0] = tmp; + tmp = me4000_inb(dev, info->cnt_context.counter_1_reg); + data[0] |= tmp << 8; + break; + case 2: + tmp = me4000_inb(dev, info->cnt_context.counter_2_reg); + data[0] = tmp; + tmp = me4000_inb(dev, info->cnt_context.counter_2_reg); + data[0] |= tmp << 8; + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_read(): Invalid channel %d\n", + dev->minor, insn->chanspec); + return -EINVAL; + } + + return 1; +} + +static int me4000_cnt_insn_write(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + + unsigned short tmp; + + CALL_PDEBUG("In me4000_cnt_insn_write()\n"); + + if (insn->n == 0) { + return 0; + } else if (insn->n > 1) { + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_write(): Invalid instruction length %d\n", + dev->minor, insn->n); + return -EINVAL; + } + + switch (insn->chanspec) { + case 0: + tmp = data[0] & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_0_reg); + tmp = (data[0] >> 8) & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_0_reg); + break; + case 1: + tmp = data[0] & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_1_reg); + tmp = (data[0] >> 8) & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_1_reg); + break; + case 2: + tmp = data[0] & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_2_reg); + tmp = (data[0] >> 8) & 0xFF; + me4000_outb(dev, tmp, info->cnt_context.counter_2_reg); + break; + default: + printk(KERN_ERR + "comedi%d: me4000: me4000_cnt_insn_write(): Invalid channel %d\n", + dev->minor, insn->chanspec); + return -EINVAL; + } + + return 1; +} + +COMEDI_PCI_INITCLEANUP(driver_me4000, me4000_pci_table); diff --git a/drivers/staging/comedi/drivers/me4000.h b/drivers/staging/comedi/drivers/me4000.h new file mode 100644 index 00000000000..f12b8873ec3 --- /dev/null +++ b/drivers/staging/comedi/drivers/me4000.h @@ -0,0 +1,446 @@ +/* + me4000.h + Register descriptions and defines for the ME-4000 board family + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998-9 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. + +*/ + +#ifndef _ME4000_H_ +#define _ME4000_H_ + +/*============================================================================= + Debug section + ===========================================================================*/ + +#undef ME4000_CALL_DEBUG // Debug function entry and exit +#undef ME4000_PORT_DEBUG // Debug port access +#undef ME4000_ISR_DEBUG // Debug the interrupt service routine +#undef ME4000_DEBUG // General purpose debug masseges + +#ifdef ME4000_CALL_DEBUG +#undef CALL_PDEBUG +#define CALL_PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor, ##args) +#else +# define CALL_PDEBUG(fmt, args...) // no debugging, do nothing +#endif + +#ifdef ME4000_PORT_DEBUG +#undef PORT_PDEBUG +#define PORT_PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor, ##args) +#else +#define PORT_PDEBUG(fmt, args...) // no debugging, do nothing +#endif + +#ifdef ME4000_ISR_DEBUG +#undef ISR_PDEBUG +#define ISR_PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor, ##args) +#else +#define ISR_PDEBUG(fmt, args...) // no debugging, do nothing +#endif + +#ifdef ME4000_DEBUG +#undef PDEBUG +#define PDEBUG(fmt, args...) printk(KERN_DEBUG"comedi%d: me4000: " fmt, dev->minor, ##args) +#else +#define PDEBUG(fmt, args...) // no debugging, do nothing +#endif + +/*============================================================================= + PCI vendor and device IDs + ===========================================================================*/ + +#define PCI_VENDOR_ID_MEILHAUS 0x1402 + +#define PCI_DEVICE_ID_MEILHAUS_ME4650 0x4650 // Low Cost version + +#define PCI_DEVICE_ID_MEILHAUS_ME4660 0x4660 // Standard version +#define PCI_DEVICE_ID_MEILHAUS_ME4660I 0x4661 // Isolated version +#define PCI_DEVICE_ID_MEILHAUS_ME4660S 0x4662 // Standard version with Sample and Hold +#define PCI_DEVICE_ID_MEILHAUS_ME4660IS 0x4663 // Isolated version with Sample and Hold + +#define PCI_DEVICE_ID_MEILHAUS_ME4670 0x4670 // Standard version +#define PCI_DEVICE_ID_MEILHAUS_ME4670I 0x4671 // Isolated version +#define PCI_DEVICE_ID_MEILHAUS_ME4670S 0x4672 // Standard version with Sample and Hold +#define PCI_DEVICE_ID_MEILHAUS_ME4670IS 0x4673 // Isolated version with Sample and Hold + +#define PCI_DEVICE_ID_MEILHAUS_ME4680 0x4680 // Standard version +#define PCI_DEVICE_ID_MEILHAUS_ME4680I 0x4681 // Isolated version +#define PCI_DEVICE_ID_MEILHAUS_ME4680S 0x4682 // Standard version with Sample and Hold +#define PCI_DEVICE_ID_MEILHAUS_ME4680IS 0x4683 // Isolated version with Sample and Hold + +/*============================================================================= + ME-4000 base register offsets + ===========================================================================*/ + +#define ME4000_AO_00_CTRL_REG 0x00 // R/W +#define ME4000_AO_00_STATUS_REG 0x04 // R/_ +#define ME4000_AO_00_FIFO_REG 0x08 // _/W +#define ME4000_AO_00_SINGLE_REG 0x0C // R/W +#define ME4000_AO_00_TIMER_REG 0x10 // _/W + +#define ME4000_AO_01_CTRL_REG 0x18 // R/W +#define ME4000_AO_01_STATUS_REG 0x1C // R/_ +#define ME4000_AO_01_FIFO_REG 0x20 // _/W +#define ME4000_AO_01_SINGLE_REG 0x24 // R/W +#define ME4000_AO_01_TIMER_REG 0x28 // _/W + +#define ME4000_AO_02_CTRL_REG 0x30 // R/W +#define ME4000_AO_02_STATUS_REG 0x34 // R/_ +#define ME4000_AO_02_FIFO_REG 0x38 // _/W +#define ME4000_AO_02_SINGLE_REG 0x3C // R/W +#define ME4000_AO_02_TIMER_REG 0x40 // _/W + +#define ME4000_AO_03_CTRL_REG 0x48 // R/W +#define ME4000_AO_03_STATUS_REG 0x4C // R/_ +#define ME4000_AO_03_FIFO_REG 0x50 // _/W +#define ME4000_AO_03_SINGLE_REG 0x54 // R/W +#define ME4000_AO_03_TIMER_REG 0x58 // _/W + +#define ME4000_AI_CTRL_REG 0x74 // _/W +#define ME4000_AI_STATUS_REG 0x74 // R/_ +#define ME4000_AI_CHANNEL_LIST_REG 0x78 // _/W +#define ME4000_AI_DATA_REG 0x7C // R/_ +#define ME4000_AI_CHAN_TIMER_REG 0x80 // _/W +#define ME4000_AI_CHAN_PRE_TIMER_REG 0x84 // _/W +#define ME4000_AI_SCAN_TIMER_LOW_REG 0x88 // _/W +#define ME4000_AI_SCAN_TIMER_HIGH_REG 0x8C // _/W +#define ME4000_AI_SCAN_PRE_TIMER_LOW_REG 0x90 // _/W +#define ME4000_AI_SCAN_PRE_TIMER_HIGH_REG 0x94 // _/W +#define ME4000_AI_START_REG 0x98 // R/_ + +#define ME4000_IRQ_STATUS_REG 0x9C // R/_ + +#define ME4000_DIO_PORT_0_REG 0xA0 // R/W +#define ME4000_DIO_PORT_1_REG 0xA4 // R/W +#define ME4000_DIO_PORT_2_REG 0xA8 // R/W +#define ME4000_DIO_PORT_3_REG 0xAC // R/W +#define ME4000_DIO_DIR_REG 0xB0 // R/W + +#define ME4000_AO_LOADSETREG_XX 0xB4 // R/W + +#define ME4000_DIO_CTRL_REG 0xB8 // R/W + +#define ME4000_AO_DEMUX_ADJUST_REG 0xBC // -/W + +#define ME4000_AI_SAMPLE_COUNTER_REG 0xC0 // _/W + +/*============================================================================= + Value to adjust Demux + ===========================================================================*/ + +#define ME4000_AO_DEMUX_ADJUST_VALUE 0x4C + +/*============================================================================= + Counter base register offsets + ===========================================================================*/ + +#define ME4000_CNT_COUNTER_0_REG 0x00 +#define ME4000_CNT_COUNTER_1_REG 0x01 +#define ME4000_CNT_COUNTER_2_REG 0x02 +#define ME4000_CNT_CTRL_REG 0x03 + +/*============================================================================= + PLX base register offsets + ===========================================================================*/ + +#define PLX_INTCSR 0x4C // Interrupt control and status register +#define PLX_ICR 0x50 // Initialization control register + +/*============================================================================= + Bits for the PLX_ICSR register + ===========================================================================*/ + +#define PLX_INTCSR_LOCAL_INT1_EN 0x01 // If set, local interrupt 1 is enabled (r/w) +#define PLX_INTCSR_LOCAL_INT1_POL 0x02 // If set, local interrupt 1 polarity is active high (r/w) +#define PLX_INTCSR_LOCAL_INT1_STATE 0x04 // If set, local interrupt 1 is active (r/_) +#define PLX_INTCSR_LOCAL_INT2_EN 0x08 // If set, local interrupt 2 is enabled (r/w) +#define PLX_INTCSR_LOCAL_INT2_POL 0x10 // If set, local interrupt 2 polarity is active high (r/w) +#define PLX_INTCSR_LOCAL_INT2_STATE 0x20 // If set, local interrupt 2 is active (r/_) +#define PLX_INTCSR_PCI_INT_EN 0x40 // If set, PCI interrupt is enabled (r/w) +#define PLX_INTCSR_SOFT_INT 0x80 // If set, a software interrupt is generated (r/w) + +/*============================================================================= + Bits for the PLX_ICR register + ===========================================================================*/ + +#define PLX_ICR_BIT_EEPROM_CLOCK_SET 0x01000000 +#define PLX_ICR_BIT_EEPROM_CHIP_SELECT 0x02000000 +#define PLX_ICR_BIT_EEPROM_WRITE 0x04000000 +#define PLX_ICR_BIT_EEPROM_READ 0x08000000 +#define PLX_ICR_BIT_EEPROM_VALID 0x10000000 + +#define PLX_ICR_MASK_EEPROM 0x1F000000 + +#define EEPROM_DELAY 1 + +/*============================================================================= + Bits for the ME4000_AO_CTRL_REG register + ===========================================================================*/ + +#define ME4000_AO_CTRL_BIT_MODE_0 0x001 +#define ME4000_AO_CTRL_BIT_MODE_1 0x002 +#define ME4000_AO_CTRL_MASK_MODE 0x003 +#define ME4000_AO_CTRL_BIT_STOP 0x004 +#define ME4000_AO_CTRL_BIT_ENABLE_FIFO 0x008 +#define ME4000_AO_CTRL_BIT_ENABLE_EX_TRIG 0x010 +#define ME4000_AO_CTRL_BIT_EX_TRIG_EDGE 0x020 +#define ME4000_AO_CTRL_BIT_IMMEDIATE_STOP 0x080 +#define ME4000_AO_CTRL_BIT_ENABLE_DO 0x100 +#define ME4000_AO_CTRL_BIT_ENABLE_IRQ 0x200 +#define ME4000_AO_CTRL_BIT_RESET_IRQ 0x400 + +/*============================================================================= + Bits for the ME4000_AO_STATUS_REG register + ===========================================================================*/ + +#define ME4000_AO_STATUS_BIT_FSM 0x01 +#define ME4000_AO_STATUS_BIT_FF 0x02 +#define ME4000_AO_STATUS_BIT_HF 0x04 +#define ME4000_AO_STATUS_BIT_EF 0x08 + +/*============================================================================= + Bits for the ME4000_AI_CTRL_REG register + ===========================================================================*/ + +#define ME4000_AI_CTRL_BIT_MODE_0 0x00000001 +#define ME4000_AI_CTRL_BIT_MODE_1 0x00000002 +#define ME4000_AI_CTRL_BIT_MODE_2 0x00000004 +#define ME4000_AI_CTRL_BIT_SAMPLE_HOLD 0x00000008 +#define ME4000_AI_CTRL_BIT_IMMEDIATE_STOP 0x00000010 +#define ME4000_AI_CTRL_BIT_STOP 0x00000020 +#define ME4000_AI_CTRL_BIT_CHANNEL_FIFO 0x00000040 +#define ME4000_AI_CTRL_BIT_DATA_FIFO 0x00000080 +#define ME4000_AI_CTRL_BIT_FULLSCALE 0x00000100 +#define ME4000_AI_CTRL_BIT_OFFSET 0x00000200 +#define ME4000_AI_CTRL_BIT_EX_TRIG_ANALOG 0x00000400 +#define ME4000_AI_CTRL_BIT_EX_TRIG 0x00000800 +#define ME4000_AI_CTRL_BIT_EX_TRIG_FALLING 0x00001000 +#define ME4000_AI_CTRL_BIT_EX_IRQ 0x00002000 +#define ME4000_AI_CTRL_BIT_EX_IRQ_RESET 0x00004000 +#define ME4000_AI_CTRL_BIT_LE_IRQ 0x00008000 +#define ME4000_AI_CTRL_BIT_LE_IRQ_RESET 0x00010000 +#define ME4000_AI_CTRL_BIT_HF_IRQ 0x00020000 +#define ME4000_AI_CTRL_BIT_HF_IRQ_RESET 0x00040000 +#define ME4000_AI_CTRL_BIT_SC_IRQ 0x00080000 +#define ME4000_AI_CTRL_BIT_SC_IRQ_RESET 0x00100000 +#define ME4000_AI_CTRL_BIT_SC_RELOAD 0x00200000 +#define ME4000_AI_CTRL_BIT_EX_TRIG_BOTH 0x80000000 + +/*============================================================================= + Bits for the ME4000_AI_STATUS_REG register + ===========================================================================*/ + +#define ME4000_AI_STATUS_BIT_EF_CHANNEL 0x00400000 +#define ME4000_AI_STATUS_BIT_HF_CHANNEL 0x00800000 +#define ME4000_AI_STATUS_BIT_FF_CHANNEL 0x01000000 +#define ME4000_AI_STATUS_BIT_EF_DATA 0x02000000 +#define ME4000_AI_STATUS_BIT_HF_DATA 0x04000000 +#define ME4000_AI_STATUS_BIT_FF_DATA 0x08000000 +#define ME4000_AI_STATUS_BIT_LE 0x10000000 +#define ME4000_AI_STATUS_BIT_FSM 0x20000000 + +/*============================================================================= + Bits for the ME4000_IRQ_STATUS_REG register + ===========================================================================*/ + +#define ME4000_IRQ_STATUS_BIT_EX 0x01 +#define ME4000_IRQ_STATUS_BIT_LE 0x02 +#define ME4000_IRQ_STATUS_BIT_AI_HF 0x04 +#define ME4000_IRQ_STATUS_BIT_AO_0_HF 0x08 +#define ME4000_IRQ_STATUS_BIT_AO_1_HF 0x10 +#define ME4000_IRQ_STATUS_BIT_AO_2_HF 0x20 +#define ME4000_IRQ_STATUS_BIT_AO_3_HF 0x40 +#define ME4000_IRQ_STATUS_BIT_SC 0x80 + +/*============================================================================= + Bits for the ME4000_DIO_CTRL_REG register + ===========================================================================*/ + +#define ME4000_DIO_CTRL_BIT_MODE_0 0x0001 +#define ME4000_DIO_CTRL_BIT_MODE_1 0x0002 +#define ME4000_DIO_CTRL_BIT_MODE_2 0x0004 +#define ME4000_DIO_CTRL_BIT_MODE_3 0x0008 +#define ME4000_DIO_CTRL_BIT_MODE_4 0x0010 +#define ME4000_DIO_CTRL_BIT_MODE_5 0x0020 +#define ME4000_DIO_CTRL_BIT_MODE_6 0x0040 +#define ME4000_DIO_CTRL_BIT_MODE_7 0x0080 + +#define ME4000_DIO_CTRL_BIT_FUNCTION_0 0x0100 +#define ME4000_DIO_CTRL_BIT_FUNCTION_1 0x0200 + +#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_0 0x0400 +#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_1 0x0800 +#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_2 0x1000 +#define ME4000_DIO_CTRL_BIT_FIFO_HIGH_3 0x2000 + +/*============================================================================= + Information about the hardware capabilities + ===========================================================================*/ + +typedef struct me4000_ao_info { + int count; + int fifo_count; +} me4000_ao_info_t; + +typedef struct me4000_ai_info { + int count; + int sh_count; + int diff_count; + int ex_trig_analog; +} me4000_ai_info_t; + +typedef struct me4000_dio_info { + int count; +} me4000_dio_info_t; + +typedef struct me4000_cnt_info { + int count; +} me4000_cnt_info_t; + +typedef struct me4000_board { + const char *name; + unsigned short device_id; + me4000_ao_info_t ao; + me4000_ai_info_t ai; + me4000_dio_info_t dio; + me4000_cnt_info_t cnt; +} me4000_board_t; + +#define thisboard ((const me4000_board_t *)dev->board_ptr) + +/*============================================================================= + Global board and subdevice information structures + ===========================================================================*/ + +typedef struct me4000_ao_context { + int irq; + + unsigned long mirror; // Store the last written value + + unsigned long ctrl_reg; + unsigned long status_reg; + unsigned long fifo_reg; + unsigned long single_reg; + unsigned long timer_reg; + unsigned long irq_status_reg; + unsigned long preload_reg; +} me4000_ao_context_t; + +typedef struct me4000_ai_context { + int irq; + + unsigned long ctrl_reg; + unsigned long status_reg; + unsigned long channel_list_reg; + unsigned long data_reg; + unsigned long chan_timer_reg; + unsigned long chan_pre_timer_reg; + unsigned long scan_timer_low_reg; + unsigned long scan_timer_high_reg; + unsigned long scan_pre_timer_low_reg; + unsigned long scan_pre_timer_high_reg; + unsigned long start_reg; + unsigned long irq_status_reg; + unsigned long sample_counter_reg; +} me4000_ai_context_t; + +typedef struct me4000_dio_context { + unsigned long dir_reg; + unsigned long ctrl_reg; + unsigned long port_0_reg; + unsigned long port_1_reg; + unsigned long port_2_reg; + unsigned long port_3_reg; +} me4000_dio_context_t; + +typedef struct me4000_cnt_context { + unsigned long ctrl_reg; + unsigned long counter_0_reg; + unsigned long counter_1_reg; + unsigned long counter_2_reg; +} me4000_cnt_context_t; + +typedef struct me4000_info { + unsigned long plx_regbase; // PLX configuration space base address + unsigned long me4000_regbase; // Base address of the ME4000 + unsigned long timer_regbase; // Base address of the timer circuit + unsigned long program_regbase; // Base address to set the program pin for the xilinx + + unsigned long plx_regbase_size; // PLX register set space + unsigned long me4000_regbase_size; // ME4000 register set space + unsigned long timer_regbase_size; // Timer circuit register set space + unsigned long program_regbase_size; // Size of program base address of the ME4000 + + unsigned int serial_no; // Serial number of the board + unsigned char hw_revision; // Hardware revision of the board + unsigned short vendor_id; // Meilhaus vendor id + unsigned short device_id; // Device id + + struct pci_dev *pci_dev_p; // General PCI information + + unsigned int irq; // IRQ assigned from the PCI BIOS + + struct me4000_ai_context ai_context; // Analog input specific context + struct me4000_ao_context ao_context[4]; // Vector with analog output specific context + struct me4000_dio_context dio_context; // Digital I/O specific context + struct me4000_cnt_context cnt_context; // Counter specific context +} me4000_info_t; + +#define info ((me4000_info_t *)dev->private) + +/*----------------------------------------------------------------------------- + Defines for analog input + ----------------------------------------------------------------------------*/ + +/* General stuff */ +#define ME4000_AI_FIFO_COUNT 2048 + +#define ME4000_AI_MIN_TICKS 66 +#define ME4000_AI_MIN_SAMPLE_TIME 2000 // Minimum sample time [ns] +#define ME4000_AI_BASE_FREQUENCY (unsigned int) 33E6 + +/* Channel list defines and masks */ +#define ME4000_AI_CHANNEL_LIST_COUNT 1024 + +#define ME4000_AI_LIST_INPUT_SINGLE_ENDED 0x000 +#define ME4000_AI_LIST_INPUT_DIFFERENTIAL 0x020 + +#define ME4000_AI_LIST_RANGE_BIPOLAR_10 0x000 +#define ME4000_AI_LIST_RANGE_BIPOLAR_2_5 0x040 +#define ME4000_AI_LIST_RANGE_UNIPOLAR_10 0x080 +#define ME4000_AI_LIST_RANGE_UNIPOLAR_2_5 0x0C0 + +#define ME4000_AI_LIST_LAST_ENTRY 0x100 + +/*----------------------------------------------------------------------------- + Defines for counters + ----------------------------------------------------------------------------*/ + +#define ME4000_CNT_COUNTER_0 0x00 +#define ME4000_CNT_COUNTER_1 0x40 +#define ME4000_CNT_COUNTER_2 0x80 + +#define ME4000_CNT_MODE_0 0x00 // Change state if zero crossing +#define ME4000_CNT_MODE_1 0x02 // Retriggerable One-Shot +#define ME4000_CNT_MODE_2 0x04 // Asymmetrical divider +#define ME4000_CNT_MODE_3 0x06 // Symmetrical divider +#define ME4000_CNT_MODE_4 0x08 // Counter start by software trigger +#define ME4000_CNT_MODE_5 0x0A // Counter start by hardware trigger + +#endif diff --git a/drivers/staging/comedi/drivers/me_daq.c b/drivers/staging/comedi/drivers/me_daq.c new file mode 100644 index 00000000000..6accec20a0f --- /dev/null +++ b/drivers/staging/comedi/drivers/me_daq.c @@ -0,0 +1,845 @@ +/* + + comedi/drivers/me_daq.c + + Hardware driver for Meilhaus data acquisition cards: + + ME-2000i, ME-2600i, ME-3000vm1 + + Copyright (C) 2002 Michael Hillmann <hillmann@syscongroup.de> + + 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: me_daq +Description: Meilhaus PCI data acquisition cards +Author: Michael Hillmann <hillmann@syscongroup.de> +Devices: [Meilhaus] ME-2600i (me_daq), ME-2000i +Status: experimental + +Supports: + + Analog Output + +Configuration options: + + [0] - PCI bus number (optional) + [1] - PCI slot number (optional) + + If bus/slot is not specified, the first available PCI + device will be used. + +The 2600 requires a firmware upload, which can be accomplished +using the -i or --init-data option of comedi_config. +The firmware can be +found in the comedi_nonfree_firmware tarball available +from http://www.comedi.org + +*/ + +#include "../comedidev.h" + +#include "comedi_pci.h" + +/*#include "me2600_fw.h" */ + +#define ME_DRIVER_NAME "me_daq" + +#define ME2000_DEVICE_ID 0x2000 +#define ME2600_DEVICE_ID 0x2600 + +#define PLX_INTCSR 0x4C /* PLX interrupt status register */ +#define XILINX_DOWNLOAD_RESET 0x42 /* Xilinx registers */ + +#define ME_CONTROL_1 0x0000 /* - | W */ +#define INTERRUPT_ENABLE (1<<15) +#define COUNTER_B_IRQ (1<<12) +#define COUNTER_A_IRQ (1<<11) +#define CHANLIST_READY_IRQ (1<<10) +#define EXT_IRQ (1<<9) +#define ADFIFO_HALFFULL_IRQ (1<<8) +#define SCAN_COUNT_ENABLE (1<<5) +#define SIMULTANEOUS_ENABLE (1<<4) +#define TRIGGER_FALLING_EDGE (1<<3) +#define CONTINUOUS_MODE (1<<2) +#define DISABLE_ADC (0<<0) +#define SOFTWARE_TRIGGERED_ADC (1<<0) +#define SCAN_TRIGGERED_ADC (2<<0) +#define EXT_TRIGGERED_ADC (3<<0) +#define ME_ADC_START 0x0000 /* R | - */ +#define ME_CONTROL_2 0x0002 /* - | W */ +#define ENABLE_ADFIFO (1<<10) +#define ENABLE_CHANLIST (1<<9) +#define ENABLE_PORT_B (1<<7) +#define ENABLE_PORT_A (1<<6) +#define ENABLE_COUNTER_B (1<<4) +#define ENABLE_COUNTER_A (1<<3) +#define ENABLE_DAC (1<<1) +#define BUFFERED_DAC (1<<0) +#define ME_DAC_UPDATE 0x0002 /* R | - */ +#define ME_STATUS 0x0004 /* R | - */ +#define COUNTER_B_IRQ_PENDING (1<<12) +#define COUNTER_A_IRQ_PENDING (1<<11) +#define CHANLIST_READY_IRQ_PENDING (1<<10) +#define EXT_IRQ_PENDING (1<<9) +#define ADFIFO_HALFFULL_IRQ_PENDING (1<<8) +#define ADFIFO_FULL (1<<4) +#define ADFIFO_HALFFULL (1<<3) +#define ADFIFO_EMPTY (1<<2) +#define CHANLIST_FULL (1<<1) +#define FST_ACTIVE (1<<0) +#define ME_RESET_INTERRUPT 0x0004 /* - | W */ +#define ME_DIO_PORT_A 0x0006 /* R | W */ +#define ME_DIO_PORT_B 0x0008 /* R | W */ +#define ME_TIMER_DATA_0 0x000A /* - | W */ +#define ME_TIMER_DATA_1 0x000C /* - | W */ +#define ME_TIMER_DATA_2 0x000E /* - | W */ +#define ME_CHANNEL_LIST 0x0010 /* - | W */ +#define ADC_UNIPOLAR (1<<6) +#define ADC_GAIN_0 (0<<4) +#define ADC_GAIN_1 (1<<4) +#define ADC_GAIN_2 (2<<4) +#define ADC_GAIN_3 (3<<4) +#define ME_READ_AD_FIFO 0x0010 /* R | - */ +#define ME_DAC_CONTROL 0x0012 /* - | W */ +#define DAC_UNIPOLAR_D (0<<4) +#define DAC_BIPOLAR_D (1<<4) +#define DAC_UNIPOLAR_C (0<<5) +#define DAC_BIPOLAR_C (1<<5) +#define DAC_UNIPOLAR_B (0<<6) +#define DAC_BIPOLAR_B (1<<6) +#define DAC_UNIPOLAR_A (0<<7) +#define DAC_BIPOLAR_A (1<<7) +#define DAC_GAIN_0_D (0<<8) +#define DAC_GAIN_1_D (1<<8) +#define DAC_GAIN_0_C (0<<9) +#define DAC_GAIN_1_C (1<<9) +#define DAC_GAIN_0_B (0<<10) +#define DAC_GAIN_1_B (1<<10) +#define DAC_GAIN_0_A (0<<11) +#define DAC_GAIN_1_A (1<<11) +#define ME_DAC_CONTROL_UPDATE 0x0012 /* R | - */ +#define ME_DAC_DATA_A 0x0014 /* - | W */ +#define ME_DAC_DATA_B 0x0016 /* - | W */ +#define ME_DAC_DATA_C 0x0018 /* - | W */ +#define ME_DAC_DATA_D 0x001A /* - | W */ +#define ME_COUNTER_ENDDATA_A 0x001C /* - | W */ +#define ME_COUNTER_ENDDATA_B 0x001E /* - | W */ +#define ME_COUNTER_STARTDATA_A 0x0020 /* - | W */ +#define ME_COUNTER_VALUE_A 0x0020 /* R | - */ +#define ME_COUNTER_STARTDATA_B 0x0022 /* - | W */ +#define ME_COUNTER_VALUE_B 0x0022 /* R | - */ + +/* Function prototypes */ +static int me_attach(comedi_device *dev, comedi_devconfig *it); +static int me_detach(comedi_device *dev); + +static const comedi_lrange me2000_ai_range = { + 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) + } +}; + +static const comedi_lrange me2600_ai_range = { + 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) + } +}; + +static const comedi_lrange me2600_ao_range = { + 3, + { + BIP_RANGE(10), + BIP_RANGE(5), + UNI_RANGE(10) + } +}; + +static DEFINE_PCI_DEVICE_TABLE(me_pci_table) = { + {PCI_VENDOR_ID_MEILHAUS, ME2600_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, + 0}, + {PCI_VENDOR_ID_MEILHAUS, ME2000_DEVICE_ID, PCI_ANY_ID, PCI_ANY_ID, 0, 0, + 0}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, me_pci_table); + +/* Board specification structure */ +struct me_board { + const char *name; /* driver name */ + int device_id; + int ao_channel_nbr; /* DA config */ + int ao_resolution; + int ao_resolution_mask; + const comedi_lrange *ao_range_list; + int ai_channel_nbr; /* AD config */ + int ai_resolution; + int ai_resolution_mask; + const comedi_lrange *ai_range_list; + int dio_channel_nbr; /* DIO config */ +}; + +static const struct me_board me_boards[] = { + { + /* -- ME-2600i -- */ + .name = ME_DRIVER_NAME, + .device_id = ME2600_DEVICE_ID, + /* Analog Output */ + .ao_channel_nbr = 4, + .ao_resolution = 12, + .ao_resolution_mask = 0x0fff, + .ao_range_list = &me2600_ao_range, + .ai_channel_nbr = 16, + /* Analog Input */ + .ai_resolution = 12, + .ai_resolution_mask = 0x0fff, + .ai_range_list = &me2600_ai_range, + .dio_channel_nbr = 32, + }, + { + /* -- ME-2000i -- */ + .name = ME_DRIVER_NAME, + .device_id = ME2000_DEVICE_ID, + /* Analog Output */ + .ao_channel_nbr = 0, + .ao_resolution = 0, + .ao_resolution_mask = 0, + .ao_range_list = NULL, + .ai_channel_nbr = 16, + /* Analog Input */ + .ai_resolution = 12, + .ai_resolution_mask = 0x0fff, + .ai_range_list = &me2000_ai_range, + .dio_channel_nbr = 32, + } +}; + +#define me_board_nbr (sizeof(me_boards)/sizeof(struct me_board)) + +static comedi_driver me_driver = { + .driver_name = ME_DRIVER_NAME, + .module = THIS_MODULE, + .attach = me_attach, + .detach = me_detach, +}; +COMEDI_PCI_INITCLEANUP(me_driver, me_pci_table); + +/* Private data structure */ +struct me_private_data { + struct pci_dev *pci_device; + void __iomem *plx_regbase; /* PLX configuration base address */ + void __iomem *me_regbase; /* Base address of the Meilhaus card */ + unsigned long plx_regbase_size; /* Size of PLX configuration space */ + unsigned long me_regbase_size; /* Size of Meilhaus space */ + + unsigned short control_1; /* Mirror of CONTROL_1 register */ + unsigned short control_2; /* Mirror of CONTROL_2 register */ + unsigned short dac_control; /* Mirror of the DAC_CONTROL register */ + int ao_readback[4]; /* Mirror of analog output data */ +}; + +#define dev_private ((struct me_private_data *)dev->private) + +/* + * ------------------------------------------------------------------ + * + * Helpful functions + * + * ------------------------------------------------------------------ + */ +static inline void sleep(unsigned sec) +{ + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(sec * HZ); +} + +/* + * ------------------------------------------------------------------ + * + * DIGITAL INPUT/OUTPUT SECTION + * + * ------------------------------------------------------------------ + */ +static int me_dio_insn_config(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int bits; + int mask = 1 << CR_CHAN(insn->chanspec); + + /* calculate port */ + if (mask & 0x0000ffff) { /* Port A in use */ + bits = 0x0000ffff; + + /* Enable Port A */ + dev_private->control_2 |= ENABLE_PORT_A; + writew(dev_private->control_2, + dev_private->me_regbase + ME_CONTROL_2); + } else { /* Port B in use */ + + bits = 0xffff0000; + + /* Enable Port B */ + dev_private->control_2 |= ENABLE_PORT_B; + writew(dev_private->control_2, + dev_private->me_regbase + ME_CONTROL_2); + } + + if (data[0]) { + /* Config port as output */ + s->io_bits |= bits; + } else { + /* Config port as input */ + s->io_bits &= ~bits; + } + + return 1; +} + +/* Digital instant input/outputs */ +static int me_dio_insn_bits(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + unsigned int mask = data[0]; + s->state &= ~mask; + s->state |= (mask & data[1]); + + mask &= s->io_bits; + if (mask & 0x0000ffff) { /* Port A */ + writew((s->state & 0xffff), + dev_private->me_regbase + ME_DIO_PORT_A); + } else { + data[1] &= ~0x0000ffff; + data[1] |= readw(dev_private->me_regbase + ME_DIO_PORT_A); + } + + if (mask & 0xffff0000) { /* Port B */ + writew(((s->state >> 16) & 0xffff), + dev_private->me_regbase + ME_DIO_PORT_B); + } else { + data[1] &= ~0xffff0000; + data[1] |= readw(dev_private->me_regbase + ME_DIO_PORT_B) << 16; + } + + return 2; +} + +/* + * ------------------------------------------------------------------ + * + * ANALOG INPUT SECTION + * + * ------------------------------------------------------------------ + */ + +/* Analog instant input */ +static int me_ai_insn_read(comedi_device *dev, comedi_subdevice *subdevice, + comedi_insn *insn, lsampl_t *data) +{ + unsigned short value; + int chan = CR_CHAN((&insn->chanspec)[0]); + int rang = CR_RANGE((&insn->chanspec)[0]); + int aref = CR_AREF((&insn->chanspec)[0]); + int i; + + /* stop any running conversion */ + dev_private->control_1 &= 0xFFFC; + writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); + + /* clear chanlist and ad fifo */ + dev_private->control_2 &= ~(ENABLE_ADFIFO | ENABLE_CHANLIST); + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* reset any pending interrupt */ + writew(0x00, dev_private->me_regbase + ME_RESET_INTERRUPT); + + /* enable the chanlist and ADC fifo */ + dev_private->control_2 |= (ENABLE_ADFIFO | ENABLE_CHANLIST); + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* write to channel list fifo */ + /* b3:b0 are the channel number */ + value = chan & 0x0f; + /* b5:b4 are the channel gain */ + value |= (rang & 0x03) << 4; + /* b6 channel polarity */ + value |= (rang & 0x04) << 4; + /* b7 single or differential */ + value |= ((aref & AREF_DIFF) ? 0x80 : 0); + writew(value & 0xff, dev_private->me_regbase + ME_CHANNEL_LIST); + + /* set ADC mode to software trigger */ + dev_private->control_1 |= SOFTWARE_TRIGGERED_ADC; + writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); + + /* start conversion by reading from ADC_START */ + readw(dev_private->me_regbase + ME_ADC_START); + + /* wait for ADC fifo not empty flag */ + for (i = 100000; i > 0; i--) + if (!(readw(dev_private->me_regbase + ME_STATUS) & 0x0004)) + break; + + /* get value from ADC fifo */ + if (i) { + data[0] = + (readw(dev_private->me_regbase + + ME_READ_AD_FIFO) ^ 0x800) & 0x0FFF; + } else { + printk(KERN_ERR "comedi%d: Cannot get single value\n", + dev->minor); + return -EIO; + } + + /* stop any running conversion */ + dev_private->control_1 &= 0xFFFC; + writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); + + return 1; +} + +/* + * ------------------------------------------------------------------ + * + * HARDWARE TRIGGERED ANALOG INPUT SECTION + * + * ------------------------------------------------------------------ + */ + +/* Cancel analog input autoscan */ +static int me_ai_cancel(comedi_device *dev, comedi_subdevice *s) +{ + /* disable interrupts */ + + /* stop any running conversion */ + dev_private->control_1 &= 0xFFFC; + writew(dev_private->control_1, dev_private->me_regbase + ME_CONTROL_1); + + return 0; +} + +/* Test analog input command */ +static int me_ai_do_cmd_test(comedi_device *dev, comedi_subdevice *s, + comedi_cmd *cmd) +{ + return 0; +} + +/* Analog input command */ +static int me_ai_do_cmd(comedi_device *dev, comedi_subdevice *subdevice) +{ + return 0; +} + +/* + * ------------------------------------------------------------------ + * + * ANALOG OUTPUT SECTION + * + * ------------------------------------------------------------------ + */ + +/* Analog instant output */ +static int me_ao_insn_write(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int chan; + int rang; + int i; + + /* Enable all DAC */ + dev_private->control_2 |= ENABLE_DAC; + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* and set DAC to "buffered" mode */ + dev_private->control_2 |= BUFFERED_DAC; + writew(dev_private->control_2, dev_private->me_regbase + ME_CONTROL_2); + + /* Set dac-control register */ + for (i = 0; i < insn->n; i++) { + chan = CR_CHAN((&insn->chanspec)[i]); + rang = CR_RANGE((&insn->chanspec)[i]); + + /* clear bits for this channel */ + dev_private->dac_control &= ~(0x0880 >> chan); + if (rang == 0) + dev_private->dac_control |= + ((DAC_BIPOLAR_A | DAC_GAIN_1_A) >> chan); + else if (rang == 1) + dev_private->dac_control |= + ((DAC_BIPOLAR_A | DAC_GAIN_0_A) >> chan); + } + writew(dev_private->dac_control, + dev_private->me_regbase + ME_DAC_CONTROL); + + /* Update dac-control register */ + readw(dev_private->me_regbase + ME_DAC_CONTROL_UPDATE); + + /* Set data register */ + for (i = 0; i < insn->n; i++) { + chan = CR_CHAN((&insn->chanspec)[i]); + writew((data[0] & s->maxdata), + dev_private->me_regbase + ME_DAC_DATA_A + (chan << 1)); + dev_private->ao_readback[chan] = (data[0] & s->maxdata); + } + + /* Update dac with data registers */ + readw(dev_private->me_regbase + ME_DAC_UPDATE); + + return i; +} + +/* Analog output readback */ +static int me_ao_insn_read(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int i; + + for (i = 0; i < insn->n; i++) { + data[i] = + dev_private->ao_readback[CR_CHAN((&insn->chanspec)[i])]; + } + + return 1; +} + +/* + * ------------------------------------------------------------------ + * + * INITIALISATION SECTION + * + * ------------------------------------------------------------------ + */ + +/* Xilinx firmware download for card: ME-2600i */ +static int me2600_xilinx_download(comedi_device *dev, + unsigned char *me2600_firmware, + unsigned int length) +{ + unsigned int value; + unsigned int file_length; + unsigned int i; + + /* disable irq's on PLX */ + writel(0x00, dev_private->plx_regbase + PLX_INTCSR); + + /* First, make a dummy read to reset xilinx */ + value = readw(dev_private->me_regbase + XILINX_DOWNLOAD_RESET); + + /* Wait until reset is over */ + sleep(1); + + /* Write a dummy value to Xilinx */ + writeb(0x00, dev_private->me_regbase + 0x0); + sleep(1); + + /* + * Format of the firmware + * Build longs from the byte-wise coded header + * Byte 1-3: length of the array + * Byte 4-7: version + * Byte 8-11: date + * Byte 12-15: reserved + */ + if (length < 16) + return -EINVAL; + file_length = (((unsigned int)me2600_firmware[0] & 0xff) << 24) + + (((unsigned int)me2600_firmware[1] & 0xff) << 16) + + (((unsigned int)me2600_firmware[2] & 0xff) << 8) + + ((unsigned int)me2600_firmware[3] & 0xff); + + /* + * Loop for writing firmware byte by byte to xilinx + * Firmware data start at offfset 16 + */ + for (i = 0; i < file_length; i++) + writeb((me2600_firmware[16 + i] & 0xff), + dev_private->me_regbase + 0x0); + + /* Write 5 dummy values to xilinx */ + for (i = 0; i < 5; i++) + writeb(0x00, dev_private->me_regbase + 0x0); + + /* Test if there was an error during download -> INTB was thrown */ + value = readl(dev_private->plx_regbase + PLX_INTCSR); + if (value & 0x20) { + /* Disable interrupt */ + writel(0x00, dev_private->plx_regbase + PLX_INTCSR); + printk(KERN_ERR "comedi%d: Xilinx download failed\n", + dev->minor); + return -EIO; + } + + /* Wait until the Xilinx is ready for real work */ + sleep(1); + + /* Enable PLX-Interrupts */ + writel(0x43, dev_private->plx_regbase + PLX_INTCSR); + + return 0; +} + +/* Reset device */ +static int me_reset(comedi_device *dev) +{ + /* Reset board */ + writew(0x00, dev_private->me_regbase + ME_CONTROL_1); + writew(0x00, dev_private->me_regbase + ME_CONTROL_2); + writew(0x00, dev_private->me_regbase + ME_RESET_INTERRUPT); + writew(0x00, dev_private->me_regbase + ME_DAC_CONTROL); + + /* Save values in the board context */ + dev_private->dac_control = 0; + dev_private->control_1 = 0; + dev_private->control_2 = 0; + + return 0; +} + +/* + * Attach + * + * - Register PCI device + * - Declare device driver capability + */ +static int me_attach(comedi_device *dev, comedi_devconfig *it) +{ + struct pci_dev *pci_device; + comedi_subdevice *subdevice; + struct me_board *board; + resource_size_t plx_regbase_tmp; + unsigned long plx_regbase_size_tmp; + resource_size_t me_regbase_tmp; + unsigned long me_regbase_size_tmp; + resource_size_t swap_regbase_tmp; + unsigned long swap_regbase_size_tmp; + resource_size_t regbase_tmp; + int result, error, i; + + /* Allocate private memory */ + if (alloc_private(dev, sizeof(struct me_private_data)) < 0) + return -ENOMEM; + + /* Probe the device to determine what device in the series it is. */ + for (pci_device = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, NULL); + pci_device != NULL; + pci_device = + pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pci_device)) { + if (pci_device->vendor == PCI_VENDOR_ID_MEILHAUS) { + for (i = 0; i < me_board_nbr; i++) { + if (me_boards[i].device_id == + pci_device->device) { + /* + * was a particular bus/slot requested? + */ + if ((it->options[0] != 0) + || (it->options[1] != 0)) { + /* + * are we on the wrong bus/slot? + */ + if (pci_device->bus->number != + it->options[0] + || PCI_SLOT(pci_device-> + devfn) != + it->options[1]) { + continue; + } + } + + dev->board_ptr = me_boards + i; + board = (struct me_board *) dev-> + board_ptr; + dev_private->pci_device = pci_device; + goto found; + } + } + } + } + + printk(KERN_ERR + "comedi%d: no supported board found! (req. bus/slot : %d/%d)\n", + dev->minor, it->options[0], it->options[1]); + return -EIO; + +found: + printk(KERN_INFO "comedi%d: found %s at PCI bus %d, slot %d\n", + dev->minor, me_boards[i].name, + pci_device->bus->number, PCI_SLOT(pci_device->devfn)); + + /* Enable PCI device and request PCI regions */ + if (comedi_pci_enable(pci_device, ME_DRIVER_NAME) < 0) { + printk(KERN_ERR "comedi%d: Failed to enable PCI device and " + "request regions\n", dev->minor); + return -EIO; + } + + /* Set data in device structure */ + dev->board_name = board->name; + + /* Read PLX register base address [PCI_BASE_ADDRESS #0]. */ + plx_regbase_tmp = pci_resource_start(pci_device, 0); + plx_regbase_size_tmp = pci_resource_len(pci_device, 0); + dev_private->plx_regbase = + ioremap(plx_regbase_tmp, plx_regbase_size_tmp); + dev_private->plx_regbase_size = plx_regbase_size_tmp; + if (!dev_private->plx_regbase) { + printk("comedi%d: Failed to remap I/O memory\n", dev->minor); + return -ENOMEM; + } + + /* Read Swap base address [PCI_BASE_ADDRESS #5]. */ + + swap_regbase_tmp = pci_resource_start(pci_device, 5); + swap_regbase_size_tmp = pci_resource_len(pci_device, 5); + + if (!swap_regbase_tmp) + printk(KERN_ERR "comedi%d: Swap not present\n", dev->minor); + + /*---------------------------------------------- Workaround start ---*/ + if (plx_regbase_tmp & 0x0080) { + printk(KERN_ERR "comedi%d: PLX-Bug detected\n", dev->minor); + + if (swap_regbase_tmp) { + regbase_tmp = plx_regbase_tmp; + plx_regbase_tmp = swap_regbase_tmp; + swap_regbase_tmp = regbase_tmp; + + result = pci_write_config_dword(pci_device, + PCI_BASE_ADDRESS_0, plx_regbase_tmp); + if (result != PCIBIOS_SUCCESSFUL) + return -EIO; + + result = pci_write_config_dword(pci_device, + PCI_BASE_ADDRESS_5, swap_regbase_tmp); + if (result != PCIBIOS_SUCCESSFUL) + return -EIO; + } else { + plx_regbase_tmp -= 0x80; + result = pci_write_config_dword(pci_device, + PCI_BASE_ADDRESS_0, plx_regbase_tmp); + if (result != PCIBIOS_SUCCESSFUL) + return -EIO; + } + } + /*--------------------------------------------- Workaround end -----*/ + + /* Read Meilhaus register base address [PCI_BASE_ADDRESS #2]. */ + + me_regbase_tmp = pci_resource_start(pci_device, 2); + me_regbase_size_tmp = pci_resource_len(pci_device, 2); + dev_private->me_regbase_size = me_regbase_size_tmp; + dev_private->me_regbase = ioremap(me_regbase_tmp, me_regbase_size_tmp); + if (!dev_private->me_regbase) { + printk(KERN_ERR "comedi%d: Failed to remap I/O memory\n", + dev->minor); + return -ENOMEM; + } + /* Download firmware and reset card */ + if (board->device_id == ME2600_DEVICE_ID) { + unsigned char *aux_data; + int aux_len; + + aux_data = comedi_aux_data(it->options, 0); + aux_len = it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]; + + if (!aux_data || aux_len < 1) { + comedi_error(dev, "You must provide me2600 firmware " + "using the --init-data option of " + "comedi_config"); + return -EINVAL; + } + me2600_xilinx_download(dev, aux_data, aux_len); + } + + me_reset(dev); + + /* device driver capabilities */ + error = alloc_subdevices(dev, 3); + if (error < 0) + return error; + + subdevice = dev->subdevices + 0; + subdevice->type = COMEDI_SUBD_AI; + subdevice->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_CMD_READ; + subdevice->n_chan = board->ai_channel_nbr; + subdevice->maxdata = board->ai_resolution_mask; + subdevice->len_chanlist = board->ai_channel_nbr; + subdevice->range_table = board->ai_range_list; + subdevice->cancel = me_ai_cancel; + subdevice->insn_read = me_ai_insn_read; + subdevice->do_cmdtest = me_ai_do_cmd_test; + subdevice->do_cmd = me_ai_do_cmd; + + subdevice = dev->subdevices + 1; + subdevice->type = COMEDI_SUBD_AO; + subdevice->subdev_flags = SDF_WRITEABLE | SDF_COMMON; + subdevice->n_chan = board->ao_channel_nbr; + subdevice->maxdata = board->ao_resolution_mask; + subdevice->len_chanlist = board->ao_channel_nbr; + subdevice->range_table = board->ao_range_list; + subdevice->insn_read = me_ao_insn_read; + subdevice->insn_write = me_ao_insn_write; + + subdevice = dev->subdevices + 2; + subdevice->type = COMEDI_SUBD_DIO; + subdevice->subdev_flags = SDF_READABLE | SDF_WRITEABLE; + subdevice->n_chan = board->dio_channel_nbr; + subdevice->maxdata = 1; + subdevice->len_chanlist = board->dio_channel_nbr; + subdevice->range_table = &range_digital; + subdevice->insn_bits = me_dio_insn_bits; + subdevice->insn_config = me_dio_insn_config; + subdevice->io_bits = 0; + + printk(KERN_INFO "comedi%d: "ME_DRIVER_NAME" attached.\n", dev->minor); + return 0; +} + +/* Detach */ +static int me_detach(comedi_device *dev) +{ + if (dev_private) { + if (dev_private->me_regbase) { + me_reset(dev); + iounmap(dev_private->me_regbase); + } + if (dev_private->plx_regbase) + iounmap(dev_private->plx_regbase); + if (dev_private->pci_device) { + if (dev_private->plx_regbase_size) + comedi_pci_disable(dev_private->pci_device); + + pci_dev_put(dev_private->pci_device); + } + } + return 0; +} diff --git a/drivers/staging/comedi/drivers/mite.c b/drivers/staging/comedi/drivers/mite.c new file mode 100644 index 00000000000..9cc527424d0 --- /dev/null +++ b/drivers/staging/comedi/drivers/mite.c @@ -0,0 +1,809 @@ +/* + comedi/drivers/mite.c + Hardware driver for NI Mite PCI interface chip + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2002 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. + +*/ + +/* + The PCI-MIO E series driver was originally written by + Tomasz Motylewski <...>, and ported to comedi by ds. + + References for specifications: + + 321747b.pdf Register Level Programmer Manual (obsolete) + 321747c.pdf Register Level Programmer Manual (new) + DAQ-STC reference manual + + Other possibly relevant info: + + 320517c.pdf User manual (obsolete) + 320517f.pdf User manual (new) + 320889a.pdf delete + 320906c.pdf maximum signal ratings + 321066a.pdf about 16x + 321791a.pdf discontinuation of at-mio-16e-10 rev. c + 321808a.pdf about at-mio-16e-10 rev P + 321837a.pdf discontinuation of at-mio-16de-10 rev d + 321838a.pdf about at-mio-16de-10 rev N + + ISSUES: + +*/ + +//#define USE_KMALLOC + +#include "mite.h" + +#include "comedi_fc.h" +#include "comedi_pci.h" +#include "../comedidev.h" + +#include <asm/system.h> + +#define PCI_MITE_SIZE 4096 +#define PCI_DAQ_SIZE 4096 +#define PCI_DAQ_SIZE_660X 8192 + +MODULE_LICENSE("GPL"); + +struct mite_struct *mite_devices = NULL; + +#define TOP_OF_PAGE(x) ((x)|(~(PAGE_MASK))) + +void mite_init(void) +{ + struct pci_dev *pcidev; + struct mite_struct *mite; + + 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)) { + if (pcidev->vendor == PCI_VENDOR_ID_NATINST) { + unsigned i; + + mite = kzalloc(sizeof(*mite), GFP_KERNEL); + if (!mite) { + printk("mite: allocation failed\n"); + pci_dev_put(pcidev); + return; + } + spin_lock_init(&mite->lock); + mite->pcidev = pci_dev_get(pcidev); + for (i = 0; i < MAX_MITE_DMA_CHANNELS; ++i) { + mite->channels[i].mite = mite; + mite->channels[i].channel = i; + mite->channels[i].done = 1; + } + mite->next = mite_devices; + mite_devices = mite; + } + } +} + +static void dump_chip_signature(u32 csigr_bits) +{ + printk("mite: version = %i, type = %i, mite mode = %i, interface mode = %i\n", mite_csigr_version(csigr_bits), mite_csigr_type(csigr_bits), mite_csigr_mmode(csigr_bits), mite_csigr_imode(csigr_bits)); + printk("mite: num channels = %i, write post fifo depth = %i, wins = %i, iowins = %i\n", mite_csigr_dmac(csigr_bits), mite_csigr_wpdep(csigr_bits), mite_csigr_wins(csigr_bits), mite_csigr_iowins(csigr_bits)); +} + +unsigned mite_fifo_size(struct mite_struct * mite, unsigned channel) +{ + unsigned fcr_bits = readl(mite->mite_io_addr + + MITE_FCR(channel)); + unsigned empty_count = (fcr_bits >> 16) & 0xff; + unsigned full_count = fcr_bits & 0xff; + return empty_count + full_count; +} + +int mite_setup2(struct mite_struct *mite, unsigned use_iodwbsr_1) +{ + unsigned long length; + resource_size_t addr; + int i; + u32 csigr_bits; + unsigned unknown_dma_burst_bits; + + if (comedi_pci_enable(mite->pcidev, "mite")) { + printk("error enabling mite and requesting io regions\n"); + return -EIO; + } + pci_set_master(mite->pcidev); + + addr = pci_resource_start(mite->pcidev, 0); + mite->mite_phys_addr = addr; + mite->mite_io_addr = ioremap(addr, PCI_MITE_SIZE); + if (!mite->mite_io_addr) { + printk("failed to remap mite io memory address\n"); + return -ENOMEM; + } + printk("MITE:0x%08llx mapped to %p ", + (unsigned long long)mite->mite_phys_addr, mite->mite_io_addr); + + addr = pci_resource_start(mite->pcidev, 1); + mite->daq_phys_addr = addr; + length = pci_resource_len(mite->pcidev, 1); + // In case of a 660x board, DAQ size is 8k instead of 4k (see as shown by lspci output) + mite->daq_io_addr = ioremap(mite->daq_phys_addr, length); + if (!mite->daq_io_addr) { + printk("failed to remap daq io memory address\n"); + return -ENOMEM; + } + printk("DAQ:0x%08llx mapped to %p\n", + (unsigned long long)mite->daq_phys_addr, mite->daq_io_addr); + + if (use_iodwbsr_1) { + writel(0, mite->mite_io_addr + MITE_IODWBSR); + printk("mite: using I/O Window Base Size register 1\n"); + writel(mite-> + daq_phys_addr | WENAB | + MITE_IODWBSR_1_WSIZE_bits(length), + mite->mite_io_addr + MITE_IODWBSR_1); + writel(0, mite->mite_io_addr + MITE_IODWCR_1); + } else { + writel(mite->daq_phys_addr | WENAB, + mite->mite_io_addr + MITE_IODWBSR); + } + /* make sure dma bursts work. I got this from running a bus analyzer + on a pxi-6281 and a pxi-6713. 6713 powered up with register value + of 0x61f and bursts worked. 6281 powered up with register value of + 0x1f and bursts didn't work. The NI windows driver reads the register, + then does a bitwise-or of 0x600 with it and writes it back. + */ + unknown_dma_burst_bits = + readl(mite->mite_io_addr + MITE_UNKNOWN_DMA_BURST_REG); + unknown_dma_burst_bits |= UNKNOWN_DMA_BURST_ENABLE_BITS; + writel(unknown_dma_burst_bits, + mite->mite_io_addr + MITE_UNKNOWN_DMA_BURST_REG); + + csigr_bits = readl(mite->mite_io_addr + MITE_CSIGR); + mite->num_channels = mite_csigr_dmac(csigr_bits); + if (mite->num_channels > MAX_MITE_DMA_CHANNELS) { + printk("mite: bug? chip claims to have %i dma channels. Setting to %i.\n", mite->num_channels, MAX_MITE_DMA_CHANNELS); + mite->num_channels = MAX_MITE_DMA_CHANNELS; + } + dump_chip_signature(csigr_bits); + for (i = 0; i < mite->num_channels; i++) { + writel(CHOR_DMARESET, mite->mite_io_addr + MITE_CHOR(i)); + /* disable interrupts */ + writel(CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | CHCR_CLR_SAR_IE | + CHCR_CLR_DONE_IE | CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE | + CHCR_CLR_LC_IE | CHCR_CLR_CONT_RB_IE, + mite->mite_io_addr + MITE_CHCR(i)); + } + mite->fifo_size = mite_fifo_size(mite, 0); + printk("mite: fifo size is %i.\n", mite->fifo_size); + mite->used = 1; + + return 0; +} + +int mite_setup(struct mite_struct *mite) +{ + return mite_setup2(mite, 0); +} + +void mite_cleanup(void) +{ + struct mite_struct *mite, *next; + + for (mite = mite_devices; mite; mite = next) { + pci_dev_put(mite->pcidev); + next = mite->next; + kfree(mite); + } +} + +void mite_unsetup(struct mite_struct *mite) +{ + //unsigned long offset, start, length; + + if (!mite) + return; + + if (mite->mite_io_addr) { + iounmap(mite->mite_io_addr); + mite->mite_io_addr = NULL; + } + if (mite->daq_io_addr) { + iounmap(mite->daq_io_addr); + mite->daq_io_addr = NULL; + } + if (mite->mite_phys_addr) { + comedi_pci_disable(mite->pcidev); + mite->mite_phys_addr = 0; + } + + mite->used = 0; +} + +void mite_list_devices(void) +{ + struct mite_struct *mite, *next; + + printk("Available NI device IDs:"); + if (mite_devices) + for (mite = mite_devices; mite; mite = next) { + next = mite->next; + printk(" 0x%04x", mite_device_id(mite)); + if (mite->used) + printk("(used)"); + } + printk("\n"); + +} + +struct mite_channel *mite_request_channel_in_range(struct mite_struct *mite, + struct mite_dma_descriptor_ring *ring, unsigned min_channel, + unsigned max_channel) +{ + int i; + unsigned long flags; + struct mite_channel *channel = NULL; + + // spin lock so mite_release_channel can be called safely from interrupts + comedi_spin_lock_irqsave(&mite->lock, flags); + for (i = min_channel; i <= max_channel; ++i) { + if (mite->channel_allocated[i] == 0) { + mite->channel_allocated[i] = 1; + channel = &mite->channels[i]; + channel->ring = ring; + break; + } + } + comedi_spin_unlock_irqrestore(&mite->lock, flags); + return channel; +} + +void mite_release_channel(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + unsigned long flags; + + // spin lock to prevent races with mite_request_channel + comedi_spin_lock_irqsave(&mite->lock, flags); + if (mite->channel_allocated[mite_chan->channel]) { + mite_dma_disarm(mite_chan); + mite_dma_reset(mite_chan); +/* disable all channel's interrupts (do it after disarm/reset so +MITE_CHCR reg isn't changed while dma is still active!) */ + writel(CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | + CHCR_CLR_SAR_IE | CHCR_CLR_DONE_IE | + CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE | + CHCR_CLR_LC_IE | CHCR_CLR_CONT_RB_IE, + mite->mite_io_addr + MITE_CHCR(mite_chan->channel)); + mite->channel_allocated[mite_chan->channel] = 0; + mite_chan->ring = NULL; + mmiowb(); + } + comedi_spin_unlock_irqrestore(&mite->lock, flags); +} + +void mite_dma_arm(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + int chor; + unsigned long flags; + + MDPRINTK("mite_dma_arm ch%i\n", channel); + /* memory barrier is intended to insure any twiddling with the buffer + is done before writing to the mite to arm dma transfer */ + smp_mb(); + /* arm */ + chor = CHOR_START; + comedi_spin_lock_irqsave(&mite->lock, flags); + mite_chan->done = 0; + writel(chor, mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); + mmiowb(); + comedi_spin_unlock_irqrestore(&mite->lock, flags); +// mite_dma_tcr(mite, channel); +} + +/**************************************/ + +int mite_buf_change(struct mite_dma_descriptor_ring *ring, comedi_async * async) +{ + unsigned int n_links; + int i; + + if (ring->descriptors) { + dma_free_coherent(ring->hw_dev, + ring->n_links * sizeof(struct mite_dma_descriptor), + ring->descriptors, ring->descriptors_dma_addr); + } + ring->descriptors = NULL; + ring->descriptors_dma_addr = 0; + ring->n_links = 0; + + if (async->prealloc_bufsz == 0) { + return 0; + } + n_links = async->prealloc_bufsz >> PAGE_SHIFT; + + MDPRINTK("ring->hw_dev=%p, n_links=0x%04x\n", ring->hw_dev, n_links); + + ring->descriptors = + dma_alloc_coherent(ring->hw_dev, + n_links * sizeof(struct mite_dma_descriptor), + &ring->descriptors_dma_addr, GFP_KERNEL); + if (!ring->descriptors) { + printk("mite: ring buffer allocation failed\n"); + return -ENOMEM; + } + ring->n_links = n_links; + + for (i = 0; i < n_links; i++) { + ring->descriptors[i].count = cpu_to_le32(PAGE_SIZE); + ring->descriptors[i].addr = + cpu_to_le32(async->buf_page_list[i].dma_addr); + ring->descriptors[i].next = + cpu_to_le32(ring->descriptors_dma_addr + (i + + 1) * sizeof(struct mite_dma_descriptor)); + } + ring->descriptors[n_links - 1].next = + cpu_to_le32(ring->descriptors_dma_addr); + /* barrier is meant to insure that all the writes to the dma descriptors + have completed before the dma controller is commanded to read them */ + smp_wmb(); + return 0; +} + +void mite_prep_dma(struct mite_channel *mite_chan, + unsigned int num_device_bits, unsigned int num_memory_bits) +{ + unsigned int chor, chcr, mcr, dcr, lkcr; + struct mite_struct *mite = mite_chan->mite; + + MDPRINTK("mite_prep_dma ch%i\n", mite_chan->channel); + + /* reset DMA and FIFO */ + chor = CHOR_DMARESET | CHOR_FRESET; + writel(chor, mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); + + /* short link chaining mode */ + chcr = CHCR_SET_DMA_IE | CHCR_LINKSHORT | CHCR_SET_DONE_IE | + CHCR_BURSTEN; + /* + * Link Complete Interrupt: interrupt every time a link + * in MITE_RING is completed. This can generate a lot of + * extra interrupts, but right now we update the values + * of buf_int_ptr and buf_int_count at each interrupt. A + * better method is to poll the MITE before each user + * "read()" to calculate the number of bytes available. + */ + chcr |= CHCR_SET_LC_IE; + if (num_memory_bits == 32 && num_device_bits == 16) { + /* Doing a combined 32 and 16 bit byteswap gets the 16 bit samples into the fifo in the right order. + Tested doing 32 bit memory to 16 bit device transfers to the analog out of a pxi-6281, + which has mite version = 1, type = 4. This also works for dma reads from the counters + on e-series boards. */ + chcr |= CHCR_BYTE_SWAP_DEVICE | CHCR_BYTE_SWAP_MEMORY; + } + if (mite_chan->dir == COMEDI_INPUT) { + chcr |= CHCR_DEV_TO_MEM; + } + writel(chcr, mite->mite_io_addr + MITE_CHCR(mite_chan->channel)); + + /* to/from memory */ + mcr = CR_RL(64) | CR_ASEQUP; + switch (num_memory_bits) { + case 8: + mcr |= CR_PSIZE8; + break; + case 16: + mcr |= CR_PSIZE16; + break; + case 32: + mcr |= CR_PSIZE32; + break; + default: + rt_printk + ("mite: bug! invalid mem bit width for dma transfer\n"); + break; + } + writel(mcr, mite->mite_io_addr + MITE_MCR(mite_chan->channel)); + + /* from/to device */ + dcr = CR_RL(64) | CR_ASEQUP; + dcr |= CR_PORTIO | CR_AMDEVICE | CR_REQSDRQ(mite_chan->channel); + switch (num_device_bits) { + case 8: + dcr |= CR_PSIZE8; + break; + case 16: + dcr |= CR_PSIZE16; + break; + case 32: + dcr |= CR_PSIZE32; + break; + default: + rt_printk + ("mite: bug! invalid dev bit width for dma transfer\n"); + break; + } + writel(dcr, mite->mite_io_addr + MITE_DCR(mite_chan->channel)); + + /* reset the DAR */ + writel(0, mite->mite_io_addr + MITE_DAR(mite_chan->channel)); + + /* the link is 32bits */ + lkcr = CR_RL(64) | CR_ASEQUP | CR_PSIZE32; + writel(lkcr, mite->mite_io_addr + MITE_LKCR(mite_chan->channel)); + + /* starting address for link chaining */ + writel(mite_chan->ring->descriptors_dma_addr, + mite->mite_io_addr + MITE_LKAR(mite_chan->channel)); + + MDPRINTK("exit mite_prep_dma\n"); +} + +u32 mite_device_bytes_transferred(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + return readl(mite->mite_io_addr + MITE_DAR(mite_chan->channel)); +} + +u32 mite_bytes_in_transit(struct mite_channel * mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + return readl(mite->mite_io_addr + + MITE_FCR(mite_chan->channel)) & 0x000000FF; +} + +// returns lower bound for number of bytes transferred from device to memory +u32 mite_bytes_written_to_memory_lb(struct mite_channel * mite_chan) +{ + u32 device_byte_count; + + device_byte_count = mite_device_bytes_transferred(mite_chan); + return device_byte_count - mite_bytes_in_transit(mite_chan); +} + +// returns upper bound for number of bytes transferred from device to memory +u32 mite_bytes_written_to_memory_ub(struct mite_channel * mite_chan) +{ + u32 in_transit_count; + + in_transit_count = mite_bytes_in_transit(mite_chan); + return mite_device_bytes_transferred(mite_chan) - in_transit_count; +} + +// returns lower bound for number of bytes read from memory for transfer to device +u32 mite_bytes_read_from_memory_lb(struct mite_channel * mite_chan) +{ + u32 device_byte_count; + + device_byte_count = mite_device_bytes_transferred(mite_chan); + return device_byte_count + mite_bytes_in_transit(mite_chan); +} + +// returns upper bound for number of bytes read from memory for transfer to device +u32 mite_bytes_read_from_memory_ub(struct mite_channel * mite_chan) +{ + u32 in_transit_count; + + in_transit_count = mite_bytes_in_transit(mite_chan); + return mite_device_bytes_transferred(mite_chan) + in_transit_count; +} + +unsigned mite_dma_tcr(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + int tcr; + int lkar; + + lkar = readl(mite->mite_io_addr + MITE_LKAR(mite_chan->channel)); + tcr = readl(mite->mite_io_addr + MITE_TCR(mite_chan->channel)); + MDPRINTK("mite_dma_tcr ch%i, lkar=0x%08x tcr=%d\n", mite_chan->channel, + lkar, tcr); + + return tcr; +} + +void mite_dma_disarm(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + unsigned chor; + + /* disarm */ + chor = CHOR_ABORT; + writel(chor, mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); +} + +int mite_sync_input_dma(struct mite_channel *mite_chan, comedi_async * async) +{ + int count; + unsigned int nbytes, old_alloc_count; + const unsigned bytes_per_scan = cfc_bytes_per_scan(async->subdevice); + + old_alloc_count = async->buf_write_alloc_count; + // write alloc as much as we can + comedi_buf_write_alloc(async, async->prealloc_bufsz); + + nbytes = mite_bytes_written_to_memory_lb(mite_chan); + if ((int)(mite_bytes_written_to_memory_ub(mite_chan) - + old_alloc_count) > 0) { + rt_printk("mite: DMA overwrite of free area\n"); + async->events |= COMEDI_CB_OVERFLOW; + return -1; + } + + count = nbytes - async->buf_write_count; + /* it's possible count will be negative due to + * conservative value returned by mite_bytes_written_to_memory_lb */ + if (count <= 0) { + return 0; + } + comedi_buf_write_free(async, count); + + async->scan_progress += count; + if (async->scan_progress >= bytes_per_scan) { + async->scan_progress %= bytes_per_scan; + async->events |= COMEDI_CB_EOS; + } + async->events |= COMEDI_CB_BLOCK; + return 0; +} + +int mite_sync_output_dma(struct mite_channel *mite_chan, comedi_async * async) +{ + int count; + u32 nbytes_ub, nbytes_lb; + unsigned int old_alloc_count; + u32 stop_count = + async->cmd.stop_arg * cfc_bytes_per_scan(async->subdevice); + + old_alloc_count = async->buf_read_alloc_count; + // read alloc as much as we can + comedi_buf_read_alloc(async, async->prealloc_bufsz); + nbytes_lb = mite_bytes_read_from_memory_lb(mite_chan); + if (async->cmd.stop_src == TRIG_COUNT && + (int)(nbytes_lb - stop_count) > 0) + nbytes_lb = stop_count; + nbytes_ub = mite_bytes_read_from_memory_ub(mite_chan); + if (async->cmd.stop_src == TRIG_COUNT && + (int)(nbytes_ub - stop_count) > 0) + nbytes_ub = stop_count; + if ((int)(nbytes_ub - old_alloc_count) > 0) { + rt_printk("mite: DMA underrun\n"); + async->events |= COMEDI_CB_OVERFLOW; + return -1; + } + count = nbytes_lb - async->buf_read_count; + if (count <= 0) { + return 0; + } + if (count) { + comedi_buf_read_free(async, count); + async->events |= COMEDI_CB_BLOCK; + } + return 0; +} + +unsigned mite_get_status(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + unsigned status; + unsigned long flags; + + comedi_spin_lock_irqsave(&mite->lock, flags); + status = readl(mite->mite_io_addr + MITE_CHSR(mite_chan->channel)); + if (status & CHSR_DONE) { + mite_chan->done = 1; + writel(CHOR_CLRDONE, + mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); + } + mmiowb(); + comedi_spin_unlock_irqrestore(&mite->lock, flags); + return status; +} + +int mite_done(struct mite_channel *mite_chan) +{ + struct mite_struct *mite = mite_chan->mite; + unsigned long flags; + int done; + + mite_get_status(mite_chan); + comedi_spin_lock_irqsave(&mite->lock, flags); + done = mite_chan->done; + comedi_spin_unlock_irqrestore(&mite->lock, flags); + return done; +} + +#ifdef DEBUG_MITE + +static void mite_decode(char **bit_str, unsigned int bits); + +/* names of bits in mite registers */ + +static const char *const mite_CHOR_strings[] = { + "start", "cont", "stop", "abort", + "freset", "clrlc", "clrrb", "clrdone", + "clr_lpause", "set_lpause", "clr_send_tc", + "set_send_tc", "12", "13", "14", + "15", "16", "17", "18", + "19", "20", "21", "22", + "23", "24", "25", "26", + "27", "28", "29", "30", + "dmareset", +}; + +static const char *const mite_CHCR_strings[] = { + "continue", "ringbuff", "2", "3", + "4", "5", "6", "7", + "8", "9", "10", "11", + "12", "13", "bursten", "fifodis", + "clr_cont_rb_ie", "set_cont_rb_ie", "clr_lc_ie", "set_lc_ie", + "clr_drdy_ie", "set_drdy_ie", "clr_mrdy_ie", "set_mrdy_ie", + "clr_done_ie", "set_done_ie", "clr_sar_ie", "set_sar_ie", + "clr_linkp_ie", "set_linkp_ie", "clr_dma_ie", "set_dma_ie", +}; + +static const char *const mite_MCR_strings[] = { + "amdevice", "1", "2", "3", + "4", "5", "portio", "portvxi", + "psizebyte", "psizehalf (byte & half = word)", "aseqxp1", "11", + "12", "13", "blocken", "berhand", + "reqsintlim/reqs0", "reqs1", "reqs2", "rd32", + "rd512", "rl1", "rl2", "rl8", + "24", "25", "26", "27", + "28", "29", "30", "stopen", +}; + +static const char *const mite_DCR_strings[] = { + "amdevice", "1", "2", "3", + "4", "5", "portio", "portvxi", + "psizebyte", "psizehalf (byte & half = word)", "aseqxp1", "aseqxp2", + "aseqxp8", "13", "blocken", "berhand", + "reqsintlim", "reqs1", "reqs2", "rd32", + "rd512", "rl1", "rl2", "rl8", + "23", "24", "25", "27", + "28", "wsdevc", "wsdevs", "rwdevpack", +}; + +static const char *const mite_LKCR_strings[] = { + "amdevice", "1", "2", "3", + "4", "5", "portio", "portvxi", + "psizebyte", "psizehalf (byte & half = word)", "asequp", "aseqdown", + "12", "13", "14", "berhand", + "16", "17", "18", "rd32", + "rd512", "rl1", "rl2", "rl8", + "24", "25", "26", "27", + "28", "29", "30", "chngend", +}; + +static const char *const mite_CHSR_strings[] = { + "d.err0", "d.err1", "m.err0", "m.err1", + "l.err0", "l.err1", "drq0", "drq1", + "end", "xferr", "operr0", "operr1", + "stops", "habort", "sabort", "error", + "16", "conts_rb", "18", "linkc", + "20", "drdy", "22", "mrdy", + "24", "done", "26", "sars", + "28", "lpauses", "30", "int", +}; + +void mite_dump_regs(struct mite_channel *mite_chan) +{ + unsigned long mite_io_addr = + (unsigned long)mite_chan->mite->mite_io_addr; + unsigned long addr = 0; + unsigned long temp = 0; + + printk("mite_dump_regs ch%i\n", mite_chan->channel); + printk("mite address is =0x%08lx\n", mite_io_addr); + + addr = mite_io_addr + MITE_CHOR(channel); + printk("mite status[CHOR]at 0x%08lx =0x%08lx\n", addr, temp = + readl(addr)); + mite_decode(mite_CHOR_strings, temp); + addr = mite_io_addr + MITE_CHCR(channel); + printk("mite status[CHCR]at 0x%08lx =0x%08lx\n", addr, temp = + readl(addr)); + mite_decode(mite_CHCR_strings, temp); + addr = mite_io_addr + MITE_TCR(channel); + printk("mite status[TCR] at 0x%08lx =0x%08x\n", addr, readl(addr)); + addr = mite_io_addr + MITE_MCR(channel); + printk("mite status[MCR] at 0x%08lx =0x%08lx\n", addr, temp = + readl(addr)); + mite_decode(mite_MCR_strings, temp); + + addr = mite_io_addr + MITE_MAR(channel); + printk("mite status[MAR] at 0x%08lx =0x%08x\n", addr, readl(addr)); + addr = mite_io_addr + MITE_DCR(channel); + printk("mite status[DCR] at 0x%08lx =0x%08lx\n", addr, temp = + readl(addr)); + mite_decode(mite_DCR_strings, temp); + addr = mite_io_addr + MITE_DAR(channel); + printk("mite status[DAR] at 0x%08lx =0x%08x\n", addr, readl(addr)); + addr = mite_io_addr + MITE_LKCR(channel); + printk("mite status[LKCR]at 0x%08lx =0x%08lx\n", addr, temp = + readl(addr)); + mite_decode(mite_LKCR_strings, temp); + addr = mite_io_addr + MITE_LKAR(channel); + printk("mite status[LKAR]at 0x%08lx =0x%08x\n", addr, readl(addr)); + + addr = mite_io_addr + MITE_CHSR(channel); + printk("mite status[CHSR]at 0x%08lx =0x%08lx\n", addr, temp = + readl(addr)); + mite_decode(mite_CHSR_strings, temp); + addr = mite_io_addr + MITE_FCR(channel); + printk("mite status[FCR] at 0x%08lx =0x%08x\n\n", addr, readl(addr)); +} + +static void mite_decode(char **bit_str, unsigned int bits) +{ + int i; + + for (i = 31; i >= 0; i--) { + if (bits & (1 << i)) { + printk(" %s", bit_str[i]); + } + } + printk("\n"); +} +#endif + +#ifdef MODULE +int __init init_module(void) +{ + mite_init(); + mite_list_devices(); + + return 0; +} + +void __exit cleanup_module(void) +{ + mite_cleanup(); +} + +EXPORT_SYMBOL(mite_dma_tcr); +EXPORT_SYMBOL(mite_dma_arm); +EXPORT_SYMBOL(mite_dma_disarm); +EXPORT_SYMBOL(mite_sync_input_dma); +EXPORT_SYMBOL(mite_sync_output_dma); +EXPORT_SYMBOL(mite_setup); +EXPORT_SYMBOL(mite_setup2); +EXPORT_SYMBOL(mite_unsetup); +#if 0 +EXPORT_SYMBOL(mite_kvmem_segment_load); +EXPORT_SYMBOL(mite_ll_from_kvmem); +EXPORT_SYMBOL(mite_setregs); +#endif +EXPORT_SYMBOL(mite_devices); +EXPORT_SYMBOL(mite_list_devices); +EXPORT_SYMBOL(mite_request_channel_in_range); +EXPORT_SYMBOL(mite_release_channel); +EXPORT_SYMBOL(mite_prep_dma); +EXPORT_SYMBOL(mite_buf_change); +EXPORT_SYMBOL(mite_bytes_written_to_memory_lb); +EXPORT_SYMBOL(mite_bytes_written_to_memory_ub); +EXPORT_SYMBOL(mite_bytes_read_from_memory_lb); +EXPORT_SYMBOL(mite_bytes_read_from_memory_ub); +EXPORT_SYMBOL(mite_bytes_in_transit); +EXPORT_SYMBOL(mite_get_status); +EXPORT_SYMBOL(mite_done); +#ifdef DEBUG_MITE +EXPORT_SYMBOL(mite_decode); +EXPORT_SYMBOL(mite_dump_regs); +#endif + +#endif diff --git a/drivers/staging/comedi/drivers/mite.h b/drivers/staging/comedi/drivers/mite.h new file mode 100644 index 00000000000..b84eafa6ff2 --- /dev/null +++ b/drivers/staging/comedi/drivers/mite.h @@ -0,0 +1,453 @@ +/* + module/mite.h + Hardware driver for NI Mite PCI interface chip + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1999 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. + +*/ + +#ifndef _MITE_H_ +#define _MITE_H_ + +#include <linux/pci.h> +#include "../comedidev.h" + +#define PCI_VENDOR_ID_NATINST 0x1093 + +// #define DEBUG_MITE +#define PCIMIO_COMPAT + +#ifdef DEBUG_MITE +#define MDPRINTK(format,args...) printk(format , ## args ) +#else +#define MDPRINTK(format,args...) +#endif + +#define MAX_MITE_DMA_CHANNELS 8 + +struct mite_dma_descriptor { + u32 count; + u32 addr; + u32 next; + u32 dar; +}; + +struct mite_dma_descriptor_ring { + struct device *hw_dev; + unsigned int n_links; + struct mite_dma_descriptor *descriptors; + dma_addr_t descriptors_dma_addr; +}; + +struct mite_channel { + struct mite_struct *mite; + unsigned channel; + int dir; + int done; + struct mite_dma_descriptor_ring *ring; +}; + +struct mite_struct { + struct mite_struct *next; + int used; + + struct pci_dev *pcidev; + resource_size_t mite_phys_addr; + void *mite_io_addr; + resource_size_t daq_phys_addr; + void *daq_io_addr; + + struct mite_channel channels[MAX_MITE_DMA_CHANNELS]; + short channel_allocated[MAX_MITE_DMA_CHANNELS]; + int num_channels; + unsigned fifo_size; + spinlock_t lock; +}; + +static inline struct mite_dma_descriptor_ring *mite_alloc_ring(struct + mite_struct *mite) +{ + struct mite_dma_descriptor_ring *ring = + kmalloc(sizeof(struct mite_dma_descriptor_ring), GFP_KERNEL); + if (ring == NULL) + return ring; + ring->hw_dev = get_device(&mite->pcidev->dev); + if (ring->hw_dev == NULL) { + kfree(ring); + return NULL; + } + ring->n_links = 0; + ring->descriptors = NULL; + ring->descriptors_dma_addr = 0; + return ring; +}; + +static inline void mite_free_ring(struct mite_dma_descriptor_ring *ring) +{ + if (ring) { + if (ring->descriptors) { + dma_free_coherent(ring->hw_dev, + ring->n_links * + sizeof(struct mite_dma_descriptor), + ring->descriptors, ring->descriptors_dma_addr); + } + put_device(ring->hw_dev); + kfree(ring); + } +}; + +extern struct mite_struct *mite_devices; + +static inline unsigned int mite_irq(struct mite_struct *mite) +{ + return mite->pcidev->irq; +}; +static inline unsigned int mite_device_id(struct mite_struct *mite) +{ + return mite->pcidev->device; +}; + +void mite_init(void); +void mite_cleanup(void); +int mite_setup(struct mite_struct *mite); +int mite_setup2(struct mite_struct *mite, unsigned use_iodwbsr_1); +void mite_unsetup(struct mite_struct *mite); +void mite_list_devices(void); +struct mite_channel *mite_request_channel_in_range(struct mite_struct *mite, + struct mite_dma_descriptor_ring *ring, unsigned min_channel, + unsigned max_channel); +static inline struct mite_channel *mite_request_channel(struct mite_struct + *mite, struct mite_dma_descriptor_ring *ring) +{ + return mite_request_channel_in_range(mite, ring, 0, + mite->num_channels - 1); +} +void mite_release_channel(struct mite_channel *mite_chan); + +unsigned mite_dma_tcr(struct mite_channel *mite_chan); +void mite_dma_arm(struct mite_channel *mite_chan); +void mite_dma_disarm(struct mite_channel *mite_chan); +int mite_sync_input_dma(struct mite_channel *mite_chan, comedi_async * async); +int mite_sync_output_dma(struct mite_channel *mite_chan, comedi_async * async); +u32 mite_bytes_written_to_memory_lb(struct mite_channel *mite_chan); +u32 mite_bytes_written_to_memory_ub(struct mite_channel *mite_chan); +u32 mite_bytes_read_from_memory_lb(struct mite_channel *mite_chan); +u32 mite_bytes_read_from_memory_ub(struct mite_channel *mite_chan); +u32 mite_bytes_in_transit(struct mite_channel *mite_chan); +unsigned mite_get_status(struct mite_channel *mite_chan); +int mite_done(struct mite_channel *mite_chan); + +#if 0 +unsigned long mite_ll_from_kvmem(struct mite_struct *mite, comedi_async * async, + int len); +void mite_setregs(struct mite_struct *mite, unsigned long ll_start, int chan, + int dir); +#endif + +void mite_prep_dma(struct mite_channel *mite_chan, + unsigned int num_device_bits, unsigned int num_memory_bits); +int mite_buf_change(struct mite_dma_descriptor_ring *ring, + comedi_async * async); + +#ifdef DEBUG_MITE +void mite_print_chsr(unsigned int chsr); +void mite_dump_regs(struct mite_channel *mite_chan); +#endif + +static inline int CHAN_OFFSET(int channel) +{ + return 0x500 + 0x100 * channel; +}; + +enum mite_registers { + /* The bits 0x90180700 in MITE_UNKNOWN_DMA_BURST_REG can be + written and read back. The bits 0x1f always read as 1. + The rest always read as zero. */ + MITE_UNKNOWN_DMA_BURST_REG = 0x28, + MITE_IODWBSR = 0xc0, //IO Device Window Base Size Register + MITE_IODWBSR_1 = 0xc4, // IO Device Window Base Size Register 1 + MITE_IODWCR_1 = 0xf4, + MITE_PCI_CONFIG_OFFSET = 0x300, + MITE_CSIGR = 0x460 //chip signature +}; +static inline int MITE_CHOR(int channel) // channel operation +{ + return CHAN_OFFSET(channel) + 0x0; +}; +static inline int MITE_CHCR(int channel) // channel control +{ + return CHAN_OFFSET(channel) + 0x4; +}; +static inline int MITE_TCR(int channel) // transfer count +{ + return CHAN_OFFSET(channel) + 0x8; +}; +static inline int MITE_MCR(int channel) // memory configuration +{ + return CHAN_OFFSET(channel) + 0xc; +}; +static inline int MITE_MAR(int channel) // memory address +{ + return CHAN_OFFSET(channel) + 0x10; +}; +static inline int MITE_DCR(int channel) // device configuration +{ + return CHAN_OFFSET(channel) + 0x14; +}; +static inline int MITE_DAR(int channel) // device address +{ + return CHAN_OFFSET(channel) + 0x18; +}; +static inline int MITE_LKCR(int channel) // link configuration +{ + return CHAN_OFFSET(channel) + 0x1c; +}; +static inline int MITE_LKAR(int channel) // link address +{ + return CHAN_OFFSET(channel) + 0x20; +}; +static inline int MITE_LLKAR(int channel) // see mite section of tnt5002 manual +{ + return CHAN_OFFSET(channel) + 0x24; +}; +static inline int MITE_BAR(int channel) // base address +{ + return CHAN_OFFSET(channel) + 0x28; +}; +static inline int MITE_BCR(int channel) // base count +{ + return CHAN_OFFSET(channel) + 0x2c; +}; +static inline int MITE_SAR(int channel) // ? address +{ + return CHAN_OFFSET(channel) + 0x30; +}; +static inline int MITE_WSCR(int channel) // ? +{ + return CHAN_OFFSET(channel) + 0x34; +}; +static inline int MITE_WSER(int channel) // ? +{ + return CHAN_OFFSET(channel) + 0x38; +}; +static inline int MITE_CHSR(int channel) // channel status +{ + return CHAN_OFFSET(channel) + 0x3c; +}; +static inline int MITE_FCR(int channel) // fifo count +{ + return CHAN_OFFSET(channel) + 0x40; +}; + +enum MITE_IODWBSR_bits { + WENAB = 0x80, // window enable +}; + +static inline unsigned MITE_IODWBSR_1_WSIZE_bits(unsigned size) +{ + unsigned order = 0; + while (size >>= 1) + ++order; + BUG_ON(order < 1); + return (order - 1) & 0x1f; +} + +enum MITE_UNKNOWN_DMA_BURST_bits { + UNKNOWN_DMA_BURST_ENABLE_BITS = 0x600 +}; + +static inline int mite_csigr_version(u32 csigr_bits) +{ + return csigr_bits & 0xf; +}; +static inline int mite_csigr_type(u32 csigr_bits) +{ // original mite = 0, minimite = 1 + return (csigr_bits >> 4) & 0xf; +}; +static inline int mite_csigr_mmode(u32 csigr_bits) +{ // mite mode, minimite = 1 + return (csigr_bits >> 8) & 0x3; +}; +static inline int mite_csigr_imode(u32 csigr_bits) +{ // cpu port interface mode, pci = 0x3 + return (csigr_bits >> 12) & 0x3; +}; +static inline int mite_csigr_dmac(u32 csigr_bits) +{ // number of dma channels + return (csigr_bits >> 16) & 0xf; +}; +static inline int mite_csigr_wpdep(u32 csigr_bits) +{ // write post fifo depth + unsigned int wpdep_bits = (csigr_bits >> 20) & 0x7; + if (wpdep_bits == 0) + return 0; + else + return 1 << (wpdep_bits - 1); +}; +static inline int mite_csigr_wins(u32 csigr_bits) +{ + return (csigr_bits >> 24) & 0x1f; +}; +static inline int mite_csigr_iowins(u32 csigr_bits) +{ // number of io windows + return (csigr_bits >> 29) & 0x7; +}; + +enum MITE_MCR_bits { + MCRPON = 0, +}; + +enum MITE_DCR_bits { + DCR_NORMAL = (1 << 29), + DCRPON = 0, +}; + +enum MITE_CHOR_bits { + CHOR_DMARESET = (1 << 31), + CHOR_SET_SEND_TC = (1 << 11), + CHOR_CLR_SEND_TC = (1 << 10), + CHOR_SET_LPAUSE = (1 << 9), + CHOR_CLR_LPAUSE = (1 << 8), + CHOR_CLRDONE = (1 << 7), + CHOR_CLRRB = (1 << 6), + CHOR_CLRLC = (1 << 5), + CHOR_FRESET = (1 << 4), + CHOR_ABORT = (1 << 3), /* stop without emptying fifo */ + CHOR_STOP = (1 << 2), /* stop after emptying fifo */ + CHOR_CONT = (1 << 1), + CHOR_START = (1 << 0), + CHOR_PON = (CHOR_CLR_SEND_TC | CHOR_CLR_LPAUSE), +}; + +enum MITE_CHCR_bits { + CHCR_SET_DMA_IE = (1 << 31), + CHCR_CLR_DMA_IE = (1 << 30), + CHCR_SET_LINKP_IE = (1 << 29), + CHCR_CLR_LINKP_IE = (1 << 28), + CHCR_SET_SAR_IE = (1 << 27), + CHCR_CLR_SAR_IE = (1 << 26), + CHCR_SET_DONE_IE = (1 << 25), + CHCR_CLR_DONE_IE = (1 << 24), + CHCR_SET_MRDY_IE = (1 << 23), + CHCR_CLR_MRDY_IE = (1 << 22), + CHCR_SET_DRDY_IE = (1 << 21), + CHCR_CLR_DRDY_IE = (1 << 20), + CHCR_SET_LC_IE = (1 << 19), + CHCR_CLR_LC_IE = (1 << 18), + CHCR_SET_CONT_RB_IE = (1 << 17), + CHCR_CLR_CONT_RB_IE = (1 << 16), + CHCR_FIFODIS = (1 << 15), + CHCR_FIFO_ON = 0, + CHCR_BURSTEN = (1 << 14), + CHCR_NO_BURSTEN = 0, + CHCR_BYTE_SWAP_DEVICE = (1 << 6), + CHCR_BYTE_SWAP_MEMORY = (1 << 4), + CHCR_DIR = (1 << 3), + CHCR_DEV_TO_MEM = CHCR_DIR, + CHCR_MEM_TO_DEV = 0, + CHCR_NORMAL = (0 << 0), + CHCR_CONTINUE = (1 << 0), + CHCR_RINGBUFF = (2 << 0), + CHCR_LINKSHORT = (4 << 0), + CHCR_LINKLONG = (5 << 0), + CHCRPON = + (CHCR_CLR_DMA_IE | CHCR_CLR_LINKP_IE | CHCR_CLR_SAR_IE | + CHCR_CLR_DONE_IE | CHCR_CLR_MRDY_IE | CHCR_CLR_DRDY_IE | + CHCR_CLR_LC_IE | CHCR_CLR_CONT_RB_IE), +}; + +enum ConfigRegister_bits { + CR_REQS_MASK = 0x7 << 16, + CR_ASEQDONT = 0x0 << 10, + CR_ASEQUP = 0x1 << 10, + CR_ASEQDOWN = 0x2 << 10, + CR_ASEQ_MASK = 0x3 << 10, + CR_PSIZE8 = (1 << 8), + CR_PSIZE16 = (2 << 8), + CR_PSIZE32 = (3 << 8), + CR_PORTCPU = (0 << 6), + CR_PORTIO = (1 << 6), + CR_PORTVXI = (2 << 6), + CR_PORTMXI = (3 << 6), + CR_AMDEVICE = (1 << 0), +}; +static inline int CR_REQS(int source) +{ + return (source & 0x7) << 16; +}; +static inline int CR_REQSDRQ(unsigned drq_line) +{ + /* This also works on m-series when + using channels (drq_line) 4 or 5. */ + return CR_REQS((drq_line & 0x3) | 0x4); +} +static inline int CR_RL(unsigned int retry_limit) +{ + int value = 0; + + while (retry_limit) { + retry_limit >>= 1; + value++; + } + if (value > 0x7) + rt_printk("comedi: bug! retry_limit too large\n"); + return (value & 0x7) << 21; +} + +enum CHSR_bits { + CHSR_INT = (1 << 31), + CHSR_LPAUSES = (1 << 29), + CHSR_SARS = (1 << 27), + CHSR_DONE = (1 << 25), + CHSR_MRDY = (1 << 23), + CHSR_DRDY = (1 << 21), + CHSR_LINKC = (1 << 19), + CHSR_CONTS_RB = (1 << 17), + CHSR_ERROR = (1 << 15), + CHSR_SABORT = (1 << 14), + CHSR_HABORT = (1 << 13), + CHSR_STOPS = (1 << 12), + CHSR_OPERR_mask = (3 << 10), + CHSR_OPERR_NOERROR = (0 << 10), + CHSR_OPERR_FIFOERROR = (1 << 10), + CHSR_OPERR_LINKERROR = (1 << 10), /* ??? */ + CHSR_XFERR = (1 << 9), + CHSR_END = (1 << 8), + CHSR_DRQ1 = (1 << 7), + CHSR_DRQ0 = (1 << 6), + CHSR_LxERR_mask = (3 << 4), + CHSR_LBERR = (1 << 4), + CHSR_LRERR = (2 << 4), + CHSR_LOERR = (3 << 4), + CHSR_MxERR_mask = (3 << 2), + CHSR_MBERR = (1 << 2), + CHSR_MRERR = (2 << 2), + CHSR_MOERR = (3 << 2), + CHSR_DxERR_mask = (3 << 0), + CHSR_DBERR = (1 << 0), + CHSR_DRERR = (2 << 0), + CHSR_DOERR = (3 << 0), +}; + +static inline void mite_dma_reset(struct mite_channel *mite_chan) +{ + writel(CHOR_DMARESET | CHOR_FRESET, + mite_chan->mite->mite_io_addr + MITE_CHOR(mite_chan->channel)); +}; + +#endif diff --git a/drivers/staging/comedi/drivers/plx9080.h b/drivers/staging/comedi/drivers/plx9080.h new file mode 100644 index 00000000000..a5a1a6808c5 --- /dev/null +++ b/drivers/staging/comedi/drivers/plx9080.h @@ -0,0 +1,429 @@ +/* plx9080.h + * + * Copyright (C) 2002,2003 Frank Mori Hess <fmhess@users.sourceforge.net> + * + * I modified this file from the plx9060.h header for the + * wanXL device driver in the linux kernel, + * for the register offsets and bit definitions. Made minor modifications, + * added plx9080 registers and + * stripped out stuff that was specifically for the wanXL driver. + * Note: I've only made sure the definitions are correct as far + * as I make use of them. There are still various plx9060-isms + * left in this header file. + * + ******************************************************************** + * + * Copyright (C) 1999 RG Studio s.c., http://www.rgstudio.com.pl/ + * Written by Krzysztof Halasa <khc@rgstudio.com.pl> + * + * Portions (C) SBE Inc., used by permission. + * + * 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. + */ + +#ifndef __COMEDI_PLX9080_H +#define __COMEDI_PLX9080_H + +// descriptor block used for chained dma transfers +struct plx_dma_desc { + volatile uint32_t pci_start_addr; + volatile uint32_t local_start_addr; + /* transfer_size is in bytes, only first 23 bits of register are used */ + volatile uint32_t transfer_size; + /* address of next descriptor (quad word aligned), plus some + * additional bits (see PLX_DMA0_DESCRIPTOR_REG) */ + volatile uint32_t next; +}; + +/********************************************************************** +** Register Offsets and Bit Definitions +** +** Note: All offsets zero relative. IE. Some standard base address +** must be added to the Register Number to properly access the register. +** +**********************************************************************/ + +#define PLX_LAS0RNG_REG 0x0000 /* L, Local Addr Space 0 Range Register */ +#define PLX_LAS1RNG_REG 0x00f0 /* L, Local Addr Space 1 Range Register */ +#define LRNG_IO 0x00000001 /* Map to: 1=I/O, 0=Mem */ +#define LRNG_ANY32 0x00000000 /* Locate anywhere in 32 bit */ +#define LRNG_LT1MB 0x00000002 /* Locate in 1st meg */ +#define LRNG_ANY64 0x00000004 /* Locate anywhere in 64 bit */ +#define LRNG_MEM_MASK 0xfffffff0 // bits that specify range for memory io +#define LRNG_IO_MASK 0xfffffffa // bits that specify range for normal io + +#define PLX_LAS0MAP_REG 0x0004 /* L, Local Addr Space 0 Remap Register */ +#define PLX_LAS1MAP_REG 0x00f4 /* L, Local Addr Space 1 Remap Register */ +#define LMAP_EN 0x00000001 /* Enable slave decode */ +#define LMAP_MEM_MASK 0xfffffff0 // bits that specify decode for memory io +#define LMAP_IO_MASK 0xfffffffa // bits that specify decode bits for normal io + +/* Mode/Arbitration Register. +*/ +#define PLX_MARB_REG 0x8 /* L, Local Arbitration Register */ +#define PLX_DMAARB_REG 0xac +enum marb_bits { + MARB_LLT_MASK = 0x000000ff, /* Local Bus Latency Timer */ + MARB_LPT_MASK = 0x0000ff00, /* Local Bus Pause Timer */ + MARB_LTEN = 0x00010000, /* Latency Timer Enable */ + MARB_LPEN = 0x00020000, /* Pause Timer Enable */ + MARB_BREQ = 0x00040000, /* Local Bus BREQ Enable */ + MARB_DMA_PRIORITY_MASK = 0x00180000, + MARB_LBDS_GIVE_UP_BUS_MODE = 0x00200000, /* local bus direct slave give up bus mode */ + MARB_DS_LLOCK_ENABLE = 0x00400000, /* direct slave LLOCKo# enable */ + MARB_PCI_REQUEST_MODE = 0x00800000, + MARB_PCIv21_MODE = 0x01000000, /* pci specification v2.1 mode */ + MARB_PCI_READ_NO_WRITE_MODE = 0x02000000, + MARB_PCI_READ_WITH_WRITE_FLUSH_MODE = 0x04000000, + MARB_GATE_TIMER_WITH_BREQ = 0x08000000, /* gate local bus latency timer with BREQ */ + MARB_PCI_READ_NO_FLUSH_MODE = 0x10000000, + MARB_USE_SUBSYSTEM_IDS = 0x20000000, +}; + +#define PLX_BIGEND_REG 0xc +enum bigend_bits { + BIGEND_CONFIG = 0x1, /* use big endian ordering for configuration register accesses */ + BIGEND_DIRECT_MASTER = 0x2, + BIGEND_DIRECT_SLAVE_LOCAL0 = 0x4, + BIGEND_ROM = 0x8, + BIGEND_BYTE_LANE = 0x10, /* use byte lane consisting of most significant bits instead of least significant */ + BIGEND_DIRECT_SLAVE_LOCAL1 = 0x20, + BIGEND_DMA1 = 0x40, + BIGEND_DMA0 = 0x80, +}; + +/* Note: The Expansion ROM stuff is only relevant to the PC environment. +** This expansion ROM code is executed by the host CPU at boot time. +** For this reason no bit definitions are provided here. +*/ +#define PLX_ROMRNG_REG 0x0010 /* L, Expn ROM Space Range Register */ +#define PLX_ROMMAP_REG 0x0014 /* L, Local Addr Space Range Register */ + +#define PLX_REGION0_REG 0x0018 /* L, Local Bus Region 0 Descriptor */ +#define RGN_WIDTH 0x00000002 /* Local bus width bits */ +#define RGN_8BITS 0x00000000 /* 08 bit Local Bus */ +#define RGN_16BITS 0x00000001 /* 16 bit Local Bus */ +#define RGN_32BITS 0x00000002 /* 32 bit Local Bus */ +#define RGN_MWS 0x0000003C /* Memory Access Wait States */ +#define RGN_0MWS 0x00000000 +#define RGN_1MWS 0x00000004 +#define RGN_2MWS 0x00000008 +#define RGN_3MWS 0x0000000C +#define RGN_4MWS 0x00000010 +#define RGN_6MWS 0x00000018 +#define RGN_8MWS 0x00000020 +#define RGN_MRE 0x00000040 /* Memory Space Ready Input Enable */ +#define RGN_MBE 0x00000080 /* Memory Space Bterm Input Enable */ +#define RGN_READ_PREFETCH_DISABLE 0x00000100 +#define RGN_ROM_PREFETCH_DISABLE 0x00000200 +#define RGN_READ_PREFETCH_COUNT_ENABLE 0x00000400 +#define RGN_RWS 0x003C0000 /* Expn ROM Wait States */ +#define RGN_RRE 0x00400000 /* ROM Space Ready Input Enable */ +#define RGN_RBE 0x00800000 /* ROM Space Bterm Input Enable */ +#define RGN_MBEN 0x01000000 /* Memory Space Burst Enable */ +#define RGN_RBEN 0x04000000 /* ROM Space Burst Enable */ +#define RGN_THROT 0x08000000 /* De-assert TRDY when FIFO full */ +#define RGN_TRD 0xF0000000 /* Target Ready Delay /8 */ + +#define PLX_REGION1_REG 0x00f8 /* L, Local Bus Region 1 Descriptor */ + +#define PLX_DMRNG_REG 0x001C /* L, Direct Master Range Register */ + +#define PLX_LBAPMEM_REG 0x0020 /* L, Lcl Base Addr for PCI mem space */ + +#define PLX_LBAPIO_REG 0x0024 /* L, Lcl Base Addr for PCI I/O space */ + +#define PLX_DMMAP_REG 0x0028 /* L, Direct Master Remap Register */ +#define DMM_MAE 0x00000001 /* Direct Mstr Memory Acc Enable */ +#define DMM_IAE 0x00000002 /* Direct Mstr I/O Acc Enable */ +#define DMM_LCK 0x00000004 /* LOCK Input Enable */ +#define DMM_PF4 0x00000008 /* Prefetch 4 Mode Enable */ +#define DMM_THROT 0x00000010 /* Assert IRDY when read FIFO full */ +#define DMM_PAF0 0x00000000 /* Programmable Almost fill level */ +#define DMM_PAF1 0x00000020 /* Programmable Almost fill level */ +#define DMM_PAF2 0x00000040 /* Programmable Almost fill level */ +#define DMM_PAF3 0x00000060 /* Programmable Almost fill level */ +#define DMM_PAF4 0x00000080 /* Programmable Almost fill level */ +#define DMM_PAF5 0x000000A0 /* Programmable Almost fill level */ +#define DMM_PAF6 0x000000C0 /* Programmable Almost fill level */ +#define DMM_PAF7 0x000000D0 /* Programmable Almost fill level */ +#define DMM_MAP 0xFFFF0000 /* Remap Address Bits */ + +#define PLX_CAR_REG 0x002C /* L, Configuration Address Register */ +#define CAR_CT0 0x00000000 /* Config Type 0 */ +#define CAR_CT1 0x00000001 /* Config Type 1 */ +#define CAR_REG 0x000000FC /* Register Number Bits */ +#define CAR_FUN 0x00000700 /* Function Number Bits */ +#define CAR_DEV 0x0000F800 /* Device Number Bits */ +#define CAR_BUS 0x00FF0000 /* Bus Number Bits */ +#define CAR_CFG 0x80000000 /* Config Spc Access Enable */ + +#define PLX_DBR_IN_REG 0x0060 /* L, PCI to Local Doorbell Register */ + +#define PLX_DBR_OUT_REG 0x0064 /* L, Local to PCI Doorbell Register */ + +#define PLX_INTRCS_REG 0x0068 /* L, Interrupt Control/Status Reg */ +#define ICS_AERR 0x00000001 /* Assert LSERR on ABORT */ +#define ICS_PERR 0x00000002 /* Assert LSERR on Parity Error */ +#define ICS_SERR 0x00000004 /* Generate PCI SERR# */ +#define ICS_MBIE 0x00000008 // mailbox interrupt enable +#define ICS_PIE 0x00000100 /* PCI Interrupt Enable */ +#define ICS_PDIE 0x00000200 /* PCI Doorbell Interrupt Enable */ +#define ICS_PAIE 0x00000400 /* PCI Abort Interrupt Enable */ +#define ICS_PLIE 0x00000800 /* PCI Local Int Enable */ +#define ICS_RAE 0x00001000 /* Retry Abort Enable */ +#define ICS_PDIA 0x00002000 /* PCI Doorbell Interrupt Active */ +#define ICS_PAIA 0x00004000 /* PCI Abort Interrupt Active */ +#define ICS_LIA 0x00008000 /* Local Interrupt Active */ +#define ICS_LIE 0x00010000 /* Local Interrupt Enable */ +#define ICS_LDIE 0x00020000 /* Local Doorbell Int Enable */ +#define ICS_DMA0_E 0x00040000 /* DMA #0 Interrupt Enable */ +#define ICS_DMA1_E 0x00080000 /* DMA #1 Interrupt Enable */ +#define ICS_LDIA 0x00100000 /* Local Doorbell Int Active */ +#define ICS_DMA0_A 0x00200000 /* DMA #0 Interrupt Active */ +#define ICS_DMA1_A 0x00400000 /* DMA #1 Interrupt Active */ +#define ICS_BIA 0x00800000 /* BIST Interrupt Active */ +#define ICS_TA_DM 0x01000000 /* Target Abort - Direct Master */ +#define ICS_TA_DMA0 0x02000000 /* Target Abort - DMA #0 */ +#define ICS_TA_DMA1 0x04000000 /* Target Abort - DMA #1 */ +#define ICS_TA_RA 0x08000000 /* Target Abort - Retry Timeout */ +#define ICS_MBIA(x) (0x10000000 << ((x) & 0x3)) // mailbox x is active + +#define PLX_CONTROL_REG 0x006C /* L, EEPROM Cntl & PCI Cmd Codes */ +#define CTL_RDMA 0x0000000E /* DMA Read Command */ +#define CTL_WDMA 0x00000070 /* DMA Write Command */ +#define CTL_RMEM 0x00000600 /* Memory Read Command */ +#define CTL_WMEM 0x00007000 /* Memory Write Command */ +#define CTL_USERO 0x00010000 /* USERO output pin control bit */ +#define CTL_USERI 0x00020000 /* USERI input pin bit */ +#define CTL_EE_CLK 0x01000000 /* EEPROM Clock line */ +#define CTL_EE_CS 0x02000000 /* EEPROM Chip Select */ +#define CTL_EE_W 0x04000000 /* EEPROM Write bit */ +#define CTL_EE_R 0x08000000 /* EEPROM Read bit */ +#define CTL_EECHK 0x10000000 /* EEPROM Present bit */ +#define CTL_EERLD 0x20000000 /* EEPROM Reload Register */ +#define CTL_RESET 0x40000000 /* !! Adapter Reset !! */ +#define CTL_READY 0x80000000 /* Local Init Done */ + +#define PLX_ID_REG 0x70 // hard-coded plx vendor and device ids + +#define PLX_REVISION_REG 0x74 // silicon revision + +#define PLX_DMA0_MODE_REG 0x80 // dma channel 0 mode register +#define PLX_DMA1_MODE_REG 0x94 // dma channel 0 mode register +#define PLX_LOCAL_BUS_16_WIDE_BITS 0x1 +#define PLX_LOCAL_BUS_32_WIDE_BITS 0x3 +#define PLX_LOCAL_BUS_WIDTH_MASK 0x3 +#define PLX_DMA_EN_READYIN_BIT 0x40 // enable ready in input +#define PLX_EN_BTERM_BIT 0x80 // enable BTERM# input +#define PLX_DMA_LOCAL_BURST_EN_BIT 0x100 // enable local burst mode +#define PLX_EN_CHAIN_BIT 0x200 // enables chaining +#define PLX_EN_DMA_DONE_INTR_BIT 0x400 // enables interrupt on dma done +#define PLX_LOCAL_ADDR_CONST_BIT 0x800 // hold local address constant (don't increment) +#define PLX_DEMAND_MODE_BIT 0x1000 // enables demand-mode for dma transfer +#define PLX_EOT_ENABLE_BIT 0x4000 +#define PLX_STOP_MODE_BIT 0x8000 +#define PLX_DMA_INTR_PCI_BIT 0x20000 // routes dma interrupt to pci bus (instead of local bus) + +#define PLX_DMA0_PCI_ADDRESS_REG 0x84 // pci address that dma transfers start at +#define PLX_DMA1_PCI_ADDRESS_REG 0x98 + +#define PLX_DMA0_LOCAL_ADDRESS_REG 0x88 // local address that dma transfers start at +#define PLX_DMA1_LOCAL_ADDRESS_REG 0x9c + +#define PLX_DMA0_TRANSFER_SIZE_REG 0x8c // number of bytes to transfer (first 23 bits) +#define PLX_DMA1_TRANSFER_SIZE_REG 0xa0 + +#define PLX_DMA0_DESCRIPTOR_REG 0x90 // descriptor pointer register +#define PLX_DMA1_DESCRIPTOR_REG 0xa4 +#define PLX_DESC_IN_PCI_BIT 0x1 // descriptor is located in pci space (not local space) +#define PLX_END_OF_CHAIN_BIT 0x2 // end of chain bit +#define PLX_INTR_TERM_COUNT 0x4 // interrupt when this descriptor's transfer is finished +#define PLX_XFER_LOCAL_TO_PCI 0x8 // transfer from local to pci bus (not pci to local) + +#define PLX_DMA0_CS_REG 0xa8 // command status register +#define PLX_DMA1_CS_REG 0xa9 +#define PLX_DMA_EN_BIT 0x1 // enable dma channel +#define PLX_DMA_START_BIT 0x2 // start dma transfer +#define PLX_DMA_ABORT_BIT 0x4 // abort dma transfer +#define PLX_CLEAR_DMA_INTR_BIT 0x8 // clear dma interrupt +#define PLX_DMA_DONE_BIT 0x10 // transfer done status bit + +#define PLX_DMA0_THRESHOLD_REG 0xb0 // command status register + +/* + * Accesses near the end of memory can cause the PLX chip + * to pre-fetch data off of end-of-ram. Limit the size of + * memory so host-side accesses cannot occur. + */ + +#define PLX_PREFETCH 32 + +/* + * The PCI Interface, via the PCI-9060 Chip, has up to eight (8) Mailbox + * Registers. The PUTS (Power-Up Test Suite) handles the board-side + * interface/interaction using the first 4 registers. Specifications for + * the use of the full PUTS' command and status interface is contained + * within a separate SBE PUTS Manual. The Host-Side Device Driver only + * uses a subset of the full PUTS interface. + */ + +/*****************************************/ +/*** MAILBOX #(-1) - MEM ACCESS STS ***/ +/*****************************************/ + +#define MBX_STS_VALID 0x57584744 /* 'WXGD' */ +#define MBX_STS_DILAV 0x44475857 /* swapped = 'DGXW' */ + +/*****************************************/ +/*** MAILBOX #0 - PUTS STATUS ***/ +/*****************************************/ + +#define MBX_STS_MASK 0x000000ff /* PUTS Status Register bits */ +#define MBX_STS_TMASK 0x0000000f /* register bits for TEST number */ + +#define MBX_STS_PCIRESET 0x00000100 /* Host issued PCI reset request */ +#define MBX_STS_BUSY 0x00000080 /* PUTS is in progress */ +#define MBX_STS_ERROR 0x00000040 /* PUTS has failed */ +#define MBX_STS_RESERVED 0x000000c0 /* Undefined -> status in transition. + We are in process of changing + bits; we SET Error bit before + RESET of Busy bit */ + +#define MBX_RESERVED_5 0x00000020 /* FYI: reserved/unused bit */ +#define MBX_RESERVED_4 0x00000010 /* FYI: reserved/unused bit */ + +/******************************************/ +/*** MAILBOX #1 - PUTS COMMANDS ***/ +/******************************************/ + +/* + * Any attempt to execute an unimplement command results in the PUTS + * interface executing a NOOP and continuing as if the offending command + * completed normally. Note: this supplies a simple method to interrogate + * mailbox command processing functionality. + */ + +#define MBX_CMD_MASK 0xffff0000 /* PUTS Command Register bits */ + +#define MBX_CMD_ABORTJ 0x85000000 /* abort and jump */ +#define MBX_CMD_RESETP 0x86000000 /* reset and pause at start */ +#define MBX_CMD_PAUSE 0x87000000 /* pause immediately */ +#define MBX_CMD_PAUSEC 0x88000000 /* pause on completion */ +#define MBX_CMD_RESUME 0x89000000 /* resume operation */ +#define MBX_CMD_STEP 0x8a000000 /* single step tests */ + +#define MBX_CMD_BSWAP 0x8c000000 /* identify byte swap scheme */ +#define MBX_CMD_BSWAP_0 0x8c000000 /* use scheme 0 */ +#define MBX_CMD_BSWAP_1 0x8c000001 /* use scheme 1 */ + +#define MBX_CMD_SETHMS 0x8d000000 /* setup host memory access window + size */ +#define MBX_CMD_SETHBA 0x8e000000 /* setup host memory access base + address */ +#define MBX_CMD_MGO 0x8f000000 /* perform memory setup and continue + (IE. Done) */ +#define MBX_CMD_NOOP 0xFF000000 /* dummy, illegal command */ + +/*****************************************/ +/*** MAILBOX #2 - MEMORY SIZE ***/ +/*****************************************/ + +#define MBX_MEMSZ_MASK 0xffff0000 /* PUTS Memory Size Register bits */ + +#define MBX_MEMSZ_128KB 0x00020000 /* 128 kilobyte board */ +#define MBX_MEMSZ_256KB 0x00040000 /* 256 kilobyte board */ +#define MBX_MEMSZ_512KB 0x00080000 /* 512 kilobyte board */ +#define MBX_MEMSZ_1MB 0x00100000 /* 1 megabyte board */ +#define MBX_MEMSZ_2MB 0x00200000 /* 2 megabyte board */ +#define MBX_MEMSZ_4MB 0x00400000 /* 4 megabyte board */ +#define MBX_MEMSZ_8MB 0x00800000 /* 8 megabyte board */ +#define MBX_MEMSZ_16MB 0x01000000 /* 16 megabyte board */ + +/***************************************/ +/*** MAILBOX #2 - BOARD TYPE ***/ +/***************************************/ + +#define MBX_BTYPE_MASK 0x0000ffff /* PUTS Board Type Register */ +#define MBX_BTYPE_FAMILY_MASK 0x0000ff00 /* PUTS Board Family Register */ +#define MBX_BTYPE_SUBTYPE_MASK 0x000000ff /* PUTS Board Subtype */ + +#define MBX_BTYPE_PLX9060 0x00000100 /* PLX family type */ +#define MBX_BTYPE_PLX9080 0x00000300 /* PLX wanXL100s family type */ + +#define MBX_BTYPE_WANXL_4 0x00000104 /* wanXL400, 4-port */ +#define MBX_BTYPE_WANXL_2 0x00000102 /* wanXL200, 2-port */ +#define MBX_BTYPE_WANXL_1s 0x00000301 /* wanXL100s, 1-port */ +#define MBX_BTYPE_WANXL_1t 0x00000401 /* wanXL100T1, 1-port */ + +/*****************************************/ +/*** MAILBOX #3 - SHMQ MAILBOX ***/ +/*****************************************/ + +#define MBX_SMBX_MASK 0x000000ff /* PUTS SHMQ Mailbox bits */ + +/***************************************/ +/*** GENERIC HOST-SIDE DRIVER ***/ +/***************************************/ + +#define MBX_ERR 0 +#define MBX_OK 1 + +/* mailbox check routine - type of testing */ +#define MBXCHK_STS 0x00 /* check for PUTS status */ +#define MBXCHK_NOWAIT 0x01 /* dont care about PUTS status */ + +/* system allocates this many bytes for address mapping mailbox space */ +#define MBX_ADDR_SPACE_360 0x80 /* wanXL100s/200/400 */ +#define MBX_ADDR_MASK_360 (MBX_ADDR_SPACE_360-1) + +static inline int plx9080_abort_dma(void *iobase, unsigned int channel) +{ + void *dma_cs_addr; + uint8_t dma_status; + const int timeout = 10000; + unsigned int i; + + if (channel) + dma_cs_addr = iobase + PLX_DMA1_CS_REG; + else + dma_cs_addr = iobase + PLX_DMA0_CS_REG; + + // abort dma transfer if necessary + dma_status = readb(dma_cs_addr); + if ((dma_status & PLX_DMA_EN_BIT) == 0) { + return 0; + } + // wait to make sure done bit is zero + for (i = 0; (dma_status & PLX_DMA_DONE_BIT) && i < timeout; i++) { + comedi_udelay(1); + dma_status = readb(dma_cs_addr); + } + if (i == timeout) { + rt_printk + ("plx9080: cancel() timed out waiting for dma %i done clear\n", + channel); + return -ETIMEDOUT; + } + // disable and abort channel + writeb(PLX_DMA_ABORT_BIT, dma_cs_addr); + // wait for dma done bit + dma_status = readb(dma_cs_addr); + for (i = 0; (dma_status & PLX_DMA_DONE_BIT) == 0 && i < timeout; i++) { + comedi_udelay(1); + dma_status = readb(dma_cs_addr); + } + if (i == timeout) { + rt_printk + ("plx9080: cancel() timed out waiting for dma %i done set\n", + channel); + return -ETIMEDOUT; + } + + return 0; +} + +#endif /* __COMEDI_PLX9080_H */ diff --git a/drivers/staging/comedi/drivers/rtd520.c b/drivers/staging/comedi/drivers/rtd520.c new file mode 100644 index 00000000000..65d5242a258 --- /dev/null +++ b/drivers/staging/comedi/drivers/rtd520.c @@ -0,0 +1,2283 @@ +/* + comedi/drivers/rtd520.c + Comedi driver for Real Time Devices (RTD) PCI4520/DM7520 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2001 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: rtd520 +Description: Real Time Devices PCI4520/DM7520 +Author: Dan Christian +Devices: [Real Time Devices] DM7520HR-1 (rtd520), DM7520HR-8, + PCI4520, PCI4520-8 +Status: Works. Only tested on DM7520-8. Not SMP safe. + +Configuration options: + [0] - PCI bus of device (optional) + If bus/slot is not specified, the first available PCI + device will be used. + [1] - PCI slot of device (optional) +*/ +/* + Created by Dan Christian, NASA Ames Research Center. + + The PCI4520 is a PCI card. The DM7520 is a PC/104-plus card. + Both have: + 8/16 12 bit ADC with FIFO and channel gain table + 8 bits high speed digital out (for external MUX) (or 8 in or 8 out) + 8 bits high speed digital in with FIFO and interrupt on change (or 8 IO) + 2 12 bit DACs with FIFOs + 2 bits output + 2 bits input + bus mastering DMA + timers: ADC sample, pacer, burst, about, delay, DA1, DA2 + sample counter + 3 user timer/counters (8254) + external interrupt + + The DM7520 has slightly fewer features (fewer gain steps). + + These boards can support external multiplexors and multi-board + synchronization, but this driver doesn't support that. + + Board docs: http://www.rtdusa.com/PC104/DM/analog%20IO/dm7520.htm + Data sheet: http://www.rtdusa.com/pdf/dm7520.pdf + Example source: http://www.rtdusa.com/examples/dm/dm7520.zip + Call them and ask for the register level manual. + PCI chip: http://www.plxtech.com/products/toolbox/9080.htm + + Notes: + This board is memory mapped. There is some IO stuff, but it isn't needed. + + I use a pretty loose naming style within the driver (rtd_blah). + All externally visible names should be rtd520_blah. + I use camelCase for structures (and inside them). + I may also use upper CamelCase for function names (old habit). + + This board is somewhat related to the RTD PCI4400 board. + + I borrowed heavily from the ni_mio_common, ni_atmio16d, mite, and + das1800, since they have the best documented code. Driver + cb_pcidas64.c uses the same DMA controller. + + As far as I can tell, the About interrupt doesnt work if Sample is + also enabled. It turns out that About really isn't needed, since + we always count down samples read. + + There was some timer/counter code, but it didn't follow the right API. + +*/ + +/* + driver status: + + Analog-In supports instruction and command mode. + + With DMA, you can sample at 1.15Mhz with 70% idle on a 400Mhz K6-2 + (single channel, 64K read buffer). I get random system lockups when + using DMA with ALI-15xx based systems. I haven't been able to test + any other chipsets. The lockups happen soon after the start of an + acquistion, not in the middle of a long run. + + Without DMA, you can do 620Khz sampling with 20% idle on a 400Mhz K6-2 + (with a 256K read buffer). + + Digital-IO and Analog-Out only support instruction mode. + +*/ + +#include <linux/delay.h> + +#include "../comedidev.h" +#include "comedi_pci.h" + +#define DRV_NAME "rtd520" + +/*====================================================================== + Driver specific stuff (tunable) +======================================================================*/ +/* Enable this to test the new DMA support. You may get hard lock ups */ +/*#define USE_DMA*/ + +/* We really only need 2 buffers. More than that means being much + smarter about knowing which ones are full. */ +#define DMA_CHAIN_COUNT 2 /* max DMA segments/buffers in a ring (min 2) */ + +/* Target period for periodic transfers. This sets the user read latency. */ +/* Note: There are certain rates where we give this up and transfer 1/2 FIFO */ +/* If this is too low, efficiency is poor */ +#define TRANS_TARGET_PERIOD 10000000 /* 10 ms (in nanoseconds) */ + +/* Set a practical limit on how long a list to support (affects memory use) */ +/* The board support a channel list up to the FIFO length (1K or 8K) */ +#define RTD_MAX_CHANLIST 128 /* max channel list that we allow */ + +/* tuning for ai/ao instruction done polling */ +#ifdef FAST_SPIN +#define WAIT_QUIETLY /* as nothing, spin on done bit */ +#define RTD_ADC_TIMEOUT 66000 /* 2 msec at 33mhz bus rate */ +#define RTD_DAC_TIMEOUT 66000 +#define RTD_DMA_TIMEOUT 33000 /* 1 msec */ +#else +/* by delaying, power and electrical noise are reduced somewhat */ +#define WAIT_QUIETLY comedi_udelay (1) +#define RTD_ADC_TIMEOUT 2000 /* in usec */ +#define RTD_DAC_TIMEOUT 2000 /* in usec */ +#define RTD_DMA_TIMEOUT 1000 /* in usec */ +#endif + +/*====================================================================== + Board specific stuff +======================================================================*/ + +/* registers */ +#define PCI_VENDOR_ID_RTD 0x1435 +/* + The board has three memory windows: las0, las1, and lcfg (the PCI chip) + Las1 has the data and can be burst DMAed 32bits at a time. +*/ +#define LCFG_PCIINDEX 0 +/* PCI region 1 is a 256 byte IO space mapping. Use??? */ +#define LAS0_PCIINDEX 2 /* PCI memory resources */ +#define LAS1_PCIINDEX 3 +#define LCFG_PCISIZE 0x100 +#define LAS0_PCISIZE 0x200 +#define LAS1_PCISIZE 0x10 + +#define RTD_CLOCK_RATE 8000000 /* 8Mhz onboard clock */ +#define RTD_CLOCK_BASE 125 /* clock period in ns */ + +/* Note: these speed are slower than the spec, but fit the counter resolution*/ +#define RTD_MAX_SPEED 1625 /* when sampling, in nanoseconds */ +/* max speed if we don't have to wait for settling */ +#define RTD_MAX_SPEED_1 875 /* if single channel, in nanoseconds */ + +#define RTD_MIN_SPEED 2097151875 /* (24bit counter) in nanoseconds */ +/* min speed when only 1 channel (no burst counter) */ +#define RTD_MIN_SPEED_1 5000000 /* 200Hz, in nanoseconds */ + +#include "rtd520.h" +#include "plx9080.h" + +/* Setup continuous ring of 1/2 FIFO transfers. See RTD manual p91 */ +#define DMA_MODE_BITS (\ + PLX_LOCAL_BUS_16_WIDE_BITS \ + | PLX_DMA_EN_READYIN_BIT \ + | PLX_DMA_LOCAL_BURST_EN_BIT \ + | PLX_EN_CHAIN_BIT \ + | PLX_DMA_INTR_PCI_BIT \ + | PLX_LOCAL_ADDR_CONST_BIT \ + | PLX_DEMAND_MODE_BIT) + +#define DMA_TRANSFER_BITS (\ +/* descriptors in PCI memory*/ PLX_DESC_IN_PCI_BIT \ +/* interrupt at end of block */ | PLX_INTR_TERM_COUNT \ +/* from board to PCI */ | PLX_XFER_LOCAL_TO_PCI) + +/*====================================================================== + Comedi specific stuff +======================================================================*/ + +/* + The board has 3 input modes and the gains of 1,2,4,...32 (, 64, 128) +*/ +static const comedi_lrange rtd_ai_7520_range = { 18, { + /* +-5V input range gain steps */ + BIP_RANGE(5.0), + BIP_RANGE(5.0 / 2), + BIP_RANGE(5.0 / 4), + BIP_RANGE(5.0 / 8), + BIP_RANGE(5.0 / 16), + BIP_RANGE(5.0 / 32), + /* +-10V input range gain steps */ + BIP_RANGE(10.0), + BIP_RANGE(10.0 / 2), + BIP_RANGE(10.0 / 4), + BIP_RANGE(10.0 / 8), + BIP_RANGE(10.0 / 16), + BIP_RANGE(10.0 / 32), + /* +10V input range gain steps */ + UNI_RANGE(10.0), + UNI_RANGE(10.0 / 2), + UNI_RANGE(10.0 / 4), + UNI_RANGE(10.0 / 8), + UNI_RANGE(10.0 / 16), + UNI_RANGE(10.0 / 32), + + } +}; + +/* PCI4520 has two more gains (6 more entries) */ +static const comedi_lrange rtd_ai_4520_range = { 24, { + /* +-5V input range gain steps */ + BIP_RANGE(5.0), + BIP_RANGE(5.0 / 2), + BIP_RANGE(5.0 / 4), + BIP_RANGE(5.0 / 8), + BIP_RANGE(5.0 / 16), + BIP_RANGE(5.0 / 32), + BIP_RANGE(5.0 / 64), + BIP_RANGE(5.0 / 128), + /* +-10V input range gain steps */ + BIP_RANGE(10.0), + BIP_RANGE(10.0 / 2), + BIP_RANGE(10.0 / 4), + BIP_RANGE(10.0 / 8), + BIP_RANGE(10.0 / 16), + BIP_RANGE(10.0 / 32), + BIP_RANGE(10.0 / 64), + BIP_RANGE(10.0 / 128), + /* +10V input range gain steps */ + UNI_RANGE(10.0), + UNI_RANGE(10.0 / 2), + UNI_RANGE(10.0 / 4), + UNI_RANGE(10.0 / 8), + UNI_RANGE(10.0 / 16), + UNI_RANGE(10.0 / 32), + UNI_RANGE(10.0 / 64), + UNI_RANGE(10.0 / 128), + } +}; + +/* Table order matches range values */ +static const comedi_lrange rtd_ao_range = { 4, { + RANGE(0, 5), + RANGE(0, 10), + RANGE(-5, 5), + RANGE(-10, 10), + } +}; + +/* + Board descriptions + */ +typedef struct rtdBoard_struct { + const char *name; /* must be first */ + int device_id; + int aiChans; + int aiBits; + int aiMaxGain; + int range10Start; /* start of +-10V range */ + int rangeUniStart; /* start of +10V range */ +} rtdBoard; + +static const rtdBoard rtd520Boards[] = { + { + name: "DM7520", + device_id:0x7520, + aiChans: 16, + aiBits: 12, + aiMaxGain:32, + range10Start:6, + rangeUniStart:12, + }, + { + name: "PCI4520", + device_id:0x4520, + aiChans: 16, + aiBits: 12, + aiMaxGain:128, + range10Start:8, + rangeUniStart:16, + }, +}; + +static DEFINE_PCI_DEVICE_TABLE(rtd520_pci_table) = { + {PCI_VENDOR_ID_RTD, 0x7520, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_RTD, 0x4520, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, rtd520_pci_table); + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const rtdBoard *)dev->board_ptr) + +/* + This structure is for data unique to this hardware driver. + This is also unique for each board in the system. +*/ +typedef struct { + /* memory mapped board structures */ + void *las0; + void *las1; + void *lcfg; + + unsigned long intCount; /* interrupt count */ + long aiCount; /* total transfer size (samples) */ + int transCount; /* # to tranfer data. 0->1/2FIFO */ + int flags; /* flag event modes */ + + /* PCI device info */ + struct pci_dev *pci_dev; + int got_regions; /* non-zero if PCI regions owned */ + + /* channel list info */ + /* chanBipolar tracks whether a channel is bipolar (and needs +2048) */ + unsigned char chanBipolar[RTD_MAX_CHANLIST / 8]; /* bit array */ + + /* read back data */ + lsampl_t aoValue[2]; /* Used for AO read back */ + + /* timer gate (when enabled) */ + u8 utcGate[4]; /* 1 extra allows simple range check */ + + /* shadow registers affect other registers, but cant be read back */ + /* The macros below update these on writes */ + u16 intMask; /* interrupt mask */ + u16 intClearMask; /* interrupt clear mask */ + u8 utcCtrl[4]; /* crtl mode for 3 utc + read back */ + u8 dioStatus; /* could be read back (dio0Ctrl) */ +#ifdef USE_DMA + /* Always DMA 1/2 FIFO. Buffer (dmaBuff?) is (at least) twice that size. + After transferring, interrupt processes 1/2 FIFO and passes to comedi */ + s16 dma0Offset; /* current processing offset (0, 1/2) */ + uint16_t *dma0Buff[DMA_CHAIN_COUNT]; /* DMA buffers (for ADC) */ + dma_addr_t dma0BuffPhysAddr[DMA_CHAIN_COUNT]; /* physical addresses */ + struct plx_dma_desc *dma0Chain; /* DMA descriptor ring for dmaBuff */ + dma_addr_t dma0ChainPhysAddr; /* physical addresses */ + /* shadow registers */ + u8 dma0Control; + u8 dma1Control; +#endif /* USE_DMA */ + unsigned fifoLen; +} rtdPrivate; + +/* bit defines for "flags" */ +#define SEND_EOS 0x01 /* send End Of Scan events */ +#define DMA0_ACTIVE 0x02 /* DMA0 is active */ +#define DMA1_ACTIVE 0x04 /* DMA1 is active */ + +/* Macros for accessing channel list bit array */ +#define CHAN_ARRAY_TEST(array,index) \ + (((array)[(index)/8] >> ((index) & 0x7)) & 0x1) +#define CHAN_ARRAY_SET(array,index) \ + (((array)[(index)/8] |= 1 << ((index) & 0x7))) +#define CHAN_ARRAY_CLEAR(array,index) \ + (((array)[(index)/8] &= ~(1 << ((index) & 0x7)))) + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((rtdPrivate *)dev->private) + +/* Macros to access registers */ + +/* Reset board */ +#define RtdResetBoard(dev) \ + writel (0, devpriv->las0+LAS0_BOARD_RESET) + +/* Reset channel gain table read pointer */ +#define RtdResetCGT(dev) \ + writel (0, devpriv->las0+LAS0_CGT_RESET) + +/* Reset channel gain table read and write pointers */ +#define RtdClearCGT(dev) \ + writel (0, devpriv->las0+LAS0_CGT_CLEAR) + +/* Reset channel gain table read and write pointers */ +#define RtdEnableCGT(dev,v) \ + writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_CGT_ENABLE) + +/* Write channel gain table entry */ +#define RtdWriteCGTable(dev,v) \ + writel (v, devpriv->las0+LAS0_CGT_WRITE) + +/* Write Channel Gain Latch */ +#define RtdWriteCGLatch(dev,v) \ + writel (v, devpriv->las0+LAS0_CGL_WRITE) + +/* Reset ADC FIFO */ +#define RtdAdcClearFifo(dev) \ + writel (0, devpriv->las0+LAS0_ADC_FIFO_CLEAR) + +/* Set ADC start conversion source select (write only) */ +#define RtdAdcConversionSource(dev,v) \ + writel (v, devpriv->las0+LAS0_ADC_CONVERSION) + +/* Set burst start source select (write only) */ +#define RtdBurstStartSource(dev,v) \ + writel (v, devpriv->las0+LAS0_BURST_START) + +/* Set Pacer start source select (write only) */ +#define RtdPacerStartSource(dev,v) \ + writel (v, devpriv->las0+LAS0_PACER_START) + +/* Set Pacer stop source select (write only) */ +#define RtdPacerStopSource(dev,v) \ + writel (v, devpriv->las0+LAS0_PACER_STOP) + +/* Set Pacer clock source select (write only) 0=external 1=internal */ +#define RtdPacerClockSource(dev,v) \ + writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_PACER_SELECT) + +/* Set sample counter source select (write only) */ +#define RtdAdcSampleCounterSource(dev,v) \ + writel (v, devpriv->las0+LAS0_ADC_SCNT_SRC) + +/* Set Pacer trigger mode select (write only) 0=single cycle, 1=repeat */ +#define RtdPacerTriggerMode(dev,v) \ + writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_PACER_REPEAT) + +/* Set About counter stop enable (write only) */ +#define RtdAboutStopEnable(dev,v) \ + writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_ACNT_STOP_ENABLE) + +/* Set external trigger polarity (write only) 0=positive edge, 1=negative */ +#define RtdTriggerPolarity(dev,v) \ + writel ((v > 0) ? 1 : 0, devpriv->las0+LAS0_ETRG_POLARITY) + +/* Start single ADC conversion */ +#define RtdAdcStart(dev) \ + writew (0, devpriv->las0+LAS0_ADC) + +/* Read one ADC data value (12bit (with sign extend) as 16bit) */ +/* Note: matches what DMA would get. Actual value >> 3 */ +#define RtdAdcFifoGet(dev) \ + readw (devpriv->las1+LAS1_ADC_FIFO) + +/* Read two ADC data values (DOESNT WORK) */ +#define RtdAdcFifoGet2(dev) \ + readl (devpriv->las1+LAS1_ADC_FIFO) + +/* FIFO status */ +#define RtdFifoStatus(dev) \ + readl (devpriv->las0+LAS0_ADC) + +/* pacer start/stop read=start, write=stop*/ +#define RtdPacerStart(dev) \ + readl (devpriv->las0+LAS0_PACER) +#define RtdPacerStop(dev) \ + writel (0, devpriv->las0+LAS0_PACER) + +/* Interrupt status */ +#define RtdInterruptStatus(dev) \ + readw (devpriv->las0+LAS0_IT) + +/* Interrupt mask */ +#define RtdInterruptMask(dev,v) \ + writew ((devpriv->intMask = (v)),devpriv->las0+LAS0_IT) + +/* Interrupt status clear (only bits set in mask) */ +#define RtdInterruptClear(dev) \ + readw (devpriv->las0+LAS0_CLEAR) + +/* Interrupt clear mask */ +#define RtdInterruptClearMask(dev,v) \ + writew ((devpriv->intClearMask = (v)), devpriv->las0+LAS0_CLEAR) + +/* Interrupt overrun status */ +#define RtdInterruptOverrunStatus(dev) \ + readl (devpriv->las0+LAS0_OVERRUN) + +/* Interrupt overrun clear */ +#define RtdInterruptOverrunClear(dev) \ + writel (0, devpriv->las0+LAS0_OVERRUN) + +/* Pacer counter, 24bit */ +#define RtdPacerCount(dev) \ + readl (devpriv->las0+LAS0_PCLK) +#define RtdPacerCounter(dev,v) \ + writel ((v) & 0xffffff,devpriv->las0+LAS0_PCLK) + +/* Burst counter, 10bit */ +#define RtdBurstCount(dev) \ + readl (devpriv->las0+LAS0_BCLK) +#define RtdBurstCounter(dev,v) \ + writel ((v) & 0x3ff,devpriv->las0+LAS0_BCLK) + +/* Delay counter, 16bit */ +#define RtdDelayCount(dev) \ + readl (devpriv->las0+LAS0_DCLK) +#define RtdDelayCounter(dev,v) \ + writel ((v) & 0xffff, devpriv->las0+LAS0_DCLK) + +/* About counter, 16bit */ +#define RtdAboutCount(dev) \ + readl (devpriv->las0+LAS0_ACNT) +#define RtdAboutCounter(dev,v) \ + writel ((v) & 0xffff, devpriv->las0+LAS0_ACNT) + +/* ADC sample counter, 10bit */ +#define RtdAdcSampleCount(dev) \ + readl (devpriv->las0+LAS0_ADC_SCNT) +#define RtdAdcSampleCounter(dev,v) \ + writel ((v) & 0x3ff, devpriv->las0+LAS0_ADC_SCNT) + +/* User Timer/Counter (8254) */ +#define RtdUtcCounterGet(dev,n) \ + readb (devpriv->las0 \ + + ((n <= 0) ? LAS0_UTC0 : ((1 == n) ? LAS0_UTC1 : LAS0_UTC2))) + +#define RtdUtcCounterPut(dev,n,v) \ + writeb ((v) & 0xff, devpriv->las0 \ + + ((n <= 0) ? LAS0_UTC0 : ((1 == n) ? LAS0_UTC1 : LAS0_UTC2))) + +/* Set UTC (8254) control byte */ +#define RtdUtcCtrlPut(dev,n,v) \ + writeb (devpriv->utcCtrl[(n) & 3] = (((n) & 3) << 6) | ((v) & 0x3f), \ + devpriv->las0 + LAS0_UTC_CTRL) + +/* Set UTCn clock source (write only) */ +#define RtdUtcClockSource(dev,n,v) \ + writew (v, devpriv->las0 \ + + ((n <= 0) ? LAS0_UTC0_CLOCK : \ + ((1 == n) ? LAS0_UTC1_CLOCK : LAS0_UTC2_CLOCK))) + +/* Set UTCn gate source (write only) */ +#define RtdUtcGateSource(dev,n,v) \ + writew (v, devpriv->las0 \ + + ((n <= 0) ? LAS0_UTC0_GATE : \ + ((1 == n) ? LAS0_UTC1_GATE : LAS0_UTC2_GATE))) + +/* User output N source select (write only) */ +#define RtdUsrOutSource(dev,n,v) \ + writel (v,devpriv->las0+((n <= 0) ? LAS0_UOUT0_SELECT : LAS0_UOUT1_SELECT)) + +/* Digital IO */ +#define RtdDio0Read(dev) \ + (readw (devpriv->las0+LAS0_DIO0) & 0xff) +#define RtdDio0Write(dev,v) \ + writew ((v) & 0xff, devpriv->las0+LAS0_DIO0) + +#define RtdDio1Read(dev) \ + (readw (devpriv->las0+LAS0_DIO1) & 0xff) +#define RtdDio1Write(dev,v) \ + writew ((v) & 0xff, devpriv->las0+LAS0_DIO1) + +#define RtdDioStatusRead(dev) \ + (readw (devpriv->las0+LAS0_DIO_STATUS) & 0xff) +#define RtdDioStatusWrite(dev,v) \ + writew ((devpriv->dioStatus = (v)), devpriv->las0+LAS0_DIO_STATUS) + +#define RtdDio0CtrlRead(dev) \ + (readw (devpriv->las0+LAS0_DIO0_CTRL) & 0xff) +#define RtdDio0CtrlWrite(dev,v) \ + writew ((v) & 0xff, devpriv->las0+LAS0_DIO0_CTRL) + +/* Digital to Analog converter */ +/* Write one data value (sign + 12bit + marker bits) */ +/* Note: matches what DMA would put. Actual value << 3 */ +#define RtdDacFifoPut(dev,n,v) \ + writew ((v), devpriv->las1 +(((n) == 0) ? LAS1_DAC1_FIFO : LAS1_DAC2_FIFO)) + +/* Start single DAC conversion */ +#define RtdDacUpdate(dev,n) \ + writew (0, devpriv->las0 +(((n) == 0) ? LAS0_DAC1 : LAS0_DAC2)) + +/* Start single DAC conversion on both DACs */ +#define RtdDacBothUpdate(dev) \ + writew (0, devpriv->las0+LAS0_DAC) + +/* Set DAC output type and range */ +#define RtdDacRange(dev,n,v) \ + writew ((v) & 7, devpriv->las0 \ + +(((n) == 0) ? LAS0_DAC1_CTRL : LAS0_DAC2_CTRL)) + +/* Reset DAC FIFO */ +#define RtdDacClearFifo(dev,n) \ + writel (0, devpriv->las0+(((n) == 0) ? LAS0_DAC1_RESET : LAS0_DAC2_RESET)) + +/* Set source for DMA 0 (write only, shadow?) */ +#define RtdDma0Source(dev,n) \ + writel ((n) & 0xf, devpriv->las0+LAS0_DMA0_SRC) + +/* Set source for DMA 1 (write only, shadow?) */ +#define RtdDma1Source(dev,n) \ + writel ((n) & 0xf, devpriv->las0+LAS0_DMA1_SRC) + +/* Reset board state for DMA 0 */ +#define RtdDma0Reset(dev) \ + writel (0, devpriv->las0+LAS0_DMA0_RESET) + +/* Reset board state for DMA 1 */ +#define RtdDma1Reset(dev) \ + writel (0, devpriv->las0+LAS0_DMA1_SRC) + +/* PLX9080 interrupt mask and status */ +#define RtdPlxInterruptRead(dev) \ + readl (devpriv->lcfg+LCFG_ITCSR) +#define RtdPlxInterruptWrite(dev,v) \ + writel (v, devpriv->lcfg+LCFG_ITCSR) + +/* Set mode for DMA 0 */ +#define RtdDma0Mode(dev,m) \ + writel ((m), devpriv->lcfg+LCFG_DMAMODE0) + +/* Set PCI address for DMA 0 */ +#define RtdDma0PciAddr(dev,a) \ + writel ((a), devpriv->lcfg+LCFG_DMAPADR0) + +/* Set local address for DMA 0 */ +#define RtdDma0LocalAddr(dev,a) \ + writel ((a), devpriv->lcfg+LCFG_DMALADR0) + +/* Set byte count for DMA 0 */ +#define RtdDma0Count(dev,c) \ + writel ((c), devpriv->lcfg+LCFG_DMASIZ0) + +/* Set next descriptor for DMA 0 */ +#define RtdDma0Next(dev,a) \ + writel ((a), devpriv->lcfg+LCFG_DMADPR0) + +/* Set mode for DMA 1 */ +#define RtdDma1Mode(dev,m) \ + writel ((m), devpriv->lcfg+LCFG_DMAMODE1) + +/* Set PCI address for DMA 1 */ +#define RtdDma1PciAddr(dev,a) \ + writel ((a), devpriv->lcfg+LCFG_DMAADR1) + +/* Set local address for DMA 1 */ +#define RtdDma1LocalAddr(dev,a) \ + writel ((a), devpriv->lcfg+LCFG_DMALADR1) + +/* Set byte count for DMA 1 */ +#define RtdDma1Count(dev,c) \ + writel ((c), devpriv->lcfg+LCFG_DMASIZ1) + +/* Set next descriptor for DMA 1 */ +#define RtdDma1Next(dev,a) \ + writel ((a), devpriv->lcfg+LCFG_DMADPR1) + +/* Set control for DMA 0 (write only, shadow?) */ +#define RtdDma0Control(dev,n) \ + writeb (devpriv->dma0Control = (n), devpriv->lcfg+LCFG_DMACSR0) + +/* Get status for DMA 0 */ +#define RtdDma0Status(dev) \ + readb (devpriv->lcfg+LCFG_DMACSR0) + +/* Set control for DMA 1 (write only, shadow?) */ +#define RtdDma1Control(dev,n) \ + writeb (devpriv->dma1Control = (n), devpriv->lcfg+LCFG_DMACSR1) + +/* Get status for DMA 1 */ +#define RtdDma1Status(dev) \ + readb (devpriv->lcfg+LCFG_DMACSR1) + +/* + * The comedi_driver structure tells the Comedi core module + * which functions to call to configure/deconfigure (attac/detach) + * the board, and also about the kernel module that contains + * the device code. + */ +static int rtd_attach(comedi_device * dev, comedi_devconfig * it); +static int rtd_detach(comedi_device * dev); + +static comedi_driver rtd520Driver = { + driver_name: DRV_NAME, + module:THIS_MODULE, + attach:rtd_attach, + detach:rtd_detach, +}; + +static int rtd_ai_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int rtd_ao_winsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int rtd_ao_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int rtd_dio_insn_bits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int rtd_dio_insn_config(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int rtd_ai_cmdtest(comedi_device * dev, comedi_subdevice * s, + comedi_cmd * cmd); +static int rtd_ai_cmd(comedi_device * dev, comedi_subdevice * s); +static int rtd_ai_cancel(comedi_device * dev, comedi_subdevice * s); +//static int rtd_ai_poll (comedi_device *dev,comedi_subdevice *s); +static int rtd_ns_to_timer(unsigned int *ns, int roundMode); +static irqreturn_t rtd_interrupt(int irq, void *d PT_REGS_ARG); +static int rtd520_probe_fifo_depth(comedi_device *dev); + +/* + * Attach is called by the Comedi core to configure the driver + * for a particular board. If you specified a board_name array + * in the driver structure, dev->board_ptr contains that + * address. + */ +static int rtd_attach(comedi_device * dev, comedi_devconfig * it) +{ /* board name and options flags */ + comedi_subdevice *s; + struct pci_dev *pcidev; + int ret; + resource_size_t physLas0; /* configuation */ + resource_size_t physLas1; /* data area */ + resource_size_t physLcfg; /* PLX9080 */ +#ifdef USE_DMA + int index; +#endif + + printk("comedi%d: rtd520 attaching.\n", dev->minor); + +#if defined (CONFIG_COMEDI_DEBUG) && defined (USE_DMA) + /* You can set this a load time: modprobe comedi comedi_debug=1 */ + if (0 == comedi_debug) /* force DMA debug printks */ + comedi_debug = 1; +#endif + + /* + * Allocate the private structure area. alloc_private() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_private(dev, sizeof(rtdPrivate)) < 0) + return -ENOMEM; + + /* + * Probe the device to determine what device in the series it is. + */ + for (pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, NULL); + pcidev != NULL; + pcidev = pci_get_device(PCI_VENDOR_ID_RTD, PCI_ANY_ID, pcidev)) { + int i; + + if (it->options[0] || it->options[1]) { + if (pcidev->bus->number != it->options[0] + || PCI_SLOT(pcidev->devfn) != + it->options[1]) { + continue; + } + } + for(i = 0; i < sizeof(rtd520Boards) / sizeof(rtd520Boards[0]); ++i) + { + if(pcidev->device == rtd520Boards[i].device_id) + { + dev->board_ptr = &rtd520Boards[i]; + break; + } + } + if(dev->board_ptr) break; /* found one */ + } + if (!pcidev) { + if (it->options[0] && it->options[1]) { + printk("No RTD card at bus=%d slot=%d.\n", + it->options[0], it->options[1]); + } else { + printk("No RTD card found.\n"); + } + return -EIO; + } + devpriv->pci_dev = pcidev; + dev->board_name = thisboard->name; + + if ((ret = comedi_pci_enable(pcidev, DRV_NAME)) < 0) { + printk("Failed to enable PCI device and request regions.\n"); + return ret; + } + devpriv->got_regions = 1; + + /* + * Initialize base addresses + */ + /* Get the physical address from PCI config */ + physLas0 = pci_resource_start(devpriv->pci_dev, LAS0_PCIINDEX); + physLas1 = pci_resource_start(devpriv->pci_dev, LAS1_PCIINDEX); + physLcfg = pci_resource_start(devpriv->pci_dev, LCFG_PCIINDEX); + /* Now have the kernel map this into memory */ + /* ASSUME page aligned */ + devpriv->las0 = ioremap_nocache(physLas0, LAS0_PCISIZE); + devpriv->las1 = ioremap_nocache(physLas1, LAS1_PCISIZE); + devpriv->lcfg = ioremap_nocache(physLcfg, LCFG_PCISIZE); + + if (!devpriv->las0 || !devpriv->las1 || !devpriv->lcfg) { + return -ENOMEM; + } + + DPRINTK("%s: LAS0=%llx, LAS1=%llx, CFG=%llx.\n", dev->board_name, + (unsigned long long)physLas0, (unsigned long long)physLas1, + (unsigned long long)physLcfg); + { /* The RTD driver does this */ + unsigned char pci_latency; + u16 revision; + /*uint32_t epld_version; */ + + pci_read_config_word(devpriv->pci_dev, PCI_REVISION_ID, + &revision); + DPRINTK("%s: PCI revision %d.\n", dev->board_name, revision); + + pci_read_config_byte(devpriv->pci_dev, + PCI_LATENCY_TIMER, &pci_latency); + if (pci_latency < 32) { + printk("%s: PCI latency changed from %d to %d\n", + dev->board_name, pci_latency, 32); + pci_write_config_byte(devpriv->pci_dev, + PCI_LATENCY_TIMER, 32); + } else { + DPRINTK("rtd520: PCI latency = %d\n", pci_latency); + } + + /* Undocumented EPLD version (doesnt match RTD driver results) */ + /*DPRINTK ("rtd520: Reading epld from %p\n", + devpriv->las0+0); + epld_version = readl (devpriv->las0+0); + if ((epld_version & 0xF0) >> 4 == 0x0F) { + DPRINTK("rtd520: pre-v8 EPLD. (%x)\n", epld_version); + } else { + DPRINTK("rtd520: EPLD version %x.\n", epld_version >> 4); + } */ + } + + /* Show board configuration */ + printk("%s:", dev->board_name); + + /* + * Allocate the subdevice structures. alloc_subdevice() is a + * convenient macro defined in comedidev.h. + */ + if (alloc_subdevices(dev, 4) < 0) { + return -ENOMEM; + } + + s = dev->subdevices + 0; + dev->read_subdev = s; + /* analog input subdevice */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = + SDF_READABLE | SDF_GROUND | SDF_COMMON | SDF_DIFF | + SDF_CMD_READ; + s->n_chan = thisboard->aiChans; + s->maxdata = (1 << thisboard->aiBits) - 1; + if (thisboard->aiMaxGain <= 32) { + s->range_table = &rtd_ai_7520_range; + } else { + s->range_table = &rtd_ai_4520_range; + } + s->len_chanlist = RTD_MAX_CHANLIST; /* devpriv->fifoLen */ + s->insn_read = rtd_ai_rinsn; + s->do_cmd = rtd_ai_cmd; + s->do_cmdtest = rtd_ai_cmdtest; + s->cancel = rtd_ai_cancel; + /*s->poll = rtd_ai_poll; *//* not ready yet */ + + s = dev->subdevices + 1; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE; + s->n_chan = 2; + s->maxdata = (1 << thisboard->aiBits) - 1; + s->range_table = &rtd_ao_range; + s->insn_write = rtd_ao_winsn; + s->insn_read = rtd_ao_rinsn; + + s = dev->subdevices + 2; + /* digital i/o subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + /* we only support port 0 right now. Ignoring port 1 and user IO */ + s->n_chan = 8; + s->maxdata = 1; + s->range_table = &range_digital; + s->insn_bits = rtd_dio_insn_bits; + s->insn_config = rtd_dio_insn_config; + + /* timer/counter subdevices (not currently supported) */ + s = dev->subdevices + 3; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 3; + s->maxdata = 0xffff; + + /* initialize board, per RTD spec */ + /* also, initialize shadow registers */ + RtdResetBoard(dev); + comedi_udelay(100); /* needed? */ + RtdPlxInterruptWrite(dev, 0); + RtdInterruptMask(dev, 0); /* and sets shadow */ + RtdInterruptClearMask(dev, ~0); /* and sets shadow */ + RtdInterruptClear(dev); /* clears bits set by mask */ + RtdInterruptOverrunClear(dev); + RtdClearCGT(dev); + RtdAdcClearFifo(dev); + RtdDacClearFifo(dev, 0); + RtdDacClearFifo(dev, 1); + /* clear digital IO fifo */ + RtdDioStatusWrite(dev, 0); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 0, 0x30); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 1, 0x30); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 2, 0x30); /* safe state, set shadow */ + RtdUtcCtrlPut(dev, 3, 0); /* safe state, set shadow */ + /* TODO: set user out source ??? */ + + /* check if our interrupt is available and get it */ + if ((ret = comedi_request_irq(devpriv->pci_dev->irq, rtd_interrupt, + IRQF_SHARED, DRV_NAME, dev)) < 0) { + printk("Could not get interrupt! (%u)\n", + devpriv->pci_dev->irq); + return ret; + } + dev->irq = devpriv->pci_dev->irq; + printk("( irq=%u )", dev->irq); + + ret = rtd520_probe_fifo_depth(dev); + if(ret < 0) { + return ret; + } + devpriv->fifoLen = ret; + printk("( fifoLen=%d )", devpriv->fifoLen); + +#ifdef USE_DMA + if (dev->irq > 0) { + printk("( DMA buff=%d )\n", DMA_CHAIN_COUNT); + /* The PLX9080 has 2 DMA controllers, but there could be 4 sources: + ADC, digital, DAC1, and DAC2. Since only the ADC supports cmd mode + right now, this isn't an issue (yet) */ + devpriv->dma0Offset = 0; + + for (index = 0; index < DMA_CHAIN_COUNT; index++) { + devpriv->dma0Buff[index] = + pci_alloc_consistent(devpriv->pci_dev, + sizeof(u16) * devpriv->fifoLen / 2, + &devpriv->dma0BuffPhysAddr[index]); + if (devpriv->dma0Buff[index] == NULL) { + ret = -ENOMEM; + goto rtd_attach_die_error; + } + /*DPRINTK ("buff[%d] @ %p virtual, %x PCI\n", + index, + devpriv->dma0Buff[index], devpriv->dma0BuffPhysAddr[index]); */ + } + + /* setup DMA descriptor ring (use cpu_to_le32 for byte ordering?) */ + devpriv->dma0Chain = + pci_alloc_consistent(devpriv->pci_dev, + sizeof(struct plx_dma_desc) * DMA_CHAIN_COUNT, + &devpriv->dma0ChainPhysAddr); + for (index = 0; index < DMA_CHAIN_COUNT; index++) { + devpriv->dma0Chain[index].pci_start_addr = + devpriv->dma0BuffPhysAddr[index]; + devpriv->dma0Chain[index].local_start_addr = + DMALADDR_ADC; + devpriv->dma0Chain[index].transfer_size = + sizeof(u16) * devpriv->fifoLen / 2; + devpriv->dma0Chain[index].next = + (devpriv->dma0ChainPhysAddr + ((index + + 1) % (DMA_CHAIN_COUNT)) + * sizeof(devpriv->dma0Chain[0])) + | DMA_TRANSFER_BITS; + /*DPRINTK ("ring[%d] @%lx PCI: %x, local: %x, N: 0x%x, next: %x\n", + index, + ((long)devpriv->dma0ChainPhysAddr + + (index * sizeof(devpriv->dma0Chain[0]))), + devpriv->dma0Chain[index].pci_start_addr, + devpriv->dma0Chain[index].local_start_addr, + devpriv->dma0Chain[index].transfer_size, + devpriv->dma0Chain[index].next); */ + } + + if (devpriv->dma0Chain == NULL) { + ret = -ENOMEM; + goto rtd_attach_die_error; + } + + RtdDma0Mode(dev, DMA_MODE_BITS); + RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL); /* set DMA trigger source */ + } else { + printk("( no IRQ->no DMA )"); + } +#endif /* USE_DMA */ + + if (dev->irq) { /* enable plx9080 interrupts */ + RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE); + } + + printk("\ncomedi%d: rtd520 driver attached.\n", dev->minor); + + return 1; + +#if 0 + /* hit an error, clean up memory and return ret */ +//rtd_attach_die_error: +#ifdef USE_DMA + for (index = 0; index < DMA_CHAIN_COUNT; index++) { + if (NULL != devpriv->dma0Buff[index]) { /* free buffer memory */ + pci_free_consistent(devpriv->pci_dev, + sizeof(u16) * devpriv->fifoLen / 2, + devpriv->dma0Buff[index], + devpriv->dma0BuffPhysAddr[index]); + devpriv->dma0Buff[index] = NULL; + } + } + if (NULL != devpriv->dma0Chain) { + pci_free_consistent(devpriv->pci_dev, + sizeof(struct plx_dma_desc) + * DMA_CHAIN_COUNT, + devpriv->dma0Chain, devpriv->dma0ChainPhysAddr); + devpriv->dma0Chain = NULL; + } +#endif /* USE_DMA */ + /* subdevices and priv are freed by the core */ + if (dev->irq) { + /* disable interrupt controller */ + RtdPlxInterruptWrite(dev, RtdPlxInterruptRead(dev) + & ~(ICS_PLIE | ICS_DMA0_E | ICS_DMA1_E)); + comedi_free_irq(dev->irq, dev); + } + + /* release all regions that were allocated */ + if (devpriv->las0) { + iounmap(devpriv->las0); + } + if (devpriv->las1) { + iounmap(devpriv->las1); + } + if (devpriv->lcfg) { + iounmap(devpriv->lcfg); + } + if (devpriv->pci_dev) { + pci_dev_put(devpriv->pci_dev); + } + return ret; +#endif +} + +/* + * _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 rtd_detach(comedi_device * dev) +{ +#ifdef USE_DMA + int index; +#endif + + DPRINTK("comedi%d: rtd520: removing (%ld ints)\n", + dev->minor, (devpriv ? devpriv->intCount : 0L)); + if (devpriv && devpriv->lcfg) { + DPRINTK("(int status 0x%x, overrun status 0x%x, fifo status 0x%x)...\n", 0xffff & RtdInterruptStatus(dev), 0xffff & RtdInterruptOverrunStatus(dev), (0xffff & RtdFifoStatus(dev)) ^ 0x6666); + } + + if (devpriv) { + /* Shut down any board ops by resetting it */ +#ifdef USE_DMA + if (devpriv->lcfg) { + RtdDma0Control(dev, 0); /* disable DMA */ + RtdDma1Control(dev, 0); /* disable DMA */ + RtdPlxInterruptWrite(dev, ICS_PIE | ICS_PLIE); + } +#endif /* USE_DMA */ + if (devpriv->las0) { + RtdResetBoard(dev); + RtdInterruptMask(dev, 0); + RtdInterruptClearMask(dev, ~0); + RtdInterruptClear(dev); /* clears bits set by mask */ + } +#ifdef USE_DMA + /* release DMA */ + for (index = 0; index < DMA_CHAIN_COUNT; index++) { + if (NULL != devpriv->dma0Buff[index]) { + pci_free_consistent(devpriv->pci_dev, + sizeof(u16) * devpriv->fifoLen / 2, + devpriv->dma0Buff[index], + devpriv->dma0BuffPhysAddr[index]); + devpriv->dma0Buff[index] = NULL; + } + } + if (NULL != devpriv->dma0Chain) { + pci_free_consistent(devpriv->pci_dev, + sizeof(struct plx_dma_desc) * DMA_CHAIN_COUNT, + devpriv->dma0Chain, devpriv->dma0ChainPhysAddr); + devpriv->dma0Chain = NULL; + } +#endif /* USE_DMA */ + + /* release IRQ */ + if (dev->irq) { + /* disable interrupt controller */ + RtdPlxInterruptWrite(dev, RtdPlxInterruptRead(dev) + & ~(ICS_PLIE | ICS_DMA0_E | ICS_DMA1_E)); + comedi_free_irq(dev->irq, dev); + } + + /* release all regions that were allocated */ + if (devpriv->las0) { + iounmap(devpriv->las0); + } + if (devpriv->las1) { + iounmap(devpriv->las1); + } + if (devpriv->lcfg) { + iounmap(devpriv->lcfg); + } + if (devpriv->pci_dev) { + if (devpriv->got_regions) { + comedi_pci_disable(devpriv->pci_dev); + } + pci_dev_put(devpriv->pci_dev); + } + } + + printk("comedi%d: rtd520: removed.\n", dev->minor); + + return 0; +} + +/* + Convert a single comedi channel-gain entry to a RTD520 table entry +*/ +static unsigned short rtdConvertChanGain(comedi_device * dev, + unsigned int comediChan, int chanIndex) +{ /* index in channel list */ + unsigned int chan, range, aref; + unsigned short r = 0; + + chan = CR_CHAN(comediChan); + range = CR_RANGE(comediChan); + aref = CR_AREF(comediChan); + + r |= chan & 0xf; + + /* Note: we also setup the channel list bipolar flag array */ + if (range < thisboard->range10Start) { /* first batch are +-5 */ + r |= 0x000; /* +-5 range */ + r |= (range & 0x7) << 4; /* gain */ + CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex); + } else if (range < thisboard->rangeUniStart) { /* second batch are +-10 */ + r |= 0x100; /* +-10 range */ + r |= ((range - thisboard->range10Start) & 0x7) << 4; /* gain */ + CHAN_ARRAY_SET(devpriv->chanBipolar, chanIndex); + } else { /* last batch is +10 */ + r |= 0x200; /* +10 range */ + r |= ((range - thisboard->rangeUniStart) & 0x7) << 4; /* gain */ + CHAN_ARRAY_CLEAR(devpriv->chanBipolar, chanIndex); + } + + switch (aref) { + case AREF_GROUND: /* on-board ground */ + break; + + case AREF_COMMON: + r |= 0x80; /* ref external analog common */ + break; + + case AREF_DIFF: + r |= 0x400; /* differential inputs */ + break; + + case AREF_OTHER: /* ??? */ + break; + } + /*printk ("chan=%d r=%d a=%d -> 0x%x\n", + chan, range, aref, r); */ + return r; +} + +/* + Setup the channel-gain table from a comedi list +*/ +static void rtd_load_channelgain_list(comedi_device * dev, + unsigned int n_chan, unsigned int *list) +{ + if (n_chan > 1) { /* setup channel gain table */ + int ii; + RtdClearCGT(dev); + RtdEnableCGT(dev, 1); /* enable table */ + for (ii = 0; ii < n_chan; ii++) { + RtdWriteCGTable(dev, rtdConvertChanGain(dev, list[ii], + ii)); + } + } else { /* just use the channel gain latch */ + RtdEnableCGT(dev, 0); /* disable table, enable latch */ + RtdWriteCGLatch(dev, rtdConvertChanGain(dev, list[0], 0)); + } +} + +/* determine fifo size by doing adc conversions until the fifo half +empty status flag clears */ +static int rtd520_probe_fifo_depth(comedi_device *dev) +{ + lsampl_t chanspec = CR_PACK(0, 0, AREF_GROUND); + unsigned i; + static const unsigned limit = 0x2000; + unsigned fifo_size = 0; + + RtdAdcClearFifo(dev); + rtd_load_channelgain_list(dev, 1, &chanspec); + RtdAdcConversionSource(dev, 0); /* software */ + /* convert samples */ + for (i = 0; i < limit; ++i) { + unsigned fifo_status; + /* trigger conversion */ + RtdAdcStart(dev); + comedi_udelay(1); + fifo_status = RtdFifoStatus(dev); + if((fifo_status & FS_ADC_HEMPTY) == 0) { + fifo_size = 2 * i; + break; + } + } + if(i == limit) + { + rt_printk("\ncomedi: %s: failed to probe fifo size.\n", DRV_NAME); + return -EIO; + } + RtdAdcClearFifo(dev); + if(fifo_size != 0x400 || fifo_size != 0x2000) + { + rt_printk("\ncomedi: %s: unexpected fifo size of %i, expected 1024 or 8192.\n", + DRV_NAME, fifo_size); + return -EIO; + } + return fifo_size; +} + +/* + "instructions" read/write data in "one-shot" or "software-triggered" + mode (simplest case). + This doesnt use interrupts. + + Note, we don't do any settling delays. Use a instruction list to + select, delay, then read. + */ +static int rtd_ai_rinsn(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + int n, ii; + int stat; + + /* clear any old fifo data */ + RtdAdcClearFifo(dev); + + /* write channel to multiplexer and clear channel gain table */ + rtd_load_channelgain_list(dev, 1, &insn->chanspec); + + /* set conversion source */ + RtdAdcConversionSource(dev, 0); /* software */ + + /* convert n samples */ + for (n = 0; n < insn->n; n++) { + s16 d; + /* trigger conversion */ + RtdAdcStart(dev); + + for (ii = 0; ii < RTD_ADC_TIMEOUT; ++ii) { + stat = RtdFifoStatus(dev); + if (stat & FS_ADC_NOT_EMPTY) /* 1 -> not empty */ + break; + WAIT_QUIETLY; + } + if (ii >= RTD_ADC_TIMEOUT) { + DPRINTK("rtd520: Error: ADC never finished! FifoStatus=0x%x\n", stat ^ 0x6666); + return -ETIMEDOUT; + } + + /* read data */ + d = RtdAdcFifoGet(dev); /* get 2s comp value */ + /*printk ("rtd520: Got 0x%x after %d usec\n", d, ii+1); */ + d = d >> 3; /* low 3 bits are marker lines */ + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, 0)) { + data[n] = d + 2048; /* convert to comedi unsigned data */ + } else { + data[n] = d; + } + } + + /* return the number of samples read/written */ + return n; +} + +/* + Get what we know is there.... Fast! + This uses 1/2 the bus cycles of read_dregs (below). + + The manual claims that we can do a lword read, but it doesn't work here. +*/ +static int ai_read_n(comedi_device * dev, comedi_subdevice * s, int count) +{ + int ii; + + for (ii = 0; ii < count; ii++) { + sampl_t sample; + s16 d; + + if (0 == devpriv->aiCount) { /* done */ + d = RtdAdcFifoGet(dev); /* Read N and discard */ + continue; + } +#if 0 + if (0 == (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY)) { /* DEBUG */ + DPRINTK("comedi: READ OOPS on %d of %d\n", ii + 1, + count); + break; + } +#endif + d = RtdAdcFifoGet(dev); /* get 2s comp value */ + + d = d >> 3; /* low 3 bits are marker lines */ + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) { + sample = d + 2048; /* convert to comedi unsigned data */ + } else { + sample = d; + } + if (!comedi_buf_put(s->async, sample)) + return -1; + + if (devpriv->aiCount > 0) /* < 0, means read forever */ + devpriv->aiCount--; + } + return 0; +} + +/* + unknown amout of data is waiting in fifo. +*/ +static int ai_read_dregs(comedi_device * dev, comedi_subdevice * s) +{ + while (RtdFifoStatus(dev) & FS_ADC_NOT_EMPTY) { /* 1 -> not empty */ + sampl_t sample; + s16 d = RtdAdcFifoGet(dev); /* get 2s comp value */ + + if (0 == devpriv->aiCount) { /* done */ + continue; /* read rest */ + } + + d = d >> 3; /* low 3 bits are marker lines */ + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) { + sample = d + 2048; /* convert to comedi unsigned data */ + } else { + sample = d; + } + if (!comedi_buf_put(s->async, sample)) + return -1; + + if (devpriv->aiCount > 0) /* < 0, means read forever */ + devpriv->aiCount--; + } + return 0; +} + +#ifdef USE_DMA +/* + Terminate a DMA transfer and wait for everything to quiet down +*/ +void abort_dma(comedi_device * dev, unsigned int channel) +{ /* DMA channel 0, 1 */ + unsigned long dma_cs_addr; /* the control/status register */ + uint8_t status; + unsigned int ii; + //unsigned long flags; + + dma_cs_addr = (unsigned long)devpriv->lcfg + + ((channel == 0) ? LCFG_DMACSR0 : LCFG_DMACSR1); + + // spinlock for plx dma control/status reg + //comedi_spin_lock_irqsave( &dev->spinlock, flags ); + + // abort dma transfer if necessary + status = readb(dma_cs_addr); + if ((status & PLX_DMA_EN_BIT) == 0) { /* not enabled (Error?) */ + DPRINTK("rtd520: AbortDma on non-active channel %d (0x%x)\n", + channel, status); + goto abortDmaExit; + } + + /* wait to make sure done bit is zero (needed?) */ + for (ii = 0; (status & PLX_DMA_DONE_BIT) && ii < RTD_DMA_TIMEOUT; ii++) { + WAIT_QUIETLY; + status = readb(dma_cs_addr); + } + if (status & PLX_DMA_DONE_BIT) { + printk("rtd520: Timeout waiting for dma %i done clear\n", + channel); + goto abortDmaExit; + } + + /* disable channel (required) */ + writeb(0, dma_cs_addr); + comedi_udelay(1); /* needed?? */ + /* set abort bit for channel */ + writeb(PLX_DMA_ABORT_BIT, dma_cs_addr); + + // wait for dma done bit to be set + status = readb(dma_cs_addr); + for (ii = 0; + (status & PLX_DMA_DONE_BIT) == 0 && ii < RTD_DMA_TIMEOUT; + ii++) { + status = readb(dma_cs_addr); + WAIT_QUIETLY; + } + if ((status & PLX_DMA_DONE_BIT) == 0) { + printk("rtd520: Timeout waiting for dma %i done set\n", + channel); + } + + abortDmaExit: + //comedi_spin_unlock_irqrestore( &dev->spinlock, flags ); +} + +/* + Process what is in the DMA transfer buffer and pass to comedi + Note: this is not re-entrant +*/ +static int ai_process_dma(comedi_device * dev, comedi_subdevice * s) +{ + int ii, n; + s16 *dp; + + if (devpriv->aiCount == 0) /* transfer already complete */ + return 0; + + dp = devpriv->dma0Buff[devpriv->dma0Offset]; + for (ii = 0; ii < devpriv->fifoLen / 2;) { /* convert samples */ + sampl_t sample; + + if (CHAN_ARRAY_TEST(devpriv->chanBipolar, s->async->cur_chan)) { + sample = (*dp >> 3) + 2048; /* convert to comedi unsigned data */ + } else { + sample = *dp >> 3; /* low 3 bits are marker lines */ + } + *dp++ = sample; /* put processed value back */ + + if (++s->async->cur_chan >= s->async->cmd.chanlist_len) + s->async->cur_chan = 0; + + ++ii; /* number ready to transfer */ + if (devpriv->aiCount > 0) { /* < 0, means read forever */ + if (--devpriv->aiCount == 0) { /* done */ + /*DPRINTK ("rtd520: Final %d samples\n", ii); */ + break; + } + } + } + + /* now pass the whole array to the comedi buffer */ + dp = devpriv->dma0Buff[devpriv->dma0Offset]; + n = comedi_buf_write_alloc(s->async, ii * sizeof(s16)); + if (n < (ii * sizeof(s16))) { /* any residual is an error */ + DPRINTK("rtd520:ai_process_dma buffer overflow %d samples!\n", + ii - (n / sizeof(s16))); + s->async->events |= COMEDI_CB_ERROR; + return -1; + } + comedi_buf_memcpy_to(s->async, 0, dp, n); + comedi_buf_write_free(s->async, n); + + /* always at least 1 scan -- 1/2 FIFO is larger than our max scan list */ + s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; + + if (++devpriv->dma0Offset >= DMA_CHAIN_COUNT) { /* next buffer */ + devpriv->dma0Offset = 0; + } + return 0; +} +#endif /* USE_DMA */ + +/* + Handle all rtd520 interrupts. + Runs atomically and is never re-entered. + This is a "slow handler"; other interrupts may be active. + The data conversion may someday happen in a "bottom half". +*/ +static irqreturn_t rtd_interrupt(int irq, /* interrupt number (ignored) */ + void *d /* our data */ + PT_REGS_ARG) +{ /* cpu context (ignored) */ + comedi_device *dev = d; /* must be called "dev" for devpriv */ + u16 status; + u16 fifoStatus; + comedi_subdevice *s = dev->subdevices + 0; /* analog in subdevice */ + + if (!dev->attached) { + return IRQ_NONE; + } + + devpriv->intCount++; /* DEBUG statistics */ + + fifoStatus = RtdFifoStatus(dev); + /* check for FIFO full, this automatically halts the ADC! */ + if (!(fifoStatus & FS_ADC_NOT_FULL)) { /* 0 -> full */ + DPRINTK("rtd520: FIFO full! fifo_status=0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); /* should be all 0s */ + goto abortTransfer; + } +#ifdef USE_DMA + if (devpriv->flags & DMA0_ACTIVE) { /* Check DMA */ + u32 istatus = RtdPlxInterruptRead(dev); + + if (istatus & ICS_DMA0_A) { + if (ai_process_dma(dev, s) < 0) { + DPRINTK("rtd520: comedi read buffer overflow (DMA) with %ld to go!\n", devpriv->aiCount); + RtdDma0Control(dev, + (devpriv-> + dma0Control & + ~PLX_DMA_START_BIT) + | PLX_CLEAR_DMA_INTR_BIT); + goto abortTransfer; + } + + /*DPRINTK ("rtd520: DMA transfer: %ld to go, istatus %x\n", + devpriv->aiCount, istatus); */ + RtdDma0Control(dev, + (devpriv->dma0Control & ~PLX_DMA_START_BIT) + | PLX_CLEAR_DMA_INTR_BIT); + if (0 == devpriv->aiCount) { /* counted down */ + DPRINTK("rtd520: Samples Done (DMA).\n"); + goto transferDone; + } + comedi_event(dev, s); + } else { + /*DPRINTK ("rtd520: No DMA ready: istatus %x\n", istatus); */ + } + } + /* Fall through and check for other interrupt sources */ +#endif /* USE_DMA */ + + status = RtdInterruptStatus(dev); + /* if interrupt was not caused by our board, or handled above */ + if (0 == status) { + return IRQ_HANDLED; + } + + if (status & IRQM_ADC_ABOUT_CNT) { /* sample count -> read FIFO */ + /* since the priority interrupt controller may have queued a sample + counter interrupt, even though we have already finished, + we must handle the possibility that there is no data here */ + if (!(fifoStatus & FS_ADC_HEMPTY)) { /* 0 -> 1/2 full */ + /*DPRINTK("rtd520: Sample int, reading 1/2FIFO. fifo_status 0x%x\n", + (fifoStatus ^ 0x6666) & 0x7777); */ + if (ai_read_n(dev, s, devpriv->fifoLen / 2) < 0) { + DPRINTK("rtd520: comedi read buffer overflow (1/2FIFO) with %ld to go!\n", devpriv->aiCount); + goto abortTransfer; + } + if (0 == devpriv->aiCount) { /* counted down */ + DPRINTK("rtd520: Samples Done (1/2). fifo_status was 0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); /* should be all 0s */ + goto transferDone; + } + comedi_event(dev, s); + } else if (devpriv->transCount > 0) { /* read often */ + /*DPRINTK("rtd520: Sample int, reading %d fifo_status 0x%x\n", + devpriv->transCount, (fifoStatus ^ 0x6666) & 0x7777); */ + if (fifoStatus & FS_ADC_NOT_EMPTY) { /* 1 -> not empty */ + if (ai_read_n(dev, s, devpriv->transCount) < 0) { + DPRINTK("rtd520: comedi read buffer overflow (N) with %ld to go!\n", devpriv->aiCount); + goto abortTransfer; + } + if (0 == devpriv->aiCount) { /* counted down */ + DPRINTK("rtd520: Samples Done (N). fifo_status was 0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); + goto transferDone; + } + comedi_event(dev, s); + } + } else { /* wait for 1/2 FIFO (old) */ + DPRINTK("rtd520: Sample int. Wait for 1/2. fifo_status 0x%x\n", (fifoStatus ^ 0x6666) & 0x7777); + } + } else { + DPRINTK("rtd520: unknown interrupt source!\n"); + } + + if (0xffff & RtdInterruptOverrunStatus(dev)) { /* interrupt overrun */ + DPRINTK("rtd520: Interrupt overrun with %ld to go! over_status=0x%x\n", devpriv->aiCount, 0xffff & RtdInterruptOverrunStatus(dev)); + goto abortTransfer; + } + + /* clear the interrupt */ + RtdInterruptClearMask(dev, status); + RtdInterruptClear(dev); + return IRQ_HANDLED; + + abortTransfer: + RtdAdcClearFifo(dev); /* clears full flag */ + s->async->events |= COMEDI_CB_ERROR; + devpriv->aiCount = 0; /* stop and don't transfer any more */ + /* fall into transferDone */ + + transferDone: + RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ + RtdPacerStop(dev); /* Stop PACER */ + RtdAdcConversionSource(dev, 0); /* software trigger only */ + RtdInterruptMask(dev, 0); /* mask out SAMPLE */ +#ifdef USE_DMA + if (devpriv->flags & DMA0_ACTIVE) { + RtdPlxInterruptWrite(dev, /* disable any more interrupts */ + RtdPlxInterruptRead(dev) & ~ICS_DMA0_E); + abort_dma(dev, 0); + devpriv->flags &= ~DMA0_ACTIVE; + /* if Using DMA, then we should have read everything by now */ + if (devpriv->aiCount > 0) { + DPRINTK("rtd520: Lost DMA data! %ld remain\n", + devpriv->aiCount); + } + } +#endif /* USE_DMA */ + + if (devpriv->aiCount > 0) { /* there shouldn't be anything left */ + fifoStatus = RtdFifoStatus(dev); + DPRINTK("rtd520: Finishing up. %ld remain, fifoStat=%x\n", devpriv->aiCount, (fifoStatus ^ 0x6666) & 0x7777); /* should read all 0s */ + ai_read_dregs(dev, s); /* read anything left in FIFO */ + } + + s->async->events |= COMEDI_CB_EOA; /* signal end to comedi */ + comedi_event(dev, s); + + /* clear the interrupt */ + status = RtdInterruptStatus(dev); + RtdInterruptClearMask(dev, status); + RtdInterruptClear(dev); + + fifoStatus = RtdFifoStatus(dev); /* DEBUG */ + DPRINTK("rtd520: Acquisition complete. %ld ints, intStat=%x, overStat=%x\n", devpriv->intCount, status, 0xffff & RtdInterruptOverrunStatus(dev)); + + return IRQ_HANDLED; +} + +#if 0 +/* + return the number of samples available +*/ +static int rtd_ai_poll(comedi_device * dev, comedi_subdevice * s) +{ + /* TODO: This needs to mask interrupts, read_dregs, and then re-enable */ + /* Not sure what to do if DMA is active */ + return s->async->buf_write_count - s->async->buf_read_count; +} +#endif + +/* + cmdtest tests a particular command to see if it is valid. + Using the cmdtest ioctl, a user can create a valid cmd + and then have it executed by the cmd ioctl (asyncronously). + + cmdtest returns 1,2,3,4 or 0, depending on which tests + the command passes. +*/ + +static int rtd_ai_cmdtest(comedi_device * dev, + comedi_subdevice * s, comedi_cmd * cmd) +{ + int err = 0; + int tmp; + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW; + 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_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) { + err++; + } + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) { + err++; + } + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) { + err++; + } + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique + and mutually compatible */ + /* note that mutual compatiblity is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT) { + err++; + } + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) { + err++; + } + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) { + err++; + } + + if (err) { + return 2; + } + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* Note: these are time periods, not actual rates */ + if (1 == cmd->chanlist_len) { /* no scanning */ + if (cmd->scan_begin_arg < RTD_MAX_SPEED_1) { + cmd->scan_begin_arg = RTD_MAX_SPEED_1; + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_UP); + err++; + } + if (cmd->scan_begin_arg > RTD_MIN_SPEED_1) { + cmd->scan_begin_arg = RTD_MIN_SPEED_1; + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_DOWN); + err++; + } + } else { + if (cmd->scan_begin_arg < RTD_MAX_SPEED) { + cmd->scan_begin_arg = RTD_MAX_SPEED; + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_UP); + err++; + } + if (cmd->scan_begin_arg > RTD_MIN_SPEED) { + cmd->scan_begin_arg = RTD_MIN_SPEED; + rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_DOWN); + err++; + } + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ + if (cmd->scan_begin_arg > 9) { + cmd->scan_begin_arg = 9; + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + if (1 == cmd->chanlist_len) { /* no scanning */ + if (cmd->convert_arg < RTD_MAX_SPEED_1) { + cmd->convert_arg = RTD_MAX_SPEED_1; + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_UP); + err++; + } + if (cmd->convert_arg > RTD_MIN_SPEED_1) { + cmd->convert_arg = RTD_MIN_SPEED_1; + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_DOWN); + err++; + } + } else { + if (cmd->convert_arg < RTD_MAX_SPEED) { + cmd->convert_arg = RTD_MAX_SPEED; + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_UP); + err++; + } + if (cmd->convert_arg > RTD_MIN_SPEED) { + cmd->convert_arg = RTD_MIN_SPEED; + rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_DOWN); + err++; + } + } + } else { + /* external trigger */ + /* see above */ + if (cmd->convert_arg > 9) { + cmd->convert_arg = 9; + err++; + } + } + +#if 0 + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } +#endif + if (cmd->stop_src == TRIG_COUNT) { + /* TODO check for rounding error due to counter wrap */ + + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) { + return 3; + } + + /* step 4: fix up any arguments */ + + if (cmd->chanlist_len > RTD_MAX_CHANLIST) { + cmd->chanlist_len = RTD_MAX_CHANLIST; + err++; + } + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + rtd_ns_to_timer(&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) { + err++; + } + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + rtd_ns_to_timer(&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) { + err++; + } + if (cmd->scan_begin_src == TRIG_TIMER + && (cmd->scan_begin_arg + < (cmd->convert_arg * cmd->scan_end_arg))) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if (err) { + return 4; + } + + return 0; +} + +/* + Execute a analog in command with many possible triggering options. + The data get stored in the async structure of the subdevice. + This is usually done by an interrupt handler. + Userland gets to the data using read calls. +*/ +static int rtd_ai_cmd(comedi_device * dev, comedi_subdevice * s) +{ + comedi_cmd *cmd = &s->async->cmd; + int timer; + + /* stop anything currently running */ + RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ + RtdPacerStop(dev); /* make sure PACER is stopped */ + RtdAdcConversionSource(dev, 0); /* software trigger only */ + RtdInterruptMask(dev, 0); +#ifdef USE_DMA + if (devpriv->flags & DMA0_ACTIVE) { /* cancel anything running */ + RtdPlxInterruptWrite(dev, /* disable any more interrupts */ + RtdPlxInterruptRead(dev) & ~ICS_DMA0_E); + abort_dma(dev, 0); + devpriv->flags &= ~DMA0_ACTIVE; + if (RtdPlxInterruptRead(dev) & ICS_DMA0_A) { /*clear pending int */ + RtdDma0Control(dev, PLX_CLEAR_DMA_INTR_BIT); + } + } + RtdDma0Reset(dev); /* reset onboard state */ +#endif /* USE_DMA */ + RtdAdcClearFifo(dev); /* clear any old data */ + RtdInterruptOverrunClear(dev); + devpriv->intCount = 0; + + if (!dev->irq) { /* we need interrupts for this */ + DPRINTK("rtd520: ERROR! No interrupt available!\n"); + return -ENXIO; + } + + /* start configuration */ + /* load channel list and reset CGT */ + rtd_load_channelgain_list(dev, cmd->chanlist_len, cmd->chanlist); + + /* setup the common case and override if needed */ + if (cmd->chanlist_len > 1) { + /*DPRINTK ("rtd520: Multi channel setup\n"); */ + RtdPacerStartSource(dev, 0); /* software triggers pacer */ + RtdBurstStartSource(dev, 1); /* PACER triggers burst */ + RtdAdcConversionSource(dev, 2); /* BURST triggers ADC */ + } else { /* single channel */ + /*DPRINTK ("rtd520: single channel setup\n"); */ + RtdPacerStartSource(dev, 0); /* software triggers pacer */ + RtdAdcConversionSource(dev, 1); /* PACER triggers ADC */ + } + RtdAboutCounter(dev, devpriv->fifoLen / 2 - 1); /* 1/2 FIFO */ + + if (TRIG_TIMER == cmd->scan_begin_src) { + /* scan_begin_arg is in nanoseconds */ + /* find out how many samples to wait before transferring */ + if (cmd->flags & TRIG_WAKE_EOS) { + /* this may generate un-sustainable interrupt rates */ + /* the application is responsible for doing the right thing */ + devpriv->transCount = cmd->chanlist_len; + devpriv->flags |= SEND_EOS; + } else { + /* arrange to transfer data periodically */ + devpriv->transCount + = + (TRANS_TARGET_PERIOD * cmd->chanlist_len) / + cmd->scan_begin_arg; + if (devpriv->transCount < cmd->chanlist_len) { + /* tranfer after each scan (and avoid 0) */ + devpriv->transCount = cmd->chanlist_len; + } else { /* make a multiple of scan length */ + devpriv->transCount = + (devpriv->transCount + + cmd->chanlist_len - 1) + / cmd->chanlist_len; + devpriv->transCount *= cmd->chanlist_len; + } + devpriv->flags |= SEND_EOS; + } + if (devpriv->transCount >= (devpriv->fifoLen / 2)) { + /* out of counter range, use 1/2 fifo instead */ + devpriv->transCount = 0; + devpriv->flags &= ~SEND_EOS; + } else { + /* interrupt for each tranfer */ + RtdAboutCounter(dev, devpriv->transCount - 1); + } + + DPRINTK("rtd520: scanLen=%d tranferCount=%d fifoLen=%d\n scanTime(ns)=%d flags=0x%x\n", cmd->chanlist_len, devpriv->transCount, devpriv->fifoLen, cmd->scan_begin_arg, devpriv->flags); + } else { /* unknown timing, just use 1/2 FIFO */ + devpriv->transCount = 0; + devpriv->flags &= ~SEND_EOS; + } + RtdPacerClockSource(dev, 1); /* use INTERNAL 8Mhz clock source */ + RtdAboutStopEnable(dev, 1); /* just interrupt, dont stop */ + + /* BUG??? these look like enumerated values, but they are bit fields */ + + /* First, setup when to stop */ + switch (cmd->stop_src) { + case TRIG_COUNT: /* stop after N scans */ + devpriv->aiCount = cmd->stop_arg * cmd->chanlist_len; + if ((devpriv->transCount > 0) + && (devpriv->transCount > devpriv->aiCount)) { + devpriv->transCount = devpriv->aiCount; + } + break; + + case TRIG_NONE: /* stop when cancel is called */ + devpriv->aiCount = -1; /* read forever */ + break; + + default: + DPRINTK("rtd520: Warning! ignoring stop_src mode %d\n", + cmd->stop_src); + } + + /* Scan timing */ + switch (cmd->scan_begin_src) { + case TRIG_TIMER: /* periodic scanning */ + timer = rtd_ns_to_timer(&cmd->scan_begin_arg, + TRIG_ROUND_NEAREST); + /* set PACER clock */ + /*DPRINTK ("rtd520: loading %d into pacer\n", timer); */ + RtdPacerCounter(dev, timer); + + break; + + case TRIG_EXT: + RtdPacerStartSource(dev, 1); /* EXTERNALy trigger pacer */ + break; + + default: + DPRINTK("rtd520: Warning! ignoring scan_begin_src mode %d\n", + cmd->scan_begin_src); + } + + /* Sample timing within a scan */ + switch (cmd->convert_src) { + case TRIG_TIMER: /* periodic */ + if (cmd->chanlist_len > 1) { /* only needed for multi-channel */ + timer = rtd_ns_to_timer(&cmd->convert_arg, + TRIG_ROUND_NEAREST); + /* setup BURST clock */ + /*DPRINTK ("rtd520: loading %d into burst\n", timer); */ + RtdBurstCounter(dev, timer); + } + + break; + + case TRIG_EXT: /* external */ + RtdBurstStartSource(dev, 2); /* EXTERNALy trigger burst */ + break; + + default: + DPRINTK("rtd520: Warning! ignoring convert_src mode %d\n", + cmd->convert_src); + } + /* end configuration */ + + /* This doesn't seem to work. There is no way to clear an interrupt + that the priority controller has queued! */ + RtdInterruptClearMask(dev, ~0); /* clear any existing flags */ + RtdInterruptClear(dev); + + /* TODO: allow multiple interrupt sources */ + if (devpriv->transCount > 0) { /* transfer every N samples */ + RtdInterruptMask(dev, IRQM_ADC_ABOUT_CNT); + DPRINTK("rtd520: Transferring every %d\n", devpriv->transCount); + } else { /* 1/2 FIFO transfers */ +#ifdef USE_DMA + devpriv->flags |= DMA0_ACTIVE; + + /* point to first transfer in ring */ + devpriv->dma0Offset = 0; + RtdDma0Mode(dev, DMA_MODE_BITS); + RtdDma0Next(dev, /* point to first block */ + devpriv->dma0Chain[DMA_CHAIN_COUNT - 1].next); + RtdDma0Source(dev, DMAS_ADFIFO_HALF_FULL); /* set DMA trigger source */ + + RtdPlxInterruptWrite(dev, /* enable interrupt */ + RtdPlxInterruptRead(dev) | ICS_DMA0_E); + /* Must be 2 steps. See PLX app note about "Starting a DMA transfer" */ + RtdDma0Control(dev, PLX_DMA_EN_BIT); /* enable DMA (clear INTR?) */ + RtdDma0Control(dev, PLX_DMA_EN_BIT | PLX_DMA_START_BIT); /*start DMA */ + DPRINTK("rtd520: Using DMA0 transfers. plxInt %x RtdInt %x\n", + RtdPlxInterruptRead(dev), devpriv->intMask); +#else /* USE_DMA */ + RtdInterruptMask(dev, IRQM_ADC_ABOUT_CNT); + DPRINTK("rtd520: Transferring every 1/2 FIFO\n"); +#endif /* USE_DMA */ + } + + /* BUG: start_src is ASSUMED to be TRIG_NOW */ + /* BUG? it seems like things are running before the "start" */ + RtdPacerStart(dev); /* Start PACER */ + return 0; +} + +/* + Stop a running data aquisition. +*/ +static int rtd_ai_cancel(comedi_device * dev, comedi_subdevice * s) +{ + u16 status; + + RtdPacerStopSource(dev, 0); /* stop on SOFTWARE stop */ + RtdPacerStop(dev); /* Stop PACER */ + RtdAdcConversionSource(dev, 0); /* software trigger only */ + RtdInterruptMask(dev, 0); + devpriv->aiCount = 0; /* stop and don't transfer any more */ +#ifdef USE_DMA + if (devpriv->flags & DMA0_ACTIVE) { + RtdPlxInterruptWrite(dev, /* disable any more interrupts */ + RtdPlxInterruptRead(dev) & ~ICS_DMA0_E); + abort_dma(dev, 0); + devpriv->flags &= ~DMA0_ACTIVE; + } +#endif /* USE_DMA */ + status = RtdInterruptStatus(dev); + DPRINTK("rtd520: Acquisition canceled. %ld ints, intStat=%x, overStat=%x\n", devpriv->intCount, status, 0xffff & RtdInterruptOverrunStatus(dev)); + return 0; +} + +/* + Given a desired period and the clock period (both in ns), + return the proper counter value (divider-1). + Sets the original period to be the true value. + Note: you have to check if the value is larger than the counter range! +*/ +static int rtd_ns_to_timer_base(unsigned int *nanosec, /* desired period (in ns) */ + int round_mode, int base) +{ /* clock period (in ns) */ + int divider; + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + if (divider < 2) + divider = 2; /* min is divide by 2 */ + + /* Note: we don't check for max, because different timers + have different ranges */ + + *nanosec = base * divider; + return divider - 1; /* countdown is divisor+1 */ +} + +/* + Given a desired period (in ns), + return the proper counter value (divider-1) for the internal clock. + Sets the original period to be the true value. +*/ +static int rtd_ns_to_timer(unsigned int *ns, int round_mode) +{ + return rtd_ns_to_timer_base(ns, round_mode, RTD_CLOCK_BASE); +} + +/* + Output one (or more) analog values to a single port as fast as possible. +*/ +static int rtd_ao_winsn(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + int range = CR_RANGE(insn->chanspec); + + /* Configure the output range (table index matches the range values) */ + RtdDacRange(dev, chan, range); + + /* Writing a list of values to an AO channel is probably not + * very useful, but that's how the interface is defined. */ + for (i = 0; i < insn->n; ++i) { + int val = data[i] << 3; + int stat = 0; /* initialize to avoid bogus warning */ + int ii; + + /* VERIFY: comedi range and offset conversions */ + + if ((range > 1) /* bipolar */ + &&(data[i] < 2048)) { + /* offset and sign extend */ + val = (((int)data[i]) - 2048) << 3; + } else { /* unipolor */ + val = data[i] << 3; + } + + DPRINTK("comedi: rtd520 DAC chan=%d range=%d writing %d as 0x%x\n", chan, range, data[i], val); + + /* a typical programming sequence */ + RtdDacFifoPut(dev, chan, val); /* put the value in */ + RtdDacUpdate(dev, chan); /* trigger the conversion */ + + devpriv->aoValue[chan] = data[i]; /* save for read back */ + + for (ii = 0; ii < RTD_DAC_TIMEOUT; ++ii) { + stat = RtdFifoStatus(dev); + /* 1 -> not empty */ + if (stat & ((0 == chan) ? FS_DAC1_NOT_EMPTY : + FS_DAC2_NOT_EMPTY)) + break; + WAIT_QUIETLY; + } + if (ii >= RTD_DAC_TIMEOUT) { + DPRINTK("rtd520: Error: DAC never finished! FifoStatus=0x%x\n", stat ^ 0x6666); + return -ETIMEDOUT; + } + } + + /* return the number of samples read/written */ + return i; +} + +/* AO subdevices should have a read insn as well as a write insn. + * Usually this means copying a value stored in devpriv. */ +static int rtd_ao_rinsn(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + + for (i = 0; i < insn->n; i++) { + data[i] = devpriv->aoValue[chan]; + } + + return i; +} + +/* + Write a masked set of bits and the read back the port. + We track what the bits should be (i.e. we don't read the port first). + + DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The + * comedi core can convert between insn_bits and insn_read/write + */ +static int rtd_dio_insn_bits(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + if (insn->n != 2) + return -EINVAL; + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + if (data[0]) { + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + + /* Write out the new digital output lines */ + RtdDio0Write(dev, s->state); + } + /* on return, data[1] contains the value of the digital + * input lines. */ + data[1] = RtdDio0Read(dev); + + /*DPRINTK("rtd520:port_0 wrote: 0x%x read: 0x%x\n", s->state, data[1]); */ + + return 2; +} + +/* + Configure one bit on a IO port as Input or Output (hence the name :-). +*/ +static int rtd_dio_insn_config(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + int chan = CR_CHAN(insn->chanspec); + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << chan; /* 1 means Out */ + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s-> + io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + return insn->n; + break; + default: + return -EINVAL; + } + + DPRINTK("rtd520: port_0_direction=0x%x (1 means out)\n", s->io_bits); + /* TODO support digital match interrupts and strobes */ + RtdDioStatusWrite(dev, 0x01); /* make Dio0Ctrl point to direction */ + RtdDio0CtrlWrite(dev, s->io_bits); /* set direction 1 means Out */ + RtdDioStatusWrite(dev, 0); /* make Dio0Ctrl clear interrupts */ + + /* port1 can only be all input or all output */ + + /* there are also 2 user input lines and 2 user output lines */ + + return 1; +} + +/* + * A convenient macro that defines init_module() and cleanup_module(), + * as necessary. + */ +COMEDI_PCI_INITCLEANUP(rtd520Driver, rtd520_pci_table); diff --git a/drivers/staging/comedi/drivers/rtd520.h b/drivers/staging/comedi/drivers/rtd520.h new file mode 100644 index 00000000000..0eb50b8e605 --- /dev/null +++ b/drivers/staging/comedi/drivers/rtd520.h @@ -0,0 +1,412 @@ +/* + comedi/drivers/rtd520.h + Comedi driver defines for Real Time Devices (RTD) PCI4520/DM7520 + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2001 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. +*/ + +/* + Created by Dan Christian, NASA Ames Research Center. + See board notes in rtd520.c +*/ + +/* + LAS0 Runtime Area + Local Address Space 0 Offset Read Function Write Function +*/ +#define LAS0_SPARE_00 0x0000 // - - +#define LAS0_SPARE_04 0x0004 // - - +#define LAS0_USER_IO 0x0008 // Read User Inputs Write User Outputs +#define LAS0_SPARE_0C 0x000C // - - +#define LAS0_ADC 0x0010 // Read FIFO Status Software A/D Start +#define LAS0_DAC1 0x0014 // - Software D/A1 Update +#define LAS0_DAC2 0x0018 // - Software D/A2 Update +#define LAS0_SPARE_1C 0x001C // - - +#define LAS0_SPARE_20 0x0020 // - - +#define LAS0_DAC 0x0024 // - Software Simultaneous D/A1 and D/A2 Update +#define LAS0_PACER 0x0028 // Software Pacer Start Software Pacer Stop +#define LAS0_TIMER 0x002C // Read Timer Counters Status HDIN Software Trigger +#define LAS0_IT 0x0030 // Read Interrupt Status Write Interrupt Enable Mask Register +#define LAS0_CLEAR 0x0034 // Clear ITs set by Clear Mask Set Interrupt Clear Mask +#define LAS0_OVERRUN 0x0038 // Read pending interrupts Clear Overrun Register +#define LAS0_SPARE_3C 0x003C // - - + +/* + LAS0 Runtime Area Timer/Counter,Dig.IO + Name Local Address Function +*/ +#define LAS0_PCLK 0x0040 // Pacer Clock value (24bit) Pacer Clock load (24bit) +#define LAS0_BCLK 0x0044 // Burst Clock value (10bit) Burst Clock load (10bit) +#define LAS0_ADC_SCNT 0x0048 // A/D Sample counter value (10bit) A/D Sample counter load (10bit) +#define LAS0_DAC1_UCNT 0x004C // D/A1 Update counter value (10 bit) D/A1 Update counter load (10bit) +#define LAS0_DAC2_UCNT 0x0050 // D/A2 Update counter value (10 bit) D/A2 Update counter load (10bit) +#define LAS0_DCNT 0x0054 // Delay counter value (16 bit) Delay counter load (16bit) +#define LAS0_ACNT 0x0058 // About counter value (16 bit) About counter load (16bit) +#define LAS0_DAC_CLK 0x005C // DAC clock value (16bit) DAC clock load (16bit) +#define LAS0_UTC0 0x0060 // 8254 TC Counter 0 User TC 0 value Load count in TC Counter 0 +#define LAS0_UTC1 0x0064 // 8254 TC Counter 1 User TC 1 value Load count in TC Counter 1 +#define LAS0_UTC2 0x0068 // 8254 TC Counter 2 User TC 2 value Load count in TC Counter 2 +#define LAS0_UTC_CTRL 0x006C // 8254 TC Control Word Program counter mode for TC +#define LAS0_DIO0 0x0070 // Digital I/O Port 0 Read Port Digital I/O Port 0 Write Port +#define LAS0_DIO1 0x0074 // Digital I/O Port 1 Read Port Digital I/O Port 1 Write Port +#define LAS0_DIO0_CTRL 0x0078 // Clear digital IRQ status flag/read Clear digital chip/program Port 0 +#define LAS0_DIO_STATUS 0x007C // Read Digital I/O Status word Program digital control register & + +/* + LAS0 Setup Area + Name Local Address Function +*/ +#define LAS0_BOARD_RESET 0x0100 // Board reset +#define LAS0_DMA0_SRC 0x0104 // DMA 0 Sources select +#define LAS0_DMA1_SRC 0x0108 // DMA 1 Sources select +#define LAS0_ADC_CONVERSION 0x010C // A/D Conversion Signal select +#define LAS0_BURST_START 0x0110 // Burst Clock Start Trigger select +#define LAS0_PACER_START 0x0114 // Pacer Clock Start Trigger select +#define LAS0_PACER_STOP 0x0118 // Pacer Clock Stop Trigger select +#define LAS0_ACNT_STOP_ENABLE 0x011C // About Counter Stop Enable +#define LAS0_PACER_REPEAT 0x0120 // Pacer Start Trigger Mode select +#define LAS0_DIN_START 0x0124 // High Speed Digital Input Sampling Signal select +#define LAS0_DIN_FIFO_CLEAR 0x0128 // Digital Input FIFO Clear +#define LAS0_ADC_FIFO_CLEAR 0x012C // A/D FIFO Clear +#define LAS0_CGT_WRITE 0x0130 // Channel Gain Table Write +#define LAS0_CGL_WRITE 0x0134 // Channel Gain Latch Write +#define LAS0_CG_DATA 0x0138 // Digital Table Write +#define LAS0_CGT_ENABLE 0x013C // Channel Gain Table Enable +#define LAS0_CG_ENABLE 0x0140 // Digital Table Enable +#define LAS0_CGT_PAUSE 0x0144 // Table Pause Enable +#define LAS0_CGT_RESET 0x0148 // Reset Channel Gain Table +#define LAS0_CGT_CLEAR 0x014C // Clear Channel Gain Table +#define LAS0_DAC1_CTRL 0x0150 // D/A1 output type/range +#define LAS0_DAC1_SRC 0x0154 // D/A1 update source +#define LAS0_DAC1_CYCLE 0x0158 // D/A1 cycle mode +#define LAS0_DAC1_RESET 0x015C // D/A1 FIFO reset +#define LAS0_DAC1_FIFO_CLEAR 0x0160 // D/A1 FIFO clear +#define LAS0_DAC2_CTRL 0x0164 // D/A2 output type/range +#define LAS0_DAC2_SRC 0x0168 // D/A2 update source +#define LAS0_DAC2_CYCLE 0x016C // D/A2 cycle mode +#define LAS0_DAC2_RESET 0x0170 // D/A2 FIFO reset +#define LAS0_DAC2_FIFO_CLEAR 0x0174 // D/A2 FIFO clear +#define LAS0_ADC_SCNT_SRC 0x0178 // A/D Sample Counter Source select +#define LAS0_PACER_SELECT 0x0180 // Pacer Clock select +#define LAS0_SBUS0_SRC 0x0184 // SyncBus 0 Source select +#define LAS0_SBUS0_ENABLE 0x0188 // SyncBus 0 enable +#define LAS0_SBUS1_SRC 0x018C // SyncBus 1 Source select +#define LAS0_SBUS1_ENABLE 0x0190 // SyncBus 1 enable +#define LAS0_SBUS2_SRC 0x0198 // SyncBus 2 Source select +#define LAS0_SBUS2_ENABLE 0x019C // SyncBus 2 enable +#define LAS0_ETRG_POLARITY 0x01A4 // External Trigger polarity select +#define LAS0_EINT_POLARITY 0x01A8 // External Interrupt polarity select +#define LAS0_UTC0_CLOCK 0x01AC // UTC0 Clock select +#define LAS0_UTC0_GATE 0x01B0 // UTC0 Gate select +#define LAS0_UTC1_CLOCK 0x01B4 // UTC1 Clock select +#define LAS0_UTC1_GATE 0x01B8 // UTC1 Gate select +#define LAS0_UTC2_CLOCK 0x01BC // UTC2 Clock select +#define LAS0_UTC2_GATE 0x01C0 // UTC2 Gate select +#define LAS0_UOUT0_SELECT 0x01C4 // User Output 0 source select +#define LAS0_UOUT1_SELECT 0x01C8 // User Output 1 source select +#define LAS0_DMA0_RESET 0x01CC // DMA0 Request state machine reset +#define LAS0_DMA1_RESET 0x01D0 // DMA1 Request state machine reset + +/* + LAS1 + Name Local Address Function +*/ +#define LAS1_ADC_FIFO 0x0000 // Read A/D FIFO (16bit) - +#define LAS1_HDIO_FIFO 0x0004 // Read High Speed Digital Input FIFO (16bit) - +#define LAS1_DAC1_FIFO 0x0008 // - Write D/A1 FIFO (16bit) +#define LAS1_DAC2_FIFO 0x000C // - Write D/A2 FIFO (16bit) + +/* + LCFG: PLX 9080 local config & runtime registers + Name Local Address Function +*/ +#define LCFG_ITCSR 0x0068 // INTCSR, Interrupt Control/Status Register +#define LCFG_DMAMODE0 0x0080 // DMA Channel 0 Mode Register +#define LCFG_DMAPADR0 0x0084 // DMA Channel 0 PCI Address Register +#define LCFG_DMALADR0 0x0088 // DMA Channel 0 Local Address Reg +#define LCFG_DMASIZ0 0x008C // DMA Channel 0 Transfer Size (Bytes) Register +#define LCFG_DMADPR0 0x0090 // DMA Channel 0 Descriptor Pointer Register +#define LCFG_DMAMODE1 0x0094 // DMA Channel 1 Mode Register +#define LCFG_DMAPADR1 0x0098 // DMA Channel 1 PCI Address Register +#define LCFG_DMALADR1 0x009C // DMA Channel 1 Local Address Register +#define LCFG_DMASIZ1 0x00A0 // DMA Channel 1 Transfer Size (Bytes) Register +#define LCFG_DMADPR1 0x00A4 // DMA Channel 1 Descriptor Pointer Register +#define LCFG_DMACSR0 0x00A8 // DMA Channel 0 Command/Status Register +#define LCFG_DMACSR1 0x00A9 // DMA Channel 0 Command/Status Register +#define LCFG_DMAARB 0x00AC // DMA Arbitration Register +#define LCFG_DMATHR 0x00B0 // DMA Threshold Register + +/*====================================================================== + Resister bit definitions +======================================================================*/ + +// FIFO Status Word Bits (RtdFifoStatus) +#define FS_DAC1_NOT_EMPTY 0x0001 // D0 - DAC1 FIFO not empty +#define FS_DAC1_HEMPTY 0x0002 // D1 - DAC1 FIFO half empty +#define FS_DAC1_NOT_FULL 0x0004 // D2 - DAC1 FIFO not full +#define FS_DAC2_NOT_EMPTY 0x0010 // D4 - DAC2 FIFO not empty +#define FS_DAC2_HEMPTY 0x0020 // D5 - DAC2 FIFO half empty +#define FS_DAC2_NOT_FULL 0x0040 // D6 - DAC2 FIFO not full +#define FS_ADC_NOT_EMPTY 0x0100 // D8 - ADC FIFO not empty +#define FS_ADC_HEMPTY 0x0200 // D9 - ADC FIFO half empty +#define FS_ADC_NOT_FULL 0x0400 // D10 - ADC FIFO not full +#define FS_DIN_NOT_EMPTY 0x1000 // D12 - DIN FIFO not empty +#define FS_DIN_HEMPTY 0x2000 // D13 - DIN FIFO half empty +#define FS_DIN_NOT_FULL 0x4000 // D14 - DIN FIFO not full + +// Timer Status Word Bits (GetTimerStatus) +#define TS_PCLK_GATE 0x0001 +// D0 - Pacer Clock Gate [0 - gated, 1 - enabled] +#define TS_BCLK_GATE 0x0002 +// D1 - Burst Clock Gate [0 - disabled, 1 - running] +#define TS_DCNT_GATE 0x0004 +// D2 - Pacer Clock Delayed Start Trigger [0 - delay over, 1 - delay in +// progress] +#define TS_ACNT_GATE 0x0008 +// D3 - Pacer Clock About Trigger [0 - completed, 1 - in progress] +#define TS_PCLK_RUN 0x0010 +// D4 - Pacer Clock Shutdown Flag [0 - Pacer Clock cannot be start +// triggered only by Software Pacer Start Command, 1 - Pacer Clock can +// be start triggered] + +// External Trigger polarity select +// External Interrupt polarity select +#define POL_POSITIVE 0x0 // positive edge +#define POL_NEGATIVE 0x1 // negative edge + +// User Output Signal select (SetUout0Source, SetUout1Source) +#define UOUT_ADC 0x0 // A/D Conversion Signal +#define UOUT_DAC1 0x1 // D/A1 Update +#define UOUT_DAC2 0x2 // D/A2 Update +#define UOUT_SOFTWARE 0x3 // Software Programmable + +// Pacer clock select (SetPacerSource) +#define PCLK_INTERNAL 1 // Internal Pacer Clock +#define PCLK_EXTERNAL 0 // External Pacer Clock + +// A/D Sample Counter Sources (SetAdcntSource, SetupSampleCounter) +#define ADC_SCNT_CGT_RESET 0x0 // needs restart with StartPacer +#define ADC_SCNT_FIFO_WRITE 0x1 + +// A/D Conversion Signal Select (for SetConversionSelect) +#define ADC_START_SOFTWARE 0x0 // Software A/D Start +#define ADC_START_PCLK 0x1 // Pacer Clock (Ext. Int. see Func.509) +#define ADC_START_BCLK 0x2 // Burst Clock +#define ADC_START_DIGITAL_IT 0x3 // Digital Interrupt +#define ADC_START_DAC1_MARKER1 0x4 // D/A 1 Data Marker 1 +#define ADC_START_DAC2_MARKER1 0x5 // D/A 2 Data Marker 1 +#define ADC_START_SBUS0 0x6 // SyncBus 0 +#define ADC_START_SBUS1 0x7 // SyncBus 1 +#define ADC_START_SBUS2 0x8 // SyncBus 2 + +// Burst Clock start trigger select (SetBurstStart) +#define BCLK_START_SOFTWARE 0x0 // Software A/D Start (StartBurst) +#define BCLK_START_PCLK 0x1 // Pacer Clock +#define BCLK_START_ETRIG 0x2 // External Trigger +#define BCLK_START_DIGITAL_IT 0x3 // Digital Interrupt +#define BCLK_START_SBUS0 0x4 // SyncBus 0 +#define BCLK_START_SBUS1 0x5 // SyncBus 1 +#define BCLK_START_SBUS2 0x6 // SyncBus 2 + +// Pacer Clock start trigger select (SetPacerStart) +#define PCLK_START_SOFTWARE 0x0 // Software Pacer Start (StartPacer) +#define PCLK_START_ETRIG 0x1 // External trigger +#define PCLK_START_DIGITAL_IT 0x2 // Digital interrupt +#define PCLK_START_UTC2 0x3 // User TC 2 out +#define PCLK_START_SBUS0 0x4 // SyncBus 0 +#define PCLK_START_SBUS1 0x5 // SyncBus 1 +#define PCLK_START_SBUS2 0x6 // SyncBus 2 +#define PCLK_START_D_SOFTWARE 0x8 // Delayed Software Pacer Start +#define PCLK_START_D_ETRIG 0x9 // Delayed external trigger +#define PCLK_START_D_DIGITAL_IT 0xA // Delayed digital interrupt +#define PCLK_START_D_UTC2 0xB // Delayed User TC 2 out +#define PCLK_START_D_SBUS0 0xC // Delayed SyncBus 0 +#define PCLK_START_D_SBUS1 0xD // Delayed SyncBus 1 +#define PCLK_START_D_SBUS2 0xE // Delayed SyncBus 2 +#define PCLK_START_ETRIG_GATED 0xF // External Trigger Gated controlled mode + +// Pacer Clock Stop Trigger select (SetPacerStop) +#define PCLK_STOP_SOFTWARE 0x0 // Software Pacer Stop (StopPacer) +#define PCLK_STOP_ETRIG 0x1 // External Trigger +#define PCLK_STOP_DIGITAL_IT 0x2 // Digital Interrupt +#define PCLK_STOP_ACNT 0x3 // About Counter +#define PCLK_STOP_UTC2 0x4 // User TC2 out +#define PCLK_STOP_SBUS0 0x5 // SyncBus 0 +#define PCLK_STOP_SBUS1 0x6 // SyncBus 1 +#define PCLK_STOP_SBUS2 0x7 // SyncBus 2 +#define PCLK_STOP_A_SOFTWARE 0x8 // About Software Pacer Stop +#define PCLK_STOP_A_ETRIG 0x9 // About External Trigger +#define PCLK_STOP_A_DIGITAL_IT 0xA // About Digital Interrupt +#define PCLK_STOP_A_UTC2 0xC // About User TC2 out +#define PCLK_STOP_A_SBUS0 0xD // About SyncBus 0 +#define PCLK_STOP_A_SBUS1 0xE // About SyncBus 1 +#define PCLK_STOP_A_SBUS2 0xF // About SyncBus 2 + +// About Counter Stop Enable +#define ACNT_STOP 0x0 // stop enable +#define ACNT_NO_STOP 0x1 // stop disabled + +// DAC update source (SetDAC1Start & SetDAC2Start) +#define DAC_START_SOFTWARE 0x0 // Software Update +#define DAC_START_CGT 0x1 // CGT controlled Update +#define DAC_START_DAC_CLK 0x2 // D/A Clock +#define DAC_START_EPCLK 0x3 // External Pacer Clock +#define DAC_START_SBUS0 0x4 // SyncBus 0 +#define DAC_START_SBUS1 0x5 // SyncBus 1 +#define DAC_START_SBUS2 0x6 // SyncBus 2 + +// DAC Cycle Mode (SetDAC1Cycle, SetDAC2Cycle, SetupDAC) +#define DAC_CYCLE_SINGLE 0x0 // not cycle +#define DAC_CYCLE_MULTI 0x1 // cycle + +// 8254 Operation Modes (Set8254Mode, SetupTimerCounter) +#define M8254_EVENT_COUNTER 0 // Event Counter +#define M8254_HW_ONE_SHOT 1 // Hardware-Retriggerable One-Shot +#define M8254_RATE_GENERATOR 2 // Rate Generator +#define M8254_SQUARE_WAVE 3 // Square Wave Mode +#define M8254_SW_STROBE 4 // Software Triggered Strobe +#define M8254_HW_STROBE 5 // Hardware Triggered Strobe (Retriggerable) + +// User Timer/Counter 0 Clock Select (SetUtc0Clock) +#define CUTC0_8MHZ 0x0 // 8MHz +#define CUTC0_EXT_TC_CLOCK1 0x1 // Ext. TC Clock 1 +#define CUTC0_EXT_TC_CLOCK2 0x2 // Ext. TC Clock 2 +#define CUTC0_EXT_PCLK 0x3 // Ext. Pacer Clock + +// User Timer/Counter 1 Clock Select (SetUtc1Clock) +#define CUTC1_8MHZ 0x0 // 8MHz +#define CUTC1_EXT_TC_CLOCK1 0x1 // Ext. TC Clock 1 +#define CUTC1_EXT_TC_CLOCK2 0x2 // Ext. TC Clock 2 +#define CUTC1_EXT_PCLK 0x3 // Ext. Pacer Clock +#define CUTC1_UTC0_OUT 0x4 // User Timer/Counter 0 out +#define CUTC1_DIN_SIGNAL 0x5 // High-Speed Digital Input Sampling signal + +// User Timer/Counter 2 Clock Select (SetUtc2Clock) +#define CUTC2_8MHZ 0x0 // 8MHz +#define CUTC2_EXT_TC_CLOCK1 0x1 // Ext. TC Clock 1 +#define CUTC2_EXT_TC_CLOCK2 0x2 // Ext. TC Clock 2 +#define CUTC2_EXT_PCLK 0x3 // Ext. Pacer Clock +#define CUTC2_UTC1_OUT 0x4 // User Timer/Counter 1 out + +// User Timer/Counter 0 Gate Select (SetUtc0Gate) +#define GUTC0_NOT_GATED 0x0 // Not gated +#define GUTC0_GATED 0x1 // Gated +#define GUTC0_EXT_TC_GATE1 0x2 // Ext. TC Gate 1 +#define GUTC0_EXT_TC_GATE2 0x3 // Ext. TC Gate 2 + +// User Timer/Counter 1 Gate Select (SetUtc1Gate) +#define GUTC1_NOT_GATED 0x0 // Not gated +#define GUTC1_GATED 0x1 // Gated +#define GUTC1_EXT_TC_GATE1 0x2 // Ext. TC Gate 1 +#define GUTC1_EXT_TC_GATE2 0x3 // Ext. TC Gate 2 +#define GUTC1_UTC0_OUT 0x4 // User Timer/Counter 0 out + +// User Timer/Counter 2 Gate Select (SetUtc2Gate) +#define GUTC2_NOT_GATED 0x0 // Not gated +#define GUTC2_GATED 0x1 // Gated +#define GUTC2_EXT_TC_GATE1 0x2 // Ext. TC Gate 1 +#define GUTC2_EXT_TC_GATE2 0x3 // Ext. TC Gate 2 +#define GUTC2_UTC1_OUT 0x4 // User Timer/Counter 1 out + +// Interrupt Source Masks (SetITMask, ClearITMask, GetITStatus) +#define IRQM_ADC_FIFO_WRITE 0x0001 // ADC FIFO Write +#define IRQM_CGT_RESET 0x0002 // Reset CGT +#define IRQM_CGT_PAUSE 0x0008 // Pause CGT +#define IRQM_ADC_ABOUT_CNT 0x0010 // About Counter out +#define IRQM_ADC_DELAY_CNT 0x0020 // Delay Counter out +#define IRQM_ADC_SAMPLE_CNT 0x0040 // ADC Sample Counter +#define IRQM_DAC1_UCNT 0x0080 // DAC1 Update Counter +#define IRQM_DAC2_UCNT 0x0100 // DAC2 Update Counter +#define IRQM_UTC1 0x0200 // User TC1 out +#define IRQM_UTC1_INV 0x0400 // User TC1 out, inverted +#define IRQM_UTC2 0x0800 // User TC2 out +#define IRQM_DIGITAL_IT 0x1000 // Digital Interrupt +#define IRQM_EXTERNAL_IT 0x2000 // External Interrupt +#define IRQM_ETRIG_RISING 0x4000 // External Trigger rising-edge +#define IRQM_ETRIG_FALLING 0x8000 // External Trigger falling-edge + +// DMA Request Sources (LAS0) +#define DMAS_DISABLED 0x0 // DMA Disabled +#define DMAS_ADC_SCNT 0x1 // ADC Sample Counter +#define DMAS_DAC1_UCNT 0x2 // D/A1 Update Counter +#define DMAS_DAC2_UCNT 0x3 // D/A2 Update Counter +#define DMAS_UTC1 0x4 // User TC1 out +#define DMAS_ADFIFO_HALF_FULL 0x8 // A/D FIFO half full +#define DMAS_DAC1_FIFO_HALF_EMPTY 0x9 // D/A1 FIFO half empty +#define DMAS_DAC2_FIFO_HALF_EMPTY 0xA // D/A2 FIFO half empty + +// DMA Local Addresses (0x40000000+LAS1 offset) +#define DMALADDR_ADC 0x40000000 // A/D FIFO +#define DMALADDR_HDIN 0x40000004 // High Speed Digital Input FIFO +#define DMALADDR_DAC1 0x40000008 // D/A1 FIFO +#define DMALADDR_DAC2 0x4000000C // D/A2 FIFO + +// Port 0 compare modes (SetDIO0CompareMode) +#define DIO_MODE_EVENT 0 // Event Mode +#define DIO_MODE_MATCH 1 // Match Mode + +// Digital Table Enable (Port 1 disable) +#define DTBL_DISABLE 0 // Enable Digital Table +#define DTBL_ENABLE 1 // Disable Digital Table + +// Sampling Signal for High Speed Digital Input (SetHdinStart) +#define HDIN_SOFTWARE 0x0 // Software Trigger +#define HDIN_ADC 0x1 // A/D Conversion Signal +#define HDIN_UTC0 0x2 // User TC out 0 +#define HDIN_UTC1 0x3 // User TC out 1 +#define HDIN_UTC2 0x4 // User TC out 2 +#define HDIN_EPCLK 0x5 // External Pacer Clock +#define HDIN_ETRG 0x6 // External Trigger + +// Channel Gain Table / Channel Gain Latch +#define CSC_LATCH 0 // Channel Gain Latch mode +#define CSC_CGT 1 // Channel Gain Table mode + +// Channel Gain Table Pause Enable +#define CGT_PAUSE_DISABLE 0 // Channel Gain Table Pause Disable +#define CGT_PAUSE_ENABLE 1 // Channel Gain Table Pause Enable + +// DAC output type/range (p63) +#define AOUT_UNIP5 0 // 0..+5 Volt +#define AOUT_UNIP10 1 // 0..+10 Volt +#define AOUT_BIP5 2 // -5..+5 Volt +#define AOUT_BIP10 3 // -10..+10 Volt + +// Ghannel Gain Table field definitions (p61) +// Gain +#define GAIN1 0 +#define GAIN2 1 +#define GAIN4 2 +#define GAIN8 3 +#define GAIN16 4 +#define GAIN32 5 +#define GAIN64 6 +#define GAIN128 7 + +// Input range/polarity +#define AIN_BIP5 0 // -5..+5 Volt +#define AIN_BIP10 1 // -10..+10 Volt +#define AIN_UNIP10 2 // 0..+10 Volt + +// non referenced single ended select bit +#define NRSE_AGND 0 // AGND referenced SE input +#define NRSE_AINS 1 // AIN SENSE referenced SE input + +// single ended vs differential +#define GND_SE 0 // Single-Ended +#define GND_DIFF 1 // Differential diff --git a/drivers/staging/comedi/drivers/s626.c b/drivers/staging/comedi/drivers/s626.c new file mode 100644 index 00000000000..469ee8c474c --- /dev/null +++ b/drivers/staging/comedi/drivers/s626.c @@ -0,0 +1,3254 @@ +/* + comedi/drivers/s626.c + Sensoray s626 Comedi driver + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef <ds@schleef.org> + + Based on Sensoray Model 626 Linux driver Version 0.2 + Copyright (C) 2002-2004 Sensoray Co., Inc. + + 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: s626 +Description: Sensoray 626 driver +Devices: [Sensoray] 626 (s626) +Authors: Gianluca Palli <gpalli@deis.unibo.it>, +Updated: Fri, 15 Feb 2008 10:28:42 +0000 +Status: experimental + +Configuration options: + [0] - PCI bus of device (optional) + [1] - PCI slot of device (optional) + If bus/slot is not specified, the first supported + PCI device found will be used. + +INSN_CONFIG instructions: + analog input: + none + + analog output: + none + + digital channel: + s626 has 3 dio subdevices (2,3 and 4) each with 16 i/o channels + supported configuration options: + INSN_CONFIG_DIO_QUERY + COMEDI_INPUT + COMEDI_OUTPUT + + encoder: + Every channel must be configured before reading. + + Example code + + insn.insn=INSN_CONFIG; //configuration instruction + insn.n=1; //number of operation (must be 1) + insn.data=&initialvalue; //initial value loaded into encoder + //during configuration + insn.subdev=5; //encoder subdevice + insn.chanspec=CR_PACK(encoder_channel,0,AREF_OTHER); //encoder_channel + //to configure + + comedi_do_insn(cf,&insn); //executing configuration +*/ + +#include <linux/kernel.h> +#include <linux/types.h> + +#include "../comedidev.h" + +#include "comedi_pci.h" + +#include "comedi_fc.h" +#include "s626.h" + +MODULE_AUTHOR("Gianluca Palli <gpalli@deis.unibo.it>"); +MODULE_DESCRIPTION("Sensoray 626 Comedi driver module"); +MODULE_LICENSE("GPL"); + +typedef struct s626_board_struct { + const char *name; + int ai_chans; + int ai_bits; + int ao_chans; + int ao_bits; + int dio_chans; + int dio_banks; + int enc_chans; +} s626_board; + +static const s626_board s626_boards[] = { + { + name: "s626", + ai_chans:S626_ADC_CHANNELS, + ai_bits: 14, + ao_chans:S626_DAC_CHANNELS, + ao_bits: 13, + dio_chans:S626_DIO_CHANNELS, + dio_banks:S626_DIO_BANKS, + enc_chans:S626_ENCODER_CHANNELS, + } +}; + +#define thisboard ((const s626_board *)dev->board_ptr) +#define PCI_VENDOR_ID_S626 0x1131 +#define PCI_DEVICE_ID_S626 0x7146 + +static DEFINE_PCI_DEVICE_TABLE(s626_pci_table) = { + {PCI_VENDOR_ID_S626, PCI_DEVICE_ID_S626, PCI_ANY_ID, PCI_ANY_ID, 0, 0, + 0}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, s626_pci_table); + +static int s626_attach(comedi_device * dev, comedi_devconfig * it); +static int s626_detach(comedi_device * dev); + +static comedi_driver driver_s626 = { + driver_name:"s626", + module:THIS_MODULE, + attach:s626_attach, + detach:s626_detach, +}; + +typedef struct { + struct pci_dev *pdev; + void *base_addr; + int got_regions; + short allocatedBuf; + uint8_t ai_cmd_running; // ai_cmd is running + uint8_t ai_continous; // continous aquisition + int ai_sample_count; // number of samples to aquire + unsigned int ai_sample_timer; // time between samples in + // units of the timer + int ai_convert_count; // conversion counter + unsigned int ai_convert_timer; // time between conversion in + // units of the timer + uint16_t CounterIntEnabs; //Counter interrupt enable + //mask for MISC2 register. + uint8_t AdcItems; //Number of items in ADC poll + //list. + DMABUF RPSBuf; //DMA buffer used to hold ADC + //(RPS1) program. + DMABUF ANABuf; //DMA buffer used to receive + //ADC data and hold DAC data. + uint32_t *pDacWBuf; //Pointer to logical adrs of + //DMA buffer used to hold DAC + //data. + uint16_t Dacpol; //Image of DAC polarity + //register. + uint8_t TrimSetpoint[12]; //Images of TrimDAC setpoints. + //registers. + uint16_t ChargeEnabled; //Image of MISC2 Battery + //Charge Enabled (0 or + //WRMISC2_CHARGE_ENABLE). + uint16_t WDInterval; //Image of MISC2 watchdog + //interval control bits. + uint32_t I2CAdrs; //I2C device address for + //onboard EEPROM (board rev + //dependent). + // short I2Cards; + lsampl_t ao_readback[S626_DAC_CHANNELS]; +} s626_private; + +typedef struct { + uint16_t RDDIn; + uint16_t WRDOut; + uint16_t RDEdgSel; + uint16_t WREdgSel; + uint16_t RDCapSel; + uint16_t WRCapSel; + uint16_t RDCapFlg; + uint16_t RDIntSel; + uint16_t WRIntSel; +} dio_private; + +static dio_private dio_private_A = { + RDDIn:LP_RDDINA, + WRDOut:LP_WRDOUTA, + RDEdgSel:LP_RDEDGSELA, + WREdgSel:LP_WREDGSELA, + RDCapSel:LP_RDCAPSELA, + WRCapSel:LP_WRCAPSELA, + RDCapFlg:LP_RDCAPFLGA, + RDIntSel:LP_RDINTSELA, + WRIntSel:LP_WRINTSELA, +}; + +static dio_private dio_private_B = { + RDDIn:LP_RDDINB, + WRDOut:LP_WRDOUTB, + RDEdgSel:LP_RDEDGSELB, + WREdgSel:LP_WREDGSELB, + RDCapSel:LP_RDCAPSELB, + WRCapSel:LP_WRCAPSELB, + RDCapFlg:LP_RDCAPFLGB, + RDIntSel:LP_RDINTSELB, + WRIntSel:LP_WRINTSELB, +}; + +static dio_private dio_private_C = { + RDDIn:LP_RDDINC, + WRDOut:LP_WRDOUTC, + RDEdgSel:LP_RDEDGSELC, + WREdgSel:LP_WREDGSELC, + RDCapSel:LP_RDCAPSELC, + WRCapSel:LP_WRCAPSELC, + RDCapFlg:LP_RDCAPFLGC, + RDIntSel:LP_RDINTSELC, + WRIntSel:LP_WRINTSELC, +}; + +/* to group dio devices (48 bits mask and data are not allowed ???) +static dio_private *dio_private_word[]={ + &dio_private_A, + &dio_private_B, + &dio_private_C, +}; +*/ + +#define devpriv ((s626_private *)dev->private) +#define diopriv ((dio_private *)s->private) + +COMEDI_PCI_INITCLEANUP_NOMODULE(driver_s626, s626_pci_table); + +//ioctl routines +static int s626_ai_insn_config(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +/* static int s626_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); */ +static int s626_ai_insn_read(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int s626_ai_cmd(comedi_device * dev, comedi_subdevice * s); +static int s626_ai_cmdtest(comedi_device * dev, comedi_subdevice * s, + comedi_cmd * cmd); +static int s626_ai_cancel(comedi_device * dev, comedi_subdevice * s); +static int s626_ao_winsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int s626_ao_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int s626_dio_insn_bits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int s626_dio_insn_config(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int s626_dio_set_irq(comedi_device * dev, unsigned int chan); +static int s626_dio_reset_irq(comedi_device * dev, unsigned int gruop, + unsigned int mask); +static int s626_dio_clear_irq(comedi_device * dev); +static int s626_enc_insn_config(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int s626_enc_insn_read(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int s626_enc_insn_write(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data); +static int s626_ns_to_timer(int *nanosec, int round_mode); +static int s626_ai_load_polllist(uint8_t * ppl, comedi_cmd * cmd); +static int s626_ai_inttrig(comedi_device * dev, comedi_subdevice * s, + unsigned int trignum); +static irqreturn_t s626_irq_handler(int irq, void *d PT_REGS_ARG); +static lsampl_t s626_ai_reg_to_uint(int data); +/* static lsampl_t s626_uint_to_reg(comedi_subdevice *s, int data); */ + +//end ioctl routines + +//internal routines +static void s626_dio_init(comedi_device * dev); +static void ResetADC(comedi_device * dev, uint8_t * ppl); +static void LoadTrimDACs(comedi_device * dev); +static void WriteTrimDAC(comedi_device * dev, uint8_t LogicalChan, + uint8_t DacData); +static uint8_t I2Cread(comedi_device * dev, uint8_t addr); +static uint32_t I2Chandshake(comedi_device * dev, uint32_t val); +static void SetDAC(comedi_device * dev, uint16_t chan, short dacdata); +static void SendDAC(comedi_device * dev, uint32_t val); +static void WriteMISC2(comedi_device * dev, uint16_t NewImage); +static void DEBItransfer(comedi_device * dev); +static uint16_t DEBIread(comedi_device * dev, uint16_t addr); +static void DEBIwrite(comedi_device * dev, uint16_t addr, uint16_t wdata); +static void DEBIreplace(comedi_device * dev, uint16_t addr, uint16_t mask, + uint16_t wdata); +static void CloseDMAB(comedi_device * dev, DMABUF * pdma, size_t bsize); + +// COUNTER OBJECT ------------------------------------------------ +typedef struct enc_private_struct { + // Pointers to functions that differ for A and B counters: + uint16_t(*GetEnable) (comedi_device * dev, struct enc_private_struct *); //Return clock enable. + uint16_t(*GetIntSrc) (comedi_device * dev, struct enc_private_struct *); //Return interrupt source. + uint16_t(*GetLoadTrig) (comedi_device * dev, struct enc_private_struct *); //Return preload trigger source. + uint16_t(*GetMode) (comedi_device * dev, struct enc_private_struct *); //Return standardized operating mode. + void (*PulseIndex) (comedi_device * dev, struct enc_private_struct *); //Generate soft index strobe. + void (*SetEnable) (comedi_device * dev, struct enc_private_struct *, uint16_t enab); //Program clock enable. + void (*SetIntSrc) (comedi_device * dev, struct enc_private_struct *, uint16_t IntSource); //Program interrupt source. + void (*SetLoadTrig) (comedi_device * dev, struct enc_private_struct *, uint16_t Trig); //Program preload trigger source. + void (*SetMode) (comedi_device * dev, struct enc_private_struct *, uint16_t Setup, uint16_t DisableIntSrc); //Program standardized operating mode. + void (*ResetCapFlags) (comedi_device * dev, struct enc_private_struct *); //Reset event capture flags. + + uint16_t MyCRA; // Address of CRA register. + uint16_t MyCRB; // Address of CRB register. + uint16_t MyLatchLsw; // Address of Latch least-significant-word + // register. + uint16_t MyEventBits[4]; // Bit translations for IntSrc -->RDMISC2. +} enc_private; //counter object + +#define encpriv ((enc_private *)(dev->subdevices+5)->private) + +//counters routines +static void s626_timer_load(comedi_device * dev, enc_private * k, int tick); +static uint32_t ReadLatch(comedi_device * dev, enc_private * k); +static void ResetCapFlags_A(comedi_device * dev, enc_private * k); +static void ResetCapFlags_B(comedi_device * dev, enc_private * k); +static uint16_t GetMode_A(comedi_device * dev, enc_private * k); +static uint16_t GetMode_B(comedi_device * dev, enc_private * k); +static void SetMode_A(comedi_device * dev, enc_private * k, uint16_t Setup, + uint16_t DisableIntSrc); +static void SetMode_B(comedi_device * dev, enc_private * k, uint16_t Setup, + uint16_t DisableIntSrc); +static void SetEnable_A(comedi_device * dev, enc_private * k, uint16_t enab); +static void SetEnable_B(comedi_device * dev, enc_private * k, uint16_t enab); +static uint16_t GetEnable_A(comedi_device * dev, enc_private * k); +static uint16_t GetEnable_B(comedi_device * dev, enc_private * k); +static void SetLatchSource(comedi_device * dev, enc_private * k, + uint16_t value); +/* static uint16_t GetLatchSource(comedi_device *dev, enc_private *k ); */ +static void SetLoadTrig_A(comedi_device * dev, enc_private * k, uint16_t Trig); +static void SetLoadTrig_B(comedi_device * dev, enc_private * k, uint16_t Trig); +static uint16_t GetLoadTrig_A(comedi_device * dev, enc_private * k); +static uint16_t GetLoadTrig_B(comedi_device * dev, enc_private * k); +static void SetIntSrc_B(comedi_device * dev, enc_private * k, + uint16_t IntSource); +static void SetIntSrc_A(comedi_device * dev, enc_private * k, + uint16_t IntSource); +static uint16_t GetIntSrc_A(comedi_device * dev, enc_private * k); +static uint16_t GetIntSrc_B(comedi_device * dev, enc_private * k); +/* static void SetClkMult(comedi_device *dev, enc_private *k, uint16_t value ) ; */ +/* static uint16_t GetClkMult(comedi_device *dev, enc_private *k ) ; */ +/* static void SetIndexPol(comedi_device *dev, enc_private *k, uint16_t value ); */ +/* static uint16_t GetClkPol(comedi_device *dev, enc_private *k ) ; */ +/* static void SetIndexSrc( comedi_device *dev,enc_private *k, uint16_t value ); */ +/* static uint16_t GetClkSrc( comedi_device *dev,enc_private *k ); */ +/* static void SetIndexSrc( comedi_device *dev,enc_private *k, uint16_t value ); */ +/* static uint16_t GetIndexSrc( comedi_device *dev,enc_private *k ); */ +static void PulseIndex_A(comedi_device * dev, enc_private * k); +static void PulseIndex_B(comedi_device * dev, enc_private * k); +static void Preload(comedi_device * dev, enc_private * k, uint32_t value); +static void CountersInit(comedi_device * dev); +//end internal routines + +///////////////////////////////////////////////////////////////////////// +// Counter objects constructor. + +// Counter overflow/index event flag masks for RDMISC2. +#define INDXMASK(C) ( 1 << ( ( (C) > 2 ) ? ( (C) * 2 - 1 ) : ( (C) * 2 + 4 ) ) ) +#define OVERMASK(C) ( 1 << ( ( (C) > 2 ) ? ( (C) * 2 + 5 ) : ( (C) * 2 + 10 ) ) ) +#define EVBITS(C) { 0, OVERMASK(C), INDXMASK(C), OVERMASK(C) | INDXMASK(C) } + +// Translation table to map IntSrc into equivalent RDMISC2 event flag +// bits. +//static const uint16_t EventBits[][4] = { EVBITS(0), EVBITS(1), EVBITS(2), EVBITS(3), EVBITS(4), EVBITS(5) }; + +/* enc_private; */ +static enc_private enc_private_data[] = { + { + GetEnable:GetEnable_A, + GetIntSrc:GetIntSrc_A, + GetLoadTrig:GetLoadTrig_A, + GetMode: GetMode_A, + PulseIndex:PulseIndex_A, + SetEnable:SetEnable_A, + SetIntSrc:SetIntSrc_A, + SetLoadTrig:SetLoadTrig_A, + SetMode: SetMode_A, + ResetCapFlags:ResetCapFlags_A, + MyCRA: LP_CR0A, + MyCRB: LP_CR0B, + MyLatchLsw:LP_CNTR0ALSW, + MyEventBits:EVBITS(0), + }, + { + GetEnable:GetEnable_A, + GetIntSrc:GetIntSrc_A, + GetLoadTrig:GetLoadTrig_A, + GetMode: GetMode_A, + PulseIndex:PulseIndex_A, + SetEnable:SetEnable_A, + SetIntSrc:SetIntSrc_A, + SetLoadTrig:SetLoadTrig_A, + SetMode: SetMode_A, + ResetCapFlags:ResetCapFlags_A, + MyCRA: LP_CR1A, + MyCRB: LP_CR1B, + MyLatchLsw:LP_CNTR1ALSW, + MyEventBits:EVBITS(1), + }, + { + GetEnable:GetEnable_A, + GetIntSrc:GetIntSrc_A, + GetLoadTrig:GetLoadTrig_A, + GetMode: GetMode_A, + PulseIndex:PulseIndex_A, + SetEnable:SetEnable_A, + SetIntSrc:SetIntSrc_A, + SetLoadTrig:SetLoadTrig_A, + SetMode: SetMode_A, + ResetCapFlags:ResetCapFlags_A, + MyCRA: LP_CR2A, + MyCRB: LP_CR2B, + MyLatchLsw:LP_CNTR2ALSW, + MyEventBits:EVBITS(2), + }, + { + GetEnable:GetEnable_B, + GetIntSrc:GetIntSrc_B, + GetLoadTrig:GetLoadTrig_B, + GetMode: GetMode_B, + PulseIndex:PulseIndex_B, + SetEnable:SetEnable_B, + SetIntSrc:SetIntSrc_B, + SetLoadTrig:SetLoadTrig_B, + SetMode: SetMode_B, + ResetCapFlags:ResetCapFlags_B, + MyCRA: LP_CR0A, + MyCRB: LP_CR0B, + MyLatchLsw:LP_CNTR0BLSW, + MyEventBits:EVBITS(3), + }, + { + GetEnable:GetEnable_B, + GetIntSrc:GetIntSrc_B, + GetLoadTrig:GetLoadTrig_B, + GetMode: GetMode_B, + PulseIndex:PulseIndex_B, + SetEnable:SetEnable_B, + SetIntSrc:SetIntSrc_B, + SetLoadTrig:SetLoadTrig_B, + SetMode: SetMode_B, + ResetCapFlags:ResetCapFlags_B, + MyCRA: LP_CR1A, + MyCRB: LP_CR1B, + MyLatchLsw:LP_CNTR1BLSW, + MyEventBits:EVBITS(4), + }, + { + GetEnable:GetEnable_B, + GetIntSrc:GetIntSrc_B, + GetLoadTrig:GetLoadTrig_B, + GetMode: GetMode_B, + PulseIndex:PulseIndex_B, + SetEnable:SetEnable_B, + SetIntSrc:SetIntSrc_B, + SetLoadTrig:SetLoadTrig_B, + SetMode: SetMode_B, + ResetCapFlags:ResetCapFlags_B, + MyCRA: LP_CR2A, + MyCRB: LP_CR2B, + MyLatchLsw:LP_CNTR2BLSW, + MyEventBits:EVBITS(5), + }, +}; + +// enab/disable a function or test status bit(s) that are accessed +// through Main Control Registers 1 or 2. +#define MC_ENABLE( REGADRS, CTRLWORD ) writel( ( (uint32_t)( CTRLWORD ) << 16 ) | (uint32_t)( CTRLWORD ),devpriv->base_addr+( REGADRS ) ) + +#define MC_DISABLE( REGADRS, CTRLWORD ) writel( (uint32_t)( CTRLWORD ) << 16 , devpriv->base_addr+( REGADRS ) ) + +#define MC_TEST( REGADRS, CTRLWORD ) ( ( readl(devpriv->base_addr+( REGADRS )) & CTRLWORD ) != 0 ) + +/* #define WR7146(REGARDS,CTRLWORD) + writel(CTRLWORD,(uint32_t)(devpriv->base_addr+(REGARDS))) */ +#define WR7146(REGARDS,CTRLWORD) writel(CTRLWORD,devpriv->base_addr+(REGARDS)) + +/* #define RR7146(REGARDS) + readl((uint32_t)(devpriv->base_addr+(REGARDS))) */ +#define RR7146(REGARDS) readl(devpriv->base_addr+(REGARDS)) + +#define BUGFIX_STREG(REGADRS) ( REGADRS - 4 ) + +// Write a time slot control record to TSL2. +#define VECTPORT( VECTNUM ) (P_TSL2 + ( (VECTNUM) << 2 )) +#define SETVECT( VECTNUM, VECTVAL ) WR7146(VECTPORT( VECTNUM ), (VECTVAL)) + +// Code macros used for constructing I2C command bytes. +#define I2C_B2(ATTR,VAL) ( ( (ATTR) << 6 ) | ( (VAL) << 24 ) ) +#define I2C_B1(ATTR,VAL) ( ( (ATTR) << 4 ) | ( (VAL) << 16 ) ) +#define I2C_B0(ATTR,VAL) ( ( (ATTR) << 2 ) | ( (VAL) << 8 ) ) + +static const comedi_lrange s626_range_table = { 2, { + RANGE(-5, 5), + RANGE(-10, 10), + } +}; + +static int s626_attach(comedi_device * dev, comedi_devconfig * it) +{ +/* uint8_t PollList; */ +/* uint16_t AdcData; */ +/* uint16_t StartVal; */ +/* uint16_t index; */ +/* unsigned int data[16]; */ + int result; + int i; + int ret; + resource_size_t resourceStart; + dma_addr_t appdma; + comedi_subdevice *s; + struct pci_dev *pdev; + + if (alloc_private(dev, sizeof(s626_private)) < 0) + return -ENOMEM; + + for (pdev = pci_get_device(PCI_VENDOR_ID_S626, PCI_DEVICE_ID_S626, + NULL); pdev != NULL; + pdev = pci_get_device(PCI_VENDOR_ID_S626, + PCI_DEVICE_ID_S626, pdev)) { + if (it->options[0] || it->options[1]) { + if (pdev->bus->number == it->options[0] && + PCI_SLOT(pdev->devfn) == it->options[1]) { + /* matches requested bus/slot */ + break; + } + } else { + /* no bus/slot specified */ + break; + } + } + devpriv->pdev = pdev; + + if (pdev == NULL) { + printk("s626_attach: Board not present!!!\n"); + return -ENODEV; + } + + if ((result = comedi_pci_enable(pdev, "s626")) < 0) { + printk("s626_attach: comedi_pci_enable fails\n"); + return -ENODEV; + } + devpriv->got_regions = 1; + + resourceStart = pci_resource_start(devpriv->pdev, 0); + + devpriv->base_addr = ioremap(resourceStart, SIZEOF_ADDRESS_SPACE); + if (devpriv->base_addr == NULL) { + printk("s626_attach: IOREMAP failed\n"); + return -ENODEV; + } + + if (devpriv->base_addr) { + //disable master interrupt + writel(0, devpriv->base_addr + P_IER); + + //soft reset + writel(MC1_SOFT_RESET, devpriv->base_addr + P_MC1); + + //DMA FIXME DMA// + DEBUG("s626_attach: DMA ALLOCATION\n"); + + //adc buffer allocation + devpriv->allocatedBuf = 0; + + if ((devpriv->ANABuf.LogicalBase = + pci_alloc_consistent(devpriv->pdev, DMABUF_SIZE, + &appdma)) == NULL) { + printk("s626_attach: DMA Memory mapping error\n"); + return -ENOMEM; + } + + devpriv->ANABuf.PhysicalBase = appdma; + + DEBUG("s626_attach: AllocDMAB ADC Logical=%p, bsize=%d, Physical=0x%x\n", devpriv->ANABuf.LogicalBase, DMABUF_SIZE, (uint32_t) devpriv->ANABuf.PhysicalBase); + + devpriv->allocatedBuf++; + + if ((devpriv->RPSBuf.LogicalBase = + pci_alloc_consistent(devpriv->pdev, DMABUF_SIZE, + &appdma)) == NULL) { + printk("s626_attach: DMA Memory mapping error\n"); + return -ENOMEM; + } + + devpriv->RPSBuf.PhysicalBase = appdma; + + DEBUG("s626_attach: AllocDMAB RPS Logical=%p, bsize=%d, Physical=0x%x\n", devpriv->RPSBuf.LogicalBase, DMABUF_SIZE, (uint32_t) devpriv->RPSBuf.PhysicalBase); + + devpriv->allocatedBuf++; + + } + + dev->board_ptr = s626_boards; + dev->board_name = thisboard->name; + + if (alloc_subdevices(dev, 6) < 0) + return -ENOMEM; + + dev->iobase = (unsigned long)devpriv->base_addr; + dev->irq = devpriv->pdev->irq; + + //set up interrupt handler + if (dev->irq == 0) { + printk(" unknown irq (bad)\n"); + } else { + if ((ret = comedi_request_irq(dev->irq, s626_irq_handler, + IRQF_SHARED, "s626", dev)) < 0) { + printk(" irq not available\n"); + dev->irq = 0; + } + } + + DEBUG("s626_attach: -- it opts %d,%d -- \n", + it->options[0], it->options[1]); + + s = dev->subdevices + 0; + /* analog input subdevice */ + dev->read_subdev = s; + /* we support single-ended (ground) and differential */ + s->type = COMEDI_SUBD_AI; + s->subdev_flags = SDF_READABLE | SDF_DIFF | SDF_CMD_READ; + s->n_chan = thisboard->ai_chans; + s->maxdata = (0xffff >> 2); + s->range_table = &s626_range_table; + s->len_chanlist = thisboard->ai_chans; /* This is the maximum chanlist + length that the board can + handle */ + s->insn_config = s626_ai_insn_config; + s->insn_read = s626_ai_insn_read; + s->do_cmd = s626_ai_cmd; + s->do_cmdtest = s626_ai_cmdtest; + s->cancel = s626_ai_cancel; + + s = dev->subdevices + 1; + /* analog output subdevice */ + s->type = COMEDI_SUBD_AO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = thisboard->ao_chans; + s->maxdata = (0x3fff); + s->range_table = &range_bipolar10; + s->insn_write = s626_ao_winsn; + s->insn_read = s626_ao_rinsn; + + s = dev->subdevices + 2; + /* digital I/O subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = S626_DIO_CHANNELS; + s->maxdata = 1; + s->io_bits = 0xffff; + s->private = &dio_private_A; + s->range_table = &range_digital; + s->insn_config = s626_dio_insn_config; + s->insn_bits = s626_dio_insn_bits; + + s = dev->subdevices + 3; + /* digital I/O subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->io_bits = 0xffff; + s->private = &dio_private_B; + s->range_table = &range_digital; + s->insn_config = s626_dio_insn_config; + s->insn_bits = s626_dio_insn_bits; + + s = dev->subdevices + 4; + /* digital I/O subdevice */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 16; + s->maxdata = 1; + s->io_bits = 0xffff; + s->private = &dio_private_C; + s->range_table = &range_digital; + s->insn_config = s626_dio_insn_config; + s->insn_bits = s626_dio_insn_bits; + + s = dev->subdevices + 5; + /* encoder (counter) subdevice */ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE | SDF_LSAMPL; + s->n_chan = thisboard->enc_chans; + s->private = enc_private_data; + s->insn_config = s626_enc_insn_config; + s->insn_read = s626_enc_insn_read; + s->insn_write = s626_enc_insn_write; + s->maxdata = 0xffffff; + s->range_table = &range_unknown; + + //stop ai_command + devpriv->ai_cmd_running = 0; + + if (devpriv->base_addr && (devpriv->allocatedBuf == 2)) { + dma_addr_t pPhysBuf; + uint16_t chan; + + // enab DEBI and audio pins, enable I2C interface. + MC_ENABLE(P_MC1, MC1_DEBI | MC1_AUDIO | MC1_I2C); + // Configure DEBI operating mode. + WR7146(P_DEBICFG, DEBI_CFG_SLAVE16 // Local bus is 16 + // bits wide. + | (DEBI_TOUT << DEBI_CFG_TOUT_BIT) // Declare DEBI + // transfer timeout + // interval. + | DEBI_SWAP // Set up byte lane + // steering. + | DEBI_CFG_INTEL); // Intel-compatible + // local bus (DEBI + // never times out). + DEBUG("s626_attach: %d debi init -- %d\n", + DEBI_CFG_SLAVE16 | (DEBI_TOUT << DEBI_CFG_TOUT_BIT) | + DEBI_SWAP | DEBI_CFG_INTEL, + DEBI_CFG_INTEL | DEBI_CFG_TOQ | DEBI_CFG_INCQ | + DEBI_CFG_16Q); + + //DEBI INIT S626 WR7146( P_DEBICFG, DEBI_CFG_INTEL | DEBI_CFG_TOQ + //| DEBI_CFG_INCQ| DEBI_CFG_16Q); //end + + // Paging is disabled. + WR7146(P_DEBIPAGE, DEBI_PAGE_DISABLE); // Disable MMU paging. + + // Init GPIO so that ADC Start* is negated. + WR7146(P_GPIO, GPIO_BASE | GPIO1_HI); + + //IsBoardRevA is a boolean that indicates whether the board is + //RevA. + + // VERSION 2.01 CHANGE: REV A & B BOARDS NOW SUPPORTED BY DYNAMIC + // EEPROM ADDRESS SELECTION. Initialize the I2C interface, which + // is used to access the onboard serial EEPROM. The EEPROM's I2C + // DeviceAddress is hardwired to a value that is dependent on the + // 626 board revision. On all board revisions, the EEPROM stores + // TrimDAC calibration constants for analog I/O. On RevB and + // higher boards, the DeviceAddress is hardwired to 0 to enable + // the EEPROM to also store the PCI SubVendorID and SubDeviceID; + // this is the address at which the SAA7146 expects a + // configuration EEPROM to reside. On RevA boards, the EEPROM + // device address, which is hardwired to 4, prevents the SAA7146 + // from retrieving PCI sub-IDs, so the SAA7146 uses its built-in + // default values, instead. + + // devpriv->I2Cards= IsBoardRevA ? 0xA8 : 0xA0; // Set I2C EEPROM + // DeviceType (0xA0) + // and DeviceAddress<<1. + + devpriv->I2CAdrs = 0xA0; // I2C device address for onboard + // eeprom(revb) + + // Issue an I2C ABORT command to halt any I2C operation in + //progress and reset BUSY flag. + WR7146(P_I2CSTAT, I2C_CLKSEL | I2C_ABORT); // Write I2C control: + // abort any I2C + // activity. + MC_ENABLE(P_MC2, MC2_UPLD_IIC); // Invoke command + // upload + while ((RR7146(P_MC2) & MC2_UPLD_IIC) == 0) ; // and wait for + // upload to + // complete. + + // Per SAA7146 data sheet, write to STATUS reg twice to reset all + // I2C error flags. + for (i = 0; i < 2; i++) { + WR7146(P_I2CSTAT, I2C_CLKSEL); // Write I2C control: reset + // error flags. + MC_ENABLE(P_MC2, MC2_UPLD_IIC); // Invoke command upload + while (!MC_TEST(P_MC2, MC2_UPLD_IIC)) ; // and wait for + // upload to + // complete. + } + + // Init audio interface functional attributes: set DAC/ADC serial + // clock rates, invert DAC serial clock so that DAC data setup + // times are satisfied, enable DAC serial clock out. + WR7146(P_ACON2, ACON2_INIT); + + // Set up TSL1 slot list, which is used to control the + // accumulation of ADC data: RSD1 = shift data in on SD1. SIB_A1 + // = store data uint8_t at next available location in FB BUFFER1 + // register. + WR7146(P_TSL1, RSD1 | SIB_A1); // Fetch ADC high data + // uint8_t. + WR7146(P_TSL1 + 4, RSD1 | SIB_A1 | EOS); // Fetch ADC low data + // uint8_t; end of + // TSL1. + + // enab TSL1 slot list so that it executes all the time. + WR7146(P_ACON1, ACON1_ADCSTART); + + // Initialize RPS registers used for ADC. + + //Physical start of RPS program. + WR7146(P_RPSADDR1, (uint32_t) devpriv->RPSBuf.PhysicalBase); + + WR7146(P_RPSPAGE1, 0); // RPS program performs no + // explicit mem writes. + WR7146(P_RPS1_TOUT, 0); // Disable RPS timeouts. + + // SAA7146 BUG WORKAROUND. Initialize SAA7146 ADC interface to a + // known state by invoking ADCs until FB BUFFER 1 register shows + // that it is correctly receiving ADC data. This is necessary + // because the SAA7146 ADC interface does not start up in a + // defined state after a PCI reset. + +/* PollList = EOPL; // Create a simple polling */ +/* // list for analog input */ +/* // channel 0. */ +/* ResetADC( dev, &PollList ); */ + +/* s626_ai_rinsn(dev,dev->subdevices,NULL,data); //( &AdcData ); // */ +/* //Get initial ADC */ +/* //value. */ + +/* StartVal = data[0]; */ + +/* // VERSION 2.01 CHANGE: TIMEOUT ADDED TO PREVENT HANGED EXECUTION. */ +/* // Invoke ADCs until the new ADC value differs from the initial */ +/* // value or a timeout occurs. The timeout protects against the */ +/* // possibility that the driver is restarting and the ADC data is a */ +/* // fixed value resulting from the applied ADC analog input being */ +/* // unusually quiet or at the rail. */ + +/* for ( index = 0; index < 500; index++ ) */ +/* { */ +/* s626_ai_rinsn(dev,dev->subdevices,NULL,data); */ +/* AdcData = data[0]; //ReadADC( &AdcData ); */ +/* if ( AdcData != StartVal ) */ +/* break; */ +/* } */ + + // end initADC + + // init the DAC interface + + // Init Audio2's output DMAC attributes: burst length = 1 DWORD, + // threshold = 1 DWORD. + WR7146(P_PCI_BT_A, 0); + + // Init Audio2's output DMA physical addresses. The protection + // address is set to 1 DWORD past the base address so that a + // single DWORD will be transferred each time a DMA transfer is + // enabled. + + pPhysBuf = + devpriv->ANABuf.PhysicalBase + + (DAC_WDMABUF_OS * sizeof(uint32_t)); + + WR7146(P_BASEA2_OUT, (uint32_t) pPhysBuf); // Buffer base adrs. + WR7146(P_PROTA2_OUT, (uint32_t) (pPhysBuf + sizeof(uint32_t))); // Protection address. + + // Cache Audio2's output DMA buffer logical address. This is + // where DAC data is buffered for A2 output DMA transfers. + devpriv->pDacWBuf = + (uint32_t *) devpriv->ANABuf.LogicalBase + + DAC_WDMABUF_OS; + + // Audio2's output channels does not use paging. The protection + // violation handling bit is set so that the DMAC will + // automatically halt and its PCI address pointer will be reset + // when the protection address is reached. + WR7146(P_PAGEA2_OUT, 8); + + // Initialize time slot list 2 (TSL2), which is used to control + // the clock generation for and serialization of data to be sent + // to the DAC devices. Slot 0 is a NOP that is used to trap TSL + // execution; this permits other slots to be safely modified + // without first turning off the TSL sequencer (which is + // apparently impossible to do). Also, SD3 (which is driven by a + // pull-up resistor) is shifted in and stored to the MSB of + // FB_BUFFER2 to be used as evidence that the slot sequence has + // not yet finished executing. + SETVECT(0, XSD2 | RSD3 | SIB_A2 | EOS); // Slot 0: Trap TSL + // execution, shift 0xFF + // into FB_BUFFER2. + + // Initialize slot 1, which is constant. Slot 1 causes a DWORD to + // be transferred from audio channel 2's output FIFO to the FIFO's + // output buffer so that it can be serialized and sent to the DAC + // during subsequent slots. All remaining slots are dynamically + // populated as required by the target DAC device. + SETVECT(1, LF_A2); // Slot 1: Fetch DWORD from Audio2's + // output FIFO. + + // Start DAC's audio interface (TSL2) running. + WR7146(P_ACON1, ACON1_DACSTART); + + //////////////////////////////////////////////////////// + + // end init DAC interface + + // Init Trim DACs to calibrated values. Do it twice because the + // SAA7146 audio channel does not always reset properly and + // sometimes causes the first few TrimDAC writes to malfunction. + + LoadTrimDACs(dev); + LoadTrimDACs(dev); // Insurance. + + ////////////////////////////////////////////////////////////////// + // Manually init all gate array hardware in case this is a soft + // reset (we have no way of determining whether this is a warm or + // cold start). This is necessary because the gate array will + // reset only in response to a PCI hard reset; there is no soft + // reset function. + + // Init all DAC outputs to 0V and init all DAC setpoint and + // polarity images. + for (chan = 0; chan < S626_DAC_CHANNELS; chan++) + SetDAC(dev, chan, 0); + + // Init image of WRMISC2 Battery Charger Enabled control bit. + // This image is used when the state of the charger control bit, + // which has no direct hardware readback mechanism, is queried. + devpriv->ChargeEnabled = 0; + + // Init image of watchdog timer interval in WRMISC2. This image + // maintains the value of the control bits of MISC2 are + // continuously reset to zero as long as the WD timer is disabled. + devpriv->WDInterval = 0; + + // Init Counter Interrupt enab mask for RDMISC2. This mask is + // applied against MISC2 when testing to determine which timer + // events are requesting interrupt service. + devpriv->CounterIntEnabs = 0; + + // Init counters. + CountersInit(dev); + + // Without modifying the state of the Battery Backup enab, disable + // the watchdog timer, set DIO channels 0-5 to operate in the + // standard DIO (vs. counter overflow) mode, disable the battery + // charger, and reset the watchdog interval selector to zero. + WriteMISC2(dev, (uint16_t) (DEBIread(dev, + LP_RDMISC2) & MISC2_BATT_ENABLE)); + + // Initialize the digital I/O subsystem. + s626_dio_init(dev); + + //enable interrupt test + // writel(IRQ_GPIO3 | IRQ_RPS1,devpriv->base_addr+P_IER); + } + + DEBUG("s626_attach: comedi%d s626 attached %04x\n", dev->minor, + (uint32_t) devpriv->base_addr); + + return 1; +} + +static lsampl_t s626_ai_reg_to_uint(int data) +{ + lsampl_t tempdata; + + tempdata = (data >> 18); + if (tempdata & 0x2000) + tempdata &= 0x1fff; + else + tempdata += (1 << 13); + + return tempdata; +} + +/* static lsampl_t s626_uint_to_reg(comedi_subdevice *s, int data){ */ +/* return 0; */ +/* } */ + +static irqreturn_t s626_irq_handler(int irq, void *d PT_REGS_ARG) +{ + comedi_device *dev = d; + comedi_subdevice *s; + comedi_cmd *cmd; + enc_private *k; + unsigned long flags; + int32_t *readaddr; + uint32_t irqtype, irqstatus; + int i = 0; + sampl_t tempdata; + uint8_t group; + uint16_t irqbit; + + DEBUG("s626_irq_handler: interrupt request recieved!!!\n"); + + if (dev->attached == 0) + return IRQ_NONE; + // lock to avoid race with comedi_poll + comedi_spin_lock_irqsave(&dev->spinlock, flags); + + //save interrupt enable register state + irqstatus = readl(devpriv->base_addr + P_IER); + + //read interrupt type + irqtype = readl(devpriv->base_addr + P_ISR); + + //disable master interrupt + writel(0, devpriv->base_addr + P_IER); + + //clear interrupt + writel(irqtype, devpriv->base_addr + P_ISR); + + //do somethings + DEBUG("s626_irq_handler: interrupt type %d\n", irqtype); + + switch (irqtype) { + case IRQ_RPS1: // end_of_scan occurs + + DEBUG("s626_irq_handler: RPS1 irq detected\n"); + + // manage ai subdevice + s = dev->subdevices; + cmd = &(s->async->cmd); + + // Init ptr to DMA buffer that holds new ADC data. We skip the + // first uint16_t in the buffer because it contains junk data from + // the final ADC of the previous poll list scan. + readaddr = (int32_t *) devpriv->ANABuf.LogicalBase + 1; + + // get the data and hand it over to comedi + for (i = 0; i < (s->async->cmd.chanlist_len); i++) { + // Convert ADC data to 16-bit integer values and copy to application + // buffer. + tempdata = s626_ai_reg_to_uint((int)*readaddr); + readaddr++; + + //put data into read buffer + // comedi_buf_put(s->async, tempdata); + if (cfc_write_to_buffer(s, tempdata) == 0) + printk("s626_irq_handler: cfc_write_to_buffer error!\n"); + + DEBUG("s626_irq_handler: ai channel %d acquired: %d\n", + i, tempdata); + } + + //end of scan occurs + s->async->events |= COMEDI_CB_EOS; + + if (!(devpriv->ai_continous)) + devpriv->ai_sample_count--; + if (devpriv->ai_sample_count <= 0) { + devpriv->ai_cmd_running = 0; + + // Stop RPS program. + MC_DISABLE(P_MC1, MC1_ERPS1); + + //send end of acquisition + s->async->events |= COMEDI_CB_EOA; + + //disable master interrupt + irqstatus = 0; + } + + if (devpriv->ai_cmd_running && cmd->scan_begin_src == TRIG_EXT) { + DEBUG("s626_irq_handler: enable interrupt on dio channel %d\n", cmd->scan_begin_arg); + + s626_dio_set_irq(dev, cmd->scan_begin_arg); + + DEBUG("s626_irq_handler: External trigger is set!!!\n"); + } + // tell comedi that data is there + DEBUG("s626_irq_handler: events %d\n", s->async->events); + comedi_event(dev, s); + break; + case IRQ_GPIO3: //check dio and conter interrupt + + DEBUG("s626_irq_handler: GPIO3 irq detected\n"); + + // manage ai subdevice + s = dev->subdevices; + cmd = &(s->async->cmd); + + //s626_dio_clear_irq(dev); + + for (group = 0; group < S626_DIO_BANKS; group++) { + irqbit = 0; + //read interrupt type + irqbit = DEBIread(dev, + ((dio_private *) (dev->subdevices + 2 + + group)->private)->RDCapFlg); + + //check if interrupt is generated from dio channels + if (irqbit) { + s626_dio_reset_irq(dev, group, irqbit); + DEBUG("s626_irq_handler: check interrupt on dio group %d %d\n", group, i); + if (devpriv->ai_cmd_running) { + //check if interrupt is an ai acquisition start trigger + if ((irqbit >> (cmd->start_arg - + (16 * group))) + == 1 + && cmd->start_src == TRIG_EXT) { + DEBUG("s626_irq_handler: Edge capture interrupt recieved from channel %d\n", cmd->start_arg); + + // Start executing the RPS program. + MC_ENABLE(P_MC1, MC1_ERPS1); + + DEBUG("s626_irq_handler: aquisition start triggered!!!\n"); + + if (cmd->scan_begin_src == + TRIG_EXT) { + DEBUG("s626_ai_cmd: enable interrupt on dio channel %d\n", cmd->scan_begin_arg); + + s626_dio_set_irq(dev, + cmd-> + scan_begin_arg); + + DEBUG("s626_irq_handler: External scan trigger is set!!!\n"); + } + } + if ((irqbit >> (cmd->scan_begin_arg - + (16 * group))) + == 1 + && cmd->scan_begin_src == + TRIG_EXT) { + DEBUG("s626_irq_handler: Edge capture interrupt recieved from channel %d\n", cmd->scan_begin_arg); + + // Trigger ADC scan loop start by setting RPS Signal 0. + MC_ENABLE(P_MC2, MC2_ADC_RPS); + + DEBUG("s626_irq_handler: scan triggered!!! %d\n", devpriv->ai_sample_count); + if (cmd->convert_src == + TRIG_EXT) { + + DEBUG("s626_ai_cmd: enable interrupt on dio channel %d group %d\n", cmd->convert_arg - (16 * group), group); + + devpriv-> + ai_convert_count + = + cmd-> + chanlist_len; + + s626_dio_set_irq(dev, + cmd-> + convert_arg); + + DEBUG("s626_irq_handler: External convert trigger is set!!!\n"); + } + + if (cmd->convert_src == + TRIG_TIMER) { + k = &encpriv[5]; + devpriv-> + ai_convert_count + = + cmd-> + chanlist_len; + k->SetEnable(dev, k, + CLKENAB_ALWAYS); + } + } + if ((irqbit >> (cmd->convert_arg - + (16 * group))) + == 1 + && cmd->convert_src == + TRIG_EXT) { + DEBUG("s626_irq_handler: Edge capture interrupt recieved from channel %d\n", cmd->convert_arg); + + // Trigger ADC scan loop start by setting RPS Signal 0. + MC_ENABLE(P_MC2, MC2_ADC_RPS); + + DEBUG("s626_irq_handler: adc convert triggered!!!\n"); + + devpriv->ai_convert_count--; + + if (devpriv->ai_convert_count > + 0) { + + DEBUG("s626_ai_cmd: enable interrupt on dio channel %d group %d\n", cmd->convert_arg - (16 * group), group); + + s626_dio_set_irq(dev, + cmd-> + convert_arg); + + DEBUG("s626_irq_handler: External trigger is set!!!\n"); + } + } + } + break; + } + } + + //read interrupt type + irqbit = DEBIread(dev, LP_RDMISC2); + + //check interrupt on counters + DEBUG("s626_irq_handler: check counters interrupt %d\n", + irqbit); + + if (irqbit & IRQ_COINT1A) { + DEBUG("s626_irq_handler: interrupt on counter 1A overflow\n"); + k = &encpriv[0]; + + //clear interrupt capture flag + k->ResetCapFlags(dev, k); + } + if (irqbit & IRQ_COINT2A) { + DEBUG("s626_irq_handler: interrupt on counter 2A overflow\n"); + k = &encpriv[1]; + + //clear interrupt capture flag + k->ResetCapFlags(dev, k); + } + if (irqbit & IRQ_COINT3A) { + DEBUG("s626_irq_handler: interrupt on counter 3A overflow\n"); + k = &encpriv[2]; + + //clear interrupt capture flag + k->ResetCapFlags(dev, k); + } + if (irqbit & IRQ_COINT1B) { + DEBUG("s626_irq_handler: interrupt on counter 1B overflow\n"); + k = &encpriv[3]; + + //clear interrupt capture flag + k->ResetCapFlags(dev, k); + } + if (irqbit & IRQ_COINT2B) { + DEBUG("s626_irq_handler: interrupt on counter 2B overflow\n"); + k = &encpriv[4]; + + //clear interrupt capture flag + k->ResetCapFlags(dev, k); + + if (devpriv->ai_convert_count > 0) { + devpriv->ai_convert_count--; + if (devpriv->ai_convert_count == 0) + k->SetEnable(dev, k, CLKENAB_INDEX); + + if (cmd->convert_src == TRIG_TIMER) { + DEBUG("s626_irq_handler: conver timer trigger!!! %d\n", devpriv->ai_convert_count); + + // Trigger ADC scan loop start by setting RPS Signal 0. + MC_ENABLE(P_MC2, MC2_ADC_RPS); + } + } + } + if (irqbit & IRQ_COINT3B) { + DEBUG("s626_irq_handler: interrupt on counter 3B overflow\n"); + k = &encpriv[5]; + + //clear interrupt capture flag + k->ResetCapFlags(dev, k); + + if (cmd->scan_begin_src == TRIG_TIMER) { + DEBUG("s626_irq_handler: scan timer trigger!!!\n"); + + // Trigger ADC scan loop start by setting RPS Signal 0. + MC_ENABLE(P_MC2, MC2_ADC_RPS); + } + + if (cmd->convert_src == TRIG_TIMER) { + DEBUG("s626_irq_handler: convert timer trigger is set\n"); + k = &encpriv[4]; + devpriv->ai_convert_count = cmd->chanlist_len; + k->SetEnable(dev, k, CLKENAB_ALWAYS); + } + } + } + + //enable interrupt + writel(irqstatus, devpriv->base_addr + P_IER); + + DEBUG("s626_irq_handler: exit interrupt service routine.\n"); + + comedi_spin_unlock_irqrestore(&dev->spinlock, flags); + return IRQ_HANDLED; +} + +static int s626_detach(comedi_device * dev) +{ + if (devpriv) { + //stop ai_command + devpriv->ai_cmd_running = 0; + + if (devpriv->base_addr) { + //interrupt mask + WR7146(P_IER, 0); // Disable master interrupt. + WR7146(P_ISR, IRQ_GPIO3 | IRQ_RPS1); // Clear board's IRQ status flag. + + // Disable the watchdog timer and battery charger. + WriteMISC2(dev, 0); + + // Close all interfaces on 7146 device. + WR7146(P_MC1, MC1_SHUTDOWN); + WR7146(P_ACON1, ACON1_BASE); + + CloseDMAB(dev, &devpriv->RPSBuf, DMABUF_SIZE); + CloseDMAB(dev, &devpriv->ANABuf, DMABUF_SIZE); + } + + if (dev->irq) { + comedi_free_irq(dev->irq, dev); + } + + if (devpriv->base_addr) { + iounmap(devpriv->base_addr); + } + + if (devpriv->pdev) { + if (devpriv->got_regions) { + comedi_pci_disable(devpriv->pdev); + } + pci_dev_put(devpriv->pdev); + } + } + + DEBUG("s626_detach: S626 detached!\n"); + + return 0; +} + +/* + * this functions build the RPS program for hardware driven acquistion + */ +void ResetADC(comedi_device * dev, uint8_t * ppl) +{ + register uint32_t *pRPS; + uint32_t JmpAdrs; + uint16_t i; + uint16_t n; + uint32_t LocalPPL; + comedi_cmd *cmd = &(dev->subdevices->async->cmd); + + // Stop RPS program in case it is currently running. + MC_DISABLE(P_MC1, MC1_ERPS1); + + // Set starting logical address to write RPS commands. + pRPS = (uint32_t *) devpriv->RPSBuf.LogicalBase; + + // Initialize RPS instruction pointer. + WR7146(P_RPSADDR1, (uint32_t) devpriv->RPSBuf.PhysicalBase); + + // Construct RPS program in RPSBuf DMA buffer + + if (cmd != NULL && cmd->scan_begin_src != TRIG_FOLLOW) { + DEBUG("ResetADC: scan_begin pause inserted\n"); + // Wait for Start trigger. + *pRPS++ = RPS_PAUSE | RPS_SIGADC; + *pRPS++ = RPS_CLRSIGNAL | RPS_SIGADC; + } + // SAA7146 BUG WORKAROUND Do a dummy DEBI Write. This is necessary + // because the first RPS DEBI Write following a non-RPS DEBI write + // seems to always fail. If we don't do this dummy write, the ADC + // gain might not be set to the value required for the first slot in + // the poll list; the ADC gain would instead remain unchanged from + // the previously programmed value. + *pRPS++ = RPS_LDREG | (P_DEBICMD >> 2); // Write DEBI Write command + // and address to shadow RAM. + *pRPS++ = DEBI_CMD_WRWORD | LP_GSEL; + *pRPS++ = RPS_LDREG | (P_DEBIAD >> 2); // Write DEBI immediate data + // to shadow RAM: + *pRPS++ = GSEL_BIPOLAR5V; // arbitrary immediate data + // value. + *pRPS++ = RPS_CLRSIGNAL | RPS_DEBI; // Reset "shadow RAM + // uploaded" flag. + *pRPS++ = RPS_UPLOAD | RPS_DEBI; // Invoke shadow RAM upload. + *pRPS++ = RPS_PAUSE | RPS_DEBI; // Wait for shadow upload to finish. + + // Digitize all slots in the poll list. This is implemented as a + // for loop to limit the slot count to 16 in case the application + // forgot to set the EOPL flag in the final slot. + for (devpriv->AdcItems = 0; devpriv->AdcItems < 16; devpriv->AdcItems++) { + // Convert application's poll list item to private board class + // format. Each app poll list item is an uint8_t with form + // (EOPL,x,x,RANGE,CHAN<3:0>), where RANGE code indicates 0 = + // +-10V, 1 = +-5V, and EOPL = End of Poll List marker. + LocalPPL = + (*ppl << 8) | (*ppl & 0x10 ? GSEL_BIPOLAR5V : + GSEL_BIPOLAR10V); + + // Switch ADC analog gain. + *pRPS++ = RPS_LDREG | (P_DEBICMD >> 2); // Write DEBI command + // and address to + // shadow RAM. + *pRPS++ = DEBI_CMD_WRWORD | LP_GSEL; + *pRPS++ = RPS_LDREG | (P_DEBIAD >> 2); // Write DEBI + // immediate data to + // shadow RAM. + *pRPS++ = LocalPPL; + *pRPS++ = RPS_CLRSIGNAL | RPS_DEBI; // Reset "shadow RAM uploaded" + // flag. + *pRPS++ = RPS_UPLOAD | RPS_DEBI; // Invoke shadow RAM upload. + *pRPS++ = RPS_PAUSE | RPS_DEBI; // Wait for shadow upload to + // finish. + + // Select ADC analog input channel. + *pRPS++ = RPS_LDREG | (P_DEBICMD >> 2); // Write DEBI command + // and address to + // shadow RAM. + *pRPS++ = DEBI_CMD_WRWORD | LP_ISEL; + *pRPS++ = RPS_LDREG | (P_DEBIAD >> 2); // Write DEBI + // immediate data to + // shadow RAM. + *pRPS++ = LocalPPL; + *pRPS++ = RPS_CLRSIGNAL | RPS_DEBI; // Reset "shadow RAM uploaded" + // flag. + *pRPS++ = RPS_UPLOAD | RPS_DEBI; // Invoke shadow RAM upload. + *pRPS++ = RPS_PAUSE | RPS_DEBI; // Wait for shadow upload to + // finish. + + // Delay at least 10 microseconds for analog input settling. + // Instead of padding with NOPs, we use RPS_JUMP instructions + // here; this allows us to produce a longer delay than is + // possible with NOPs because each RPS_JUMP flushes the RPS' + // instruction prefetch pipeline. + JmpAdrs = + (uint32_t) devpriv->RPSBuf.PhysicalBase + + (uint32_t) ((unsigned long)pRPS - + (unsigned long)devpriv->RPSBuf.LogicalBase); + for (i = 0; i < (10 * RPSCLK_PER_US / 2); i++) { + JmpAdrs += 8; // Repeat to implement time delay: + *pRPS++ = RPS_JUMP; // Jump to next RPS instruction. + *pRPS++ = JmpAdrs; + } + + if (cmd != NULL && cmd->convert_src != TRIG_NOW) { + DEBUG("ResetADC: convert pause inserted\n"); + // Wait for Start trigger. + *pRPS++ = RPS_PAUSE | RPS_SIGADC; + *pRPS++ = RPS_CLRSIGNAL | RPS_SIGADC; + } + // Start ADC by pulsing GPIO1. + *pRPS++ = RPS_LDREG | (P_GPIO >> 2); // Begin ADC Start pulse. + *pRPS++ = GPIO_BASE | GPIO1_LO; + *pRPS++ = RPS_NOP; + // VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. + *pRPS++ = RPS_LDREG | (P_GPIO >> 2); // End ADC Start pulse. + *pRPS++ = GPIO_BASE | GPIO1_HI; + + // Wait for ADC to complete (GPIO2 is asserted high when ADC not + // busy) and for data from previous conversion to shift into FB + // BUFFER 1 register. + *pRPS++ = RPS_PAUSE | RPS_GPIO2; // Wait for ADC done. + + // Transfer ADC data from FB BUFFER 1 register to DMA buffer. + *pRPS++ = RPS_STREG | (BUGFIX_STREG(P_FB_BUFFER1) >> 2); + *pRPS++ = + (uint32_t) devpriv->ANABuf.PhysicalBase + + (devpriv->AdcItems << 2); + + // If this slot's EndOfPollList flag is set, all channels have + // now been processed. + if (*ppl++ & EOPL) { + devpriv->AdcItems++; // Adjust poll list item count. + break; // Exit poll list processing loop. + } + } + DEBUG("ResetADC: ADC items %d \n", devpriv->AdcItems); + + // VERSION 2.01 CHANGE: DELAY CHANGED FROM 250NS to 2US. Allow the + // ADC to stabilize for 2 microseconds before starting the final + // (dummy) conversion. This delay is necessary to allow sufficient + // time between last conversion finished and the start of the dummy + // conversion. Without this delay, the last conversion's data value + // is sometimes set to the previous conversion's data value. + for (n = 0; n < (2 * RPSCLK_PER_US); n++) + *pRPS++ = RPS_NOP; + + // Start a dummy conversion to cause the data from the last + // conversion of interest to be shifted in. + *pRPS++ = RPS_LDREG | (P_GPIO >> 2); // Begin ADC Start pulse. + *pRPS++ = GPIO_BASE | GPIO1_LO; + *pRPS++ = RPS_NOP; + // VERSION 2.03 CHANGE: STRETCH OUT ADC START PULSE. + *pRPS++ = RPS_LDREG | (P_GPIO >> 2); // End ADC Start pulse. + *pRPS++ = GPIO_BASE | GPIO1_HI; + + // Wait for the data from the last conversion of interest to arrive + // in FB BUFFER 1 register. + *pRPS++ = RPS_PAUSE | RPS_GPIO2; // Wait for ADC done. + + // Transfer final ADC data from FB BUFFER 1 register to DMA buffer. + *pRPS++ = RPS_STREG | (BUGFIX_STREG(P_FB_BUFFER1) >> 2); // + *pRPS++ = + (uint32_t) devpriv->ANABuf.PhysicalBase + + (devpriv->AdcItems << 2); + + // Indicate ADC scan loop is finished. + // *pRPS++= RPS_CLRSIGNAL | RPS_SIGADC ; // Signal ReadADC() that scan is done. + + //invoke interrupt + if (devpriv->ai_cmd_running == 1) { + DEBUG("ResetADC: insert irq in ADC RPS task\n"); + *pRPS++ = RPS_IRQ; + } + // Restart RPS program at its beginning. + *pRPS++ = RPS_JUMP; // Branch to start of RPS program. + *pRPS++ = (uint32_t) devpriv->RPSBuf.PhysicalBase; + + // End of RPS program build + // ------------------------------------------------------------ +} + +/* TO COMPLETE, IF NECESSARY */ +static int s626_ai_insn_config(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + + return -EINVAL; +} + +/* static int s626_ai_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data) */ +/* { */ +/* register uint8_t i; */ +/* register int32_t *readaddr; */ + +/* DEBUG("as626_ai_rinsn: ai_rinsn enter \n"); */ + +/* // Trigger ADC scan loop start by setting RPS Signal 0. */ +/* MC_ENABLE( P_MC2, MC2_ADC_RPS ); */ + +/* // Wait until ADC scan loop is finished (RPS Signal 0 reset). */ +/* while ( MC_TEST( P_MC2, MC2_ADC_RPS ) ); */ + +/* // Init ptr to DMA buffer that holds new ADC data. We skip the */ +/* // first uint16_t in the buffer because it contains junk data from */ +/* // the final ADC of the previous poll list scan. */ +/* readaddr = (uint32_t *)devpriv->ANABuf.LogicalBase + 1; */ + +/* // Convert ADC data to 16-bit integer values and copy to application */ +/* // buffer. */ +/* for ( i = 0; i < devpriv->AdcItems; i++ ) { */ +/* *data = s626_ai_reg_to_uint( *readaddr++ ); */ +/* DEBUG("s626_ai_rinsn: data %d \n",*data); */ +/* data++; */ +/* } */ + +/* DEBUG("s626_ai_rinsn: ai_rinsn escape \n"); */ +/* return i; */ +/* } */ + +static int s626_ai_insn_read(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + uint16_t chan = CR_CHAN(insn->chanspec); + uint16_t range = CR_RANGE(insn->chanspec); + uint16_t AdcSpec = 0; + uint32_t GpioImage; + int n; + +/* //interrupt call test */ +/* writel(IRQ_GPIO3,devpriv->base_addr+P_PSR); //Writing a logical 1 */ +/* //into any of the RPS_PSR */ +/* //bits causes the */ +/* //corresponding interrupt */ +/* //to be generated if */ +/* //enabled */ + + DEBUG("s626_ai_insn_read: entering\n"); + + // Convert application's ADC specification into form + // appropriate for register programming. + if (range == 0) + AdcSpec = (chan << 8) | (GSEL_BIPOLAR5V); + else + AdcSpec = (chan << 8) | (GSEL_BIPOLAR10V); + + // Switch ADC analog gain. + DEBIwrite(dev, LP_GSEL, AdcSpec); // Set gain. + + // Select ADC analog input channel. + DEBIwrite(dev, LP_ISEL, AdcSpec); // Select channel. + + for (n = 0; n < insn->n; n++) { + + // Delay 10 microseconds for analog input settling. + comedi_udelay(10); + + // Start ADC by pulsing GPIO1 low. + GpioImage = RR7146(P_GPIO); + // Assert ADC Start command + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + // and stretch it out. + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + // Negate ADC Start command. + WR7146(P_GPIO, GpioImage | GPIO1_HI); + + // Wait for ADC to complete (GPIO2 is asserted high when + // ADC not busy) and for data from previous conversion to + // shift into FB BUFFER 1 register. + + // Wait for ADC done. + while (!(RR7146(P_PSR) & PSR_GPIO2)) ; + + // Fetch ADC data. + if (n != 0) + data[n - 1] = s626_ai_reg_to_uint(RR7146(P_FB_BUFFER1)); + + // Allow the ADC to stabilize for 4 microseconds before + // starting the next (final) conversion. This delay is + // necessary to allow sufficient time between last + // conversion finished and the start of the next + // conversion. Without this delay, the last conversion's + // data value is sometimes set to the previous + // conversion's data value. + comedi_udelay(4); + } + + // Start a dummy conversion to cause the data from the + // previous conversion to be shifted in. + GpioImage = RR7146(P_GPIO); + + //Assert ADC Start command + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + // and stretch it out. + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + WR7146(P_GPIO, GpioImage & ~GPIO1_HI); + // Negate ADC Start command. + WR7146(P_GPIO, GpioImage | GPIO1_HI); + + // Wait for the data to arrive in FB BUFFER 1 register. + + // Wait for ADC done. + while (!(RR7146(P_PSR) & PSR_GPIO2)) ; + + // Fetch ADC data from audio interface's input shift + // register. + + // Fetch ADC data. + if (n != 0) + data[n - 1] = s626_ai_reg_to_uint(RR7146(P_FB_BUFFER1)); + + DEBUG("s626_ai_insn_read: samples %d, data %d\n", n, data[n - 1]); + + return n; +} + +static int s626_ai_load_polllist(uint8_t * ppl, comedi_cmd * cmd) +{ + + int n; + + for (n = 0; n < cmd->chanlist_len; n++) { + if (CR_RANGE((cmd->chanlist)[n]) == 0) + ppl[n] = (CR_CHAN((cmd->chanlist)[n])) | (RANGE_5V); + else + ppl[n] = (CR_CHAN((cmd->chanlist)[n])) | (RANGE_10V); + } + ppl[n - 1] |= EOPL; + + return n; +} + +static int s626_ai_inttrig(comedi_device * dev, comedi_subdevice * s, + unsigned int trignum) +{ + if (trignum != 0) + return -EINVAL; + + DEBUG("s626_ai_inttrig: trigger adc start..."); + + // Start executing the RPS program. + MC_ENABLE(P_MC1, MC1_ERPS1); + + s->async->inttrig = NULL; + + DEBUG(" done\n"); + + return 1; +} + +/* TO COMPLETE */ +static int s626_ai_cmd(comedi_device * dev, comedi_subdevice * s) +{ + + uint8_t ppl[16]; + comedi_cmd *cmd = &s->async->cmd; + enc_private *k; + int tick; + + DEBUG("s626_ai_cmd: entering command function\n"); + + if (devpriv->ai_cmd_running) { + printk("s626_ai_cmd: Another ai_cmd is running %d\n", + dev->minor); + return -EBUSY; + } + //disable interrupt + writel(0, devpriv->base_addr + P_IER); + + //clear interrupt request + writel(IRQ_RPS1 | IRQ_GPIO3, devpriv->base_addr + P_ISR); + + //clear any pending interrupt + s626_dio_clear_irq(dev); + // s626_enc_clear_irq(dev); + + //reset ai_cmd_running flag + devpriv->ai_cmd_running = 0; + + // test if cmd is valid + if (cmd == NULL) { + DEBUG("s626_ai_cmd: NULL command\n"); + return -EINVAL; + } else { + DEBUG("s626_ai_cmd: command recieved!!!\n"); + } + + if (dev->irq == 0) { + comedi_error(dev, + "s626_ai_cmd: cannot run command without an irq"); + return -EIO; + } + + s626_ai_load_polllist(ppl, cmd); + devpriv->ai_cmd_running = 1; + devpriv->ai_convert_count = 0; + + switch (cmd->scan_begin_src) { + case TRIG_FOLLOW: + break; + case TRIG_TIMER: + // set a conter to generate adc trigger at scan_begin_arg interval + k = &encpriv[5]; + tick = s626_ns_to_timer((int *)&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + + //load timer value and enable interrupt + s626_timer_load(dev, k, tick); + k->SetEnable(dev, k, CLKENAB_ALWAYS); + + DEBUG("s626_ai_cmd: scan trigger timer is set with value %d\n", + tick); + + break; + case TRIG_EXT: + // set the digital line and interrupt for scan trigger + if (cmd->start_src != TRIG_EXT) + s626_dio_set_irq(dev, cmd->scan_begin_arg); + + DEBUG("s626_ai_cmd: External scan trigger is set!!!\n"); + + break; + } + + switch (cmd->convert_src) { + case TRIG_NOW: + break; + case TRIG_TIMER: + // set a conter to generate adc trigger at convert_arg interval + k = &encpriv[4]; + tick = s626_ns_to_timer((int *)&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + + //load timer value and enable interrupt + s626_timer_load(dev, k, tick); + k->SetEnable(dev, k, CLKENAB_INDEX); + + DEBUG("s626_ai_cmd: convert trigger timer is set with value %d\n", tick); + break; + case TRIG_EXT: + // set the digital line and interrupt for convert trigger + if (cmd->scan_begin_src != TRIG_EXT + && cmd->start_src == TRIG_EXT) + s626_dio_set_irq(dev, cmd->convert_arg); + + DEBUG("s626_ai_cmd: External convert trigger is set!!!\n"); + + break; + } + + switch (cmd->stop_src) { + case TRIG_COUNT: + // data arrives as one packet + devpriv->ai_sample_count = cmd->stop_arg; + devpriv->ai_continous = 0; + break; + case TRIG_NONE: + // continous aquisition + devpriv->ai_continous = 1; + devpriv->ai_sample_count = 0; + break; + } + + ResetADC(dev, ppl); + + switch (cmd->start_src) { + case TRIG_NOW: + // Trigger ADC scan loop start by setting RPS Signal 0. + // MC_ENABLE( P_MC2, MC2_ADC_RPS ); + + // Start executing the RPS program. + MC_ENABLE(P_MC1, MC1_ERPS1); + + DEBUG("s626_ai_cmd: ADC triggered\n"); + s->async->inttrig = NULL; + break; + case TRIG_EXT: + //configure DIO channel for acquisition trigger + s626_dio_set_irq(dev, cmd->start_arg); + + DEBUG("s626_ai_cmd: External start trigger is set!!!\n"); + + s->async->inttrig = NULL; + break; + case TRIG_INT: + s->async->inttrig = s626_ai_inttrig; + break; + } + + //enable interrupt + writel(IRQ_GPIO3 | IRQ_RPS1, devpriv->base_addr + P_IER); + + DEBUG("s626_ai_cmd: command function terminated\n"); + + return 0; +} + +static int s626_ai_cmdtest(comedi_device * dev, comedi_subdevice * s, + comedi_cmd * cmd) +{ + int err = 0; + int tmp; + + /* cmdtest tests a particular command to see if it is valid. Using + * the cmdtest ioctl, a user can create a valid cmd and then have it + * executes by the cmd ioctl. + * + * cmdtest returns 1,2,3,4 or 0, depending on which tests the + * command passes. */ + + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | 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 | TRIG_FOLLOW; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT | TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually + compatible */ + + /* note that mutual compatiblity is not an issue here */ + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_EXT + && 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) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_src != TRIG_EXT && cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->start_src == TRIG_EXT && cmd->start_arg < 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->start_src == TRIG_EXT && cmd->start_arg > 39) { + cmd->start_arg = 39; + err++; + } + + if (cmd->scan_begin_src == TRIG_EXT && cmd->scan_begin_arg < 0) { + cmd->scan_begin_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_EXT && cmd->scan_begin_arg > 39) { + cmd->scan_begin_arg = 39; + err++; + } + + if (cmd->convert_src == TRIG_EXT && cmd->convert_arg < 0) { + cmd->convert_arg = 0; + err++; + } + + if (cmd->convert_src == TRIG_EXT && cmd->convert_arg > 39) { + cmd->convert_arg = 39; + err++; + } +#define MAX_SPEED 200000 /* in nanoseconds */ +#define MIN_SPEED 2000000000 /* in nanoseconds */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (cmd->scan_begin_arg < MAX_SPEED) { + cmd->scan_begin_arg = MAX_SPEED; + err++; + } + if (cmd->scan_begin_arg > MIN_SPEED) { + cmd->scan_begin_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* should be level/edge, hi/lo specification here */ + /* should specify multiple external triggers */ +/* if(cmd->scan_begin_arg>9){ */ +/* cmd->scan_begin_arg=9; */ +/* err++; */ +/* } */ + } + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < MAX_SPEED) { + cmd->convert_arg = MAX_SPEED; + err++; + } + if (cmd->convert_arg > MIN_SPEED) { + cmd->convert_arg = MIN_SPEED; + err++; + } + } else { + /* external trigger */ + /* see above */ +/* if(cmd->convert_arg>9){ */ +/* cmd->convert_arg=9; */ +/* err++; */ +/* } */ + } + + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + if (cmd->stop_src == TRIG_COUNT) { + if (cmd->stop_arg > 0x00ffffff) { + cmd->stop_arg = 0x00ffffff; + err++; + } + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + if (cmd->scan_begin_src == TRIG_TIMER) { + tmp = cmd->scan_begin_arg; + s626_ns_to_timer((int *)&cmd->scan_begin_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->scan_begin_arg) + err++; + } + if (cmd->convert_src == TRIG_TIMER) { + tmp = cmd->convert_arg; + s626_ns_to_timer((int *)&cmd->convert_arg, + cmd->flags & TRIG_ROUND_MASK); + if (tmp != cmd->convert_arg) + err++; + if (cmd->scan_begin_src == TRIG_TIMER && + cmd->scan_begin_arg < + cmd->convert_arg * cmd->scan_end_arg) { + cmd->scan_begin_arg = + cmd->convert_arg * cmd->scan_end_arg; + err++; + } + } + + if (err) + return 4; + + return 0; +} + +static int s626_ai_cancel(comedi_device * dev, comedi_subdevice * s) +{ + // Stop RPS program in case it is currently running. + MC_DISABLE(P_MC1, MC1_ERPS1); + + //disable master interrupt + writel(0, devpriv->base_addr + P_IER); + + devpriv->ai_cmd_running = 0; + + return 0; +} + +/* This function doesn't require a particular form, this is just what + * happens to be used in some of the drivers. It should convert ns + * nanoseconds to a counter value suitable for programming the device. + * Also, it should adjust ns so that it cooresponds to the actual time + * that the device will use. */ +static int s626_ns_to_timer(int *nanosec, int round_mode) +{ + int divider, base; + + base = 500; //2MHz internal clock + + switch (round_mode) { + case TRIG_ROUND_NEAREST: + default: + divider = (*nanosec + base / 2) / base; + break; + case TRIG_ROUND_DOWN: + divider = (*nanosec) / base; + break; + case TRIG_ROUND_UP: + divider = (*nanosec + base - 1) / base; + break; + } + + *nanosec = base * divider; + return divider - 1; +} + +static int s626_ao_winsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + + int i; + uint16_t chan = CR_CHAN(insn->chanspec); + int16_t dacdata; + + for (i = 0; i < insn->n; i++) { + dacdata = (int16_t) data[i]; + devpriv->ao_readback[CR_CHAN(insn->chanspec)] = data[i]; + dacdata -= (0x1fff); + + SetDAC(dev, chan, dacdata); + } + + return i; +} + +static int s626_ao_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + int i; + + for (i = 0; i < insn->n; i++) { + data[i] = devpriv->ao_readback[CR_CHAN(insn->chanspec)]; + } + + return i; +} + +///////////////////////////////////////////////////////////////////// +/////////////// DIGITAL I/O FUNCTIONS ///////////////////////////// +///////////////////////////////////////////////////////////////////// +// All DIO functions address a group of DIO channels by means of +// "group" argument. group may be 0, 1 or 2, which correspond to DIO +// ports A, B and C, respectively. +///////////////////////////////////////////////////////////////////// + +static void s626_dio_init(comedi_device * dev) +{ + uint16_t group; + comedi_subdevice *s; + + // Prepare to treat writes to WRCapSel as capture disables. + DEBIwrite(dev, LP_MISC1, MISC1_NOEDCAP); + + // For each group of sixteen channels ... + for (group = 0; group < S626_DIO_BANKS; group++) { + s = dev->subdevices + 2 + group; + DEBIwrite(dev, diopriv->WRIntSel, 0); // Disable all interrupts. + DEBIwrite(dev, diopriv->WRCapSel, 0xFFFF); // Disable all event + // captures. + DEBIwrite(dev, diopriv->WREdgSel, 0); // Init all DIOs to + // default edge + // polarity. + DEBIwrite(dev, diopriv->WRDOut, 0); // Program all outputs + // to inactive state. + } + DEBUG("s626_dio_init: DIO initialized \n"); +} + +/* DIO devices are slightly special. Although it is possible to + * implement the insn_read/insn_write interface, it is much more + * useful to applications if you implement the insn_bits interface. + * This allows packed reading/writing of the DIO channels. The comedi + * core can convert between insn_bits and insn_read/write */ + +static int s626_dio_insn_bits(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + + /* Length of data must be 2 (mask and new data, see below) */ + if (insn->n == 0) { + return 0; + } + if (insn->n != 2) { + printk("comedi%d: s626: s626_dio_insn_bits(): Invalid instruction length\n", dev->minor); + return -EINVAL; + } + + /* + * The insn data consists of a mask in data[0] and the new data in + * data[1]. The mask defines which bits we are concerning about. + * The new data must be anded with the mask. Each channel + * corresponds to a bit. + */ + if (data[0]) { + /* Check if requested ports are configured for output */ + if ((s->io_bits & data[0]) != data[0]) + return -EIO; + + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + + /* Write out the new digital output lines */ + + DEBIwrite(dev, diopriv->WRDOut, s->state); + } + data[1] = DEBIread(dev, diopriv->RDDIn); + + return 2; +} + +static int s626_dio_insn_config(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + + switch (data[0]) { + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s->io_bits & (1 << CR_CHAN(insn-> + chanspec))) ? COMEDI_OUTPUT : + COMEDI_INPUT; + return insn->n; + break; + case COMEDI_INPUT: + s->io_bits &= ~(1 << CR_CHAN(insn->chanspec)); + break; + case COMEDI_OUTPUT: + s->io_bits |= 1 << CR_CHAN(insn->chanspec); + break; + default: + return -EINVAL; + break; + } + DEBIwrite(dev, diopriv->WRDOut, s->io_bits); + + return 1; +} + +static int s626_dio_set_irq(comedi_device * dev, unsigned int chan) +{ + unsigned int group; + unsigned int bitmask; + unsigned int status; + + //select dio bank + group = chan / 16; + bitmask = 1 << (chan - (16 * group)); + DEBUG("s626_dio_set_irq: enable interrupt on dio channel %d group %d\n", + chan - (16 * group), group); + + //set channel to capture positive edge + status = DEBIread(dev, + ((dio_private *) (dev->subdevices + 2 + + group)->private)->RDEdgSel); + DEBIwrite(dev, + ((dio_private *) (dev->subdevices + 2 + + group)->private)->WREdgSel, bitmask | status); + + //enable interrupt on selected channel + status = DEBIread(dev, + ((dio_private *) (dev->subdevices + 2 + + group)->private)->RDIntSel); + DEBIwrite(dev, + ((dio_private *) (dev->subdevices + 2 + + group)->private)->WRIntSel, bitmask | status); + + //enable edge capture write command + DEBIwrite(dev, LP_MISC1, MISC1_EDCAP); + + //enable edge capture on selected channel + status = DEBIread(dev, + ((dio_private *) (dev->subdevices + 2 + + group)->private)->RDCapSel); + DEBIwrite(dev, + ((dio_private *) (dev->subdevices + 2 + + group)->private)->WRCapSel, bitmask | status); + + return 0; +} + +static int s626_dio_reset_irq(comedi_device * dev, unsigned int group, + unsigned int mask) +{ + DEBUG("s626_dio_reset_irq: disable interrupt on dio channel %d group %d\n", mask, group); + + //disable edge capture write command + DEBIwrite(dev, LP_MISC1, MISC1_NOEDCAP); + + //enable edge capture on selected channel + DEBIwrite(dev, + ((dio_private *) (dev->subdevices + 2 + + group)->private)->WRCapSel, mask); + + return 0; +} + +static int s626_dio_clear_irq(comedi_device * dev) +{ + unsigned int group; + + //disable edge capture write command + DEBIwrite(dev, LP_MISC1, MISC1_NOEDCAP); + + for (group = 0; group < S626_DIO_BANKS; group++) { + //clear pending events and interrupt + DEBIwrite(dev, + ((dio_private *) (dev->subdevices + 2 + + group)->private)->WRCapSel, 0xffff); + } + + return 0; +} + +/* Now this function initializes the value of the counter (data[0]) + and set the subdevice. To complete with trigger and interrupt + configuration */ +static int s626_enc_insn_config(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + uint16_t Setup = (LOADSRC_INDX << BF_LOADSRC) | // Preload upon + // index. + (INDXSRC_SOFT << BF_INDXSRC) | // Disable hardware index. + (CLKSRC_COUNTER << BF_CLKSRC) | // Operating mode is Counter. + (CLKPOL_POS << BF_CLKPOL) | // Active high clock. + //( CNTDIR_UP << BF_CLKPOL ) | // Count direction is Down. + (CLKMULT_1X << BF_CLKMULT) | // Clock multiplier is 1x. + (CLKENAB_INDEX << BF_CLKENAB); + /* uint16_t DisableIntSrc=TRUE; */ + // uint32_t Preloadvalue; //Counter initial value + uint16_t valueSrclatch = LATCHSRC_AB_READ; + uint16_t enab = CLKENAB_ALWAYS; + enc_private *k = &encpriv[CR_CHAN(insn->chanspec)]; + + DEBUG("s626_enc_insn_config: encoder config\n"); + + // (data==NULL) ? (Preloadvalue=0) : (Preloadvalue=data[0]); + + k->SetMode(dev, k, Setup, TRUE); + Preload(dev, k, *(insn->data)); + k->PulseIndex(dev, k); + SetLatchSource(dev, k, valueSrclatch); + k->SetEnable(dev, k, (uint16_t) (enab != 0)); + + return insn->n; +} + +static int s626_enc_insn_read(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + + int n; + enc_private *k = &encpriv[CR_CHAN(insn->chanspec)]; + + DEBUG("s626_enc_insn_read: encoder read channel %d \n", + CR_CHAN(insn->chanspec)); + + for (n = 0; n < insn->n; n++) + data[n] = ReadLatch(dev, k); + + DEBUG("s626_enc_insn_read: encoder sample %d\n", data[n]); + + return n; +} + +static int s626_enc_insn_write(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + + enc_private *k = &encpriv[CR_CHAN(insn->chanspec)]; + + DEBUG("s626_enc_insn_write: encoder write channel %d \n", + CR_CHAN(insn->chanspec)); + + // Set the preload register + Preload(dev, k, data[0]); + + // Software index pulse forces the preload register to load + // into the counter + k->SetLoadTrig(dev, k, 0); + k->PulseIndex(dev, k); + k->SetLoadTrig(dev, k, 2); + + DEBUG("s626_enc_insn_write: End encoder write\n"); + + return 1; +} + +static void s626_timer_load(comedi_device * dev, enc_private * k, int tick) +{ + uint16_t Setup = (LOADSRC_INDX << BF_LOADSRC) | // Preload upon + // index. + (INDXSRC_SOFT << BF_INDXSRC) | // Disable hardware index. + (CLKSRC_TIMER << BF_CLKSRC) | // Operating mode is Timer. + (CLKPOL_POS << BF_CLKPOL) | // Active high clock. + (CNTDIR_DOWN << BF_CLKPOL) | // Count direction is Down. + (CLKMULT_1X << BF_CLKMULT) | // Clock multiplier is 1x. + (CLKENAB_INDEX << BF_CLKENAB); + uint16_t valueSrclatch = LATCHSRC_A_INDXA; + // uint16_t enab=CLKENAB_ALWAYS; + + k->SetMode(dev, k, Setup, FALSE); + + // Set the preload register + Preload(dev, k, tick); + + // Software index pulse forces the preload register to load + // into the counter + k->SetLoadTrig(dev, k, 0); + k->PulseIndex(dev, k); + + //set reload on counter overflow + k->SetLoadTrig(dev, k, 1); + + //set interrupt on overflow + k->SetIntSrc(dev, k, INTSRC_OVER); + + SetLatchSource(dev, k, valueSrclatch); + // k->SetEnable(dev,k,(uint16_t)(enab != 0)); +} + +/////////////////////////////////////////////////////////////////////// +///////////////////// DAC FUNCTIONS ///////////////////////////////// +/////////////////////////////////////////////////////////////////////// + +// Slot 0 base settings. +#define VECT0 ( XSD2 | RSD3 | SIB_A2 ) // Slot 0 always shifts in + // 0xFF and store it to + // FB_BUFFER2. + +// TrimDac LogicalChan-to-PhysicalChan mapping table. +static uint8_t trimchan[] = { 10, 9, 8, 3, 2, 7, 6, 1, 0, 5, 4 }; + +// TrimDac LogicalChan-to-EepromAdrs mapping table. +static uint8_t trimadrs[] = + { 0x40, 0x41, 0x42, 0x50, 0x51, 0x52, 0x53, 0x60, 0x61, 0x62, 0x63 }; + +static void LoadTrimDACs(comedi_device * dev) +{ + register uint8_t i; + + // Copy TrimDac setpoint values from EEPROM to TrimDacs. + for (i = 0; i < (sizeof(trimchan) / sizeof(trimchan[0])); i++) + WriteTrimDAC(dev, i, I2Cread(dev, trimadrs[i])); +} + +static void WriteTrimDAC(comedi_device * dev, uint8_t LogicalChan, + uint8_t DacData) +{ + uint32_t chan; + + // Save the new setpoint in case the application needs to read it back later. + devpriv->TrimSetpoint[LogicalChan] = (uint8_t) DacData; + + // Map logical channel number to physical channel number. + chan = (uint32_t) trimchan[LogicalChan]; + + // Set up TSL2 records for TrimDac write operation. All slots shift + // 0xFF in from pulled-up SD3 so that the end of the slot sequence + // can be detected. + SETVECT(2, XSD2 | XFIFO_1 | WS3); // Slot 2: Send high uint8_t + // to target TrimDac. + SETVECT(3, XSD2 | XFIFO_0 | WS3); // Slot 3: Send low uint8_t to + // target TrimDac. + SETVECT(4, XSD2 | XFIFO_3 | WS1); // Slot 4: Send NOP high + // uint8_t to DAC0 to keep + // clock running. + SETVECT(5, XSD2 | XFIFO_2 | WS1 | EOS); // Slot 5: Send NOP low + // uint8_t to DAC0. + + // Construct and transmit target DAC's serial packet: ( 0000 AAAA + // ),( DDDD DDDD ),( 0x00 ),( 0x00 ) where A<3:0> is the DAC + // channel's address, and D<7:0> is the DAC setpoint. Append a WORD + // value (that writes a channel 0 NOP command to a non-existent main + // DAC channel) that serves to keep the clock running after the + // packet has been sent to the target DAC. + + SendDAC(dev, ((uint32_t) chan << 8) // Address the DAC channel + // within the trimdac device. + | (uint32_t) DacData); // Include DAC setpoint data. +} + +///////////////////////////////////////////////////////////////////////// +//////////////// EEPROM ACCESS FUNCTIONS ////////////////////////////// +///////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////// +// Read uint8_t from EEPROM. + +static uint8_t I2Cread(comedi_device * dev, uint8_t addr) +{ + uint8_t rtnval; + + // Send EEPROM target address. + if (I2Chandshake(dev, I2C_B2(I2C_ATTRSTART, I2CW) // Byte2 = I2C + // command: + // write to + // I2C EEPROM + // device. + | I2C_B1(I2C_ATTRSTOP, addr) // Byte1 = EEPROM + // internal target + // address. + | I2C_B0(I2C_ATTRNOP, 0))) // Byte0 = Not + // sent. + { + // Abort function and declare error if handshake failed. + DEBUG("I2Cread: error handshake I2Cread a\n"); + return 0; + } + // Execute EEPROM read. + if (I2Chandshake(dev, I2C_B2(I2C_ATTRSTART, I2CR) // Byte2 = I2C + // command: read + // from I2C EEPROM + // device. + | I2C_B1(I2C_ATTRSTOP, 0) // Byte1 receives + // uint8_t from + // EEPROM. + | I2C_B0(I2C_ATTRNOP, 0))) // Byte0 = Not + // sent. + { + // Abort function and declare error if handshake failed. + DEBUG("I2Cread: error handshake I2Cread b\n"); + return 0; + } + // Return copy of EEPROM value. + rtnval = (uint8_t) (RR7146(P_I2CCTRL) >> 16); + return rtnval; +} + +static uint32_t I2Chandshake(comedi_device * dev, uint32_t val) +{ + // Write I2C command to I2C Transfer Control shadow register. + WR7146(P_I2CCTRL, val); + + // Upload I2C shadow registers into working registers and wait for + // upload confirmation. + + MC_ENABLE(P_MC2, MC2_UPLD_IIC); + while (!MC_TEST(P_MC2, MC2_UPLD_IIC)) ; + + // Wait until I2C bus transfer is finished or an error occurs. + while ((RR7146(P_I2CCTRL) & (I2C_BUSY | I2C_ERR)) == I2C_BUSY) ; + + // Return non-zero if I2C error occured. + return RR7146(P_I2CCTRL) & I2C_ERR; + +} + +// Private helper function: Write setpoint to an application DAC channel. + +static void SetDAC(comedi_device * dev, uint16_t chan, short dacdata) +{ + register uint16_t signmask; + register uint32_t WSImage; + + // Adjust DAC data polarity and set up Polarity Control Register + // image. + signmask = 1 << chan; + if (dacdata < 0) { + dacdata = -dacdata; + devpriv->Dacpol |= signmask; + } else + devpriv->Dacpol &= ~signmask; + + // Limit DAC setpoint value to valid range. + if ((uint16_t) dacdata > 0x1FFF) + dacdata = 0x1FFF; + + // Set up TSL2 records (aka "vectors") for DAC update. Vectors V2 + // and V3 transmit the setpoint to the target DAC. V4 and V5 send + // data to a non-existent TrimDac channel just to keep the clock + // running after sending data to the target DAC. This is necessary + // to eliminate the clock glitch that would otherwise occur at the + // end of the target DAC's serial data stream. When the sequence + // restarts at V0 (after executing V5), the gate array automatically + // disables gating for the DAC clock and all DAC chip selects. + WSImage = (chan & 2) ? WS1 : WS2; // Choose DAC chip select to + // be asserted. + SETVECT(2, XSD2 | XFIFO_1 | WSImage); // Slot 2: Transmit high + // data byte to target DAC. + SETVECT(3, XSD2 | XFIFO_0 | WSImage); // Slot 3: Transmit low data + // byte to target DAC. + SETVECT(4, XSD2 | XFIFO_3 | WS3); // Slot 4: Transmit to + // non-existent TrimDac + // channel to keep clock + SETVECT(5, XSD2 | XFIFO_2 | WS3 | EOS); // Slot 5: running after + // writing target DAC's + // low data byte. + + // Construct and transmit target DAC's serial packet: ( A10D DDDD + // ),( DDDD DDDD ),( 0x0F ),( 0x00 ) where A is chan<0>, and D<12:0> + // is the DAC setpoint. Append a WORD value (that writes to a + // non-existent TrimDac channel) that serves to keep the clock + // running after the packet has been sent to the target DAC. + SendDAC(dev, 0x0F000000 //Continue clock after target DAC + //data (write to non-existent + //trimdac). + | 0x00004000 // Address the two main dual-DAC + // devices (TSL's chip select enables + // target device). + | ((uint32_t) (chan & 1) << 15) // Address the DAC + // channel within the + // device. + | (uint32_t) dacdata); // Include DAC setpoint data. + +} + +//////////////////////////////////////////////////////// +// Private helper function: Transmit serial data to DAC via Audio +// channel 2. Assumes: (1) TSL2 slot records initialized, and (2) +// Dacpol contains valid target image. + +static void SendDAC(comedi_device * dev, uint32_t val) +{ + + // START THE SERIAL CLOCK RUNNING ------------- + + // Assert DAC polarity control and enable gating of DAC serial clock + // and audio bit stream signals. At this point in time we must be + // assured of being in time slot 0. If we are not in slot 0, the + // serial clock and audio stream signals will be disabled; this is + // because the following DEBIwrite statement (which enables signals + // to be passed through the gate array) would execute before the + // trailing edge of WS1/WS3 (which turns off the signals), thus + // causing the signals to be inactive during the DAC write. + DEBIwrite(dev, LP_DACPOL, devpriv->Dacpol); + + // TRANSFER OUTPUT DWORD VALUE INTO A2'S OUTPUT FIFO ---------------- + + // Copy DAC setpoint value to DAC's output DMA buffer. + + //WR7146( (uint32_t)devpriv->pDacWBuf, val ); + *devpriv->pDacWBuf = val; + + // enab the output DMA transfer. This will cause the DMAC to copy + // the DAC's data value to A2's output FIFO. The DMA transfer will + // then immediately terminate because the protection address is + // reached upon transfer of the first DWORD value. + MC_ENABLE(P_MC1, MC1_A2OUT); + + // While the DMA transfer is executing ... + + // Reset Audio2 output FIFO's underflow flag (along with any other + // FIFO underflow/overflow flags). When set, this flag will + // indicate that we have emerged from slot 0. + WR7146(P_ISR, ISR_AFOU); + + // Wait for the DMA transfer to finish so that there will be data + // available in the FIFO when time slot 1 tries to transfer a DWORD + // from the FIFO to the output buffer register. We test for DMA + // Done by polling the DMAC enable flag; this flag is automatically + // cleared when the transfer has finished. + while ((RR7146(P_MC1) & MC1_A2OUT) != 0) ; + + // START THE OUTPUT STREAM TO THE TARGET DAC -------------------- + + // FIFO data is now available, so we enable execution of time slots + // 1 and higher by clearing the EOS flag in slot 0. Note that SD3 + // will be shifted in and stored in FB_BUFFER2 for end-of-slot-list + // detection. + SETVECT(0, XSD2 | RSD3 | SIB_A2); + + // Wait for slot 1 to execute to ensure that the Packet will be + // transmitted. This is detected by polling the Audio2 output FIFO + // underflow flag, which will be set when slot 1 execution has + // finished transferring the DAC's data DWORD from the output FIFO + // to the output buffer register. + while ((RR7146(P_SSR) & SSR_AF2_OUT) == 0) ; + + // Set up to trap execution at slot 0 when the TSL sequencer cycles + // back to slot 0 after executing the EOS in slot 5. Also, + // simultaneously shift out and in the 0x00 that is ALWAYS the value + // stored in the last byte to be shifted out of the FIFO's DWORD + // buffer register. + SETVECT(0, XSD2 | XFIFO_2 | RSD2 | SIB_A2 | EOS); + + // WAIT FOR THE TRANSACTION TO FINISH ----------------------- + + // Wait for the TSL to finish executing all time slots before + // exiting this function. We must do this so that the next DAC + // write doesn't start, thereby enabling clock/chip select signals: + // 1. Before the TSL sequence cycles back to slot 0, which disables + // the clock/cs signal gating and traps slot // list execution. If + // we have not yet finished slot 5 then the clock/cs signals are + // still gated and we have // not finished transmitting the stream. + // 2. While slots 2-5 are executing due to a late slot 0 trap. In + // this case, the slot sequence is currently // repeating, but with + // clock/cs signals disabled. We must wait for slot 0 to trap + // execution before setting // up the next DAC setpoint DMA transfer + // and enabling the clock/cs signals. To detect the end of slot 5, + // we test for the FB_BUFFER2 MSB contents to be equal to 0xFF. If + // the TSL has not yet finished executing slot 5 ... + if ((RR7146(P_FB_BUFFER2) & 0xFF000000) != 0) { + // The trap was set on time and we are still executing somewhere + // in slots 2-5, so we now wait for slot 0 to execute and trap + // TSL execution. This is detected when FB_BUFFER2 MSB changes + // from 0xFF to 0x00, which slot 0 causes to happen by shifting + // out/in on SD2 the 0x00 that is always referenced by slot 5. + while ((RR7146(P_FB_BUFFER2) & 0xFF000000) != 0) ; + } + // Either (1) we were too late setting the slot 0 trap; the TSL + // sequencer restarted slot 0 before we could set the EOS trap flag, + // or (2) we were not late and execution is now trapped at slot 0. + // In either case, we must now change slot 0 so that it will store + // value 0xFF (instead of 0x00) to FB_BUFFER2 next time it executes. + // In order to do this, we reprogram slot 0 so that it will shift in + // SD3, which is driven only by a pull-up resistor. + SETVECT(0, RSD3 | SIB_A2 | EOS); + + // Wait for slot 0 to execute, at which time the TSL is setup for + // the next DAC write. This is detected when FB_BUFFER2 MSB changes + // from 0x00 to 0xFF. + while ((RR7146(P_FB_BUFFER2) & 0xFF000000) == 0) ; +} + +static void WriteMISC2(comedi_device * dev, uint16_t NewImage) +{ + DEBIwrite(dev, LP_MISC1, MISC1_WENABLE); // enab writes to + // MISC2 register. + DEBIwrite(dev, LP_WRMISC2, NewImage); // Write new image to MISC2. + DEBIwrite(dev, LP_MISC1, MISC1_WDISABLE); // Disable writes to MISC2. +} + +///////////////////////////////////////////////////////////////////// +// Initialize the DEBI interface for all transfers. + +static uint16_t DEBIread(comedi_device * dev, uint16_t addr) +{ + uint16_t retval; + + // Set up DEBI control register value in shadow RAM. + WR7146(P_DEBICMD, DEBI_CMD_RDWORD | addr); + + // Execute the DEBI transfer. + DEBItransfer(dev); + + // Fetch target register value. + retval = (uint16_t) RR7146(P_DEBIAD); + + // Return register value. + return retval; +} + +// Execute a DEBI transfer. This must be called from within a +// critical section. +static void DEBItransfer(comedi_device * dev) +{ + // Initiate upload of shadow RAM to DEBI control register. + MC_ENABLE(P_MC2, MC2_UPLD_DEBI); + + // Wait for completion of upload from shadow RAM to DEBI control + // register. + while (!MC_TEST(P_MC2, MC2_UPLD_DEBI)) ; + + // Wait until DEBI transfer is done. + while (RR7146(P_PSR) & PSR_DEBI_S) ; +} + +// Write a value to a gate array register. +static void DEBIwrite(comedi_device * dev, uint16_t addr, uint16_t wdata) +{ + + // Set up DEBI control register value in shadow RAM. + WR7146(P_DEBICMD, DEBI_CMD_WRWORD | addr); + WR7146(P_DEBIAD, wdata); + + // Execute the DEBI transfer. + DEBItransfer(dev); +} + +///////////////////////////////////////////////////////////////////////////// +// Replace the specified bits in a gate array register. Imports: mask +// specifies bits that are to be preserved, wdata is new value to be +// or'd with the masked original. +static void DEBIreplace(comedi_device * dev, uint16_t addr, uint16_t mask, + uint16_t wdata) +{ + + // Copy target gate array register into P_DEBIAD register. + WR7146(P_DEBICMD, DEBI_CMD_RDWORD | addr); // Set up DEBI control + // reg value in shadow + // RAM. + DEBItransfer(dev); // Execute the DEBI + // Read transfer. + + // Write back the modified image. + WR7146(P_DEBICMD, DEBI_CMD_WRWORD | addr); // Set up DEBI control + // reg value in shadow + // RAM. + + WR7146(P_DEBIAD, wdata | ((uint16_t) RR7146(P_DEBIAD) & mask)); // Modify the register image. + DEBItransfer(dev); // Execute the DEBI Write transfer. +} + +static void CloseDMAB(comedi_device * dev, DMABUF * pdma, size_t bsize) +{ + void *vbptr; + dma_addr_t vpptr; + + DEBUG("CloseDMAB: Entering S626DRV_CloseDMAB():\n"); + if (pdma == NULL) + return; + //find the matching allocation from the board struct + + vbptr = pdma->LogicalBase; + vpptr = pdma->PhysicalBase; + if (vbptr) { + pci_free_consistent(devpriv->pdev, bsize, vbptr, vpptr); + pdma->LogicalBase = 0; + pdma->PhysicalBase = 0; + + DEBUG("CloseDMAB(): Logical=%p, bsize=%d, Physical=0x%x\n", + vbptr, bsize, (uint32_t) vpptr); + } +} + +//////////////////////////////////////////////////////////////////////// +///////////////// COUNTER FUNCTIONS ////////////////////////////////// +//////////////////////////////////////////////////////////////////////// +// All counter functions address a specific counter by means of the +// "Counter" argument, which is a logical counter number. The Counter +// argument may have any of the following legal values: 0=0A, 1=1A, +// 2=2A, 3=0B, 4=1B, 5=2B. +//////////////////////////////////////////////////////////////////////// + +// Forward declarations for functions that are common to both A and B +// counters: + +///////////////////////////////////////////////////////////////////// +//////////////////// PRIVATE COUNTER FUNCTIONS ///////////////////// +///////////////////////////////////////////////////////////////////// + +///////////////////////////////////////////////////////////////// +// Read a counter's output latch. + +static uint32_t ReadLatch(comedi_device * dev, enc_private * k) +{ + register uint32_t value; + //DEBUG FIXME DEBUG("ReadLatch: Read Latch enter\n"); + + // Latch counts and fetch LSW of latched counts value. + value = (uint32_t) DEBIread(dev, k->MyLatchLsw); + + // Fetch MSW of latched counts and combine with LSW. + value |= ((uint32_t) DEBIread(dev, k->MyLatchLsw + 2) << 16); + + // DEBUG FIXME DEBUG("ReadLatch: Read Latch exit\n"); + + // Return latched counts. + return value; +} + +/////////////////////////////////////////////////////////////////// +// Reset a counter's index and overflow event capture flags. + +static void ResetCapFlags_A(comedi_device * dev, enc_private * k) +{ + DEBIreplace(dev, k->MyCRB, (uint16_t) (~CRBMSK_INTCTRL), + CRBMSK_INTRESETCMD | CRBMSK_INTRESET_A); +} + +static void ResetCapFlags_B(comedi_device * dev, enc_private * k) +{ + DEBIreplace(dev, k->MyCRB, (uint16_t) (~CRBMSK_INTCTRL), + CRBMSK_INTRESETCMD | CRBMSK_INTRESET_B); +} + +///////////////////////////////////////////////////////////////////////// +// Return counter setup in a format (COUNTER_SETUP) that is consistent +// for both A and B counters. + +static uint16_t GetMode_A(comedi_device * dev, enc_private * k) +{ + register uint16_t cra; + register uint16_t crb; + register uint16_t setup; + + // Fetch CRA and CRB register images. + cra = DEBIread(dev, k->MyCRA); + crb = DEBIread(dev, k->MyCRB); + + // Populate the standardized counter setup bit fields. Note: + // IndexSrc is restricted to ENC_X or IndxPol. + setup = ((cra & STDMSK_LOADSRC) // LoadSrc = LoadSrcA. + | ((crb << (STDBIT_LATCHSRC - CRBBIT_LATCHSRC)) & STDMSK_LATCHSRC) // LatchSrc = LatchSrcA. + | ((cra << (STDBIT_INTSRC - CRABIT_INTSRC_A)) & STDMSK_INTSRC) // IntSrc = IntSrcA. + | ((cra << (STDBIT_INDXSRC - (CRABIT_INDXSRC_A + 1))) & STDMSK_INDXSRC) // IndxSrc = IndxSrcA<1>. + | ((cra >> (CRABIT_INDXPOL_A - STDBIT_INDXPOL)) & STDMSK_INDXPOL) // IndxPol = IndxPolA. + | ((crb >> (CRBBIT_CLKENAB_A - STDBIT_CLKENAB)) & STDMSK_CLKENAB)); // ClkEnab = ClkEnabA. + + // Adjust mode-dependent parameters. + if (cra & (2 << CRABIT_CLKSRC_A)) // If Timer mode (ClkSrcA<1> == 1): + setup |= ((CLKSRC_TIMER << STDBIT_CLKSRC) // Indicate Timer mode. + | ((cra << (STDBIT_CLKPOL - CRABIT_CLKSRC_A)) & STDMSK_CLKPOL) // Set ClkPol to indicate count direction (ClkSrcA<0>). + | (MULT_X1 << STDBIT_CLKMULT)); // ClkMult must be 1x in Timer mode. + + else // If Counter mode (ClkSrcA<1> == 0): + setup |= ((CLKSRC_COUNTER << STDBIT_CLKSRC) // Indicate Counter mode. + | ((cra >> (CRABIT_CLKPOL_A - STDBIT_CLKPOL)) & STDMSK_CLKPOL) // Pass through ClkPol. + | (((cra & CRAMSK_CLKMULT_A) == (MULT_X0 << CRABIT_CLKMULT_A)) ? // Force ClkMult to 1x if not legal, else pass through. + (MULT_X1 << STDBIT_CLKMULT) : + ((cra >> (CRABIT_CLKMULT_A - + STDBIT_CLKMULT)) & + STDMSK_CLKMULT))); + + // Return adjusted counter setup. + return setup; +} + +static uint16_t GetMode_B(comedi_device * dev, enc_private * k) +{ + register uint16_t cra; + register uint16_t crb; + register uint16_t setup; + + // Fetch CRA and CRB register images. + cra = DEBIread(dev, k->MyCRA); + crb = DEBIread(dev, k->MyCRB); + + // Populate the standardized counter setup bit fields. Note: + // IndexSrc is restricted to ENC_X or IndxPol. + setup = (((crb << (STDBIT_INTSRC - CRBBIT_INTSRC_B)) & STDMSK_INTSRC) // IntSrc = IntSrcB. + | ((crb << (STDBIT_LATCHSRC - CRBBIT_LATCHSRC)) & STDMSK_LATCHSRC) // LatchSrc = LatchSrcB. + | ((crb << (STDBIT_LOADSRC - CRBBIT_LOADSRC_B)) & STDMSK_LOADSRC) // LoadSrc = LoadSrcB. + | ((crb << (STDBIT_INDXPOL - CRBBIT_INDXPOL_B)) & STDMSK_INDXPOL) // IndxPol = IndxPolB. + | ((crb >> (CRBBIT_CLKENAB_B - STDBIT_CLKENAB)) & STDMSK_CLKENAB) // ClkEnab = ClkEnabB. + | ((cra >> ((CRABIT_INDXSRC_B + 1) - STDBIT_INDXSRC)) & STDMSK_INDXSRC)); // IndxSrc = IndxSrcB<1>. + + // Adjust mode-dependent parameters. + if ((crb & CRBMSK_CLKMULT_B) == (MULT_X0 << CRBBIT_CLKMULT_B)) // If Extender mode (ClkMultB == MULT_X0): + setup |= ((CLKSRC_EXTENDER << STDBIT_CLKSRC) // Indicate Extender mode. + | (MULT_X1 << STDBIT_CLKMULT) // Indicate multiplier is 1x. + | ((cra >> (CRABIT_CLKSRC_B - STDBIT_CLKPOL)) & STDMSK_CLKPOL)); // Set ClkPol equal to Timer count direction (ClkSrcB<0>). + + else if (cra & (2 << CRABIT_CLKSRC_B)) // If Timer mode (ClkSrcB<1> == 1): + setup |= ((CLKSRC_TIMER << STDBIT_CLKSRC) // Indicate Timer mode. + | (MULT_X1 << STDBIT_CLKMULT) // Indicate multiplier is 1x. + | ((cra >> (CRABIT_CLKSRC_B - STDBIT_CLKPOL)) & STDMSK_CLKPOL)); // Set ClkPol equal to Timer count direction (ClkSrcB<0>). + + else // If Counter mode (ClkSrcB<1> == 0): + setup |= ((CLKSRC_COUNTER << STDBIT_CLKSRC) // Indicate Timer mode. + | ((crb >> (CRBBIT_CLKMULT_B - STDBIT_CLKMULT)) & STDMSK_CLKMULT) // Clock multiplier is passed through. + | ((crb << (STDBIT_CLKPOL - CRBBIT_CLKPOL_B)) & STDMSK_CLKPOL)); // Clock polarity is passed through. + + // Return adjusted counter setup. + return setup; +} + +///////////////////////////////////////////////////////////////////////////////////////////// +// Set the operating mode for the specified counter. The setup +// parameter is treated as a COUNTER_SETUP data type. The following +// parameters are programmable (all other parms are ignored): ClkMult, +// ClkPol, ClkEnab, IndexSrc, IndexPol, LoadSrc. + +static void SetMode_A(comedi_device * dev, enc_private * k, uint16_t Setup, + uint16_t DisableIntSrc) +{ + register uint16_t cra; + register uint16_t crb; + register uint16_t setup = Setup; // Cache the Standard Setup. + + // Initialize CRA and CRB images. + cra = ((setup & CRAMSK_LOADSRC_A) // Preload trigger is passed through. + | ((setup & STDMSK_INDXSRC) >> (STDBIT_INDXSRC - (CRABIT_INDXSRC_A + 1)))); // IndexSrc is restricted to ENC_X or IndxPol. + + crb = (CRBMSK_INTRESETCMD | CRBMSK_INTRESET_A // Reset any pending CounterA event captures. + | ((setup & STDMSK_CLKENAB) << (CRBBIT_CLKENAB_A - STDBIT_CLKENAB))); // Clock enable is passed through. + + // Force IntSrc to Disabled if DisableIntSrc is asserted. + if (!DisableIntSrc) + cra |= ((setup & STDMSK_INTSRC) >> (STDBIT_INTSRC - + CRABIT_INTSRC_A)); + + // Populate all mode-dependent attributes of CRA & CRB images. + switch ((setup & STDMSK_CLKSRC) >> STDBIT_CLKSRC) { + case CLKSRC_EXTENDER: // Extender Mode: Force to Timer mode + // (Extender valid only for B counters). + + case CLKSRC_TIMER: // Timer Mode: + cra |= ((2 << CRABIT_CLKSRC_A) // ClkSrcA<1> selects system clock + | ((setup & STDMSK_CLKPOL) >> (STDBIT_CLKPOL - CRABIT_CLKSRC_A)) // with count direction (ClkSrcA<0>) obtained from ClkPol. + | (1 << CRABIT_CLKPOL_A) // ClkPolA behaves as always-on clock enable. + | (MULT_X1 << CRABIT_CLKMULT_A)); // ClkMult must be 1x. + break; + + default: // Counter Mode: + cra |= (CLKSRC_COUNTER // Select ENC_C and ENC_D as clock/direction inputs. + | ((setup & STDMSK_CLKPOL) << (CRABIT_CLKPOL_A - STDBIT_CLKPOL)) // Clock polarity is passed through. + | (((setup & STDMSK_CLKMULT) == (MULT_X0 << STDBIT_CLKMULT)) ? // Force multiplier to x1 if not legal, otherwise pass through. + (MULT_X1 << CRABIT_CLKMULT_A) : + ((setup & STDMSK_CLKMULT) << (CRABIT_CLKMULT_A - + STDBIT_CLKMULT)))); + } + + // Force positive index polarity if IndxSrc is software-driven only, + // otherwise pass it through. + if (~setup & STDMSK_INDXSRC) + cra |= ((setup & STDMSK_INDXPOL) << (CRABIT_INDXPOL_A - + STDBIT_INDXPOL)); + + // If IntSrc has been forced to Disabled, update the MISC2 interrupt + // enable mask to indicate the counter interrupt is disabled. + if (DisableIntSrc) + devpriv->CounterIntEnabs &= ~k->MyEventBits[3]; + + // While retaining CounterB and LatchSrc configurations, program the + // new counter operating mode. + DEBIreplace(dev, k->MyCRA, CRAMSK_INDXSRC_B | CRAMSK_CLKSRC_B, cra); + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_INTCTRL | CRBMSK_CLKENAB_A)), crb); +} + +static void SetMode_B(comedi_device * dev, enc_private * k, uint16_t Setup, + uint16_t DisableIntSrc) +{ + register uint16_t cra; + register uint16_t crb; + register uint16_t setup = Setup; // Cache the Standard Setup. + + // Initialize CRA and CRB images. + cra = ((setup & STDMSK_INDXSRC) << ((CRABIT_INDXSRC_B + 1) - STDBIT_INDXSRC)); // IndexSrc field is restricted to ENC_X or IndxPol. + + crb = (CRBMSK_INTRESETCMD | CRBMSK_INTRESET_B // Reset event captures and disable interrupts. + | ((setup & STDMSK_CLKENAB) << (CRBBIT_CLKENAB_B - STDBIT_CLKENAB)) // Clock enable is passed through. + | ((setup & STDMSK_LOADSRC) >> (STDBIT_LOADSRC - CRBBIT_LOADSRC_B))); // Preload trigger source is passed through. + + // Force IntSrc to Disabled if DisableIntSrc is asserted. + if (!DisableIntSrc) + crb |= ((setup & STDMSK_INTSRC) >> (STDBIT_INTSRC - + CRBBIT_INTSRC_B)); + + // Populate all mode-dependent attributes of CRA & CRB images. + switch ((setup & STDMSK_CLKSRC) >> STDBIT_CLKSRC) { + case CLKSRC_TIMER: // Timer Mode: + cra |= ((2 << CRABIT_CLKSRC_B) // ClkSrcB<1> selects system clock + | ((setup & STDMSK_CLKPOL) << (CRABIT_CLKSRC_B - STDBIT_CLKPOL))); // with direction (ClkSrcB<0>) obtained from ClkPol. + crb |= ((1 << CRBBIT_CLKPOL_B) // ClkPolB behaves as always-on clock enable. + | (MULT_X1 << CRBBIT_CLKMULT_B)); // ClkMultB must be 1x. + break; + + case CLKSRC_EXTENDER: // Extender Mode: + cra |= ((2 << CRABIT_CLKSRC_B) // ClkSrcB source is OverflowA (same as "timer") + | ((setup & STDMSK_CLKPOL) << (CRABIT_CLKSRC_B - STDBIT_CLKPOL))); // with direction obtained from ClkPol. + crb |= ((1 << CRBBIT_CLKPOL_B) // ClkPolB controls IndexB -- always set to active. + | (MULT_X0 << CRBBIT_CLKMULT_B)); // ClkMultB selects OverflowA as the clock source. + break; + + default: // Counter Mode: + cra |= (CLKSRC_COUNTER << CRABIT_CLKSRC_B); // Select ENC_C and ENC_D as clock/direction inputs. + crb |= (((setup & STDMSK_CLKPOL) >> (STDBIT_CLKPOL - CRBBIT_CLKPOL_B)) // ClkPol is passed through. + | (((setup & STDMSK_CLKMULT) == (MULT_X0 << STDBIT_CLKMULT)) ? // Force ClkMult to x1 if not legal, otherwise pass through. + (MULT_X1 << CRBBIT_CLKMULT_B) : + ((setup & STDMSK_CLKMULT) << (CRBBIT_CLKMULT_B - + STDBIT_CLKMULT)))); + } + + // Force positive index polarity if IndxSrc is software-driven only, + // otherwise pass it through. + if (~setup & STDMSK_INDXSRC) + crb |= ((setup & STDMSK_INDXPOL) >> (STDBIT_INDXPOL - + CRBBIT_INDXPOL_B)); + + // If IntSrc has been forced to Disabled, update the MISC2 interrupt + // enable mask to indicate the counter interrupt is disabled. + if (DisableIntSrc) + devpriv->CounterIntEnabs &= ~k->MyEventBits[3]; + + // While retaining CounterA and LatchSrc configurations, program the + // new counter operating mode. + DEBIreplace(dev, k->MyCRA, + (uint16_t) (~(CRAMSK_INDXSRC_B | CRAMSK_CLKSRC_B)), cra); + DEBIreplace(dev, k->MyCRB, CRBMSK_CLKENAB_A | CRBMSK_LATCHSRC, crb); +} + +//////////////////////////////////////////////////////////////////////// +// Return/set a counter's enable. enab: 0=always enabled, 1=enabled by index. + +static void SetEnable_A(comedi_device * dev, enc_private * k, uint16_t enab) +{ + DEBUG("SetEnable_A: SetEnable_A enter 3541\n"); + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_INTCTRL | CRBMSK_CLKENAB_A)), + (uint16_t) (enab << CRBBIT_CLKENAB_A)); +} + +static void SetEnable_B(comedi_device * dev, enc_private * k, uint16_t enab) +{ + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_INTCTRL | CRBMSK_CLKENAB_B)), + (uint16_t) (enab << CRBBIT_CLKENAB_B)); +} + +static uint16_t GetEnable_A(comedi_device * dev, enc_private * k) +{ + return (DEBIread(dev, k->MyCRB) >> CRBBIT_CLKENAB_A) & 1; +} + +static uint16_t GetEnable_B(comedi_device * dev, enc_private * k) +{ + return (DEBIread(dev, k->MyCRB) >> CRBBIT_CLKENAB_B) & 1; +} + +//////////////////////////////////////////////////////////////////////// +// Return/set a counter pair's latch trigger source. 0: On read +// access, 1: A index latches A, 2: B index latches B, 3: A overflow +// latches B. + +static void SetLatchSource(comedi_device * dev, enc_private * k, uint16_t value) +{ + DEBUG("SetLatchSource: SetLatchSource enter 3550 \n"); + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_INTCTRL | CRBMSK_LATCHSRC)), + (uint16_t) (value << CRBBIT_LATCHSRC)); + + DEBUG("SetLatchSource: SetLatchSource exit \n"); +} + +/* static uint16_t GetLatchSource(comedi_device *dev, enc_private *k ) */ +/* { */ +/* return ( DEBIread( dev, k->MyCRB) >> CRBBIT_LATCHSRC ) & 3; */ +/* } */ + +///////////////////////////////////////////////////////////////////////// +// Return/set the event that will trigger transfer of the preload +// register into the counter. 0=ThisCntr_Index, 1=ThisCntr_Overflow, +// 2=OverflowA (B counters only), 3=disabled. + +static void SetLoadTrig_A(comedi_device * dev, enc_private * k, uint16_t Trig) +{ + DEBIreplace(dev, k->MyCRA, (uint16_t) (~CRAMSK_LOADSRC_A), + (uint16_t) (Trig << CRABIT_LOADSRC_A)); +} + +static void SetLoadTrig_B(comedi_device * dev, enc_private * k, uint16_t Trig) +{ + DEBIreplace(dev, k->MyCRB, + (uint16_t) (~(CRBMSK_LOADSRC_B | CRBMSK_INTCTRL)), + (uint16_t) (Trig << CRBBIT_LOADSRC_B)); +} + +static uint16_t GetLoadTrig_A(comedi_device * dev, enc_private * k) +{ + return (DEBIread(dev, k->MyCRA) >> CRABIT_LOADSRC_A) & 3; +} + +static uint16_t GetLoadTrig_B(comedi_device * dev, enc_private * k) +{ + return (DEBIread(dev, k->MyCRB) >> CRBBIT_LOADSRC_B) & 3; +} + +//////////////////// +// Return/set counter interrupt source and clear any captured +// index/overflow events. IntSource: 0=Disabled, 1=OverflowOnly, +// 2=IndexOnly, 3=IndexAndOverflow. + +static void SetIntSrc_A(comedi_device * dev, enc_private * k, + uint16_t IntSource) +{ + // Reset any pending counter overflow or index captures. + DEBIreplace(dev, k->MyCRB, (uint16_t) (~CRBMSK_INTCTRL), + CRBMSK_INTRESETCMD | CRBMSK_INTRESET_A); + + // Program counter interrupt source. + DEBIreplace(dev, k->MyCRA, ~CRAMSK_INTSRC_A, + (uint16_t) (IntSource << CRABIT_INTSRC_A)); + + // Update MISC2 interrupt enable mask. + devpriv->CounterIntEnabs = + (devpriv->CounterIntEnabs & ~k->MyEventBits[3]) | k-> + MyEventBits[IntSource]; +} + +static void SetIntSrc_B(comedi_device * dev, enc_private * k, + uint16_t IntSource) +{ + uint16_t crb; + + // Cache writeable CRB register image. + crb = DEBIread(dev, k->MyCRB) & ~CRBMSK_INTCTRL; + + // Reset any pending counter overflow or index captures. + DEBIwrite(dev, k->MyCRB, + (uint16_t) (crb | CRBMSK_INTRESETCMD | CRBMSK_INTRESET_B)); + + // Program counter interrupt source. + DEBIwrite(dev, k->MyCRB, + (uint16_t) ((crb & ~CRBMSK_INTSRC_B) | (IntSource << + CRBBIT_INTSRC_B))); + + // Update MISC2 interrupt enable mask. + devpriv->CounterIntEnabs = + (devpriv->CounterIntEnabs & ~k->MyEventBits[3]) | k-> + MyEventBits[IntSource]; +} + +static uint16_t GetIntSrc_A(comedi_device * dev, enc_private * k) +{ + return (DEBIread(dev, k->MyCRA) >> CRABIT_INTSRC_A) & 3; +} + +static uint16_t GetIntSrc_B(comedi_device * dev, enc_private * k) +{ + return (DEBIread(dev, k->MyCRB) >> CRBBIT_INTSRC_B) & 3; +} + +///////////////////////////////////////////////////////////////////////// +// Return/set the clock multiplier. + +/* static void SetClkMult(comedi_device *dev, enc_private *k, uint16_t value ) */ +/* { */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_CLKMULT ) | ( value << STDBIT_CLKMULT ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetClkMult(comedi_device *dev, enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_CLKMULT ) & 3; */ +/* } */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Return/set the clock polarity. */ + +/* static void SetClkPol( comedi_device *dev,enc_private *k, uint16_t value ) */ +/* { */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_CLKPOL ) | ( value << STDBIT_CLKPOL ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetClkPol(comedi_device *dev, enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_CLKPOL ) & 1; */ +/* } */ + +/* /////////////////////////////////////////////////////////////////////// */ +/* // Return/set the clock source. */ + +/* static void SetClkSrc( comedi_device *dev,enc_private *k, uint16_t value ) */ +/* { */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_CLKSRC ) | ( value << STDBIT_CLKSRC ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetClkSrc( comedi_device *dev,enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_CLKSRC ) & 3; */ +/* } */ + +/* //////////////////////////////////////////////////////////////////////// */ +/* // Return/set the index polarity. */ + +/* static void SetIndexPol(comedi_device *dev, enc_private *k, uint16_t value ) */ +/* { */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_INDXPOL ) | ( (value != 0) << STDBIT_INDXPOL ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetIndexPol(comedi_device *dev, enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_INDXPOL ) & 1; */ +/* } */ + +/* //////////////////////////////////////////////////////////////////////// */ +/* // Return/set the index source. */ + +/* static void SetIndexSrc(comedi_device *dev, enc_private *k, uint16_t value ) */ +/* { */ +/* DEBUG("SetIndexSrc: set index src enter 3700\n"); */ +/* k->SetMode(dev, k, (uint16_t)( ( k->GetMode(dev, k ) & ~STDMSK_INDXSRC ) | ( (value != 0) << STDBIT_INDXSRC ) ), FALSE ); */ +/* } */ + +/* static uint16_t GetIndexSrc(comedi_device *dev, enc_private *k ) */ +/* { */ +/* return ( k->GetMode(dev, k ) >> STDBIT_INDXSRC ) & 1; */ +/* } */ + +/////////////////////////////////////////////////////////////////// +// Generate an index pulse. + +static void PulseIndex_A(comedi_device * dev, enc_private * k) +{ + register uint16_t cra; + + DEBUG("PulseIndex_A: pulse index enter\n"); + + cra = DEBIread(dev, k->MyCRA); // Pulse index. + DEBIwrite(dev, k->MyCRA, (uint16_t) (cra ^ CRAMSK_INDXPOL_A)); + DEBUG("PulseIndex_A: pulse index step1\n"); + DEBIwrite(dev, k->MyCRA, cra); +} + +static void PulseIndex_B(comedi_device * dev, enc_private * k) +{ + register uint16_t crb; + + crb = DEBIread(dev, k->MyCRB) & ~CRBMSK_INTCTRL; // Pulse index. + DEBIwrite(dev, k->MyCRB, (uint16_t) (crb ^ CRBMSK_INDXPOL_B)); + DEBIwrite(dev, k->MyCRB, crb); +} + +///////////////////////////////////////////////////////// +// Write value into counter preload register. + +static void Preload(comedi_device * dev, enc_private * k, uint32_t value) +{ + DEBUG("Preload: preload enter\n"); + DEBIwrite(dev, (uint16_t) (k->MyLatchLsw), (uint16_t) value); // Write value to preload register. + DEBUG("Preload: preload step 1\n"); + DEBIwrite(dev, (uint16_t) (k->MyLatchLsw + 2), + (uint16_t) (value >> 16)); +} + +static void CountersInit(comedi_device * dev) +{ + int chan; + enc_private *k; + uint16_t Setup = (LOADSRC_INDX << BF_LOADSRC) | // Preload upon + // index. + (INDXSRC_SOFT << BF_INDXSRC) | // Disable hardware index. + (CLKSRC_COUNTER << BF_CLKSRC) | // Operating mode is counter. + (CLKPOL_POS << BF_CLKPOL) | // Active high clock. + (CNTDIR_UP << BF_CLKPOL) | // Count direction is up. + (CLKMULT_1X << BF_CLKMULT) | // Clock multiplier is 1x. + (CLKENAB_INDEX << BF_CLKENAB); // Enabled by index + + // Disable all counter interrupts and clear any captured counter events. + for (chan = 0; chan < S626_ENCODER_CHANNELS; chan++) { + k = &encpriv[chan]; + k->SetMode(dev, k, Setup, TRUE); + k->SetIntSrc(dev, k, 0); + k->ResetCapFlags(dev, k); + k->SetEnable(dev, k, CLKENAB_ALWAYS); + } + DEBUG("CountersInit: counters initialized \n"); + +} diff --git a/drivers/staging/comedi/drivers/s626.h b/drivers/staging/comedi/drivers/s626.h new file mode 100644 index 00000000000..11d8b1ceb0b --- /dev/null +++ b/drivers/staging/comedi/drivers/s626.h @@ -0,0 +1,802 @@ +/* + comedi/drivers/s626.h + Sensoray s626 Comedi driver, header file + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef <ds@schleef.org> + + Based on Sensoray Model 626 Linux driver Version 0.2 + Copyright (C) 2002-2004 Sensoray Co., Inc. + + 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: s626.o (s626.ko) + Description: Sensoray 626 driver + Devices: Sensoray s626 + Authors: Gianluca Palli <gpalli@deis.unibo.it>, + Updated: Thu, 12 Jul 2005 + Status: experimental + + Configuration Options: + analog input: + none + + analog output: + none + + digital channel: + s626 has 3 dio subdevices (2,3 and 4) each with 16 i/o channels + supported configuration options: + INSN_CONFIG_DIO_QUERY + COMEDI_INPUT + COMEDI_OUTPUT + + encoder: + Every channel must be configured before reading. + + Example code + + insn.insn=INSN_CONFIG; //configuration instruction + insn.n=1; //number of operation (must be 1) + insn.data=&initialvalue; //initial value loaded into encoder + //during configuration + insn.subdev=5; //encoder subdevice + insn.chanspec=CR_PACK(encoder_channel,0,AREF_OTHER); //encoder_channel + //to configure + + comedi_do_insn(cf,&insn); //executing configuration +*/ + +#ifdef _DEBUG_ +#define DEBUG(...); rt_printk(__VA_ARGS__); +#else +#define DEBUG(...) +#endif + +#if !defined(TRUE) +#define TRUE (1) +#endif + +#if !defined(FALSE) +#define FALSE (0) +#endif + +#if !defined(EXTERN) +#if defined(__cplusplus) +#define EXTERN extern "C" +#else +#define EXTERN extern +#endif +#endif + +#if !defined(INLINE) +#define INLINE static __inline +#endif + +///////////////////////////////////////////////////// +#include<linux/slab.h> + +#define S626_SIZE 0x0200 +#define SIZEOF_ADDRESS_SPACE 0x0200 +#define DMABUF_SIZE 4096 // 4k pages + +#define S626_ADC_CHANNELS 16 +#define S626_DAC_CHANNELS 4 +#define S626_ENCODER_CHANNELS 6 +#define S626_DIO_CHANNELS 48 +#define S626_DIO_BANKS 3 // Number of DIO groups. +#define S626_DIO_EXTCHANS 40 // Number of + // extended-capability + // DIO channels. + +#define NUM_TRIMDACS 12 // Number of valid TrimDAC channels. + +// PCI bus interface types. +#define INTEL 1 // Intel bus type. +#define MOTOROLA 2 // Motorola bus type. + +////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////// +#define PLATFORM INTEL // *** SELECT PLATFORM TYPE *** +////////////////////////////////////////////////////////// + +#define RANGE_5V 0x10 // +/-5V range +#define RANGE_10V 0x00 // +/-10V range + +#define EOPL 0x80 // End of ADC poll list marker. +#define GSEL_BIPOLAR5V 0x00F0 // LP_GSEL setting for 5V bipolar range. +#define GSEL_BIPOLAR10V 0x00A0 // LP_GSEL setting for 10V bipolar range. + +// Error codes that must be visible to this base class. +#define ERR_ILLEGAL_PARM 0x00010000 // Illegal function parameter value was specified. +#define ERR_I2C 0x00020000 // I2C error. +#define ERR_COUNTERSETUP 0x00200000 // Illegal setup specified for counter channel. +#define ERR_DEBI_TIMEOUT 0x00400000 // DEBI transfer timed out. + +// Organization (physical order) and size (in DWORDs) of logical DMA buffers contained by ANA_DMABUF. +#define ADC_DMABUF_DWORDS 40 // ADC DMA buffer must hold 16 samples, plus pre/post garbage samples. +#define DAC_WDMABUF_DWORDS 1 // DAC output DMA buffer holds a single sample. + +// All remaining space in 4KB DMA buffer is available for the RPS1 program. + +// Address offsets, in DWORDS, from base of DMA buffer. +#define DAC_WDMABUF_OS ADC_DMABUF_DWORDS + +// Interrupt enab bit in ISR and IER. +#define IRQ_GPIO3 0x00000040 // IRQ enable for GPIO3. +#define IRQ_RPS1 0x10000000 +#define ISR_AFOU 0x00000800 // Audio fifo + // under/overflow + // detected. +#define IRQ_COINT1A 0x0400 // conter 1A overflow + // interrupt mask +#define IRQ_COINT1B 0x0800 // conter 1B overflow + // interrupt mask +#define IRQ_COINT2A 0x1000 // conter 2A overflow + // interrupt mask +#define IRQ_COINT2B 0x2000 // conter 2B overflow + // interrupt mask +#define IRQ_COINT3A 0x4000 // conter 3A overflow + // interrupt mask +#define IRQ_COINT3B 0x8000 // conter 3B overflow + // interrupt mask + +// RPS command codes. +#define RPS_CLRSIGNAL 0x00000000 // CLEAR SIGNAL +#define RPS_SETSIGNAL 0x10000000 // SET SIGNAL +#define RPS_NOP 0x00000000 // NOP +#define RPS_PAUSE 0x20000000 // PAUSE +#define RPS_UPLOAD 0x40000000 // UPLOAD +#define RPS_JUMP 0x80000000 // JUMP +#define RPS_LDREG 0x90000100 // LDREG (1 uint32_t only) +#define RPS_STREG 0xA0000100 // STREG (1 uint32_t only) +#define RPS_STOP 0x50000000 // STOP +#define RPS_IRQ 0x60000000 // IRQ + +#define RPS_LOGICAL_OR 0x08000000 // Logical OR conditionals. +#define RPS_INVERT 0x04000000 // Test for negated semaphores. +#define RPS_DEBI 0x00000002 // DEBI done + +#define RPS_SIG0 0x00200000 // RPS semaphore 0 (used by ADC). +#define RPS_SIG1 0x00400000 // RPS semaphore 1 (used by DAC). +#define RPS_SIG2 0x00800000 // RPS semaphore 2 (not used). +#define RPS_GPIO2 0x00080000 // RPS GPIO2 +#define RPS_GPIO3 0x00100000 // RPS GPIO3 + +#define RPS_SIGADC RPS_SIG0 // Trigger/status for ADC's RPS program. +#define RPS_SIGDAC RPS_SIG1 // Trigger/status for DAC's RPS program. + +// RPS clock parameters. +#define RPSCLK_SCALAR 8 // This is apparent ratio of PCI/RPS clks (undocumented!!). +#define RPSCLK_PER_US ( 33 / RPSCLK_SCALAR ) // Number of RPS clocks in one microsecond. + +// Event counter source addresses. +#define SBA_RPS_A0 0x27 // Time of RPS0 busy, in PCI clocks. + +// GPIO constants. +#define GPIO_BASE 0x10004000 // GPIO 0,2,3 = inputs, GPIO3 = IRQ; GPIO1 = out. +#define GPIO1_LO 0x00000000 // GPIO1 set to LOW. +#define GPIO1_HI 0x00001000 // GPIO1 set to HIGH. + +// Primary Status Register (PSR) constants. +#define PSR_DEBI_E 0x00040000 // DEBI event flag. +#define PSR_DEBI_S 0x00080000 // DEBI status flag. +#define PSR_A2_IN 0x00008000 // Audio output DMA2 protection address reached. +#define PSR_AFOU 0x00000800 // Audio FIFO under/overflow detected. +#define PSR_GPIO2 0x00000020 // GPIO2 input pin: 0=AdcBusy, 1=AdcIdle. +#define PSR_EC0S 0x00000001 // Event counter 0 threshold reached. + +// Secondary Status Register (SSR) constants. +#define SSR_AF2_OUT 0x00000200 // Audio 2 output FIFO under/overflow detected. + +// Master Control Register 1 (MC1) constants. +#define MC1_SOFT_RESET 0x80000000 // Invoke 7146 soft reset. +#define MC1_SHUTDOWN 0x3FFF0000 // Shut down all MC1-controlled enables. + +#define MC1_ERPS1 0x2000 // enab/disable RPS task 1. +#define MC1_ERPS0 0x1000 // enab/disable RPS task 0. +#define MC1_DEBI 0x0800 // enab/disable DEBI pins. +#define MC1_AUDIO 0x0200 // enab/disable audio port pins. +#define MC1_I2C 0x0100 // enab/disable I2C interface. +#define MC1_A2OUT 0x0008 // enab/disable transfer on A2 out. +#define MC1_A2IN 0x0004 // enab/disable transfer on A2 in. +#define MC1_A1IN 0x0001 // enab/disable transfer on A1 in. + +// Master Control Register 2 (MC2) constants. +#define MC2_UPLD_DEBIq 0x00020002 // Upload DEBI registers. +#define MC2_UPLD_IICq 0x00010001 // Upload I2C registers. +#define MC2_RPSSIG2_ONq 0x20002000 // Assert RPS_SIG2. +#define MC2_RPSSIG1_ONq 0x10001000 // Assert RPS_SIG1. +#define MC2_RPSSIG0_ONq 0x08000800 // Assert RPS_SIG0. +#define MC2_UPLD_DEBI_MASKq 0x00000002 // Upload DEBI mask. +#define MC2_UPLD_IIC_MASKq 0x00000001 // Upload I2C mask. +#define MC2_RPSSIG2_MASKq 0x00002000 // RPS_SIG2 bit mask. +#define MC2_RPSSIG1_MASKq 0x00001000 // RPS_SIG1 bit mask. +#define MC2_RPSSIG0_MASKq 0x00000800 // RPS_SIG0 bit mask. + +#define MC2_DELAYTRIG_4USq MC2_RPSSIG1_ON +#define MC2_DELAYBUSY_4USq MC2_RPSSIG1_MASK + +#define MC2_DELAYTRIG_6USq MC2_RPSSIG2_ON +#define MC2_DELAYBUSY_6USq MC2_RPSSIG2_MASK + +#define MC2_UPLD_DEBI 0x0002 // Upload DEBI. +#define MC2_UPLD_IIC 0x0001 // Upload I2C. +#define MC2_RPSSIG2 0x2000 // RPS signal 2 (not used). +#define MC2_RPSSIG1 0x1000 // RPS signal 1 (DAC RPS busy). +#define MC2_RPSSIG0 0x0800 // RPS signal 0 (ADC RPS busy). + +#define MC2_ADC_RPS MC2_RPSSIG0 // ADC RPS busy. +#define MC2_DAC_RPS MC2_RPSSIG1 // DAC RPS busy. + +///////////////////oldies/////////// +#define MC2_UPLD_DEBIQ 0x00020002 // Upload DEBI registers. +#define MC2_UPLD_IICQ 0x00010001 // Upload I2C registers. +//////////////////////////////////////// + +// PCI BUS (SAA7146) REGISTER ADDRESS OFFSETS //////////////////////// +#define P_PCI_BT_A 0x004C // Audio DMA + // burst/threshold + // control. +#define P_DEBICFG 0x007C // DEBI configuration. +#define P_DEBICMD 0x0080 // DEBI command. +#define P_DEBIPAGE 0x0084 // DEBI page. +#define P_DEBIAD 0x0088 // DEBI target address. +#define P_I2CCTRL 0x008C // I2C control. +#define P_I2CSTAT 0x0090 // I2C status. +#define P_BASEA2_IN 0x00AC // Audio input 2 base + // physical DMAbuf + // address. +#define P_PROTA2_IN 0x00B0 // Audio input 2 + // physical DMAbuf + // protection address. +#define P_PAGEA2_IN 0x00B4 // Audio input 2 + // paging attributes. +#define P_BASEA2_OUT 0x00B8 // Audio output 2 base + // physical DMAbuf + // address. +#define P_PROTA2_OUT 0x00BC // Audio output 2 + // physical DMAbuf + // protection address. +#define P_PAGEA2_OUT 0x00C0 // Audio output 2 + // paging attributes. +#define P_RPSPAGE0 0x00C4 // RPS0 page. +#define P_RPSPAGE1 0x00C8 // RPS1 page. +#define P_RPS0_TOUT 0x00D4 // RPS0 time-out. +#define P_RPS1_TOUT 0x00D8 // RPS1 time-out. +#define P_IER 0x00DC // Interrupt enable. +#define P_GPIO 0x00E0 // General-purpose I/O. +#define P_EC1SSR 0x00E4 // Event counter set 1 + // source select. +#define P_ECT1R 0x00EC // Event counter + // threshold set 1. +#define P_ACON1 0x00F4 // Audio control 1. +#define P_ACON2 0x00F8 // Audio control 2. +#define P_MC1 0x00FC // Master control 1. +#define P_MC2 0x0100 // Master control 2. +#define P_RPSADDR0 0x0104 // RPS0 instruction pointer. +#define P_RPSADDR1 0x0108 // RPS1 instruction pointer. +#define P_ISR 0x010C // Interrupt status. +#define P_PSR 0x0110 // Primary status. +#define P_SSR 0x0114 // Secondary status. +#define P_EC1R 0x0118 // Event counter set 1. +#define P_ADP4 0x0138 // Logical audio DMA + // pointer of audio + // input FIFO A2_IN. +#define P_FB_BUFFER1 0x0144 // Audio feedback buffer 1. +#define P_FB_BUFFER2 0x0148 // Audio feedback buffer 2. +#define P_TSL1 0x0180 // Audio time slot list 1. +#define P_TSL2 0x01C0 // Audio time slot list 2. + +// LOCAL BUS (GATE ARRAY) REGISTER ADDRESS OFFSETS ///////////////// +// Analog I/O registers: +#define LP_DACPOL 0x0082 // Write DAC polarity. +#define LP_GSEL 0x0084 // Write ADC gain. +#define LP_ISEL 0x0086 // Write ADC channel select. +// Digital I/O (write only): +#define LP_WRINTSELA 0x0042 // Write A interrupt enable. +#define LP_WREDGSELA 0x0044 // Write A edge selection. +#define LP_WRCAPSELA 0x0046 // Write A capture enable. +#define LP_WRDOUTA 0x0048 // Write A digital output. +#define LP_WRINTSELB 0x0052 // Write B interrupt enable. +#define LP_WREDGSELB 0x0054 // Write B edge selection. +#define LP_WRCAPSELB 0x0056 // Write B capture enable. +#define LP_WRDOUTB 0x0058 // Write B digital output. +#define LP_WRINTSELC 0x0062 // Write C interrupt enable. +#define LP_WREDGSELC 0x0064 // Write C edge selection. +#define LP_WRCAPSELC 0x0066 // Write C capture enable. +#define LP_WRDOUTC 0x0068 // Write C digital output. + +// Digital I/O (read only): +#define LP_RDDINA 0x0040 // Read digital input. +#define LP_RDCAPFLGA 0x0048 // Read edges captured. +#define LP_RDINTSELA 0x004A // Read interrupt + // enable register. +#define LP_RDEDGSELA 0x004C // Read edge + // selection + // register. +#define LP_RDCAPSELA 0x004E // Read capture + // enable register. +#define LP_RDDINB 0x0050 // Read digital input. +#define LP_RDCAPFLGB 0x0058 // Read edges captured. +#define LP_RDINTSELB 0x005A // Read interrupt + // enable register. +#define LP_RDEDGSELB 0x005C // Read edge + // selection + // register. +#define LP_RDCAPSELB 0x005E // Read capture + // enable register. +#define LP_RDDINC 0x0060 // Read digital input. +#define LP_RDCAPFLGC 0x0068 // Read edges captured. +#define LP_RDINTSELC 0x006A // Read interrupt + // enable register. +#define LP_RDEDGSELC 0x006C // Read edge + // selection + // register. +#define LP_RDCAPSELC 0x006E // Read capture + // enable register. +// Counter Registers (read/write): +#define LP_CR0A 0x0000 // 0A setup register. +#define LP_CR0B 0x0002 // 0B setup register. +#define LP_CR1A 0x0004 // 1A setup register. +#define LP_CR1B 0x0006 // 1B setup register. +#define LP_CR2A 0x0008 // 2A setup register. +#define LP_CR2B 0x000A // 2B setup register. +// Counter PreLoad (write) and Latch (read) Registers: +#define LP_CNTR0ALSW 0x000C // 0A lsw. +#define LP_CNTR0AMSW 0x000E // 0A msw. +#define LP_CNTR0BLSW 0x0010 // 0B lsw. +#define LP_CNTR0BMSW 0x0012 // 0B msw. +#define LP_CNTR1ALSW 0x0014 // 1A lsw. +#define LP_CNTR1AMSW 0x0016 // 1A msw. +#define LP_CNTR1BLSW 0x0018 // 1B lsw. +#define LP_CNTR1BMSW 0x001A // 1B msw. +#define LP_CNTR2ALSW 0x001C // 2A lsw. +#define LP_CNTR2AMSW 0x001E // 2A msw. +#define LP_CNTR2BLSW 0x0020 // 2B lsw. +#define LP_CNTR2BMSW 0x0022 // 2B msw. +// Miscellaneous Registers (read/write): +#define LP_MISC1 0x0088 // Read/write Misc1. +#define LP_WRMISC2 0x0090 // Write Misc2. +#define LP_RDMISC2 0x0082 // Read Misc2. + +// Bit masks for MISC1 register that are the same for reads and writes. +#define MISC1_WENABLE 0x8000 // enab writes to + // MISC2 (except Clear + // Watchdog bit). +#define MISC1_WDISABLE 0x0000 // Disable writes to MISC2. +#define MISC1_EDCAP 0x1000 // enab edge capture + // on DIO chans + // specified by + // LP_WRCAPSELx. +#define MISC1_NOEDCAP 0x0000 // Disable edge + // capture on + // specified DIO + // chans. + +// Bit masks for MISC1 register reads. +#define RDMISC1_WDTIMEOUT 0x4000 // Watchdog timer timed out. + +// Bit masks for MISC2 register writes. +#define WRMISC2_WDCLEAR 0x8000 // Reset watchdog + // timer to zero. +#define WRMISC2_CHARGE_ENABLE 0x4000 // enab battery + // trickle charging. + +// Bit masks for MISC2 register that are the same for reads and writes. +#define MISC2_BATT_ENABLE 0x0008 // Backup battery enable. +#define MISC2_WDENABLE 0x0004 // Watchdog timer enable. +#define MISC2_WDPERIOD_MASK 0x0003 // Watchdog interval + // select mask. + +// Bit masks for ACON1 register. +#define A2_RUN 0x40000000 // Run A2 based on TSL2. +#define A1_RUN 0x20000000 // Run A1 based on TSL1. +#define A1_SWAP 0x00200000 // Use big-endian for A1. +#define A2_SWAP 0x00100000 // Use big-endian for A2. +#define WS_MODES 0x00019999 // WS0 = TSL1 trigger + // input, WS1-WS4 = + // CS* outputs. + +#if PLATFORM == INTEL // Base ACON1 config: + // always run A1 based + // on TSL1. +#define ACON1_BASE ( WS_MODES | A1_RUN ) +#elif PLATFORM == MOTOROLA +#define ACON1_BASE ( WS_MODES | A1_RUN | A1_SWAP | A2_SWAP ) +#endif + +#define ACON1_ADCSTART ACON1_BASE // Start ADC: run A1 + // based on TSL1. +#define ACON1_DACSTART ( ACON1_BASE | A2_RUN ) // Start + // transmit to + // DAC: run A2 + // based on + // TSL2. +#define ACON1_DACSTOP ACON1_BASE // Halt A2. + +// Bit masks for ACON2 register. +#define A1_CLKSRC_BCLK1 0x00000000 // A1 bit rate = BCLK1 (ADC). +#define A2_CLKSRC_X1 0x00800000 // A2 bit rate = ACLK/1 (DACs). +#define A2_CLKSRC_X2 0x00C00000 // A2 bit rate = ACLK/2 (DACs). +#define A2_CLKSRC_X4 0x01400000 // A2 bit rate = ACLK/4 (DACs). +#define INVERT_BCLK2 0x00100000 // Invert BCLK2 (DACs). +#define BCLK2_OE 0x00040000 // enab BCLK2 (DACs). +#define ACON2_XORMASK 0x000C0000 // XOR mask for ACON2 + // active-low bits. + +#define ACON2_INIT ( ACON2_XORMASK ^ ( A1_CLKSRC_BCLK1 | A2_CLKSRC_X2 | INVERT_BCLK2 | BCLK2_OE ) ) + +// Bit masks for timeslot records. +#define WS1 0x40000000 // WS output to assert. +#define WS2 0x20000000 +#define WS3 0x10000000 +#define WS4 0x08000000 +#define RSD1 0x01000000 // Shift A1 data in on SD1. +#define SDW_A1 0x00800000 // Store rcv'd char at + // next char slot of + // DWORD1 buffer. +#define SIB_A1 0x00400000 // Store rcv'd char at + // next char slot of + // FB1 buffer. +#define SF_A1 0x00200000 // Write unsigned long + // buffer to input + // FIFO. + +//Select parallel-to-serial converter's data source: +#define XFIFO_0 0x00000000 // Data fifo byte 0. +#define XFIFO_1 0x00000010 // Data fifo byte 1. +#define XFIFO_2 0x00000020 // Data fifo byte 2. +#define XFIFO_3 0x00000030 // Data fifo byte 3. +#define XFB0 0x00000040 // FB_BUFFER byte 0. +#define XFB1 0x00000050 // FB_BUFFER byte 1. +#define XFB2 0x00000060 // FB_BUFFER byte 2. +#define XFB3 0x00000070 // FB_BUFFER byte 3. +#define SIB_A2 0x00000200 // Store next dword + // from A2's input + // shifter to FB2 + // buffer. +#define SF_A2 0x00000100 // Store next dword + // from A2's input + // shifter to its + // input fifo. +#define LF_A2 0x00000080 // Load next dword + // from A2's output + // fifo into its + // output dword + // buffer. +#define XSD2 0x00000008 // Shift data out on SD2. +#define RSD3 0x00001800 // Shift data in on SD3. +#define RSD2 0x00001000 // Shift data in on SD2. +#define LOW_A2 0x00000002 // Drive last SD low + // for 7 clks, then + // tri-state. +#define EOS 0x00000001 // End of superframe. + +////////////////////// + +// I2C configuration constants. +#define I2C_CLKSEL 0x0400 // I2C bit rate = + // PCIclk/480 = 68.75 + // KHz. +#define I2C_BITRATE 68.75 // I2C bus data bit + // rate (determined by + // I2C_CLKSEL) in KHz. +#define I2C_WRTIME 15.0 // Worst case time,in + // msec, for EEPROM + // internal write op. + +// I2C manifest constants. + +// Max retries to wait for EEPROM write. +#define I2C_RETRIES ( I2C_WRTIME * I2C_BITRATE / 9.0 ) +#define I2C_ERR 0x0002 // I2C control/status + // flag ERROR. +#define I2C_BUSY 0x0001 // I2C control/status + // flag BUSY. +#define I2C_ABORT 0x0080 // I2C status flag ABORT. +#define I2C_ATTRSTART 0x3 // I2C attribute START. +#define I2C_ATTRCONT 0x2 // I2C attribute CONT. +#define I2C_ATTRSTOP 0x1 // I2C attribute STOP. +#define I2C_ATTRNOP 0x0 // I2C attribute NOP. + +// I2C read command | EEPROM address. +#define I2CR ( devpriv->I2CAdrs | 1 ) + +// I2C write command | EEPROM address. +#define I2CW ( devpriv->I2CAdrs ) + +// Code macros used for constructing I2C command bytes. +#define I2C_B2(ATTR,VAL) ( ( (ATTR) << 6 ) | ( (VAL) << 24 ) ) +#define I2C_B1(ATTR,VAL) ( ( (ATTR) << 4 ) | ( (VAL) << 16 ) ) +#define I2C_B0(ATTR,VAL) ( ( (ATTR) << 2 ) | ( (VAL) << 8 ) ) + +//////////////////////////////////////////////////////// +//oldest +#define P_DEBICFGq 0x007C // DEBI configuration. +#define P_DEBICMDq 0x0080 // DEBI command. +#define P_DEBIPAGEq 0x0084 // DEBI page. +#define P_DEBIADq 0x0088 // DEBI target address. + +#define DEBI_CFG_TOQ 0x03C00000 // timeout (15 PCI cycles) +#define DEBI_CFG_FASTQ 0x10000000 // fast mode enable +#define DEBI_CFG_16Q 0x00080000 // 16-bit access enable +#define DEBI_CFG_INCQ 0x00040000 // enable address increment +#define DEBI_CFG_TIMEROFFQ 0x00010000 // disable timer +#define DEBI_CMD_RDQ 0x00050000 // read immediate 2 bytes +#define DEBI_CMD_WRQ 0x00040000 // write immediate 2 bytes +#define DEBI_PAGE_DISABLEQ 0x00000000 // paging disable + +/////////////////////////////////////////// +// DEBI command constants. +#define DEBI_CMD_SIZE16 ( 2 << 17 ) // Transfer size is + // always 2 bytes. +#define DEBI_CMD_READ 0x00010000 // Read operation. +#define DEBI_CMD_WRITE 0x00000000 // Write operation. + +// Read immediate 2 bytes. +#define DEBI_CMD_RDWORD ( DEBI_CMD_READ | DEBI_CMD_SIZE16 ) + +// Write immediate 2 bytes. +#define DEBI_CMD_WRWORD ( DEBI_CMD_WRITE | DEBI_CMD_SIZE16 ) + +// DEBI configuration constants. +#define DEBI_CFG_XIRQ_EN 0x80000000 // enab external + // interrupt on GPIO3. +#define DEBI_CFG_XRESUME 0x40000000 // Resume block + // transfer when XIRQ + // deasserted. +#define DEBI_CFG_FAST 0x10000000 // Fast mode enable. + +// 4-bit field that specifies DEBI timeout value in PCI clock cycles: +#define DEBI_CFG_TOUT_BIT 22 // Finish DEBI cycle after + // this many clocks. + +// 2-bit field that specifies Endian byte lane steering: +#define DEBI_CFG_SWAP_NONE 0x00000000 // Straight - don't + // swap any bytes + // (Intel). +#define DEBI_CFG_SWAP_2 0x00100000 // 2-byte swap (Motorola). +#define DEBI_CFG_SWAP_4 0x00200000 // 4-byte swap. +#define DEBI_CFG_16 0x00080000 // Slave is able to + // serve 16-bit + // cycles. + +#define DEBI_CFG_SLAVE16 0x00080000 // Slave is able to + // serve 16-bit + // cycles. +#define DEBI_CFG_INC 0x00040000 // enab address + // increment for block + // transfers. +#define DEBI_CFG_INTEL 0x00020000 // Intel style local bus. +#define DEBI_CFG_TIMEROFF 0x00010000 // Disable timer. + +#if PLATFORM == INTEL + +#define DEBI_TOUT 7 // Wait 7 PCI clocks + // (212 ns) before + // polling RDY. + +// Intel byte lane steering (pass through all byte lanes). +#define DEBI_SWAP DEBI_CFG_SWAP_NONE + +#elif PLATFORM == MOTOROLA + +#define DEBI_TOUT 15 // Wait 15 PCI clocks (454 ns) + // maximum before timing out. +#define DEBI_SWAP DEBI_CFG_SWAP_2 // Motorola byte lane steering. + +#endif + +// DEBI page table constants. +#define DEBI_PAGE_DISABLE 0x00000000 // Paging disable. + +///////////////////EXTRA FROM OTHER SANSORAY * .h//////// + +// LoadSrc values: +#define LOADSRC_INDX 0 // Preload core in response to + // Index. +#define LOADSRC_OVER 1 // Preload core in response to + // Overflow. +#define LOADSRCB_OVERA 2 // Preload B core in response + // to A Overflow. +#define LOADSRC_NONE 3 // Never preload core. + +// IntSrc values: +#define INTSRC_NONE 0 // Interrupts disabled. +#define INTSRC_OVER 1 // Interrupt on Overflow. +#define INTSRC_INDX 2 // Interrupt on Index. +#define INTSRC_BOTH 3 // Interrupt on Index or Overflow. + +// LatchSrc values: +#define LATCHSRC_AB_READ 0 // Latch on read. +#define LATCHSRC_A_INDXA 1 // Latch A on A Index. +#define LATCHSRC_B_INDXB 2 // Latch B on B Index. +#define LATCHSRC_B_OVERA 3 // Latch B on A Overflow. + +// IndxSrc values: +#define INDXSRC_HARD 0 // Hardware or software index. +#define INDXSRC_SOFT 1 // Software index only. + +// IndxPol values: +#define INDXPOL_POS 0 // Index input is active high. +#define INDXPOL_NEG 1 // Index input is active low. + +// ClkSrc values: +#define CLKSRC_COUNTER 0 // Counter mode. +#define CLKSRC_TIMER 2 // Timer mode. +#define CLKSRC_EXTENDER 3 // Extender mode. + +// ClkPol values: +#define CLKPOL_POS 0 // Counter/Extender clock is + // active high. +#define CLKPOL_NEG 1 // Counter/Extender clock is + // active low. +#define CNTDIR_UP 0 // Timer counts up. +#define CNTDIR_DOWN 1 // Timer counts down. + +// ClkEnab values: +#define CLKENAB_ALWAYS 0 // Clock always enabled. +#define CLKENAB_INDEX 1 // Clock is enabled by index. + +// ClkMult values: +#define CLKMULT_4X 0 // 4x clock multiplier. +#define CLKMULT_2X 1 // 2x clock multiplier. +#define CLKMULT_1X 2 // 1x clock multiplier. + +// Bit Field positions in COUNTER_SETUP structure: +#define BF_LOADSRC 9 // Preload trigger. +#define BF_INDXSRC 7 // Index source. +#define BF_INDXPOL 6 // Index polarity. +#define BF_CLKSRC 4 // Clock source. +#define BF_CLKPOL 3 // Clock polarity/count direction. +#define BF_CLKMULT 1 // Clock multiplier. +#define BF_CLKENAB 0 // Clock enable. + +// Enumerated counter operating modes specified by ClkSrc bit field in +// a COUNTER_SETUP. + +#define CLKSRC_COUNTER 0 // Counter: ENC_C clock, ENC_D + // direction. +#define CLKSRC_TIMER 2 // Timer: SYS_C clock, + // direction specified by + // ClkPol. +#define CLKSRC_EXTENDER 3 // Extender: OVR_A clock, + // ENC_D direction. + +// Enumerated counter clock multipliers. + +#define MULT_X0 0x0003 // Supports no multipliers; + // fixed physical multiplier = + // 3. +#define MULT_X1 0x0002 // Supports multiplier x1; + // fixed physical multiplier = + // 2. +#define MULT_X2 0x0001 // Supports multipliers x1, + // x2; physical multipliers = + // 1 or 2. +#define MULT_X4 0x0000 // Supports multipliers x1, + // x2, x4; physical + // multipliers = 0, 1 or 2. + +// Sanity-check limits for parameters. + +#define NUM_COUNTERS 6 // Maximum valid counter + // logical channel number. +#define NUM_INTSOURCES 4 +#define NUM_LATCHSOURCES 4 +#define NUM_CLKMULTS 4 +#define NUM_CLKSOURCES 4 +#define NUM_CLKPOLS 2 +#define NUM_INDEXPOLS 2 +#define NUM_INDEXSOURCES 2 +#define NUM_LOADTRIGS 4 + +// Bit field positions in CRA and CRB counter control registers. + +// Bit field positions in CRA: +#define CRABIT_INDXSRC_B 14 // B index source. +#define CRABIT_CLKSRC_B 12 // B clock source. +#define CRABIT_INDXPOL_A 11 // A index polarity. +#define CRABIT_LOADSRC_A 9 // A preload trigger. +#define CRABIT_CLKMULT_A 7 // A clock multiplier. +#define CRABIT_INTSRC_A 5 // A interrupt source. +#define CRABIT_CLKPOL_A 4 // A clock polarity. +#define CRABIT_INDXSRC_A 2 // A index source. +#define CRABIT_CLKSRC_A 0 // A clock source. + +// Bit field positions in CRB: +#define CRBBIT_INTRESETCMD 15 // Interrupt reset command. +#define CRBBIT_INTRESET_B 14 // B interrupt reset enable. +#define CRBBIT_INTRESET_A 13 // A interrupt reset enable. +#define CRBBIT_CLKENAB_A 12 // A clock enable. +#define CRBBIT_INTSRC_B 10 // B interrupt source. +#define CRBBIT_LATCHSRC 8 // A/B latch source. +#define CRBBIT_LOADSRC_B 6 // B preload trigger. +#define CRBBIT_CLKMULT_B 3 // B clock multiplier. +#define CRBBIT_CLKENAB_B 2 // B clock enable. +#define CRBBIT_INDXPOL_B 1 // B index polarity. +#define CRBBIT_CLKPOL_B 0 // B clock polarity. + +// Bit field masks for CRA and CRB. + +#define CRAMSK_INDXSRC_B ( (uint16_t)( 3 << CRABIT_INDXSRC_B) ) +#define CRAMSK_CLKSRC_B ( (uint16_t)( 3 << CRABIT_CLKSRC_B) ) +#define CRAMSK_INDXPOL_A ( (uint16_t)( 1 << CRABIT_INDXPOL_A) ) +#define CRAMSK_LOADSRC_A ( (uint16_t)( 3 << CRABIT_LOADSRC_A) ) +#define CRAMSK_CLKMULT_A ( (uint16_t)( 3 << CRABIT_CLKMULT_A) ) +#define CRAMSK_INTSRC_A ( (uint16_t)( 3 << CRABIT_INTSRC_A) ) +#define CRAMSK_CLKPOL_A ( (uint16_t)( 3 << CRABIT_CLKPOL_A) ) +#define CRAMSK_INDXSRC_A ( (uint16_t)( 3 << CRABIT_INDXSRC_A) ) +#define CRAMSK_CLKSRC_A ( (uint16_t)( 3 << CRABIT_CLKSRC_A) ) + +#define CRBMSK_INTRESETCMD ( (uint16_t)( 1 << CRBBIT_INTRESETCMD) ) +#define CRBMSK_INTRESET_B ( (uint16_t)( 1 << CRBBIT_INTRESET_B) ) +#define CRBMSK_INTRESET_A ( (uint16_t)( 1 << CRBBIT_INTRESET_A) ) +#define CRBMSK_CLKENAB_A ( (uint16_t)( 1 << CRBBIT_CLKENAB_A) ) +#define CRBMSK_INTSRC_B ( (uint16_t)( 3 << CRBBIT_INTSRC_B) ) +#define CRBMSK_LATCHSRC ( (uint16_t)( 3 << CRBBIT_LATCHSRC) ) +#define CRBMSK_LOADSRC_B ( (uint16_t)( 3 << CRBBIT_LOADSRC_B) ) +#define CRBMSK_CLKMULT_B ( (uint16_t)( 3 << CRBBIT_CLKMULT_B) ) +#define CRBMSK_CLKENAB_B ( (uint16_t)( 1 << CRBBIT_CLKENAB_B) ) +#define CRBMSK_INDXPOL_B ( (uint16_t)( 1 << CRBBIT_INDXPOL_B) ) +#define CRBMSK_CLKPOL_B ( (uint16_t)( 1 << CRBBIT_CLKPOL_B) ) + +#define CRBMSK_INTCTRL ( CRBMSK_INTRESETCMD | CRBMSK_INTRESET_A | CRBMSK_INTRESET_B ) // Interrupt reset control bits. + +// Bit field positions for standardized SETUP structure. + +#define STDBIT_INTSRC 13 +#define STDBIT_LATCHSRC 11 +#define STDBIT_LOADSRC 9 +#define STDBIT_INDXSRC 7 +#define STDBIT_INDXPOL 6 +#define STDBIT_CLKSRC 4 +#define STDBIT_CLKPOL 3 +#define STDBIT_CLKMULT 1 +#define STDBIT_CLKENAB 0 + +// Bit field masks for standardized SETUP structure. + +#define STDMSK_INTSRC ( (uint16_t)( 3 << STDBIT_INTSRC ) ) +#define STDMSK_LATCHSRC ( (uint16_t)( 3 << STDBIT_LATCHSRC ) ) +#define STDMSK_LOADSRC ( (uint16_t)( 3 << STDBIT_LOADSRC ) ) +#define STDMSK_INDXSRC ( (uint16_t)( 1 << STDBIT_INDXSRC ) ) +#define STDMSK_INDXPOL ( (uint16_t)( 1 << STDBIT_INDXPOL ) ) +#define STDMSK_CLKSRC ( (uint16_t)( 3 << STDBIT_CLKSRC ) ) +#define STDMSK_CLKPOL ( (uint16_t)( 1 << STDBIT_CLKPOL ) ) +#define STDMSK_CLKMULT ( (uint16_t)( 3 << STDBIT_CLKMULT ) ) +#define STDMSK_CLKENAB ( (uint16_t)( 1 << STDBIT_CLKENAB ) ) + +////////////////////////////////////////////////////////// + +/* typedef struct indexCounter */ +/* { */ +/* unsigned int ao; */ +/* unsigned int ai; */ +/* unsigned int digout; */ +/* unsigned int digin; */ +/* unsigned int enc; */ +/* }CallCounter; */ + +typedef struct bufferDMA { + dma_addr_t PhysicalBase; + void *LogicalBase; + uint32_t DMAHandle; +} DMABUF; diff --git a/drivers/staging/comedi/drivers/usbdux.c b/drivers/staging/comedi/drivers/usbdux.c new file mode 100644 index 00000000000..35138257be7 --- /dev/null +++ b/drivers/staging/comedi/drivers/usbdux.c @@ -0,0 +1,2932 @@ +#define DRIVER_VERSION "v2.1" +#define DRIVER_AUTHOR "Bernd Porr, BerndPorr@f2s.com" +#define DRIVER_DESC "Stirling/ITL USB-DUX -- Bernd.Porr@f2s.com" +/* + comedi/drivers/usbdux.c + Copyright (C) 2003-2007 Bernd Porr, Bernd.Porr@f2s.com + + 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: usbdux +Description: University of Stirling USB DAQ & INCITE Technology Limited +Devices: [ITL] USB-DUX (usbdux.o) +Author: Bernd Porr <BerndPorr@f2s.com> +Updated: 25 Nov 2007 +Status: Testing +Configuration options: + You have to upload firmware with the -i option. The + firmware is usually installed under /usr/share/usb or + /usr/local/share/usb or /lib/firmware. + +Connection scheme for the counter at the digital port: + 0=/CLK0, 1=UP/DOWN0, 2=RESET0, 4=/CLK1, 5=UP/DOWN1, 6=RESET1. + The sampling rate of the counter is approximately 500Hz. + +Please note that under USB2.0 the length of the channel list determines +the max sampling rate. If you sample only one channel you get 8kHz +sampling rate. If you sample two channels you get 4kHz and so on. +*/ +/* + * I must give credit here to Chris Baugher who + * wrote the driver for AT-MIO-16d. I used some parts of this + * driver. I also must give credits to David Brownell + * who supported me with the USB development. + * + * Bernd Porr + * + * + * Revision history: + * 0.94: D/A output should work now with any channel list combinations + * 0.95: .owner commented out for kernel vers below 2.4.19 + * sanity checks in ai/ao_cmd + * 0.96: trying to get it working with 2.6, moved all memory alloc to comedi's + * attach final USB IDs + * moved memory allocation completely to the corresponding comedi + * functions firmware upload is by fxload and no longer by comedi (due to + * enumeration) + * 0.97: USB IDs received, adjusted table + * 0.98: SMP, locking, memroy alloc: moved all usb memory alloc + * to the usb subsystem and moved all comedi related memory + * alloc to comedi. + * | kernel | registration | usbdux-usb | usbdux-comedi | comedi | + * 0.99: USB 2.0: changed protocol to isochronous transfer + * IRQ transfer is too buggy and too risky in 2.0 + * for the high speed ISO transfer is now a working version + * available + * 0.99b: Increased the iso transfer buffer for high sp.to 10 buffers. Some VIA + * chipsets miss out IRQs. Deeper buffering is needed. + * 1.00: full USB 2.0 support for the A/D converter. Now: max 8kHz sampling + * rate. + * Firmware vers 1.00 is needed for this. + * Two 16 bit up/down/reset counter with a sampling rate of 1kHz + * And loads of cleaning up, in particular streamlining the + * bulk transfers. + * 1.1: moved EP4 transfers to EP1 to make space for a PWM output on EP4 + * 1.2: added PWM suport via EP4 + * 2.0: PWM seems to be stable and is not interfering with the other functions + * 2.1: changed PWM API + * + */ + +/* generates loads of debug info */ +/* #define NOISY_DUX_DEBUGBUG */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/usb.h> +#include <linux/smp_lock.h> +#include <linux/fcntl.h> +#include <linux/compiler.h> + +#include "../comedidev.h" + +#define BOARDNAME "usbdux" + +/* timeout for the USB-transfer */ +#define EZTIMEOUT 30 + +/* constants for "firmware" upload and download */ +#define USBDUXSUB_FIRMWARE 0xA0 +#define VENDOR_DIR_IN 0xC0 +#define VENDOR_DIR_OUT 0x40 + +/* internal adresses of the 8051 processor */ +#define USBDUXSUB_CPUCS 0xE600 + +/* + * the minor device number, major is 180 only for debugging purposes and to + * upload special firmware (programming the eeprom etc) which is not compatible + * with the comedi framwork + */ +#define USBDUXSUB_MINOR 32 + +/* max lenghth of the transfer-buffer for software upload */ +#define TB_LEN 0x2000 + +/* Input endpoint number: ISO/IRQ */ +#define ISOINEP 6 + +/* Output endpoint number: ISO/IRQ */ +#define ISOOUTEP 2 + +/* This EP sends DUX commands to USBDUX */ +#define COMMAND_OUT_EP 1 + +/* This EP receives the DUX commands from USBDUX */ +#define COMMAND_IN_EP 8 + +/* Output endpoint for PWM */ +#define PWM_EP 4 + +/* 300Hz max frequ under PWM */ +#define MIN_PWM_PERIOD ((long)(1E9/300)) + +/* Default PWM frequency */ +#define PWM_DEFAULT_PERIOD ((long)(1E9/100)) + +/* Number of channels */ +#define NUMCHANNELS 8 + +/* Size of one A/D value */ +#define SIZEADIN ((sizeof(int16_t))) + +/* + * Size of the input-buffer IN BYTES + * Always multiple of 8 for 8 microframes which is needed in the highspeed mode + */ +#define SIZEINBUF ((8*SIZEADIN)) + +/* 16 bytes. */ +#define SIZEINSNBUF 16 + +/* Number of DA channels */ +#define NUMOUTCHANNELS 8 + +/* size of one value for the D/A converter: channel and value */ +#define SIZEDAOUT ((sizeof(int8_t)+sizeof(int16_t))) + +/* + * Size of the output-buffer in bytes + * Actually only the first 4 triplets are used but for the + * high speed mode we need to pad it to 8 (microframes). + */ +#define SIZEOUTBUF ((8*SIZEDAOUT)) + +/* + * Size of the buffer for the dux commands: just now max size is determined + * by the analogue out + command byte + panic bytes... + */ +#define SIZEOFDUXBUFFER ((8*SIZEDAOUT+2)) + +/* Number of in-URBs which receive the data: min=2 */ +#define NUMOFINBUFFERSFULL 5 + +/* Number of out-URBs which send the data: min=2 */ +#define NUMOFOUTBUFFERSFULL 5 + +/* Number of in-URBs which receive the data: min=5 */ +/* must have more buffers due to buggy USB ctr */ +#define NUMOFINBUFFERSHIGH 10 + +/* Number of out-URBs which send the data: min=5 */ +/* must have more buffers due to buggy USB ctr */ +#define NUMOFOUTBUFFERSHIGH 10 + +/* Total number of usbdux devices */ +#define NUMUSBDUX 16 + +/* Analogue in subdevice */ +#define SUBDEV_AD 0 + +/* Analogue out subdevice */ +#define SUBDEV_DA 1 + +/* Digital I/O */ +#define SUBDEV_DIO 2 + +/* counter */ +#define SUBDEV_COUNTER 3 + +/* timer aka pwm output */ +#define SUBDEV_PWM 4 + +/* number of retries to get the right dux command */ +#define RETRIES 10 + +/**************************************************/ +/* comedi constants */ +static const comedi_lrange range_usbdux_ai_range = { 4, { + BIP_RANGE(4.096), + BIP_RANGE(4.096 / 2), + UNI_RANGE(4.096), + UNI_RANGE(4.096 / 2) + } +}; + +static const comedi_lrange range_usbdux_ao_range = { 2, { + BIP_RANGE(4.096), + UNI_RANGE(4.096), + } +}; + +/* + * private structure of one subdevice + */ + +/* + * This is the structure which holds all the data of + * this driver one sub device just now: A/D + */ +struct usbduxsub { + /* attached? */ + int attached; + /* is it associated with a subdevice? */ + int probed; + /* pointer to the usb-device */ + struct usb_device *usbdev; + /* actual number of in-buffers */ + int numOfInBuffers; + /* actual number of out-buffers */ + int numOfOutBuffers; + /* ISO-transfer handling: buffers */ + struct urb **urbIn; + struct urb **urbOut; + /* pwm-transfer handling */ + struct urb *urbPwm; + /* PWM period */ + lsampl_t pwmPeriod; + /* PWM internal delay for the GPIF in the FX2 */ + int8_t pwmDelay; + /* size of the PWM buffer which holds the bit pattern */ + int sizePwmBuf; + /* input buffer for the ISO-transfer */ + int16_t *inBuffer; + /* input buffer for single insn */ + int16_t *insnBuffer; + /* output buffer for single DA outputs */ + int16_t *outBuffer; + /* interface number */ + int ifnum; + /* interface structure in 2.6 */ + struct usb_interface *interface; + /* comedi device for the interrupt context */ + comedi_device *comedidev; + /* is it USB_SPEED_HIGH or not? */ + short int high_speed; + /* asynchronous command is running */ + short int ai_cmd_running; + short int ao_cmd_running; + /* pwm is running */ + short int pwm_cmd_running; + /* continous aquisition */ + short int ai_continous; + short int ao_continous; + /* number of samples to aquire */ + int ai_sample_count; + int ao_sample_count; + /* time between samples in units of the timer */ + unsigned int ai_timer; + unsigned int ao_timer; + /* counter between aquisitions */ + unsigned int ai_counter; + unsigned int ao_counter; + /* interval in frames/uframes */ + unsigned int ai_interval; + /* D/A commands */ + int8_t *dac_commands; + /* commands */ + int8_t *dux_commands; + struct semaphore sem; +}; + +/* + * The pointer to the private usb-data of the driver is also the private data + * for the comedi-device. This has to be global as the usb subsystem needs + * global variables. The other reason is that this structure must be there + * _before_ any comedi command is issued. The usb subsystem must be initialised + * before comedi can access it. + */ +static struct usbduxsub usbduxsub[NUMUSBDUX]; + +static DECLARE_MUTEX(start_stop_sem); + +/* + * Stops the data acquision + * It should be safe to call this function from any context + */ +static int usbduxsub_unlink_InURBs(struct usbduxsub *usbduxsub_tmp) +{ + int i = 0; + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbIn) { + for (i = 0; i < usbduxsub_tmp->numOfInBuffers; i++) { + if (usbduxsub_tmp->urbIn[i]) { + /* We wait here until all transfers have been + * cancelled. */ + usb_kill_urb(usbduxsub_tmp->urbIn[i]); + } + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi: usbdux: unlinked InURB %d, err=%d\n", + i, err); + } + } + return err; +} + +/* + * This will stop a running acquisition operation + * Is called from within this driver from both the + * interrupt context and from comedi + */ +static int usbdux_ai_stop(struct usbduxsub *this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) { + dev_err(&this_usbduxsub->interface->dev, + "comedi?: usbdux_ai_stop: this_usbduxsub=NULL!\n"); + return -EFAULT; + } + dev_dbg(&this_usbduxsub->interface->dev, "comedi: usbdux_ai_stop\n"); + + if (do_unlink) { + /* stop aquistion */ + ret = usbduxsub_unlink_InURBs(this_usbduxsub); + } + + this_usbduxsub->ai_cmd_running = 0; + + return ret; +} + +/* + * This will cancel a running acquisition operation. + * This is called by comedi but never from inside the driver. + */ +static int usbdux_ai_cancel(comedi_device *dev, comedi_subdevice *s) +{ + struct usbduxsub *this_usbduxsub; + int res = 0; + + /* force unlink of all urbs */ + this_usbduxsub = dev->private; + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, "comedi: usbdux_ai_cancel\n"); + + /* prevent other CPUs from submitting new commands just now */ + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + /* unlink only if the urb really has been submitted */ + res = usbdux_ai_stop(this_usbduxsub, this_usbduxsub->ai_cmd_running); + up(&this_usbduxsub->sem); + return res; +} + +/* analogue IN - interrupt service routine */ +static void usbduxsub_ai_IsocIrq(struct urb *urb) +{ + int i, err, n; + struct usbduxsub *this_usbduxsub; + comedi_device *this_comedidev; + comedi_subdevice *s; + + /* the context variable points to the subdevice */ + this_comedidev = urb->context; + /* the private structure of the subdevice is struct usbduxsub */ + this_usbduxsub = this_comedidev->private; + /* subdevice which is the AD converter */ + s = this_comedidev->subdevices + SUBDEV_AD; + + /* first we test if something unusual has just happened */ + switch (urb->status) { + case 0: + /* copy the result in the transfer buffer */ + memcpy(this_usbduxsub->inBuffer, + urb->transfer_buffer, SIZEINBUF); + break; + case -EILSEQ: + /* error in the ISOchronous data */ + /* we don't copy the data into the transfer buffer */ + /* and recycle the last data byte */ + dev_dbg(&urb->dev->dev, + "comedi%d: usbdux: CRC error in ISO IN stream.\n", + this_usbduxsub->comedidev->minor); + + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* happens after an unlink command */ + if (this_usbduxsub->ai_cmd_running) { + /* we are still running a command */ + /* tell this comedi */ + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* stop the transfer w/o unlink */ + usbdux_ai_stop(this_usbduxsub, 0); + } + return; + + default: + /* a real error on the bus */ + /* pass error to comedi if we are really running a command */ + if (this_usbduxsub->ai_cmd_running) { + dev_err(&urb->dev->dev, + "Non-zero urb status received in ai intr " + "context: %d\n", urb->status); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* don't do an unlink here */ + usbdux_ai_stop(this_usbduxsub, 0); + } + return; + } + + /* + * at this point we are reasonably sure that nothing dodgy has happened + * are we running a command? + */ + if (unlikely((!(this_usbduxsub->ai_cmd_running)))) { + /* + * not running a command, do not continue execution if no + * asynchronous command is running in particular not resubmit + */ + return; + } + + urb->dev = this_usbduxsub->usbdev; + + /* resubmit the urb */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + dev_err(&urb->dev->dev, + "comedi_: urb resubmit failed in int-context! err=%d\n", + err); + if (err == -EL2NSYNC) + dev_err(&urb->dev->dev, + "buggy USB host controller or bug in IRQ " + "handler!\n"); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* don't do an unlink here */ + usbdux_ai_stop(this_usbduxsub, 0); + return; + } + + this_usbduxsub->ai_counter--; + if (likely(this_usbduxsub->ai_counter > 0)) + return; + + /* timer zero, transfer measurements to comedi */ + this_usbduxsub->ai_counter = this_usbduxsub->ai_timer; + + /* test, if we transmit only a fixed number of samples */ + if (!(this_usbduxsub->ai_continous)) { + /* not continous, fixed number of samples */ + this_usbduxsub->ai_sample_count--; + /* all samples received? */ + if (this_usbduxsub->ai_sample_count < 0) { + /* prevent a resubmit next time */ + usbdux_ai_stop(this_usbduxsub, 0); + /* say comedi that the acquistion is over */ + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + return; + } + } + /* get the data from the USB bus and hand it over to comedi */ + n = s->async->cmd.chanlist_len; + for (i = 0; i < n; i++) { + /* transfer data */ + if (CR_RANGE(s->async->cmd.chanlist[i]) <= 1) { + comedi_buf_put + (s->async, + le16_to_cpu(this_usbduxsub-> + inBuffer[i]) ^ 0x800); + } else { + comedi_buf_put + (s->async, + le16_to_cpu(this_usbduxsub->inBuffer[i])); + } + } + /* tell comedi that data is there */ + comedi_event(this_usbduxsub->comedidev, s); +} + +static int usbduxsub_unlink_OutURBs(struct usbduxsub *usbduxsub_tmp) +{ + int i = 0; + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbOut) { + for (i = 0; i < usbduxsub_tmp->numOfOutBuffers; i++) { + if (usbduxsub_tmp->urbOut[i]) + usb_kill_urb(usbduxsub_tmp->urbOut[i]); + + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi: usbdux: unlinked OutURB %d: res=%d\n", + i, err); + } + } + return err; +} + +/* This will cancel a running acquisition operation + * in any context. + */ +static int usbdux_ao_stop(struct usbduxsub *this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) + return -EFAULT; + dev_dbg(&this_usbduxsub->interface->dev, "comedi: usbdux_ao_cancel\n"); + + if (do_unlink) + ret = usbduxsub_unlink_OutURBs(this_usbduxsub); + + this_usbduxsub->ao_cmd_running = 0; + + return ret; +} + +/* force unlink, is called by comedi */ +static int usbdux_ao_cancel(comedi_device *dev, comedi_subdevice *s) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int res = 0; + + if (!this_usbduxsub) + return -EFAULT; + + /* prevent other CPUs from submitting a command just now */ + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + /* unlink only if it is really running */ + res = usbdux_ao_stop(this_usbduxsub, this_usbduxsub->ao_cmd_running); + up(&this_usbduxsub->sem); + return res; +} + +static void usbduxsub_ao_IsocIrq(struct urb *urb) +{ + int i, ret; + int8_t *datap; + struct usbduxsub *this_usbduxsub; + comedi_device *this_comedidev; + comedi_subdevice *s; + + /* the context variable points to the subdevice */ + this_comedidev = urb->context; + /* the private structure of the subdevice is struct usbduxsub */ + this_usbduxsub = this_comedidev->private; + + s = this_comedidev->subdevices + SUBDEV_DA; + + switch (urb->status) { + case 0: + /* success */ + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* after an unlink command, unplug, ... etc */ + /* no unlink needed here. Already shutting down. */ + if (this_usbduxsub->ao_cmd_running) { + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + usbdux_ao_stop(this_usbduxsub, 0); + } + return; + + default: + /* a real error */ + if (this_usbduxsub->ao_cmd_running) { + dev_err(&urb->dev->dev, + "comedi_: Non-zero urb status received in ao " + "intr context: %d\n", urb->status); + s->async->events |= COMEDI_CB_ERROR; + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + /* we do an unlink if we are in the high speed mode */ + usbdux_ao_stop(this_usbduxsub, 0); + } + return; + } + + /* are we actually running? */ + if (!(this_usbduxsub->ao_cmd_running)) + return; + + /* normal operation: executing a command in this subdevice */ + this_usbduxsub->ao_counter--; + if (this_usbduxsub->ao_counter <= 0) { + /* timer zero */ + this_usbduxsub->ao_counter = this_usbduxsub->ao_timer; + + /* handle non continous aquisition */ + if (!(this_usbduxsub->ao_continous)) { + /* fixed number of samples */ + this_usbduxsub->ao_sample_count--; + if (this_usbduxsub->ao_sample_count < 0) { + /* all samples transmitted */ + usbdux_ao_stop(this_usbduxsub, 0); + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxsub->comedidev, s); + /* no resubmit of the urb */ + return; + } + } + /* transmit data to the USB bus */ + ((uint8_t *) (urb->transfer_buffer))[0] = + s->async->cmd.chanlist_len; + for (i = 0; i < s->async->cmd.chanlist_len; i++) { + sampl_t temp; + if (i >= NUMOUTCHANNELS) + break; + + /* pointer to the DA */ + datap = + (&(((int8_t *)urb->transfer_buffer)[i * 3 + 1])); + /* get the data from comedi */ + ret = comedi_buf_get(s->async, &temp); + datap[0] = temp; + datap[1] = temp >> 8; + datap[2] = this_usbduxsub->dac_commands[i]; + /* printk("data[0]=%x, data[1]=%x, data[2]=%x\n", */ + /* datap[0],datap[1],datap[2]); */ + if (ret < 0) { + dev_err(&urb->dev->dev, + "comedi: buffer underflow\n"); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_OVERFLOW; + } + /* transmit data to comedi */ + s->async->events |= COMEDI_CB_BLOCK; + comedi_event(this_usbduxsub->comedidev, s); + } + } + urb->transfer_buffer_length = SIZEOUTBUF; + urb->dev = this_usbduxsub->usbdev; + urb->status = 0; + if (this_usbduxsub->ao_cmd_running) { + if (this_usbduxsub->high_speed) { + /* uframes */ + urb->interval = 8; + } else { + /* frames */ + urb->interval = 1; + } + urb->number_of_packets = 1; + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = SIZEOUTBUF; + urb->iso_frame_desc[0].status = 0; + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(&urb->dev->dev, + "comedi_: ao urb resubm failed in int-cont. " + "ret=%d", ret); + if (ret == EL2NSYNC) + dev_err(&urb->dev->dev, + "buggy USB host controller or bug in " + "IRQ handling!\n"); + + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxsub->comedidev, s); + /* don't do an unlink here */ + usbdux_ao_stop(this_usbduxsub, 0); + } + } +} + +static int usbduxsub_start(struct usbduxsub *usbduxsub) +{ + int errcode = 0; + uint8_t local_transfer_buffer[16]; + + if (usbduxsub->probed) { + /* 7f92 to zero */ + local_transfer_buffer[0] = 0; + errcode = usb_control_msg(usbduxsub->usbdev, + /* create a pipe for a control transfer */ + usb_sndctrlpipe(usbduxsub->usbdev, 0), + /* bRequest, "Firmware" */ + USBDUXSUB_FIRMWARE, + /* bmRequestType */ + VENDOR_DIR_OUT, + /* Value */ + USBDUXSUB_CPUCS, + /* Index */ + 0x0000, + /* address of the transfer buffer */ + local_transfer_buffer, + /* Length */ + 1, + /* Timeout */ + EZTIMEOUT); + if (errcode < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: control msg failed (start)\n"); + return errcode; + } + } + return 0; +} + +static int usbduxsub_stop(struct usbduxsub *usbduxsub) +{ + int errcode = 0; + + uint8_t local_transfer_buffer[16]; + if (usbduxsub->probed) { + /* 7f92 to one */ + local_transfer_buffer[0] = 1; + errcode = usb_control_msg(usbduxsub->usbdev, + usb_sndctrlpipe(usbduxsub->usbdev, 0), + /* bRequest, "Firmware" */ + USBDUXSUB_FIRMWARE, + /* bmRequestType */ + VENDOR_DIR_OUT, + /* Value */ + USBDUXSUB_CPUCS, + /* Index */ + 0x0000, local_transfer_buffer, + /* Length */ + 1, + /* Timeout */ + EZTIMEOUT); + if (errcode < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: control msg failed (stop)\n"); + return errcode; + } + } + return 0; +} + +static int usbduxsub_upload(struct usbduxsub *usbduxsub, + uint8_t *local_transfer_buffer, + unsigned int startAddr, unsigned int len) +{ + int errcode; + + if (usbduxsub->probed) { + dev_dbg(&usbduxsub->interface->dev, + "comedi%d: usbdux: uploading %d bytes" + " to addr %d, first byte=%d.\n", + usbduxsub->comedidev->minor, len, + startAddr, local_transfer_buffer[0]); + errcode = usb_control_msg(usbduxsub->usbdev, + usb_sndctrlpipe(usbduxsub->usbdev, 0), + /* brequest, firmware */ + USBDUXSUB_FIRMWARE, + /* bmRequestType */ + VENDOR_DIR_OUT, + /* value */ + startAddr, + /* index */ + 0x0000, + /* our local safe buffer */ + local_transfer_buffer, + /* length */ + len, + /* timeout */ + EZTIMEOUT); + dev_dbg(&usbduxsub->interface->dev, + "comedi_: result=%d\n", errcode); + if (errcode < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: upload failed\n"); + return errcode; + } + } else { + /* no device on the bus for this index */ + return -EFAULT; + } + return 0; +} + +static int firmwareUpload(struct usbduxsub *usbduxsub, uint8_t *firmwareBinary, + int sizeFirmware) +{ + int ret; + + if (!firmwareBinary) + return 0; + + ret = usbduxsub_stop(usbduxsub); + if (ret < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: can not stop firmware\n"); + return ret; + } + ret = usbduxsub_upload(usbduxsub, firmwareBinary, 0, sizeFirmware); + if (ret < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: firmware upload failed\n"); + return ret; + } + ret = usbduxsub_start(usbduxsub); + if (ret < 0) { + dev_err(&usbduxsub->interface->dev, + "comedi_: can not start firmware\n"); + return ret; + } + return 0; +} + +static int usbduxsub_submit_InURBs(struct usbduxsub *usbduxsub) +{ + int i, errFlag; + + if (!usbduxsub) + return -EFAULT; + + /* Submit all URBs and start the transfer on the bus */ + for (i = 0; i < usbduxsub->numOfInBuffers; i++) { + /* in case of a resubmission after an unlink... */ + usbduxsub->urbIn[i]->interval = usbduxsub->ai_interval; + usbduxsub->urbIn[i]->context = usbduxsub->comedidev; + usbduxsub->urbIn[i]->dev = usbduxsub->usbdev; + usbduxsub->urbIn[i]->status = 0; + usbduxsub->urbIn[i]->transfer_flags = URB_ISO_ASAP; + dev_dbg(&usbduxsub->interface->dev, + "comedi%d: submitting in-urb[%d]: %p,%p intv=%d\n", + usbduxsub->comedidev->minor, i, + (usbduxsub->urbIn[i]->context), + (usbduxsub->urbIn[i]->dev), + (usbduxsub->urbIn[i]->interval)); + errFlag = usb_submit_urb(usbduxsub->urbIn[i], GFP_ATOMIC); + if (errFlag) { + dev_err(&usbduxsub->interface->dev, + "comedi_: ai: usb_submit_urb(%d) error %d\n", + i, errFlag); + return errFlag; + } + } + return 0; +} + +static int usbduxsub_submit_OutURBs(struct usbduxsub *usbduxsub) +{ + int i, errFlag; + + if (!usbduxsub) + return -EFAULT; + + for (i = 0; i < usbduxsub->numOfOutBuffers; i++) { + dev_dbg(&usbduxsub->interface->dev, + "comedi_: submitting out-urb[%d]\n", i); + /* in case of a resubmission after an unlink... */ + usbduxsub->urbOut[i]->context = usbduxsub->comedidev; + usbduxsub->urbOut[i]->dev = usbduxsub->usbdev; + usbduxsub->urbOut[i]->status = 0; + usbduxsub->urbOut[i]->transfer_flags = URB_ISO_ASAP; + errFlag = usb_submit_urb(usbduxsub->urbOut[i], GFP_ATOMIC); + if (errFlag) { + dev_err(&usbduxsub->interface->dev, + "comedi_: ao: usb_submit_urb(%d) error %d\n", + i, errFlag); + return errFlag; + } + } + return 0; +} + +static int usbdux_ai_cmdtest(comedi_device *dev, comedi_subdevice *s, + comedi_cmd *cmd) +{ + int err = 0, tmp, i; + unsigned int tmpTimer; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!(this_usbduxsub->probed)) + return -ENODEV; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_cmdtest\n", dev->minor); + + /* make sure triggers are valid */ + /* Only immediate triggers are allowed */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + /* trigger should happen timed */ + tmp = cmd->scan_begin_src; + /* start a new _scan_ with a timer */ + cmd->scan_begin_src &= TRIG_TIMER; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + /* scanning is continous */ + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_NOW; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + /* issue a trigger when scan is finished and start a new scan */ + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + /* trigger at the end of count events or not, stop condition or not */ + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique and mutually compatible + * note that mutual compatiblity is not an issue here + */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + if (this_usbduxsub->high_speed) { + /* + * In high speed mode microframes are possible. + * However, during one microframe we can roughly + * sample one channel. Thus, the more channels + * are in the channel list the more time we need. + */ + i = 1; + /* find a power of 2 for the number of channels */ + while (i < (cmd->chanlist_len)) + i = i * 2; + + if (cmd->scan_begin_arg < (1000000 / 8 * i)) { + cmd->scan_begin_arg = 1000000 / 8 * i; + err++; + } + /* now calc the real sampling rate with all the + * rounding errors */ + tmpTimer = + ((unsigned int)(cmd->scan_begin_arg / 125000)) * + 125000; + if (cmd->scan_begin_arg != tmpTimer) { + cmd->scan_begin_arg = tmpTimer; + err++; + } + } else { + /* full speed */ + /* 1kHz scans every USB frame */ + if (cmd->scan_begin_arg < 1000000) { + cmd->scan_begin_arg = 1000000; + err++; + } + /* + * calc the real sampling rate with the rounding errors + */ + tmpTimer = ((unsigned int)(cmd->scan_begin_arg / + 1000000)) * 1000000; + if (cmd->scan_begin_arg != tmpTimer) { + cmd->scan_begin_arg = tmpTimer; + err++; + } + } + } + /* the same argument */ + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + if (err) + return 3; + + return 0; +} + +/* + * creates the ADC command for the MAX1271 + * range is the range value from comedi + */ +static int8_t create_adc_command(unsigned int chan, int range) +{ + int8_t p = (range <= 1); + int8_t r = ((range % 2) == 0); + return (chan << 4) | ((p == 1) << 2) | ((r == 1) << 3); +} + +/* bulk transfers to usbdux */ + +#define SENDADCOMMANDS 0 +#define SENDDACOMMANDS 1 +#define SENDDIOCONFIGCOMMAND 2 +#define SENDDIOBITSCOMMAND 3 +#define SENDSINGLEAD 4 +#define READCOUNTERCOMMAND 5 +#define WRITECOUNTERCOMMAND 6 +#define SENDPWMON 7 +#define SENDPWMOFF 8 + +static int send_dux_commands(struct usbduxsub *this_usbduxsub, int cmd_type) +{ + int result, nsent; + + this_usbduxsub->dux_commands[0] = cmd_type; +#ifdef NOISY_DUX_DEBUGBUG + printk(KERN_DEBUG "comedi%d: usbdux: dux_commands: ", + this_usbduxsub->comedidev->minor); + for (result = 0; result < SIZEOFDUXBUFFER; result++) + printk(" %02x", this_usbduxsub->dux_commands[result]); + printk("\n"); +#endif + result = usb_bulk_msg(this_usbduxsub->usbdev, + usb_sndbulkpipe(this_usbduxsub->usbdev, + COMMAND_OUT_EP), + this_usbduxsub->dux_commands, SIZEOFDUXBUFFER, + &nsent, 10); + if (result < 0) + dev_err(&this_usbduxsub->interface->dev, "comedi%d: " + "could not transmit dux_command to the usb-device, " + "err=%d\n", this_usbduxsub->comedidev->minor, result); + + return result; +} + +static int receive_dux_commands(struct usbduxsub *this_usbduxsub, int command) +{ + int result = (-EFAULT); + int nrec; + int i; + + for (i = 0; i < RETRIES; i++) { + result = usb_bulk_msg(this_usbduxsub->usbdev, + usb_rcvbulkpipe(this_usbduxsub->usbdev, + COMMAND_IN_EP), + this_usbduxsub->insnBuffer, SIZEINSNBUF, + &nrec, 1); + if (result < 0) { + dev_err(&this_usbduxsub->interface->dev, "comedi%d: " + "insn: USB error %d while receiving DUX command" + "\n", this_usbduxsub->comedidev->minor, result); + return result; + } + if (le16_to_cpu(this_usbduxsub->insnBuffer[0]) == command) + return result; + } + /* this is only reached if the data has been requested a couple of + * times */ + dev_err(&this_usbduxsub->interface->dev, "comedi%d: insn: " + "wrong data returned from firmware: want cmd %d, got cmd %d.\n", + this_usbduxsub->comedidev->minor, command, + le16_to_cpu(this_usbduxsub->insnBuffer[0])); + return -EFAULT; +} + +static int usbdux_ai_inttrig(comedi_device *dev, comedi_subdevice *s, + unsigned int trignum) +{ + int ret; + struct usbduxsub *this_usbduxsub = dev->private; + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_inttrig\n", dev->minor); + + if (trignum != 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_inttrig: invalid trignum\n", + dev->minor); + up(&this_usbduxsub->sem); + return -EINVAL; + } + if (!(this_usbduxsub->ai_cmd_running)) { + this_usbduxsub->ai_cmd_running = 1; + ret = usbduxsub_submit_InURBs(this_usbduxsub); + if (ret < 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_inttrig: " + "urbSubmit: err=%d\n", dev->minor, ret); + this_usbduxsub->ai_cmd_running = 0; + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ai_inttrig but acqu is already running\n", + dev->minor); + } + up(&this_usbduxsub->sem); + return 1; +} + +static int usbdux_ai_cmd(comedi_device *dev, comedi_subdevice *s) +{ + comedi_cmd *cmd = &s->async->cmd; + unsigned int chan, range; + int i, ret; + struct usbduxsub *this_usbduxsub = dev->private; + int result; + + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ai_cmd\n", dev->minor); + + /* block other CPUs from starting an ai_cmd */ + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ai_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, "comedi%d: " + "ai_cmd not possible. Another ai_cmd is running.\n", + dev->minor); + up(&this_usbduxsub->sem); + return -EBUSY; + } + /* set current channel of the running aquisition to zero */ + s->async->cur_chan = 0; + + this_usbduxsub->dux_commands[1] = cmd->chanlist_len; + for (i = 0; i < cmd->chanlist_len; ++i) { + chan = CR_CHAN(cmd->chanlist[i]); + range = CR_RANGE(cmd->chanlist[i]); + if (i >= NUMCHANNELS) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: channel list too long\n", + dev->minor); + break; + } + this_usbduxsub->dux_commands[i + 2] = + create_adc_command(chan, range); + } + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi %d: sending commands to the usb device: size=%u\n", + dev->minor, NUMCHANNELS); + + result = send_dux_commands(this_usbduxsub, SENDADCOMMANDS); + if (result < 0) { + up(&this_usbduxsub->sem); + return result; + } + + if (this_usbduxsub->high_speed) { + /* + * every channel gets a time window of 125us. Thus, if we + * sample all 8 channels we need 1ms. If we sample only one + * channel we need only 125us + */ + this_usbduxsub->ai_interval = 1; + /* find a power of 2 for the interval */ + while ((this_usbduxsub->ai_interval) < (cmd->chanlist_len)) { + this_usbduxsub->ai_interval = + (this_usbduxsub->ai_interval) * 2; + } + this_usbduxsub->ai_timer = cmd->scan_begin_arg / (125000 * + (this_usbduxsub->ai_interval)); + } else { + /* interval always 1ms */ + this_usbduxsub->ai_interval = 1; + this_usbduxsub->ai_timer = cmd->scan_begin_arg / 1000000; + } + if (this_usbduxsub->ai_timer < 1) { + dev_err(&this_usbduxsub->interface->dev, "comedi%d: ai_cmd: " + "timer=%d, scan_begin_arg=%d. " + "Not properly tested by cmdtest?\n", dev->minor, + this_usbduxsub->ai_timer, cmd->scan_begin_arg); + up(&this_usbduxsub->sem); + return -EINVAL; + } + this_usbduxsub->ai_counter = this_usbduxsub->ai_timer; + + if (cmd->stop_src == TRIG_COUNT) { + /* data arrives as one packet */ + this_usbduxsub->ai_sample_count = cmd->stop_arg; + this_usbduxsub->ai_continous = 0; + } else { + /* continous aquisition */ + this_usbduxsub->ai_continous = 1; + this_usbduxsub->ai_sample_count = 0; + } + + if (cmd->start_src == TRIG_NOW) { + /* enable this acquisition operation */ + this_usbduxsub->ai_cmd_running = 1; + ret = usbduxsub_submit_InURBs(this_usbduxsub); + if (ret < 0) { + this_usbduxsub->ai_cmd_running = 0; + /* fixme: unlink here?? */ + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + /* don't enable the acquision operation */ + /* wait for an internal signal */ + s->async->inttrig = usbdux_ai_inttrig; + } + up(&this_usbduxsub->sem); + return 0; +} + +/* Mode 0 is used to get a single conversion on demand */ +static int usbdux_ai_insn_read(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int i; + lsampl_t one = 0; + int chan, range; + int err; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return 0; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ai_insn_read, insn->n=%d, insn->subdev=%d\n", + dev->minor, insn->n, insn->subdev); + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ai_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ai_insn_read not possible. " + "Async Command is running.\n", dev->minor); + up(&this_usbduxsub->sem); + return 0; + } + + /* sample one channel */ + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + /* set command for the first channel */ + this_usbduxsub->dux_commands[1] = create_adc_command(chan, range); + + /* adc commands */ + err = send_dux_commands(this_usbduxsub, SENDSINGLEAD); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + for (i = 0; i < insn->n; i++) { + err = receive_dux_commands(this_usbduxsub, SENDSINGLEAD); + if (err < 0) { + up(&this_usbduxsub->sem); + return 0; + } + one = le16_to_cpu(this_usbduxsub->insnBuffer[1]); + if (CR_RANGE(insn->chanspec) <= 1) + one = one ^ 0x800; + + data[i] = one; + } + up(&this_usbduxsub->sem); + return i; +} + +/************************************/ +/* analog out */ + +static int usbdux_ao_insn_read(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int i; + int chan = CR_CHAN(insn->chanspec); + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + for (i = 0; i < insn->n; i++) + data[i] = this_usbduxsub->outBuffer[chan]; + + up(&this_usbduxsub->sem); + return i; +} + +static int usbdux_ao_insn_write(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int i, err; + int chan = CR_CHAN(insn->chanspec); + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ao_insn_write\n", dev->minor); + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ao_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ao_insn_write: " + "ERROR: asynchronous ao_cmd is running\n", dev->minor); + up(&this_usbduxsub->sem); + return 0; + } + + for (i = 0; i < insn->n; i++) { + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ao_insn_write: data[chan=%d,i=%d]=%d\n", + dev->minor, chan, i, data[i]); + + /* number of channels: 1 */ + this_usbduxsub->dux_commands[1] = 1; + /* one 16 bit value */ + *((int16_t *) (this_usbduxsub->dux_commands + 2)) = + cpu_to_le16(data[i]); + this_usbduxsub->outBuffer[chan] = data[i]; + /* channel number */ + this_usbduxsub->dux_commands[4] = (chan << 6); + err = send_dux_commands(this_usbduxsub, SENDDACOMMANDS); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + } + up(&this_usbduxsub->sem); + + return i; +} + +static int usbdux_ao_inttrig(comedi_device *dev, comedi_subdevice *s, + unsigned int trignum) +{ + int ret; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (trignum != 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ao_inttrig: invalid trignum\n", + dev->minor); + return -EINVAL; + } + if (!(this_usbduxsub->ao_cmd_running)) { + this_usbduxsub->ao_cmd_running = 1; + ret = usbduxsub_submit_OutURBs(this_usbduxsub); + if (ret < 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ao_inttrig: submitURB: " + "err=%d\n", dev->minor, ret); + this_usbduxsub->ao_cmd_running = 0; + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: ao_inttrig but acqu is already running.\n", + dev->minor); + } + up(&this_usbduxsub->sem); + return 1; +} + +static int usbdux_ao_cmdtest(comedi_device *dev, comedi_subdevice *s, + comedi_cmd *cmd) +{ + int err = 0, tmp; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + if (!(this_usbduxsub->probed)) + return -ENODEV; + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: usbdux_ao_cmdtest\n", dev->minor); + + /* make sure triggers are valid */ + /* Only immediate triggers are allowed */ + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + /* trigger should happen timed */ + tmp = cmd->scan_begin_src; + /* just now we scan also in the high speed mode every frame */ + /* this is due to ehci driver limitations */ + if (0) { /* (this_usbduxsub->high_speed) */ + /* start immidiately a new scan */ + /* the sampling rate is set by the coversion rate */ + cmd->scan_begin_src &= TRIG_FOLLOW; + } else { + /* start a new scan (output at once) with a timer */ + cmd->scan_begin_src &= TRIG_TIMER; + } + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + /* scanning is continous */ + tmp = cmd->convert_src; + /* we always output at 1kHz just now all channels at once */ + if (0) { /* (this_usbduxsub->high_speed) */ + /* + * in usb-2.0 only one conversion it tranmitted but with 8kHz/n + */ + cmd->convert_src &= TRIG_TIMER; + } else { + /* all conversion events happen simultaneously with a rate of + * 1kHz/n */ + cmd->convert_src &= TRIG_NOW; + } + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + /* issue a trigger when scan is finished and start a new scan */ + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + /* trigger at the end of count events or not, stop condition or not */ + tmp = cmd->stop_src; + cmd->stop_src &= TRIG_COUNT | TRIG_NONE; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* + * step 2: make sure trigger sources are unique and mutually compatible + * note that mutual compatiblity is not an issue here + */ + if (cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT && + cmd->scan_begin_src != TRIG_TIMER) + err++; + if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (cmd->scan_begin_src == TRIG_FOLLOW) { + /* internal trigger */ + if (cmd->scan_begin_arg != 0) { + cmd->scan_begin_arg = 0; + err++; + } + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + /* timer */ + if (cmd->scan_begin_arg < 1000000) { + cmd->scan_begin_arg = 1000000; + err++; + } + } + /* not used now, is for later use */ + if (cmd->convert_src == TRIG_TIMER) { + if (cmd->convert_arg < 125000) { + cmd->convert_arg = 125000; + err++; + } + } + + /* the same argument */ + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->stop_src == TRIG_COUNT) { + /* any count is allowed */ + } else { + /* TRIG_NONE */ + if (cmd->stop_arg != 0) { + cmd->stop_arg = 0; + err++; + } + } + + dev_dbg(&this_usbduxsub->interface->dev, "comedi%d: err=%d, " + "scan_begin_src=%d, scan_begin_arg=%d, convert_src=%d, " + "convert_arg=%d\n", dev->minor, err, cmd->scan_begin_src, + cmd->scan_begin_arg, cmd->convert_src, cmd->convert_arg); + + if (err) + return 3; + + return 0; +} + +static int usbdux_ao_cmd(comedi_device *dev, comedi_subdevice *s) +{ + comedi_cmd *cmd = &s->async->cmd; + unsigned int chan, gain; + int i, ret; + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s\n", dev->minor, __func__); + + /* set current channel of the running aquisition to zero */ + s->async->cur_chan = 0; + for (i = 0; i < cmd->chanlist_len; ++i) { + chan = CR_CHAN(cmd->chanlist[i]); + gain = CR_RANGE(cmd->chanlist[i]); + if (i >= NUMOUTCHANNELS) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: %s: channel list too long\n", + dev->minor, __func__); + break; + } + this_usbduxsub->dac_commands[i] = (chan << 6); + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: dac command for ch %d is %x\n", + dev->minor, i, this_usbduxsub->dac_commands[i]); + } + + /* we count in steps of 1ms (125us) */ + /* 125us mode not used yet */ + if (0) { /* (this_usbduxsub->high_speed) */ + /* 125us */ + /* timing of the conversion itself: every 125 us */ + this_usbduxsub->ao_timer = cmd->convert_arg / 125000; + } else { + /* 1ms */ + /* timing of the scan: we get all channels at once */ + this_usbduxsub->ao_timer = cmd->scan_begin_arg / 1000000; + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: scan_begin_src=%d, scan_begin_arg=%d, " + "convert_src=%d, convert_arg=%d\n", dev->minor, + cmd->scan_begin_src, cmd->scan_begin_arg, + cmd->convert_src, cmd->convert_arg); + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: ao_timer=%d (ms)\n", + dev->minor, this_usbduxsub->ao_timer); + if (this_usbduxsub->ao_timer < 1) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: usbdux: ao_timer=%d, " + "scan_begin_arg=%d. " + "Not properly tested by cmdtest?\n", + dev->minor, this_usbduxsub->ao_timer, + cmd->scan_begin_arg); + up(&this_usbduxsub->sem); + return -EINVAL; + } + } + this_usbduxsub->ao_counter = this_usbduxsub->ao_timer; + + if (cmd->stop_src == TRIG_COUNT) { + /* not continous */ + /* counter */ + /* high speed also scans everything at once */ + if (0) { /* (this_usbduxsub->high_speed) */ + this_usbduxsub->ao_sample_count = + (cmd->stop_arg) * (cmd->scan_end_arg); + } else { + /* there's no scan as the scan has been */ + /* perf inside the FX2 */ + /* data arrives as one packet */ + this_usbduxsub->ao_sample_count = cmd->stop_arg; + } + this_usbduxsub->ao_continous = 0; + } else { + /* continous aquisition */ + this_usbduxsub->ao_continous = 1; + this_usbduxsub->ao_sample_count = 0; + } + + if (cmd->start_src == TRIG_NOW) { + /* enable this acquisition operation */ + this_usbduxsub->ao_cmd_running = 1; + ret = usbduxsub_submit_OutURBs(this_usbduxsub); + if (ret < 0) { + this_usbduxsub->ao_cmd_running = 0; + /* fixme: unlink here?? */ + up(&this_usbduxsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + /* submit the urbs later */ + /* wait for an internal signal */ + s->async->inttrig = usbdux_ao_inttrig; + } + + up(&this_usbduxsub->sem); + return 0; +} + +static int usbdux_dio_insn_config(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int chan = CR_CHAN(insn->chanspec); + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + s->io_bits |= 1 << chan; /* 1 means Out */ + break; + case INSN_CONFIG_DIO_INPUT: + s->io_bits &= ~(1 << chan); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (s-> + io_bits & (1 << chan)) ? COMEDI_OUTPUT : COMEDI_INPUT; + break; + default: + return -EINVAL; + break; + } + /* we don't tell the firmware here as it would take 8 frames */ + /* to submit the information. We do it in the insn_bits. */ + return insn->n; +} + +static int usbdux_dio_insn_bits(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + + struct usbduxsub *this_usbduxsub = dev->private; + int err; + + if (!this_usbduxsub) + return -EFAULT; + + + if (insn->n != 2) + return -EINVAL; + + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + + /* The insn data is a mask in data[0] and the new data + * in data[1], each channel cooresponding to a bit. */ + s->state &= ~data[0]; + s->state |= data[0] & data[1]; + this_usbduxsub->dux_commands[1] = s->io_bits; + this_usbduxsub->dux_commands[2] = s->state; + + /* This command also tells the firmware to return */ + /* the digital input lines */ + err = send_dux_commands(this_usbduxsub, SENDDIOBITSCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + err = receive_dux_commands(this_usbduxsub, SENDDIOBITSCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + data[1] = le16_to_cpu(this_usbduxsub->insnBuffer[1]); + up(&this_usbduxsub->sem); + return 2; +} + +/* reads the 4 counters, only two are used just now */ +static int usbdux_counter_read(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int chan = insn->chanspec; + int err; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + + err = send_dux_commands(this_usbduxsub, READCOUNTERCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + err = receive_dux_commands(this_usbduxsub, READCOUNTERCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + data[0] = le16_to_cpu(this_usbduxsub->insnBuffer[chan + 1]); + up(&this_usbduxsub->sem); + return 1; +} + +static int usbdux_counter_write(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int err; + + if (!this_usbduxsub) + return -EFAULT; + + down(&this_usbduxsub->sem); + + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + + this_usbduxsub->dux_commands[1] = insn->chanspec; + *((int16_t *) (this_usbduxsub->dux_commands + 2)) = cpu_to_le16(*data); + + err = send_dux_commands(this_usbduxsub, WRITECOUNTERCOMMAND); + if (err < 0) { + up(&this_usbduxsub->sem); + return err; + } + + up(&this_usbduxsub->sem); + + return 1; +} + +static int usbdux_counter_config(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + /* nothing to do so far */ + return 2; +} + +/***********************************/ +/* PWM */ + +static int usbduxsub_unlink_PwmURBs(struct usbduxsub *usbduxsub_tmp) +{ + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbPwm) { + if (usbduxsub_tmp->urbPwm) + usb_kill_urb(usbduxsub_tmp->urbPwm); + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi: unlinked PwmURB: res=%d\n", err); + } + return err; +} + +/* This cancels a running acquisition operation + * in any context. + */ +static int usbdux_pwm_stop(struct usbduxsub *this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) + return -EFAULT; + + dev_dbg(&this_usbduxsub->interface->dev, "comedi: %s\n", __func__); + if (do_unlink) + ret = usbduxsub_unlink_PwmURBs(this_usbduxsub); + + + this_usbduxsub->pwm_cmd_running = 0; + + return ret; +} + +/* force unlink - is called by comedi */ +static int usbdux_pwm_cancel(comedi_device *dev, comedi_subdevice *s) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int res = 0; + + /* unlink only if it is really running */ + res = usbdux_pwm_stop(this_usbduxsub, this_usbduxsub->pwm_cmd_running); + + dev_dbg(&this_usbduxsub->interface->dev, + "comedi %d: sending pwm off command to the usb device.\n", + dev->minor); + res = send_dux_commands(this_usbduxsub, SENDPWMOFF); + if (res < 0) + return res; + + return res; +} + +static void usbduxsub_pwm_irq(struct urb *urb) +{ + int ret; + struct usbduxsub *this_usbduxsub; + comedi_device *this_comedidev; + comedi_subdevice *s; + + /* printk(KERN_DEBUG "PWM: IRQ\n"); */ + + /* the context variable points to the subdevice */ + this_comedidev = urb->context; + /* the private structure of the subdevice is struct usbduxsub */ + this_usbduxsub = this_comedidev->private; + + s = this_comedidev->subdevices + SUBDEV_DA; + + switch (urb->status) { + case 0: + /* success */ + break; + + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + /* + * after an unlink command, unplug, ... etc + * no unlink needed here. Already shutting down. + */ + if (this_usbduxsub->pwm_cmd_running) + usbdux_pwm_stop(this_usbduxsub, 0); + + return; + + default: + /* a real error */ + if (this_usbduxsub->pwm_cmd_running) { + dev_err(&this_usbduxsub->interface->dev, + "comedi_: Non-zero urb status received in " + "pwm intr context: %d\n", urb->status); + usbdux_pwm_stop(this_usbduxsub, 0); + } + return; + } + + /* are we actually running? */ + if (!(this_usbduxsub->pwm_cmd_running)) + return; + + urb->transfer_buffer_length = this_usbduxsub->sizePwmBuf; + urb->dev = this_usbduxsub->usbdev; + urb->status = 0; + if (this_usbduxsub->pwm_cmd_running) { + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) { + dev_err(&this_usbduxsub->interface->dev, + "comedi_: pwm urb resubm failed in int-cont. " + "ret=%d", ret); + if (ret == EL2NSYNC) + dev_err(&this_usbduxsub->interface->dev, + "buggy USB host controller or bug in " + "IRQ handling!\n"); + + /* don't do an unlink here */ + usbdux_pwm_stop(this_usbduxsub, 0); + } + } +} + +static int usbduxsub_submit_PwmURBs(struct usbduxsub *usbduxsub) +{ + int errFlag; + + if (!usbduxsub) + return -EFAULT; + + dev_dbg(&usbduxsub->interface->dev, "comedi_: submitting pwm-urb\n"); + + /* in case of a resubmission after an unlink... */ + usb_fill_bulk_urb(usbduxsub->urbPwm, + usbduxsub->usbdev, + usb_sndbulkpipe(usbduxsub->usbdev, PWM_EP), + usbduxsub->urbPwm->transfer_buffer, + usbduxsub->sizePwmBuf, usbduxsub_pwm_irq, usbduxsub->comedidev); + + errFlag = usb_submit_urb(usbduxsub->urbPwm, GFP_ATOMIC); + if (errFlag) { + dev_err(&usbduxsub->interface->dev, + "comedi_: usbdux: pwm: usb_submit_urb error %d\n", + errFlag); + return errFlag; + } + return 0; +} + +static int usbdux_pwm_period(comedi_device *dev, comedi_subdevice *s, + lsampl_t period) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int fx2delay = 255; + + if (period < MIN_PWM_PERIOD) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: illegal period setting for pwm.\n", + dev->minor); + return -EAGAIN; + } else { + fx2delay = period / ((int)(6*512*(1.0/0.033))) - 6; + if (fx2delay > 255) { + dev_err(&this_usbduxsub->interface->dev, + "comedi%d: period %d for pwm is too low.\n", + dev->minor, period); + return -EAGAIN; + } + } + this_usbduxsub->pwmDelay = fx2delay; + this_usbduxsub->pwmPeriod = period; + dev_dbg(&this_usbduxsub->interface->dev, "%s: frequ=%d, period=%d\n", + __func__, period, fx2delay); + return 0; +} + +/* is called from insn so there's no need to do all the sanity checks */ +static int usbdux_pwm_start(comedi_device *dev, comedi_subdevice *s) +{ + int ret, i; + struct usbduxsub *this_usbduxsub = dev->private; + + dev_dbg(&this_usbduxsub->interface->dev, "comedi%d: %s\n", + dev->minor, __func__); + + if (this_usbduxsub->pwm_cmd_running) { + /* already running */ + return 0; + } + + this_usbduxsub->dux_commands[1] = ((int8_t) this_usbduxsub->pwmDelay); + ret = send_dux_commands(this_usbduxsub, SENDPWMON); + if (ret < 0) + return ret; + + /* initalise the buffer */ + for (i = 0; i < this_usbduxsub->sizePwmBuf; i++) + ((char *)(this_usbduxsub->urbPwm->transfer_buffer))[i] = 0; + + this_usbduxsub->pwm_cmd_running = 1; + ret = usbduxsub_submit_PwmURBs(this_usbduxsub); + if (ret < 0) { + this_usbduxsub->pwm_cmd_running = 0; + return ret; + } + return 0; +} + +/* generates the bit pattern for PWM with the optional sign bit */ +static int usbdux_pwm_pattern(comedi_device *dev, comedi_subdevice *s, + int channel, lsampl_t value, lsampl_t sign) +{ + struct usbduxsub *this_usbduxsub = dev->private; + int i, szbuf; + char *pBuf; + char pwm_mask; + char sgn_mask; + char c; + + if (!this_usbduxsub) + return -EFAULT; + + /* this is the DIO bit which carries the PWM data */ + pwm_mask = (1 << channel); + /* this is the DIO bit which carries the optional direction bit */ + sgn_mask = (16 << channel); + /* this is the buffer which will be filled with the with bit */ + /* pattern for one period */ + szbuf = this_usbduxsub->sizePwmBuf; + pBuf = (char *)(this_usbduxsub->urbPwm->transfer_buffer); + for (i = 0; i < szbuf; i++) { + c = *pBuf; + /* reset bits */ + c = c & (~pwm_mask); + /* set the bit as long as the index is lower than the value */ + if (i < value) + c = c | pwm_mask; + /* set the optional sign bit for a relay */ + if (!sign) { + /* positive value */ + c = c & (~sgn_mask); + } else { + /* negative value */ + c = c | sgn_mask; + } + *(pBuf++) = c; + } + return 1; +} + +static int usbdux_pwm_write(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + + if (!this_usbduxsub) + return -EFAULT; + + if ((insn->n) != 1) { + /* + * doesn't make sense to have more than one value here because + * it would just overwrite the PWM buffer a couple of times + */ + return -EINVAL; + } + + /* + * the sign is set via a special INSN only, this gives us 8 bits for + * normal operation + * relay sign 0 by default + */ + return usbdux_pwm_pattern(dev, s, CR_CHAN(insn->chanspec), + data[0], 0); +} + +static int usbdux_pwm_read(comedi_device *x1, comedi_subdevice *x2, + comedi_insn *x3, lsampl_t *x4) +{ + /* not needed */ + return -EINVAL; +}; + +/* switches on/off PWM */ +static int usbdux_pwm_config(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + struct usbduxsub *this_usbduxsub = dev->private; + switch (data[0]) { + case INSN_CONFIG_ARM: + /* switch it on */ + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s: pwm on\n", dev->minor, __func__); + /* + * if not zero the PWM is limited to a certain time which is + * not supported here + */ + if (data[1] != 0) + return -EINVAL; + return usbdux_pwm_start(dev, s); + case INSN_CONFIG_DISARM: + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s: pwm off\n", dev->minor, __func__); + return usbdux_pwm_cancel(dev, s); + case INSN_CONFIG_GET_PWM_STATUS: + /* + * to check if the USB transmission has failed or in case PWM + * was limited to n cycles to check if it has terminated + */ + data[1] = this_usbduxsub->pwm_cmd_running; + return 0; + case INSN_CONFIG_PWM_SET_PERIOD: + dev_dbg(&this_usbduxsub->interface->dev, + "comedi%d: %s: setting period\n", dev->minor, __func__); + return usbdux_pwm_period(dev, s, data[1]); + case INSN_CONFIG_PWM_GET_PERIOD: + data[1] = this_usbduxsub->pwmPeriod; + return 0; + case INSN_CONFIG_PWM_SET_H_BRIDGE: + /* value in the first byte and the sign in the second for a + relay */ + return usbdux_pwm_pattern(dev, s, + /* the channel number */ + CR_CHAN(insn->chanspec), + /* actual PWM data */ + data[1], + /* just a sign */ + (data[2] != 0)); + case INSN_CONFIG_PWM_GET_H_BRIDGE: + /* values are not kept in this driver, nothing to return here */ + return -EINVAL; + } + return -EINVAL; +} + +/* end of PWM */ +/*****************************************************************/ + +static void tidy_up(struct usbduxsub *usbduxsub_tmp) +{ + int i; + + if (!usbduxsub_tmp) + return; + dev_dbg(&usbduxsub_tmp->interface->dev, "comedi_: tiding up\n"); + + /* shows the usb subsystem that the driver is down */ + if (usbduxsub_tmp->interface) + usb_set_intfdata(usbduxsub_tmp->interface, NULL); + + usbduxsub_tmp->probed = 0; + + if (usbduxsub_tmp->urbIn) { + if (usbduxsub_tmp->ai_cmd_running) { + usbduxsub_tmp->ai_cmd_running = 0; + usbduxsub_unlink_InURBs(usbduxsub_tmp); + } + for (i = 0; i < usbduxsub_tmp->numOfInBuffers; i++) { + kfree(usbduxsub_tmp->urbIn[i]->transfer_buffer); + usbduxsub_tmp->urbIn[i]->transfer_buffer = NULL; + usb_kill_urb(usbduxsub_tmp->urbIn[i]); + usb_free_urb(usbduxsub_tmp->urbIn[i]); + usbduxsub_tmp->urbIn[i] = NULL; + } + kfree(usbduxsub_tmp->urbIn); + usbduxsub_tmp->urbIn = NULL; + } + if (usbduxsub_tmp->urbOut) { + if (usbduxsub_tmp->ao_cmd_running) { + usbduxsub_tmp->ao_cmd_running = 0; + usbduxsub_unlink_OutURBs(usbduxsub_tmp); + } + for (i = 0; i < usbduxsub_tmp->numOfOutBuffers; i++) { + if (usbduxsub_tmp->urbOut[i]->transfer_buffer) { + kfree(usbduxsub_tmp->urbOut[i]-> + transfer_buffer); + usbduxsub_tmp->urbOut[i]->transfer_buffer = + NULL; + } + if (usbduxsub_tmp->urbOut[i]) { + usb_kill_urb(usbduxsub_tmp->urbOut[i]); + usb_free_urb(usbduxsub_tmp->urbOut[i]); + usbduxsub_tmp->urbOut[i] = NULL; + } + } + kfree(usbduxsub_tmp->urbOut); + usbduxsub_tmp->urbOut = NULL; + } + if (usbduxsub_tmp->urbPwm) { + if (usbduxsub_tmp->pwm_cmd_running) { + usbduxsub_tmp->pwm_cmd_running = 0; + usbduxsub_unlink_PwmURBs(usbduxsub_tmp); + } + kfree(usbduxsub_tmp->urbPwm->transfer_buffer); + usbduxsub_tmp->urbPwm->transfer_buffer = NULL; + usb_kill_urb(usbduxsub_tmp->urbPwm); + usb_free_urb(usbduxsub_tmp->urbPwm); + usbduxsub_tmp->urbPwm = NULL; + } + kfree(usbduxsub_tmp->inBuffer); + usbduxsub_tmp->inBuffer = NULL; + kfree(usbduxsub_tmp->insnBuffer); + usbduxsub_tmp->insnBuffer = NULL; + kfree(usbduxsub_tmp->inBuffer); + usbduxsub_tmp->inBuffer = NULL; + kfree(usbduxsub_tmp->dac_commands); + usbduxsub_tmp->dac_commands = NULL; + kfree(usbduxsub_tmp->dux_commands); + usbduxsub_tmp->dux_commands = NULL; + usbduxsub_tmp->ai_cmd_running = 0; + usbduxsub_tmp->ao_cmd_running = 0; + usbduxsub_tmp->pwm_cmd_running = 0; +} + +static unsigned hex2unsigned(char *h) +{ + unsigned hi, lo; + + if (h[0] > '9') + hi = h[0] - 'A' + 0x0a; + else + hi = h[0] - '0'; + + if (h[1] > '9') + lo = h[1] - 'A' + 0x0a; + else + lo = h[1] - '0'; + + return hi * 0x10 + lo; +} + +/* for FX2 */ +#define FIRMWARE_MAX_LEN 0x2000 + +/* taken from David Brownell's fxload and adjusted for this driver */ +static int read_firmware(struct usbduxsub *usbduxsub, void *firmwarePtr, + long size) +{ + struct device *dev = &usbduxsub->interface->dev; + int i = 0; + unsigned char *fp = (char *)firmwarePtr; + unsigned char *firmwareBinary = NULL; + int res = 0; + int maxAddr = 0; + + firmwareBinary = kzalloc(FIRMWARE_MAX_LEN, GFP_KERNEL); + if (!firmwareBinary) { + dev_err(dev, "comedi_: mem alloc for firmware failed\n"); + return -ENOMEM; + } + + for (;;) { + char buf[256], *cp; + char type; + int len; + int idx, off; + int j = 0; + + /* get one line */ + while ((i < size) && (fp[i] != 13) && (fp[i] != 10)) { + buf[j] = fp[i]; + i++; + j++; + if (j >= sizeof(buf)) { + dev_err(dev, "comedi_: bogus firmware file!\n"); + return -1; + } + } + /* get rid of LF/CR/... */ + while ((i < size) && ((fp[i] == 13) || (fp[i] == 10) + || (fp[i] == 0))) { + i++; + } + + buf[j] = 0; + /* dev_dbg(dev, "comedi_: buf=%s\n", buf); */ + + /* + * EXTENSION: + * "# comment-till-end-of-line", for copyrights etc + */ + if (buf[0] == '#') + continue; + + if (buf[0] != ':') { + dev_err(dev, "comedi_: upload: not an ihex record: %s", + buf); + return -EFAULT; + } + + /* Read the length field (up to 16 bytes) */ + len = hex2unsigned(buf + 1); + + /* Read the target offset */ + off = (hex2unsigned(buf + 3) * 0x0100) + hex2unsigned(buf + 5); + + if ((off + len) > maxAddr) + maxAddr = off + len; + + + if (maxAddr >= FIRMWARE_MAX_LEN) { + dev_err(dev, "comedi_: firmware upload goes " + "beyond FX2 RAM boundaries.\n"); + return -EFAULT; + } + /* dev_dbg(dev, "comedi_: off=%x, len=%x:\n", off, len); */ + + /* Read the record type */ + type = hex2unsigned(buf + 7); + + /* If this is an EOF record, then make it so. */ + if (type == 1) + break; + + + if (type != 0) { + dev_err(dev, "comedi_: unsupported record type: %u\n", + type); + return -EFAULT; + } + + for (idx = 0, cp = buf + 9; idx < len; idx += 1, cp += 2) { + firmwareBinary[idx + off] = hex2unsigned(cp); + /*printk("%02x ",firmwareBinary[idx+off]); */ + } + /*printk("\n"); */ + + if (i >= size) { + dev_err(dev, "comedi_: unexpected end of hex file\n"); + break; + } + + } + res = firmwareUpload(usbduxsub, firmwareBinary, maxAddr + 1); + kfree(firmwareBinary); + return res; +} + +/* allocate memory for the urbs and initialise them */ +static int usbduxsub_probe(struct usb_interface *uinterf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(uinterf); + struct device *dev = &uinterf->dev; + int i; + int index; + + dev_dbg(dev, "comedi_: usbdux_: " + "finding a free structure for the usb-device\n"); + + down(&start_stop_sem); + /* look for a free place in the usbdux array */ + index = -1; + for (i = 0; i < NUMUSBDUX; i++) { + if (!(usbduxsub[i].probed)) { + index = i; + break; + } + } + + /* no more space */ + if (index == -1) { + dev_err(dev, "Too many usbdux-devices connected.\n"); + up(&start_stop_sem); + return -EMFILE; + } + dev_dbg(dev, "comedi_: usbdux: " + "usbduxsub[%d] is ready to connect to comedi.\n", index); + + init_MUTEX(&(usbduxsub[index].sem)); + /* save a pointer to the usb device */ + usbduxsub[index].usbdev = udev; + + /* 2.6: save the interface itself */ + usbduxsub[index].interface = uinterf; + /* get the interface number from the interface */ + usbduxsub[index].ifnum = uinterf->altsetting->desc.bInterfaceNumber; + /* hand the private data over to the usb subsystem */ + /* will be needed for disconnect */ + usb_set_intfdata(uinterf, &(usbduxsub[index])); + + dev_dbg(dev, "comedi_: usbdux: ifnum=%d\n", usbduxsub[index].ifnum); + + /* test if it is high speed (USB 2.0) */ + usbduxsub[index].high_speed = + (usbduxsub[index].usbdev->speed == USB_SPEED_HIGH); + + /* create space for the commands of the DA converter */ + usbduxsub[index].dac_commands = kzalloc(NUMOUTCHANNELS, GFP_KERNEL); + if (!usbduxsub[index].dac_commands) { + dev_err(dev, "comedi_: usbdux: " + "error alloc space for dac commands\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space for the commands going to the usb device */ + usbduxsub[index].dux_commands = kzalloc(SIZEOFDUXBUFFER, GFP_KERNEL); + if (!usbduxsub[index].dux_commands) { + dev_err(dev, "comedi_: usbdux: " + "error alloc space for dac commands\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space for the in buffer and set it to zero */ + usbduxsub[index].inBuffer = kzalloc(SIZEINBUF, GFP_KERNEL); + if (!(usbduxsub[index].inBuffer)) { + dev_err(dev, "comedi_: usbdux: " + "could not alloc space for inBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space of the instruction buffer */ + usbduxsub[index].insnBuffer = kzalloc(SIZEINSNBUF, GFP_KERNEL); + if (!(usbduxsub[index].insnBuffer)) { + dev_err(dev, "comedi_: usbdux: " + "could not alloc space for insnBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* create space for the outbuffer */ + usbduxsub[index].outBuffer = kzalloc(SIZEOUTBUF, GFP_KERNEL); + if (!(usbduxsub[index].outBuffer)) { + dev_err(dev, "comedi_: usbdux: " + "could not alloc space for outBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + /* setting to alternate setting 3: enabling iso ep and bulk ep. */ + i = usb_set_interface(usbduxsub[index].usbdev, + usbduxsub[index].ifnum, 3); + if (i < 0) { + dev_err(dev, "comedi_: usbdux%d: " + "could not set alternate setting 3 in high speed.\n", + index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENODEV; + } + if (usbduxsub[index].high_speed) + usbduxsub[index].numOfInBuffers = NUMOFINBUFFERSHIGH; + else + usbduxsub[index].numOfInBuffers = NUMOFINBUFFERSFULL; + + usbduxsub[index].urbIn = + kzalloc(sizeof(struct urb *) * usbduxsub[index].numOfInBuffers, + GFP_KERNEL); + if (!(usbduxsub[index].urbIn)) { + dev_err(dev, "comedi_: usbdux: Could not alloc. urbIn array\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + for (i = 0; i < usbduxsub[index].numOfInBuffers; i++) { + /* one frame: 1ms */ + usbduxsub[index].urbIn[i] = usb_alloc_urb(1, GFP_KERNEL); + if (usbduxsub[index].urbIn[i] == NULL) { + dev_err(dev, "comedi_: usbdux%d: " + "Could not alloc. urb(%d)\n", index, i); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbIn[i]->dev = usbduxsub[index].usbdev; + /* will be filled later with a pointer to the comedi-device */ + /* and ONLY then the urb should be submitted */ + usbduxsub[index].urbIn[i]->context = NULL; + usbduxsub[index].urbIn[i]->pipe = + usb_rcvisocpipe(usbduxsub[index].usbdev, ISOINEP); + usbduxsub[index].urbIn[i]->transfer_flags = URB_ISO_ASAP; + usbduxsub[index].urbIn[i]->transfer_buffer = + kzalloc(SIZEINBUF, GFP_KERNEL); + if (!(usbduxsub[index].urbIn[i]->transfer_buffer)) { + dev_err(dev, "comedi_: usbdux%d: " + "could not alloc. transb.\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbIn[i]->complete = usbduxsub_ai_IsocIrq; + usbduxsub[index].urbIn[i]->number_of_packets = 1; + usbduxsub[index].urbIn[i]->transfer_buffer_length = SIZEINBUF; + usbduxsub[index].urbIn[i]->iso_frame_desc[0].offset = 0; + usbduxsub[index].urbIn[i]->iso_frame_desc[0].length = SIZEINBUF; + } + + /* out */ + if (usbduxsub[index].high_speed) + usbduxsub[index].numOfOutBuffers = NUMOFOUTBUFFERSHIGH; + else + usbduxsub[index].numOfOutBuffers = NUMOFOUTBUFFERSFULL; + + usbduxsub[index].urbOut = + kzalloc(sizeof(struct urb *) * usbduxsub[index].numOfOutBuffers, + GFP_KERNEL); + if (!(usbduxsub[index].urbOut)) { + dev_err(dev, "comedi_: usbdux: " + "Could not alloc. urbOut array\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + for (i = 0; i < usbduxsub[index].numOfOutBuffers; i++) { + /* one frame: 1ms */ + usbduxsub[index].urbOut[i] = usb_alloc_urb(1, GFP_KERNEL); + if (usbduxsub[index].urbOut[i] == NULL) { + dev_err(dev, "comedi_: usbdux%d: " + "Could not alloc. urb(%d)\n", index, i); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbOut[i]->dev = usbduxsub[index].usbdev; + /* will be filled later with a pointer to the comedi-device */ + /* and ONLY then the urb should be submitted */ + usbduxsub[index].urbOut[i]->context = NULL; + usbduxsub[index].urbOut[i]->pipe = + usb_sndisocpipe(usbduxsub[index].usbdev, ISOOUTEP); + usbduxsub[index].urbOut[i]->transfer_flags = URB_ISO_ASAP; + usbduxsub[index].urbOut[i]->transfer_buffer = + kzalloc(SIZEOUTBUF, GFP_KERNEL); + if (!(usbduxsub[index].urbOut[i]->transfer_buffer)) { + dev_err(dev, "comedi_: usbdux%d: " + "could not alloc. transb.\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbOut[i]->complete = usbduxsub_ao_IsocIrq; + usbduxsub[index].urbOut[i]->number_of_packets = 1; + usbduxsub[index].urbOut[i]->transfer_buffer_length = SIZEOUTBUF; + usbduxsub[index].urbOut[i]->iso_frame_desc[0].offset = 0; + usbduxsub[index].urbOut[i]->iso_frame_desc[0].length = + SIZEOUTBUF; + if (usbduxsub[index].high_speed) { + /* uframes */ + usbduxsub[index].urbOut[i]->interval = 8; + } else { + /* frames */ + usbduxsub[index].urbOut[i]->interval = 1; + } + } + + /* pwm */ + if (usbduxsub[index].high_speed) { + /* max bulk ep size in high speed */ + usbduxsub[index].sizePwmBuf = 512; + usbduxsub[index].urbPwm = usb_alloc_urb(0, GFP_KERNEL); + if (usbduxsub[index].urbPwm == NULL) { + dev_err(dev, "comedi_: usbdux%d: " + "Could not alloc. pwm urb\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxsub[index].urbPwm->transfer_buffer = + kzalloc(usbduxsub[index].sizePwmBuf, GFP_KERNEL); + if (!(usbduxsub[index].urbPwm->transfer_buffer)) { + dev_err(dev, "comedi_: usbdux%d: " + "could not alloc. transb. for pwm\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + } else { + usbduxsub[index].urbPwm = NULL; + usbduxsub[index].sizePwmBuf = 0; + } + + usbduxsub[index].ai_cmd_running = 0; + usbduxsub[index].ao_cmd_running = 0; + usbduxsub[index].pwm_cmd_running = 0; + + /* we've reached the bottom of the function */ + usbduxsub[index].probed = 1; + up(&start_stop_sem); + dev_info(dev, "comedi_: usbdux%d " + "has been successfully initialised.\n", index); + /* success */ + return 0; +} + +static void usbduxsub_disconnect(struct usb_interface *intf) +{ + struct usbduxsub *usbduxsub_tmp = usb_get_intfdata(intf); + struct usb_device *udev = interface_to_usbdev(intf); + + if (!usbduxsub_tmp) { + dev_err(&intf->dev, + "comedi_: disconnect called with null pointer.\n"); + return; + } + if (usbduxsub_tmp->usbdev != udev) { + dev_err(&intf->dev, + "comedi_: BUG! called with wrong ptr!!!\n"); + return; + } + down(&start_stop_sem); + down(&usbduxsub_tmp->sem); + tidy_up(usbduxsub_tmp); + up(&usbduxsub_tmp->sem); + up(&start_stop_sem); + dev_dbg(&intf->dev, "comedi_: disconnected from the usb\n"); +} + +/* is called when comedi-config is called */ +static int usbdux_attach(comedi_device *dev, comedi_devconfig *it) +{ + int ret; + int index; + int i; + struct usbduxsub *udev; + + comedi_subdevice *s = NULL; + dev->private = NULL; + + down(&start_stop_sem); + /* find a valid device which has been detected by the probe function of + * the usb */ + index = -1; + for (i = 0; i < NUMUSBDUX; i++) { + if ((usbduxsub[i].probed) && (!usbduxsub[i].attached)) { + index = i; + break; + } + } + + if (index < 0) { + printk(KERN_ERR "comedi%d: usbdux: error: attach failed, no " + "usbdux devs connected to the usb bus.\n", dev->minor); + up(&start_stop_sem); + return -ENODEV; + } + + udev = &usbduxsub[index]; + down(&udev->sem); + /* pointer back to the corresponding comedi device */ + udev->comedidev = dev; + + /* trying to upload the firmware into the chip */ + if (comedi_aux_data(it->options, 0) && + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { + read_firmware(udev, comedi_aux_data(it->options, 0), + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]); + } + + dev->board_name = BOARDNAME; + + /* set number of subdevices */ + if (udev->high_speed) { + /* with pwm */ + dev->n_subdevices = 5; + } else { + /* without pwm */ + dev->n_subdevices = 4; + } + + /* allocate space for the subdevices */ + ret = alloc_subdevices(dev, dev->n_subdevices); + if (ret < 0) { + dev_err(&udev->interface->dev, + "comedi%d: error alloc space for subdev\n", dev->minor); + up(&start_stop_sem); + return ret; + } + + dev_info(&udev->interface->dev, + "comedi%d: usb-device %d is attached to comedi.\n", + dev->minor, index); + /* private structure is also simply the usb-structure */ + dev->private = udev; + + /* the first subdevice is the A/D converter */ + s = dev->subdevices + SUBDEV_AD; + /* the URBs get the comedi subdevice */ + /* which is responsible for reading */ + /* this is the subdevice which reads data */ + dev->read_subdev = s; + /* the subdevice receives as private structure the */ + /* usb-structure */ + s->private = NULL; + /* analog input */ + s->type = COMEDI_SUBD_AI; + /* readable and ref is to ground */ + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + /* 8 channels */ + s->n_chan = 8; + /* length of the channellist */ + s->len_chanlist = 8; + /* callback functions */ + s->insn_read = usbdux_ai_insn_read; + s->do_cmdtest = usbdux_ai_cmdtest; + s->do_cmd = usbdux_ai_cmd; + s->cancel = usbdux_ai_cancel; + /* max value from the A/D converter (12bit) */ + s->maxdata = 0xfff; + /* range table to convert to physical units */ + s->range_table = (&range_usbdux_ai_range); + + /* analog out */ + s = dev->subdevices + SUBDEV_DA; + /* analog out */ + s->type = COMEDI_SUBD_AO; + /* backward pointer */ + dev->write_subdev = s; + /* the subdevice receives as private structure the */ + /* usb-structure */ + s->private = NULL; + /* are writable */ + s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_CMD_WRITE; + /* 4 channels */ + s->n_chan = 4; + /* length of the channellist */ + s->len_chanlist = 4; + /* 12 bit resolution */ + s->maxdata = 0x0fff; + /* bipolar range */ + s->range_table = (&range_usbdux_ao_range); + /* callback */ + s->do_cmdtest = usbdux_ao_cmdtest; + s->do_cmd = usbdux_ao_cmd; + s->cancel = usbdux_ao_cancel; + s->insn_read = usbdux_ao_insn_read; + s->insn_write = usbdux_ao_insn_write; + + /* digital I/O */ + s = dev->subdevices + SUBDEV_DIO; + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = 8; + s->maxdata = 1; + s->range_table = (&range_digital); + s->insn_bits = usbdux_dio_insn_bits; + s->insn_config = usbdux_dio_insn_config; + /* we don't use it */ + s->private = NULL; + + /* counter */ + s = dev->subdevices + SUBDEV_COUNTER; + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_WRITABLE | SDF_READABLE; + s->n_chan = 4; + s->maxdata = 0xFFFF; + s->insn_read = usbdux_counter_read; + s->insn_write = usbdux_counter_write; + s->insn_config = usbdux_counter_config; + + if (udev->high_speed) { + /* timer / pwm */ + s = dev->subdevices + SUBDEV_PWM; + s->type = COMEDI_SUBD_PWM; + s->subdev_flags = SDF_WRITABLE | SDF_PWM_HBRIDGE; + s->n_chan = 8; + /* this defines the max duty cycle resolution */ + s->maxdata = udev->sizePwmBuf; + s->insn_write = usbdux_pwm_write; + s->insn_read = usbdux_pwm_read; + s->insn_config = usbdux_pwm_config; + usbdux_pwm_period(dev, s, PWM_DEFAULT_PERIOD); + } + /* finally decide that it's attached */ + udev->attached = 1; + + up(&udev->sem); + + up(&start_stop_sem); + + dev_info(&udev->interface->dev, "comedi%d: attached to usbdux.\n", + dev->minor); + + return 0; +} + +static int usbdux_detach(comedi_device *dev) +{ + struct usbduxsub *usbduxsub_tmp; + + if (!dev) { + printk(KERN_ERR + "comedi?: usbdux: detach without dev variable...\n"); + return -EFAULT; + } + + usbduxsub_tmp = dev->private; + if (!usbduxsub_tmp) { + printk(KERN_ERR + "comedi?: usbdux: detach without ptr to usbduxsub[]\n"); + return -EFAULT; + } + + dev_dbg(&usbduxsub_tmp->interface->dev, "comedi%d: detach usb device\n", + dev->minor); + + down(&usbduxsub_tmp->sem); + /* Don't allow detach to free the private structure */ + /* It's one entry of of usbduxsub[] */ + dev->private = NULL; + usbduxsub_tmp->attached = 0; + usbduxsub_tmp->comedidev = NULL; + dev_dbg(&usbduxsub_tmp->interface->dev, + "comedi%d: detach: successfully removed\n", dev->minor); + up(&usbduxsub_tmp->sem); + return 0; +} + +/* main driver struct */ +static comedi_driver driver_usbdux = { + .driver_name = "usbdux", + .module = THIS_MODULE, + .attach = usbdux_attach, + .detach = usbdux_detach, +}; + +static void init_usb_devices(void) +{ + int index; + + /* all devices entries are invalid to begin with */ + /* they will become valid by the probe function */ + /* and then finally by the attach-function */ + for (index = 0; index < NUMUSBDUX; index++) { + memset(&(usbduxsub[index]), 0x00, sizeof(usbduxsub[index])); + init_MUTEX(&(usbduxsub[index].sem)); + } +} + +/* Table with the USB-devices: just now only testing IDs */ +static struct usb_device_id usbduxsub_table[] = { + {USB_DEVICE(0x13d8, 0x0001) }, + {USB_DEVICE(0x13d8, 0x0002) }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usbduxsub_table); + +/* The usbduxsub-driver */ +static struct usb_driver usbduxsub_driver = { + .name = BOARDNAME, + .probe = usbduxsub_probe, + .disconnect = usbduxsub_disconnect, + .id_table = usbduxsub_table, +}; + +/* Can't use the nice macro as I have also to initialise the USB */ +/* subsystem: */ +/* registering the usb-system _and_ the comedi-driver */ +static int init_usbdux(void) +{ + printk(KERN_INFO KBUILD_MODNAME ": " + DRIVER_VERSION ":" DRIVER_DESC "\n"); + init_usb_devices(); + usb_register(&usbduxsub_driver); + comedi_driver_register(&driver_usbdux); + return 0; +} + +/* deregistering the comedi driver and the usb-subsystem */ +static void exit_usbdux(void) +{ + comedi_driver_unregister(&driver_usbdux); + usb_deregister(&usbduxsub_driver); +} + +module_init(init_usbdux); +module_exit(exit_usbdux); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/drivers/usbduxfast.c b/drivers/staging/comedi/drivers/usbduxfast.c new file mode 100644 index 00000000000..3a00ff0cfc5 --- /dev/null +++ b/drivers/staging/comedi/drivers/usbduxfast.c @@ -0,0 +1,1778 @@ +#define DRIVER_VERSION "v0.99a" +#define DRIVER_AUTHOR "Bernd Porr, BerndPorr@f2s.com" +#define DRIVER_DESC "USB-DUXfast, BerndPorr@f2s.com" +/* + comedi/drivers/usbduxfast.c + Copyright (C) 2004 Bernd Porr, Bernd.Porr@f2s.com + + 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: usbduxfast +Description: ITL USB-DUXfast +Devices: [ITL] USB-DUX (usbduxfast.o) +Author: Bernd Porr <BerndPorr@f2s.com> +Updated: 04 Dec 2006 +Status: testing +*/ + +/* + * I must give credit here to Chris Baugher who + * wrote the driver for AT-MIO-16d. I used some parts of this + * driver. I also must give credits to David Brownell + * who supported me with the USB development. + * + * Bernd Porr + * + * + * Revision history: + * 0.9: Dropping the first data packet which seems to be from the last transfer. + * Buffer overflows in the FX2 are handed over to comedi. + * 0.92: Dropping now 4 packets. The quad buffer has to be emptied. + * Added insn command basically for testing. Sample rate is 1MHz/16ch=62.5kHz + * 0.99: Ian Abbott pointed out a bug which has been corrected. Thanks! + * 0.99a: added external trigger. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/usb.h> +#include <linux/smp_lock.h> +#include <linux/fcntl.h> +#include <linux/compiler.h> +#include "comedi_fc.h" +#include "../comedidev.h" + +// (un)comment this if you want to have debug info. +//#define CONFIG_COMEDI_DEBUG +#undef CONFIG_COMEDI_DEBUG + +#define BOARDNAME "usbduxfast" + +// timeout for the USB-transfer +#define EZTIMEOUT 30 + +// constants for "firmware" upload and download +#define USBDUXFASTSUB_FIRMWARE 0xA0 +#define VENDOR_DIR_IN 0xC0 +#define VENDOR_DIR_OUT 0x40 + +// internal adresses of the 8051 processor +#define USBDUXFASTSUB_CPUCS 0xE600 + +// max lenghth of the transfer-buffer for software upload +#define TB_LEN 0x2000 + +// Input endpoint number +#define BULKINEP 6 + +// Endpoint for the A/D channellist: bulk OUT +#define CHANNELLISTEP 4 + +// Number of channels +#define NUMCHANNELS 32 + +// size of the waveform descriptor +#define WAVESIZE 0x20 + +// Size of one A/D value +#define SIZEADIN ((sizeof(int16_t))) + +// Size of the input-buffer IN BYTES +#define SIZEINBUF 512 + +// 16 bytes. +#define SIZEINSNBUF 512 + +// Size of the buffer for the dux commands +#define SIZEOFDUXBUFFER 256 // bytes + +// Number of in-URBs which receive the data: min=5 +#define NUMOFINBUFFERSHIGH 10 + +// Total number of usbduxfast devices +#define NUMUSBDUXFAST 16 + +// Number of subdevices +#define N_SUBDEVICES 1 + +// Analogue in subdevice +#define SUBDEV_AD 0 + +// min delay steps for more than one channel +// basically when the mux gives up. ;-) +#define MIN_SAMPLING_PERIOD 9 // steps at 30MHz in the FX2 + +// Max number of 1/30MHz delay steps: +#define MAX_SAMPLING_PERIOD 500 + +// Number of received packets to ignore before we start handing data over to comedi. +// It's quad buffering and we have to ignore 4 packets. +#define PACKETS_TO_IGNORE 4 + +///////////////////////////////////////////// +// comedi constants +static const comedi_lrange range_usbduxfast_ai_range = { 2, { + BIP_RANGE(0.75), + BIP_RANGE(0.5), + } +}; + +/* + * private structure of one subdevice + */ + +// This is the structure which holds all the data of this driver +// one sub device just now: A/D +typedef struct { + // attached? + int attached; + // is it associated with a subdevice? + int probed; + // pointer to the usb-device + struct usb_device *usbdev; + // BULK-transfer handling: urb + struct urb *urbIn; + int8_t *transfer_buffer; + // input buffer for single insn + int16_t *insnBuffer; + // interface number + int ifnum; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + // interface structure in 2.6 + struct usb_interface *interface; +#endif + // comedi device for the interrupt context + comedi_device *comedidev; + // asynchronous command is running + short int ai_cmd_running; + // continous aquisition + short int ai_continous; + // number of samples to aquire + long int ai_sample_count; + // commands + uint8_t *dux_commands; + // counter which ignores the first buffers + int ignore; + struct semaphore sem; +} usbduxfastsub_t; + +// The pointer to the private usb-data of the driver +// is also the private data for the comedi-device. +// This has to be global as the usb subsystem needs +// global variables. The other reason is that this +// structure must be there _before_ any comedi +// command is issued. The usb subsystem must be +// initialised before comedi can access it. +static usbduxfastsub_t usbduxfastsub[NUMUSBDUXFAST]; + +static DECLARE_MUTEX(start_stop_sem); + +// bulk transfers to usbduxfast + +#define SENDADCOMMANDS 0 +#define SENDINITEP6 1 + +static int send_dux_commands(usbduxfastsub_t * this_usbduxfastsub, int cmd_type) +{ + int result, nsent; + this_usbduxfastsub->dux_commands[0] = cmd_type; +#ifdef CONFIG_COMEDI_DEBUG + int i; + printk("comedi%d: usbduxfast: dux_commands: ", + this_usbduxfastsub->comedidev->minor); + for (i = 0; i < SIZEOFDUXBUFFER; i++) { + printk(" %02x", this_usbduxfastsub->dux_commands[i]); + } + printk("\n"); +#endif + result = usb_bulk_msg(this_usbduxfastsub->usbdev, + usb_sndbulkpipe(this_usbduxfastsub->usbdev, + CHANNELLISTEP), + this_usbduxfastsub->dux_commands, SIZEOFDUXBUFFER, + &nsent, 10000); + if (result < 0) { + printk("comedi%d: could not transmit dux_commands to the usb-device, err=%d\n", this_usbduxfastsub->comedidev->minor, result); + } + return result; +} + +// Stops the data acquision +// It should be safe to call this function from any context +static int usbduxfastsub_unlink_InURBs(usbduxfastsub_t * usbduxfastsub_tmp) +{ + int j = 0; + int err = 0; + + if (usbduxfastsub_tmp && usbduxfastsub_tmp->urbIn) { + usbduxfastsub_tmp->ai_cmd_running = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,8) + j = usb_unlink_urb(usbduxfastsub_tmp->urbIn); + if (j < 0) { + err = j; + } +#else + // waits until a running transfer is over + usb_kill_urb(usbduxfastsub_tmp->urbIn); + j = 0; +#endif + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi: usbduxfast: unlinked InURB: res=%d\n", j); +#endif + return err; +} + +/* This will stop a running acquisition operation */ +// Is called from within this driver from both the +// interrupt context and from comedi +static int usbduxfast_ai_stop(usbduxfastsub_t * this_usbduxfastsub, + int do_unlink) +{ + int ret = 0; + + if (!this_usbduxfastsub) { + printk("comedi?: usbduxfast_ai_stop: this_usbduxfastsub=NULL!\n"); + return -EFAULT; + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi: usbduxfast_ai_stop\n"); +#endif + + this_usbduxfastsub->ai_cmd_running = 0; + + if (do_unlink) { + // stop aquistion + ret = usbduxfastsub_unlink_InURBs(this_usbduxfastsub); + } + + return ret; +} + +// This will cancel a running acquisition operation. +// This is called by comedi but never from inside the +// driver. +static int usbduxfast_ai_cancel(comedi_device * dev, comedi_subdevice * s) +{ + usbduxfastsub_t *this_usbduxfastsub; + int res = 0; + + // force unlink of all urbs +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi: usbduxfast_ai_cancel\n"); +#endif + this_usbduxfastsub = dev->private; + if (!this_usbduxfastsub) { + printk("comedi: usbduxfast_ai_cancel: this_usbduxfastsub=NULL\n"); + return -EFAULT; + } + down(&this_usbduxfastsub->sem); + if (!(this_usbduxfastsub->probed)) { + up(&this_usbduxfastsub->sem); + return -ENODEV; + } + // unlink + res = usbduxfast_ai_stop(this_usbduxfastsub, 1); + up(&this_usbduxfastsub->sem); + + return res; +} + +// analogue IN +// interrupt service routine +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +static void usbduxfastsub_ai_Irq(struct urb *urb) +#else +static void usbduxfastsub_ai_Irq(struct urb *urb PT_REGS_ARG) +#endif +{ + int n, err; + usbduxfastsub_t *this_usbduxfastsub; + comedi_device *this_comedidev; + comedi_subdevice *s; + uint16_t *p; + + // sanity checks + // is the urb there? + if (!urb) { + printk("comedi_: usbduxfast_: ao int-handler called with urb=NULL!\n"); + return; + } + // the context variable points to the subdevice + this_comedidev = urb->context; + if (!this_comedidev) { + printk("comedi_: usbduxfast_: urb context is a NULL pointer!\n"); + return; + } + // the private structure of the subdevice is usbduxfastsub_t + this_usbduxfastsub = this_comedidev->private; + if (!this_usbduxfastsub) { + printk("comedi_: usbduxfast_: private of comedi subdev is a NULL pointer!\n"); + return; + } + // are we running a command? + if (unlikely(!(this_usbduxfastsub->ai_cmd_running))) { + // not running a command + // do not continue execution if no asynchronous command is running + // in particular not resubmit + return; + } + + if (unlikely(!(this_usbduxfastsub->attached))) { + // no comedi device there + return; + } + // subdevice which is the AD converter + s = this_comedidev->subdevices + SUBDEV_AD; + + // first we test if something unusual has just happened + switch (urb->status) { + case 0: + break; + + // happens after an unlink command or when the device is plugged out + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + // tell this comedi + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxfastsub->comedidev, s); + // stop the transfer w/o unlink + usbduxfast_ai_stop(this_usbduxfastsub, 0); + return; + + default: + printk("comedi%d: usbduxfast: non-zero urb status received in ai intr context: %d\n", this_usbduxfastsub->comedidev->minor, urb->status); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxfastsub->comedidev, s); + usbduxfast_ai_stop(this_usbduxfastsub, 0); + return; + } + + p = urb->transfer_buffer; + if (!this_usbduxfastsub->ignore) { + if (!(this_usbduxfastsub->ai_continous)) { + // not continous, fixed number of samples + n = urb->actual_length / sizeof(uint16_t); + if (unlikely(this_usbduxfastsub->ai_sample_count < n)) { + // we have send only a fraction of the bytes received + cfc_write_array_to_buffer(s, + urb->transfer_buffer, + this_usbduxfastsub->ai_sample_count * + sizeof(uint16_t)); + usbduxfast_ai_stop(this_usbduxfastsub, 0); + // say comedi that the acquistion is over + s->async->events |= COMEDI_CB_EOA; + comedi_event(this_usbduxfastsub->comedidev, s); + return; + } + this_usbduxfastsub->ai_sample_count -= n; + } + // write the full buffer to comedi + cfc_write_array_to_buffer(s, + urb->transfer_buffer, urb->actual_length); + + // tell comedi that data is there + comedi_event(this_usbduxfastsub->comedidev, s); + + } else { + // ignore this packet + this_usbduxfastsub->ignore--; + } + + // command is still running + // resubmit urb for BULK transfer + urb->dev = this_usbduxfastsub->usbdev; + urb->status = 0; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + printk("comedi%d: usbduxfast: urb resubm failed: %d", + this_usbduxfastsub->comedidev->minor, err); + s->async->events |= COMEDI_CB_EOA; + s->async->events |= COMEDI_CB_ERROR; + comedi_event(this_usbduxfastsub->comedidev, s); + usbduxfast_ai_stop(this_usbduxfastsub, 0); + } +} + +static int usbduxfastsub_start(usbduxfastsub_t * usbduxfastsub) +{ + int errcode = 0; + unsigned char local_transfer_buffer[16]; + + if (usbduxfastsub->probed) { + // 7f92 to zero + local_transfer_buffer[0] = 0; + errcode = usb_control_msg(usbduxfastsub->usbdev, + // create a pipe for a control transfer + usb_sndctrlpipe(usbduxfastsub->usbdev, 0), + // bRequest, "Firmware" + USBDUXFASTSUB_FIRMWARE, + // bmRequestType + VENDOR_DIR_OUT, + // Value + USBDUXFASTSUB_CPUCS, + // Index + 0x0000, + // address of the transfer buffer + local_transfer_buffer, + // Length + 1, + // Timeout + EZTIMEOUT); + if (errcode < 0) { + printk("comedi_: usbduxfast_: control msg failed (start)\n"); + return errcode; + } + } + return 0; +} + +static int usbduxfastsub_stop(usbduxfastsub_t * usbduxfastsub) +{ + int errcode = 0; + + unsigned char local_transfer_buffer[16]; + if (usbduxfastsub->probed) { + // 7f92 to one + local_transfer_buffer[0] = 1; + errcode = usb_control_msg(usbduxfastsub->usbdev, + usb_sndctrlpipe(usbduxfastsub->usbdev, 0), + // bRequest, "Firmware" + USBDUXFASTSUB_FIRMWARE, + // bmRequestType + VENDOR_DIR_OUT, + // Value + USBDUXFASTSUB_CPUCS, + // Index + 0x0000, local_transfer_buffer, + // Length + 1, + // Timeout + EZTIMEOUT); + if (errcode < 0) { + printk("comedi_: usbduxfast: control msg failed (stop)\n"); + return errcode; + } + } + return 0; +} + +static int usbduxfastsub_upload(usbduxfastsub_t * usbduxfastsub, + unsigned char *local_transfer_buffer, + unsigned int startAddr, unsigned int len) +{ + int errcode; + + if (usbduxfastsub->probed) { +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbduxfast: uploading %d bytes", + usbduxfastsub->comedidev->minor, len); + printk(" to addr %d, first byte=%d.\n", + startAddr, local_transfer_buffer[0]); +#endif + errcode = usb_control_msg(usbduxfastsub->usbdev, + usb_sndctrlpipe(usbduxfastsub->usbdev, 0), + // brequest, firmware + USBDUXFASTSUB_FIRMWARE, + // bmRequestType + VENDOR_DIR_OUT, + // value + startAddr, + // index + 0x0000, + // our local safe buffer + local_transfer_buffer, + // length + len, + // timeout + EZTIMEOUT); +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbduxfast: result=%d\n", errcode); +#endif + if (errcode < 0) { + printk("comedi_: usbduxfast: uppload failed\n"); + return errcode; + } + } else { + // no device on the bus for this index + return -EFAULT; + } + return 0; +} + +int firmwareUpload(usbduxfastsub_t * usbduxfastsub, + unsigned char *firmwareBinary, int sizeFirmware) +{ + int ret; + + if (!firmwareBinary) { + return 0; + } + ret = usbduxfastsub_stop(usbduxfastsub); + if (ret < 0) { + printk("comedi_: usbduxfast: can not stop firmware\n"); + return ret; + } + ret = usbduxfastsub_upload(usbduxfastsub, + firmwareBinary, 0, sizeFirmware); + if (ret < 0) { + printk("comedi_: usbduxfast: firmware upload failed\n"); + return ret; + } + ret = usbduxfastsub_start(usbduxfastsub); + if (ret < 0) { + printk("comedi_: usbduxfast: can not start firmware\n"); + return ret; + } + return 0; +} + +int usbduxfastsub_submit_InURBs(usbduxfastsub_t * usbduxfastsub) +{ + int errFlag; + + if (!usbduxfastsub) { + return -EFAULT; + } + usb_fill_bulk_urb(usbduxfastsub->urbIn, + usbduxfastsub->usbdev, + usb_rcvbulkpipe(usbduxfastsub->usbdev, BULKINEP), + usbduxfastsub->transfer_buffer, + SIZEINBUF, usbduxfastsub_ai_Irq, usbduxfastsub->comedidev); + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbduxfast: submitting in-urb: %x,%x\n", + usbduxfastsub->comedidev->minor, + (int)(usbduxfastsub->urbIn->context), + (int)(usbduxfastsub->urbIn->dev)); +#endif + errFlag = usb_submit_urb(usbduxfastsub->urbIn, GFP_ATOMIC); + if (errFlag) { + printk("comedi_: usbduxfast: ai: usb_submit_urb error %d\n", + errFlag); + return errFlag; + } + return 0; +} + +static int usbduxfast_ai_cmdtest(comedi_device * dev, + comedi_subdevice * s, comedi_cmd * cmd) +{ + int err = 0, stop_mask = 0; + long int steps, tmp = 0; + int minSamplPer; + usbduxfastsub_t *this_usbduxfastsub = dev->private; + if (!(this_usbduxfastsub->probed)) { + return -ENODEV; + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbduxfast_ai_cmdtest\n", dev->minor); + printk("comedi%d: usbduxfast: convert_arg=%u scan_begin_arg=%u\n", + dev->minor, cmd->convert_arg, cmd->scan_begin_arg); +#endif + /* step 1: make sure trigger sources are trivially valid */ + + tmp = cmd->start_src; + cmd->start_src &= TRIG_NOW | TRIG_EXT | TRIG_INT; + if (!cmd->start_src || tmp != cmd->start_src) + err++; + + tmp = cmd->scan_begin_src; + cmd->scan_begin_src &= TRIG_TIMER | TRIG_FOLLOW | TRIG_EXT; + if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src) + err++; + + tmp = cmd->convert_src; + cmd->convert_src &= TRIG_TIMER | TRIG_EXT; + if (!cmd->convert_src || tmp != cmd->convert_src) + err++; + + tmp = cmd->scan_end_src; + cmd->scan_end_src &= TRIG_COUNT; + if (!cmd->scan_end_src || tmp != cmd->scan_end_src) + err++; + + tmp = cmd->stop_src; + stop_mask = TRIG_COUNT | TRIG_NONE; + cmd->stop_src &= stop_mask; + if (!cmd->stop_src || tmp != cmd->stop_src) + err++; + + if (err) + return 1; + + /* step 2: make sure trigger sources are unique and mutually compatible */ + + if (cmd->start_src != TRIG_NOW && + cmd->start_src != TRIG_EXT && cmd->start_src != TRIG_INT) + err++; + if (cmd->scan_begin_src != TRIG_TIMER && + cmd->scan_begin_src != TRIG_FOLLOW && + cmd->scan_begin_src != TRIG_EXT) + err++; + if (cmd->convert_src != TRIG_TIMER && cmd->convert_src != TRIG_EXT) + err++; + if (cmd->stop_src != TRIG_COUNT && + cmd->stop_src != TRIG_EXT && cmd->stop_src != TRIG_NONE) + err++; + + // can't have external stop and start triggers at once + if (cmd->start_src == TRIG_EXT && cmd->stop_src == TRIG_EXT) + err++; + + if (err) + return 2; + + /* step 3: make sure arguments are trivially compatible */ + + if (cmd->start_src == TRIG_NOW && cmd->start_arg != 0) { + cmd->start_arg = 0; + err++; + } + + if (!cmd->chanlist_len) { + err++; + } + if (cmd->scan_end_arg != cmd->chanlist_len) { + cmd->scan_end_arg = cmd->chanlist_len; + err++; + } + + if (cmd->chanlist_len == 1) { + minSamplPer = 1; + } else { + minSamplPer = MIN_SAMPLING_PERIOD; + } + + if (cmd->convert_src == TRIG_TIMER) { + steps = cmd->convert_arg * 30; + if (steps < (minSamplPer * 1000)) { + steps = minSamplPer * 1000; + } + if (steps > (MAX_SAMPLING_PERIOD * 1000)) { + steps = MAX_SAMPLING_PERIOD * 1000; + } + // calc arg again + tmp = steps / 30; + if (cmd->convert_arg != tmp) { + cmd->convert_arg = tmp; + err++; + } + } + + if (cmd->scan_begin_src == TRIG_TIMER) { + err++; + } + // stop source + switch (cmd->stop_src) { + 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; + // TRIG_EXT doesn't care since it doesn't trigger off a numbered channel + default: + break; + } + + if (err) + return 3; + + /* step 4: fix up any arguments */ + + return 0; + +} + +static int usbduxfast_ai_inttrig(comedi_device * dev, + comedi_subdevice * s, unsigned int trignum) +{ + int ret; + usbduxfastsub_t *this_usbduxfastsub = dev->private; + if (!this_usbduxfastsub) { + return -EFAULT; + } + down(&this_usbduxfastsub->sem); + if (!(this_usbduxfastsub->probed)) { + up(&this_usbduxfastsub->sem); + return -ENODEV; + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbduxfast_ai_inttrig\n", dev->minor); +#endif + + if (trignum != 0) { + printk("comedi%d: usbduxfast_ai_inttrig: invalid trignum\n", + dev->minor); + up(&this_usbduxfastsub->sem); + return -EINVAL; + } + if (!(this_usbduxfastsub->ai_cmd_running)) { + this_usbduxfastsub->ai_cmd_running = 1; + ret = usbduxfastsub_submit_InURBs(this_usbduxfastsub); + if (ret < 0) { + printk("comedi%d: usbduxfast_ai_inttrig: urbSubmit: err=%d\n", dev->minor, ret); + this_usbduxfastsub->ai_cmd_running = 0; + up(&this_usbduxfastsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + printk("comedi%d: ai_inttrig but acqu is already running\n", + dev->minor); + } + up(&this_usbduxfastsub->sem); + return 1; +} + +// offsets for the GPIF bytes +// the first byte is the command byte +#define LENBASE 1+0x00 +#define OPBASE 1+0x08 +#define OUTBASE 1+0x10 +#define LOGBASE 1+0x18 + +static int usbduxfast_ai_cmd(comedi_device * dev, comedi_subdevice * s) +{ + comedi_cmd *cmd = &s->async->cmd; + unsigned int chan, gain, rngmask = 0xff; + int i, j, ret; + usbduxfastsub_t *this_usbduxfastsub = dev->private; + int result; + long steps, steps_tmp; + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbduxfast_ai_cmd\n", dev->minor); +#endif + if (!this_usbduxfastsub) { + return -EFAULT; + } + down(&this_usbduxfastsub->sem); + if (!(this_usbduxfastsub->probed)) { + up(&this_usbduxfastsub->sem); + return -ENODEV; + } + if (this_usbduxfastsub->ai_cmd_running) { + printk("comedi%d: ai_cmd not possible. Another ai_cmd is running.\n", dev->minor); + up(&this_usbduxfastsub->sem); + return -EBUSY; + } + // set current channel of the running aquisition to zero + s->async->cur_chan = 0; + + // ignore the first buffers from the device if there is an error condition + this_usbduxfastsub->ignore = PACKETS_TO_IGNORE; + + if (cmd->chanlist_len > 0) { + gain = CR_RANGE(cmd->chanlist[0]); + for (i = 0; i < cmd->chanlist_len; ++i) { + chan = CR_CHAN(cmd->chanlist[i]); + if (chan != i) { + printk("comedi%d: cmd is accepting only consecutive channels.\n", dev->minor); + up(&this_usbduxfastsub->sem); + return -EINVAL; + } + if ((gain != CR_RANGE(cmd->chanlist[i])) + && (cmd->chanlist_len > 3)) { + printk("comedi%d: the gain must be the same for all channels.\n", dev->minor); + up(&this_usbduxfastsub->sem); + return -EINVAL; + } + if (i >= NUMCHANNELS) { + printk("comedi%d: channel list too long\n", + dev->minor); + break; + } + } + } + steps = 0; + if (cmd->scan_begin_src == TRIG_TIMER) { + printk("comedi%d: usbduxfast: scan_begin_src==TRIG_TIMER not valid.\n", dev->minor); + up(&this_usbduxfastsub->sem); + return -EINVAL; + } + if (cmd->convert_src == TRIG_TIMER) { + steps = (cmd->convert_arg * 30) / 1000; + } + if ((steps < MIN_SAMPLING_PERIOD) && (cmd->chanlist_len != 1)) { + printk("comedi%d: usbduxfast: ai_cmd: steps=%ld, scan_begin_arg=%d. Not properly tested by cmdtest?\n", dev->minor, steps, cmd->scan_begin_arg); + up(&this_usbduxfastsub->sem); + return -EINVAL; + } + if (steps > MAX_SAMPLING_PERIOD) { + printk("comedi%d: usbduxfast: ai_cmd: sampling rate too low.\n", + dev->minor); + up(&this_usbduxfastsub->sem); + return -EINVAL; + } + if ((cmd->start_src == TRIG_EXT) && (cmd->chanlist_len != 1) + && (cmd->chanlist_len != 16)) { + printk("comedi%d: usbduxfast: ai_cmd: TRIG_EXT only with 1 or 16 channels possible.\n", dev->minor); + up(&this_usbduxfastsub->sem); + return -EINVAL; + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbduxfast: steps=%ld, convert_arg=%u, ai_timer=%u\n", + dev->minor, + steps, cmd->convert_arg, this_usbduxfastsub->ai_timer); +#endif + + switch (cmd->chanlist_len) { + // one channel + case 1: + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + + // for external trigger: looping in this state until the RDY0 pin + // becomes zero + if (cmd->start_src == TRIG_EXT) { // we loop here until ready has been set + this_usbduxfastsub->dux_commands[LENBASE + 0] = 0x01; // branch back to state 0 + this_usbduxfastsub->dux_commands[OPBASE + 0] = 0x01; // deceision state w/o data + this_usbduxfastsub->dux_commands[OUTBASE + 0] = + 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 0] = 0x00; // RDY0 = 0 + } else { // we just proceed to state 1 + this_usbduxfastsub->dux_commands[LENBASE + 0] = 1; + this_usbduxfastsub->dux_commands[OPBASE + 0] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 0] = + 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 0] = 0; + } + + if (steps < MIN_SAMPLING_PERIOD) { + // for fast single channel aqu without mux + if (steps <= 1) { + // we just stay here at state 1 and rexecute the same state + // this gives us 30MHz sampling rate + this_usbduxfastsub->dux_commands[LENBASE + 1] = 0x89; // branch back to state 1 + this_usbduxfastsub->dux_commands[OPBASE + 1] = 0x03; // deceision state with data + this_usbduxfastsub->dux_commands[OUTBASE + 1] = + 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 1] = 0xFF; // doesn't matter + } else { + // we loop through two states: data and delay: max rate is 15Mhz + this_usbduxfastsub->dux_commands[LENBASE + 1] = + steps - 1; + this_usbduxfastsub->dux_commands[OPBASE + 1] = 0x02; // data + this_usbduxfastsub->dux_commands[OUTBASE + 1] = + 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 1] = 0; // doesn't matter + + this_usbduxfastsub->dux_commands[LENBASE + 2] = 0x09; // branch back to state 1 + this_usbduxfastsub->dux_commands[OPBASE + 2] = 0x01; // deceision state w/o data + this_usbduxfastsub->dux_commands[OUTBASE + 2] = + 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 2] = 0xFF; // doesn't matter + } + } else { + // we loop through 3 states: 2x delay and 1x data. This gives a min + // sampling rate of 60kHz. + + // we have 1 state with duration 1 + steps = steps - 1; + + // do the first part of the delay + this_usbduxfastsub->dux_commands[LENBASE + 1] = + steps / 2; + this_usbduxfastsub->dux_commands[OPBASE + 1] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 1] = + 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 1] = 0; + + // and the second part + this_usbduxfastsub->dux_commands[LENBASE + 2] = + steps - steps / 2; + this_usbduxfastsub->dux_commands[OPBASE + 2] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 2] = + 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 2] = 0; + + // get the data and branch back + this_usbduxfastsub->dux_commands[LENBASE + 3] = 0x09; // branch back to state 1 + this_usbduxfastsub->dux_commands[OPBASE + 3] = 0x03; // deceision state w data + this_usbduxfastsub->dux_commands[OUTBASE + 3] = + 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 3] = 0xFF; // doesn't matter + } + break; + + case 2: + // two channels + // commit data to the FIFO + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + this_usbduxfastsub->dux_commands[LENBASE + 0] = 1; + this_usbduxfastsub->dux_commands[OPBASE + 0] = 0x02; // data + this_usbduxfastsub->dux_commands[OUTBASE + 0] = 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 0] = 0; + + // we have 1 state with duration 1: state 0 + steps_tmp = steps - 1; + + if (CR_RANGE(cmd->chanlist[1]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + // do the first part of the delay + this_usbduxfastsub->dux_commands[LENBASE + 1] = steps_tmp / 2; + this_usbduxfastsub->dux_commands[OPBASE + 1] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 1] = 0xFE & rngmask; //count + this_usbduxfastsub->dux_commands[LOGBASE + 1] = 0; + + // and the second part + this_usbduxfastsub->dux_commands[LENBASE + 2] = + steps_tmp - steps_tmp / 2; + this_usbduxfastsub->dux_commands[OPBASE + 2] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 2] = 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 2] = 0; + + this_usbduxfastsub->dux_commands[LENBASE + 3] = 1; + this_usbduxfastsub->dux_commands[OPBASE + 3] = 0x02; // data + this_usbduxfastsub->dux_commands[OUTBASE + 3] = 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 3] = 0; + + // we have 2 states with duration 1: step 6 and the IDLE state + steps_tmp = steps - 2; + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + // do the first part of the delay + this_usbduxfastsub->dux_commands[LENBASE + 4] = steps_tmp / 2; + this_usbduxfastsub->dux_commands[OPBASE + 4] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 4] = (0xFF - 0x02) & rngmask; //reset + this_usbduxfastsub->dux_commands[LOGBASE + 4] = 0; + + // and the second part + this_usbduxfastsub->dux_commands[LENBASE + 5] = + steps_tmp - steps_tmp / 2; + this_usbduxfastsub->dux_commands[OPBASE + 5] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 5] = 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 5] = 0; + + this_usbduxfastsub->dux_commands[LENBASE + 6] = 1; + this_usbduxfastsub->dux_commands[OPBASE + 6] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 6] = 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 6] = 0; + break; + + case 3: + // three channels + for (j = 0; j < 1; j++) { + if (CR_RANGE(cmd->chanlist[j]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + // commit data to the FIFO and do the first part of the delay + this_usbduxfastsub->dux_commands[LENBASE + j * 2] = + steps / 2; + this_usbduxfastsub->dux_commands[OPBASE + j * 2] = 0x02; // data + this_usbduxfastsub->dux_commands[OUTBASE + j * 2] = 0xFF & rngmask; // no change + this_usbduxfastsub->dux_commands[LOGBASE + j * 2] = 0; + + if (CR_RANGE(cmd->chanlist[j + 1]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + // do the second part of the delay + this_usbduxfastsub->dux_commands[LENBASE + j * 2 + 1] = + steps - steps / 2; + this_usbduxfastsub->dux_commands[OPBASE + j * 2 + 1] = 0; // no data + this_usbduxfastsub->dux_commands[OUTBASE + j * 2 + 1] = 0xFE & rngmask; //count + this_usbduxfastsub->dux_commands[LOGBASE + j * 2 + 1] = + 0; + } + + // 2 steps with duration 1: the idele step and step 6: + steps_tmp = steps - 2; + // commit data to the FIFO and do the first part of the delay + this_usbduxfastsub->dux_commands[LENBASE + 4] = steps_tmp / 2; + this_usbduxfastsub->dux_commands[OPBASE + 4] = 0x02; // data + this_usbduxfastsub->dux_commands[OUTBASE + 4] = 0xFF & rngmask; // no change + this_usbduxfastsub->dux_commands[LOGBASE + 4] = 0; + + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + // do the second part of the delay + this_usbduxfastsub->dux_commands[LENBASE + 5] = + steps_tmp - steps_tmp / 2; + this_usbduxfastsub->dux_commands[OPBASE + 5] = 0; // no data + this_usbduxfastsub->dux_commands[OUTBASE + 5] = (0xFF - 0x02) & rngmask; // reset + this_usbduxfastsub->dux_commands[LOGBASE + 5] = 0; + + this_usbduxfastsub->dux_commands[LENBASE + 6] = 1; + this_usbduxfastsub->dux_commands[OPBASE + 6] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 6] = 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 6] = 0; + + case 16: + if (CR_RANGE(cmd->chanlist[0]) > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + if (cmd->start_src == TRIG_EXT) { // we loop here until ready has been set + this_usbduxfastsub->dux_commands[LENBASE + 0] = 0x01; // branch back to state 0 + this_usbduxfastsub->dux_commands[OPBASE + 0] = 0x01; // deceision state w/o data + this_usbduxfastsub->dux_commands[OUTBASE + 0] = (0xFF - 0x02) & rngmask; // reset + this_usbduxfastsub->dux_commands[LOGBASE + 0] = 0x00; // RDY0 = 0 + } else { // we just proceed to state 1 + this_usbduxfastsub->dux_commands[LENBASE + 0] = 255; // 30us reset pulse + this_usbduxfastsub->dux_commands[OPBASE + 0] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 0] = (0xFF - 0x02) & rngmask; // reset + this_usbduxfastsub->dux_commands[LOGBASE + 0] = 0; + } + + // commit data to the FIFO + this_usbduxfastsub->dux_commands[LENBASE + 1] = 1; + this_usbduxfastsub->dux_commands[OPBASE + 1] = 0x02; // data + this_usbduxfastsub->dux_commands[OUTBASE + 1] = 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 1] = 0; + + // we have 2 states with duration 1 + steps = steps - 2; + + // do the first part of the delay + this_usbduxfastsub->dux_commands[LENBASE + 2] = steps / 2; + this_usbduxfastsub->dux_commands[OPBASE + 2] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 2] = 0xFE & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 2] = 0; + + // and the second part + this_usbduxfastsub->dux_commands[LENBASE + 3] = + steps - steps / 2; + this_usbduxfastsub->dux_commands[OPBASE + 3] = 0; + this_usbduxfastsub->dux_commands[OUTBASE + 3] = 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 3] = 0; + + this_usbduxfastsub->dux_commands[LENBASE + 4] = 0x09; // branch back to state 1 + this_usbduxfastsub->dux_commands[OPBASE + 4] = 0x01; // deceision state w/o data + this_usbduxfastsub->dux_commands[OUTBASE + 4] = 0xFF & rngmask; + this_usbduxfastsub->dux_commands[LOGBASE + 4] = 0xFF; // doesn't matter + + break; + + default: + printk("comedi %d: unsupported combination of channels\n", + dev->minor); + up(&this_usbduxfastsub->sem); + return -EFAULT; + } + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi %d: sending commands to the usb device\n", dev->minor); +#endif + // 0 means that the AD commands are sent + result = send_dux_commands(this_usbduxfastsub, SENDADCOMMANDS); + if (result < 0) { + printk("comedi%d: adc command could not be submitted. Aborting...\n", dev->minor); + up(&this_usbduxfastsub->sem); + return result; + } + if (cmd->stop_src == TRIG_COUNT) { + this_usbduxfastsub->ai_sample_count = + (cmd->stop_arg) * (cmd->scan_end_arg); + if (usbduxfastsub->ai_sample_count < 1) { + printk("comedi%d: (cmd->stop_arg)*(cmd->scan_end_arg)<1, aborting.\n", dev->minor); + up(&this_usbduxfastsub->sem); + return -EFAULT; + } + this_usbduxfastsub->ai_continous = 0; + } else { + // continous aquisition + this_usbduxfastsub->ai_continous = 1; + this_usbduxfastsub->ai_sample_count = 0; + } + + if ((cmd->start_src == TRIG_NOW) || (cmd->start_src == TRIG_EXT)) { + // enable this acquisition operation + this_usbduxfastsub->ai_cmd_running = 1; + ret = usbduxfastsub_submit_InURBs(this_usbduxfastsub); + if (ret < 0) { + this_usbduxfastsub->ai_cmd_running = 0; + // fixme: unlink here?? + up(&this_usbduxfastsub->sem); + return ret; + } + s->async->inttrig = NULL; + } else { + /* TRIG_INT */ + // don't enable the acquision operation + // wait for an internal signal + s->async->inttrig = usbduxfast_ai_inttrig; + } + up(&this_usbduxfastsub->sem); + + return 0; +} + +/* Mode 0 is used to get a single conversion on demand */ +static int usbduxfast_ai_insn_read(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + int i, j, n, actual_length; + int chan, range, rngmask; + int err; + usbduxfastsub_t *usbduxfastsub = dev->private; + + if (!usbduxfastsub) { + printk("comedi%d: ai_insn_read: no usb dev.\n", dev->minor); + return -ENODEV; + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: ai_insn_read, insn->n=%d, insn->subdev=%d\n", + dev->minor, insn->n, insn->subdev); +#endif + down(&usbduxfastsub->sem); + if (!(usbduxfastsub->probed)) { + up(&usbduxfastsub->sem); + return -ENODEV; + } + if (usbduxfastsub->ai_cmd_running) { + printk("comedi%d: ai_insn_read not possible. Async Command is running.\n", dev->minor); + up(&usbduxfastsub->sem); + return -EBUSY; + } + // sample one channel + chan = CR_CHAN(insn->chanspec); + range = CR_RANGE(insn->chanspec); + // set command for the first channel + + if (range > 0) + rngmask = 0xff - 0x04; + else + rngmask = 0xff; + // commit data to the FIFO + usbduxfastsub->dux_commands[LENBASE + 0] = 1; + usbduxfastsub->dux_commands[OPBASE + 0] = 0x02; // data + usbduxfastsub->dux_commands[OUTBASE + 0] = 0xFF & rngmask; + usbduxfastsub->dux_commands[LOGBASE + 0] = 0; + + // do the first part of the delay + usbduxfastsub->dux_commands[LENBASE + 1] = 12; + usbduxfastsub->dux_commands[OPBASE + 1] = 0; + usbduxfastsub->dux_commands[OUTBASE + 1] = 0xFE & rngmask; + usbduxfastsub->dux_commands[LOGBASE + 1] = 0; + + usbduxfastsub->dux_commands[LENBASE + 2] = 1; + usbduxfastsub->dux_commands[OPBASE + 2] = 0; + usbduxfastsub->dux_commands[OUTBASE + 2] = 0xFE & rngmask; + usbduxfastsub->dux_commands[LOGBASE + 2] = 0; + + usbduxfastsub->dux_commands[LENBASE + 3] = 1; + usbduxfastsub->dux_commands[OPBASE + 3] = 0; + usbduxfastsub->dux_commands[OUTBASE + 3] = 0xFE & rngmask; + usbduxfastsub->dux_commands[LOGBASE + 3] = 0; + + usbduxfastsub->dux_commands[LENBASE + 4] = 1; + usbduxfastsub->dux_commands[OPBASE + 4] = 0; + usbduxfastsub->dux_commands[OUTBASE + 4] = 0xFE & rngmask; + usbduxfastsub->dux_commands[LOGBASE + 4] = 0; + + // second part + usbduxfastsub->dux_commands[LENBASE + 5] = 12; + usbduxfastsub->dux_commands[OPBASE + 5] = 0; + usbduxfastsub->dux_commands[OUTBASE + 5] = 0xFF & rngmask; + usbduxfastsub->dux_commands[LOGBASE + 5] = 0; + + usbduxfastsub->dux_commands[LENBASE + 6] = 1; + usbduxfastsub->dux_commands[OPBASE + 6] = 0; + usbduxfastsub->dux_commands[OUTBASE + 6] = 0xFF & rngmask; + usbduxfastsub->dux_commands[LOGBASE + 0] = 0; + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi %d: sending commands to the usb device\n", dev->minor); +#endif + // 0 means that the AD commands are sent + err = send_dux_commands(usbduxfastsub, SENDADCOMMANDS); + if (err < 0) { + printk("comedi%d: adc command could not be submitted. Aborting...\n", dev->minor); + up(&usbduxfastsub->sem); + return err; + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbduxfast: submitting in-urb: %x,%x\n", + usbduxfastsub->comedidev->minor, + (int)(usbduxfastsub->urbIn->context), + (int)(usbduxfastsub->urbIn->dev)); +#endif + for (i = 0; i < PACKETS_TO_IGNORE; i++) { + err = usb_bulk_msg(usbduxfastsub->usbdev, + usb_rcvbulkpipe(usbduxfastsub->usbdev, + BULKINEP), + usbduxfastsub->transfer_buffer, SIZEINBUF, + &actual_length, 10000); + if (err < 0) { + printk("comedi%d: insn timeout. No data.\n", + dev->minor); + up(&usbduxfastsub->sem); + return err; + } + } + // data points + for (i = 0; i < insn->n;) { + err = usb_bulk_msg(usbduxfastsub->usbdev, + usb_rcvbulkpipe(usbduxfastsub->usbdev, + BULKINEP), + usbduxfastsub->transfer_buffer, SIZEINBUF, + &actual_length, 10000); + if (err < 0) { + printk("comedi%d: insn data error: %d\n", + dev->minor, err); + up(&usbduxfastsub->sem); + return err; + } + n = actual_length / sizeof(uint16_t); + if ((n % 16) != 0) { + printk("comedi%d: insn data packet corrupted.\n", + dev->minor); + up(&usbduxfastsub->sem); + return -EINVAL; + } + for (j = chan; (j < n) && (i < insn->n); j = j + 16) { + data[i] = + ((uint16_t *) (usbduxfastsub-> + transfer_buffer))[j]; + i++; + } + } + up(&usbduxfastsub->sem); + return i; +} + +static unsigned hex2unsigned(char *h) +{ + unsigned hi, lo; + if (h[0] > '9') { + hi = h[0] - 'A' + 0x0a; + } else { + hi = h[0] - '0'; + } + if (h[1] > '9') { + lo = h[1] - 'A' + 0x0a; + } else { + lo = h[1] - '0'; + } + return hi * 0x10 + lo; +} + +// for FX2 +#define FIRMWARE_MAX_LEN 0x2000 + +// taken from David Brownell's fxload and adjusted for this driver +static int read_firmware(usbduxfastsub_t * usbduxfastsub, void *firmwarePtr, + long size) +{ + int i = 0; + unsigned char *fp = (char *)firmwarePtr; + unsigned char *firmwareBinary = NULL; + int res = 0; + int maxAddr = 0; + + firmwareBinary = kmalloc(FIRMWARE_MAX_LEN, GFP_KERNEL); + if (!firmwareBinary) { + printk("comedi_: usbduxfast: mem alloc for firmware failed\n"); + return -ENOMEM; + } + + for (;;) { + char buf[256], *cp; + char type; + int len; + int idx, off; + int j = 0; + + // get one line + while ((i < size) && (fp[i] != 13) && (fp[i] != 10)) { + buf[j] = fp[i]; + i++; + j++; + if (j >= sizeof(buf)) { + printk("comedi_: usbduxfast: bogus firmware file!\n"); + return -1; + } + } + // get rid of LF/CR/... + while ((i < size) && ((fp[i] == 13) || (fp[i] == 10) + || (fp[i] == 0))) { + i++; + } + + buf[j] = 0; + //printk("comedi_: buf=%s\n",buf); + + /* EXTENSION: "# comment-till-end-of-line", for copyrights etc */ + if (buf[0] == '#') + continue; + + if (buf[0] != ':') { + printk("comedi_: usbduxfast: upload: not an ihex record: %s", buf); + return -EFAULT; + } + + /* Read the length field (up to 16 bytes) */ + len = hex2unsigned(buf + 1); + + /* Read the target offset */ + off = (hex2unsigned(buf + 3) * 0x0100) + hex2unsigned(buf + 5); + + if ((off + len) > maxAddr) { + maxAddr = off + len; + } + + if (maxAddr >= FIRMWARE_MAX_LEN) { + printk("comedi_: usbduxfast: firmware upload goes beyond FX2 RAM boundaries."); + return -EFAULT; + } + //printk("comedi_: usbduxfast: off=%x, len=%x:",off,len); + + /* Read the record type */ + type = hex2unsigned(buf + 7); + + /* If this is an EOF record, then make it so. */ + if (type == 1) { + break; + } + + if (type != 0) { + printk("comedi_: usbduxfast: unsupported record type: %u\n", type); + return -EFAULT; + } + + for (idx = 0, cp = buf + 9; idx < len; idx += 1, cp += 2) { + firmwareBinary[idx + off] = hex2unsigned(cp); + //printk("%02x ",firmwareBinary[idx+off]); + } + //printk("\n"); + + if (i >= size) { + printk("comedi_: usbduxfast: unexpected end of hex file\n"); + break; + } + + } + res = firmwareUpload(usbduxfastsub, firmwareBinary, maxAddr + 1); + kfree(firmwareBinary); + return res; +} + +static void tidy_up(usbduxfastsub_t * usbduxfastsub_tmp) +{ +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbduxfast: tiding up\n"); +#endif + if (!usbduxfastsub_tmp) { + return; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + // shows the usb subsystem that the driver is down + if (usbduxfastsub_tmp->interface) { + usb_set_intfdata(usbduxfastsub_tmp->interface, NULL); + } +#endif + + usbduxfastsub_tmp->probed = 0; + + if (usbduxfastsub_tmp->urbIn) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,8) + // waits until a running transfer is over + // thus, under 2.4 hotplugging while a command + // is running is not safe + usb_kill_urb(usbduxfastsub_tmp->urbIn); +#endif + if (usbduxfastsub_tmp->transfer_buffer) { + kfree(usbduxfastsub_tmp->transfer_buffer); + usbduxfastsub_tmp->transfer_buffer = NULL; + } + usb_free_urb(usbduxfastsub_tmp->urbIn); + usbduxfastsub_tmp->urbIn = NULL; + } + if (usbduxfastsub_tmp->insnBuffer) { + kfree(usbduxfastsub_tmp->insnBuffer); + usbduxfastsub_tmp->insnBuffer = NULL; + } + if (usbduxfastsub_tmp->dux_commands) { + kfree(usbduxfastsub_tmp->dux_commands); + usbduxfastsub_tmp->dux_commands = NULL; + } + usbduxfastsub_tmp->ai_cmd_running = 0; +} + +// allocate memory for the urbs and initialise them +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +static void *usbduxfastsub_probe(struct usb_device *udev, + unsigned int interfnum, const struct usb_device_id *id) +{ +#else +static int usbduxfastsub_probe(struct usb_interface *uinterf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(uinterf); +#endif + int i; + int index; + + if (udev->speed != USB_SPEED_HIGH) { + printk("comedi_: usbduxfast_: This driver needs USB 2.0 to operate. Aborting...\n"); + return -ENODEV; + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbduxfast_: finding a free structure for the usb-device\n"); +#endif + down(&start_stop_sem); + // look for a free place in the usbduxfast array + index = -1; + for (i = 0; i < NUMUSBDUXFAST; i++) { + if (!(usbduxfastsub[i].probed)) { + index = i; + break; + } + } + + // no more space + if (index == -1) { + printk("Too many usbduxfast-devices connected.\n"); + up(&start_stop_sem); + return -EMFILE; + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbduxfast: usbduxfastsub[%d] is ready to connect to comedi.\n", index); +#endif + + init_MUTEX(&(usbduxfastsub[index].sem)); + // save a pointer to the usb device + usbduxfastsub[index].usbdev = udev; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + // save the interface number + usbduxfastsub[index].ifnum = interfnum; +#else + // 2.6: save the interface itself + usbduxfastsub[index].interface = uinterf; + // get the interface number from the interface + usbduxfastsub[index].ifnum = uinterf->altsetting->desc.bInterfaceNumber; + // hand the private data over to the usb subsystem + // will be needed for disconnect + usb_set_intfdata(uinterf, &(usbduxfastsub[index])); +#endif + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbduxfast: ifnum=%d\n", usbduxfastsub[index].ifnum); +#endif + // create space for the commands going to the usb device + usbduxfastsub[index].dux_commands = kmalloc(SIZEOFDUXBUFFER, + GFP_KERNEL); + if (!usbduxfastsub[index].dux_commands) { + printk("comedi_: usbduxfast: error alloc space for dac commands\n"); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + // create space of the instruction buffer + usbduxfastsub[index].insnBuffer = kmalloc(SIZEINSNBUF, GFP_KERNEL); + if (!(usbduxfastsub[index].insnBuffer)) { + printk("comedi_: usbduxfast: could not alloc space for insnBuffer\n"); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + // setting to alternate setting 1: enabling bulk ep + i = usb_set_interface(usbduxfastsub[index].usbdev, + usbduxfastsub[index].ifnum, 1); + if (i < 0) { + printk("comedi_: usbduxfast%d: could not switch to alternate setting 1.\n", index); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENODEV; + } + usbduxfastsub[index].urbIn = usb_alloc_urb(0, GFP_KERNEL); + if (usbduxfastsub[index].urbIn == NULL) { + printk("comedi_: usbduxfast%d: Could not alloc. urb\n", index); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + usbduxfastsub[index].transfer_buffer = kmalloc(SIZEINBUF, GFP_KERNEL); + if (!(usbduxfastsub[index].transfer_buffer)) { + printk("comedi_: usbduxfast%d: could not alloc. transb.\n", + index); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return -ENOMEM; + } + // we've reached the bottom of the function + usbduxfastsub[index].probed = 1; + up(&start_stop_sem); + printk("comedi_: usbduxfast%d has been successfully initialized.\n", + index); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + return (void *)(&usbduxfastsub[index]); +#else + // success + return 0; +#endif +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +static void usbduxfastsub_disconnect(struct usb_device *udev, void *ptr) +{ + usbduxfastsub_t *usbduxfastsub_tmp = (usbduxfastsub_t *) ptr; +#else +static void usbduxfastsub_disconnect(struct usb_interface *intf) +{ + usbduxfastsub_t *usbduxfastsub_tmp = usb_get_intfdata(intf); + struct usb_device *udev = interface_to_usbdev(intf); +#endif + if (!usbduxfastsub_tmp) { + printk("comedi_: usbduxfast: disconnect called with null pointer.\n"); + return; + } + if (usbduxfastsub_tmp->usbdev != udev) { + printk("comedi_: usbduxfast: BUG! called with wrong ptr!!!\n"); + return; + } + down(&start_stop_sem); + down(&usbduxfastsub_tmp->sem); + tidy_up(usbduxfastsub_tmp); + up(&usbduxfastsub_tmp->sem); + up(&start_stop_sem); +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbduxfast: disconnected from the usb\n"); +#endif +} + +// is called when comedi-config is called +static int usbduxfast_attach(comedi_device * dev, comedi_devconfig * it) +{ + int ret; + int index; + int i; + comedi_subdevice *s = NULL; + dev->private = NULL; + + down(&start_stop_sem); + // find a valid device which has been detected by the probe function of the usb + index = -1; + for (i = 0; i < NUMUSBDUXFAST; i++) { + if ((usbduxfastsub[i].probed) && (!usbduxfastsub[i].attached)) { + index = i; + break; + } + } + + if (index < 0) { + printk("comedi%d: usbduxfast: error: attach failed, no usbduxfast devs connected to the usb bus.\n", dev->minor); + up(&start_stop_sem); + return -ENODEV; + } + + down(&(usbduxfastsub[index].sem)); + // pointer back to the corresponding comedi device + usbduxfastsub[index].comedidev = dev; + + // trying to upload the firmware into the chip + if (comedi_aux_data(it->options, 0) && + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]) { + read_firmware(usbduxfastsub, + comedi_aux_data(it->options, 0), + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]); + } + + dev->board_name = BOARDNAME; + + /* set number of subdevices */ + dev->n_subdevices = N_SUBDEVICES; + + // allocate space for the subdevices + if ((ret = alloc_subdevices(dev, N_SUBDEVICES)) < 0) { + printk("comedi%d: usbduxfast: error alloc space for subdev\n", + dev->minor); + up(&start_stop_sem); + return ret; + } + + printk("comedi%d: usbduxfast: usb-device %d is attached to comedi.\n", + dev->minor, index); + // private structure is also simply the usb-structure + dev->private = usbduxfastsub + index; + // the first subdevice is the A/D converter + s = dev->subdevices + SUBDEV_AD; + // the URBs get the comedi subdevice + // which is responsible for reading + // this is the subdevice which reads data + dev->read_subdev = s; + // the subdevice receives as private structure the + // usb-structure + s->private = NULL; + // analog input + s->type = COMEDI_SUBD_AI; + // readable and ref is to ground + s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ; + // 16 channels + s->n_chan = 16; + // length of the channellist + s->len_chanlist = 16; + // callback functions + s->insn_read = usbduxfast_ai_insn_read; + s->do_cmdtest = usbduxfast_ai_cmdtest; + s->do_cmd = usbduxfast_ai_cmd; + s->cancel = usbduxfast_ai_cancel; + // max value from the A/D converter (12bit+1 bit for overflow) + s->maxdata = 0x1000; + // range table to convert to physical units + s->range_table = &range_usbduxfast_ai_range; + + // finally decide that it's attached + usbduxfastsub[index].attached = 1; + + up(&(usbduxfastsub[index].sem)); + + up(&start_stop_sem); + + printk("comedi%d: successfully attached to usbduxfast.\n", dev->minor); + + return 0; +} + +static int usbduxfast_detach(comedi_device * dev) +{ + usbduxfastsub_t *usbduxfastsub_tmp; + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbduxfast: detach usb device\n", dev->minor); +#endif + + if (!dev) { + printk("comedi?: usbduxfast: detach without dev variable...\n"); + return -EFAULT; + } + + usbduxfastsub_tmp = dev->private; + if (!usbduxfastsub_tmp) { + printk("comedi?: usbduxfast: detach without ptr to usbduxfastsub[]\n"); + return -EFAULT; + } + + down(&usbduxfastsub_tmp->sem); + down(&start_stop_sem); + // Don't allow detach to free the private structure + // It's one entry of of usbduxfastsub[] + dev->private = NULL; + usbduxfastsub_tmp->attached = 0; + usbduxfastsub_tmp->comedidev = NULL; +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbduxfast: detach: successfully removed\n", + dev->minor); +#endif + up(&start_stop_sem); + up(&usbduxfastsub_tmp->sem); + return 0; +} + +/* main driver struct */ +static comedi_driver driver_usbduxfast = { + driver_name:"usbduxfast", + module:THIS_MODULE, + attach:usbduxfast_attach, + detach:usbduxfast_detach, +}; + +static void init_usb_devices(void) +{ + int index; +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbduxfast: setting all possible devs to invalid\n"); +#endif + // all devices entries are invalid to begin with + // they will become valid by the probe function + // and then finally by the attach-function + for (index = 0; index < NUMUSBDUXFAST; index++) { + memset(&(usbduxfastsub[index]), 0x00, + sizeof(usbduxfastsub[index])); + init_MUTEX(&(usbduxfastsub[index].sem)); + } +} + +// Table with the USB-devices: just now only testing IDs +static struct usb_device_id usbduxfastsub_table[] = { + // { USB_DEVICE(0x4b4, 0x8613), //testing + // }, + {USB_DEVICE(0x13d8, 0x0010) //real ID + }, + {USB_DEVICE(0x13d8, 0x0011) //real ID + }, + {} /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, usbduxfastsub_table); + +// The usbduxfastsub-driver +static struct usb_driver usbduxfastsub_driver = { +#ifdef COMEDI_HAVE_USB_DRIVER_OWNER + owner:THIS_MODULE, +#endif + name:BOARDNAME, + probe:usbduxfastsub_probe, + disconnect:usbduxfastsub_disconnect, + id_table:usbduxfastsub_table, +}; + +// Can't use the nice macro as I have also to initialise the USB +// subsystem: +// registering the usb-system _and_ the comedi-driver +static int init_usbduxfast(void) +{ + printk(KERN_INFO KBUILD_MODNAME ": " + DRIVER_VERSION ":" DRIVER_DESC "\n"); + init_usb_devices(); + usb_register(&usbduxfastsub_driver); + comedi_driver_register(&driver_usbduxfast); + return 0; +} + +// deregistering the comedi driver and the usb-subsystem +static void exit_usbduxfast(void) +{ + comedi_driver_unregister(&driver_usbduxfast); + usb_deregister(&usbduxfastsub_driver); +} + +module_init(init_usbduxfast); +module_exit(exit_usbduxfast); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/comedi/interrupt.h b/drivers/staging/comedi/interrupt.h new file mode 100644 index 00000000000..3038e461948 --- /dev/null +++ b/drivers/staging/comedi/interrupt.h @@ -0,0 +1,60 @@ +/* + linux/interrupt.h compatibility header + + 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. + */ + +#ifndef __COMPAT_LINUX_INTERRUPT_H_ +#define __COMPAT_LINUX_INTERRUPT_H_ + +#include <linux/interrupt.h> + +#include <linux/version.h> + +#ifndef IRQ_NONE +typedef void irqreturn_t; +#define IRQ_NONE +#define IRQ_HANDLED +#define IRQ_RETVAL(x) (void)(x) +#endif + +#ifndef IRQF_DISABLED +#define IRQF_DISABLED SA_INTERRUPT +#define IRQF_SAMPLE_RANDOM SA_SAMPLE_RANDOM +#define IRQF_SHARED SA_SHIRQ +#define IRQF_PROBE_SHARED SA_PROBEIRQ +#define IRQF_PERCPU SA_PERCPU +#ifdef SA_TRIGGER_MASK +#define IRQF_TRIGGER_NONE 0 +#define IRQF_TRIGGER_LOW SA_TRIGGER_LOW +#define IRQF_TRIGGER_HIGH SA_TRIGGER_HIGH +#define IRQF_TRIGGER_FALLING SA_TRIGGER_FALLING +#define IRQF_TRIGGER_RISING SA_TRIGGER_RISING +#define IRQF_TRIGGER_MASK SA_TRIGGER_MASK +#else +#define IRQF_TRIGGER_NONE 0 +#define IRQF_TRIGGER_LOW 0 +#define IRQF_TRIGGER_HIGH 0 +#define IRQF_TRIGGER_FALLING 0 +#define IRQF_TRIGGER_RISING 0 +#define IRQF_TRIGGER_MASK 0 +#endif +#endif + +#define PT_REGS_ARG +#define PT_REGS_CALL +#define PT_REGS_NULL + +#endif diff --git a/drivers/staging/comedi/kcomedilib/Makefile b/drivers/staging/comedi/kcomedilib/Makefile new file mode 100644 index 00000000000..ffcc9ad32ad --- /dev/null +++ b/drivers/staging/comedi/kcomedilib/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_COMEDI) += kcomedilib.o + +kcomedilib-objs := \ + data.o \ + ksyms.o \ + dio.o \ + kcomedilib_main.o \ + get.o diff --git a/drivers/staging/comedi/kcomedilib/data.c b/drivers/staging/comedi/kcomedilib/data.c new file mode 100644 index 00000000000..79aec204115 --- /dev/null +++ b/drivers/staging/comedi/kcomedilib/data.c @@ -0,0 +1,89 @@ +/* + kcomedilib/data.c + implements comedi_data_*() functions + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "../comedi.h" +#include "../comedilib.h" +#include "../comedidev.h" /* for comedi_udelay() */ + +#include <linux/string.h> + +int comedi_data_write(comedi_t * dev, unsigned int subdev, unsigned int chan, + unsigned int range, unsigned int aref, lsampl_t data) +{ + comedi_insn insn; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_WRITE; + insn.n = 1; + insn.data = &data; + insn.subdev = subdev; + insn.chanspec = CR_PACK(chan, range, aref); + + return comedi_do_insn(dev, &insn); +} + +int comedi_data_read(comedi_t * dev, unsigned int subdev, unsigned int chan, + unsigned int range, unsigned int aref, lsampl_t * data) +{ + comedi_insn insn; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_READ; + insn.n = 1; + insn.data = data; + insn.subdev = subdev; + insn.chanspec = CR_PACK(chan, range, aref); + + return comedi_do_insn(dev, &insn); +} + +int comedi_data_read_hint(comedi_t * dev, unsigned int subdev, + unsigned int chan, unsigned int range, unsigned int aref) +{ + comedi_insn insn; + lsampl_t dummy_data; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_READ; + insn.n = 0; + insn.data = &dummy_data; + insn.subdev = subdev; + insn.chanspec = CR_PACK(chan, range, aref); + + return comedi_do_insn(dev, &insn); +} + +int comedi_data_read_delayed(comedi_t * dev, unsigned int subdev, + unsigned int chan, unsigned int range, unsigned int aref, + lsampl_t * data, unsigned int nano_sec) +{ + int retval; + + retval = comedi_data_read_hint(dev, subdev, chan, range, aref); + if (retval < 0) + return retval; + + comedi_udelay((nano_sec + 999) / 1000); + + return comedi_data_read(dev, subdev, chan, range, aref, data); +} diff --git a/drivers/staging/comedi/kcomedilib/dio.c b/drivers/staging/comedi/kcomedilib/dio.c new file mode 100644 index 00000000000..a9f488aed36 --- /dev/null +++ b/drivers/staging/comedi/kcomedilib/dio.c @@ -0,0 +1,95 @@ +/* + kcomedilib/dio.c + implements comedi_dio_*() functions + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "../comedi.h" +#include "../comedilib.h" + +#include <linux/string.h> + +int comedi_dio_config(comedi_t * dev, unsigned int subdev, unsigned int chan, + unsigned int io) +{ + comedi_insn insn; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_CONFIG; + insn.n = 1; + insn.data = &io; + insn.subdev = subdev; + insn.chanspec = CR_PACK(chan, 0, 0); + + return comedi_do_insn(dev, &insn); +} + +int comedi_dio_read(comedi_t * dev, unsigned int subdev, unsigned int chan, + unsigned int *val) +{ + comedi_insn insn; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_READ; + insn.n = 1; + insn.data = val; + insn.subdev = subdev; + insn.chanspec = CR_PACK(chan, 0, 0); + + return comedi_do_insn(dev, &insn); +} + +int comedi_dio_write(comedi_t * dev, unsigned int subdev, unsigned int chan, + unsigned int val) +{ + comedi_insn insn; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_WRITE; + insn.n = 1; + insn.data = &val; + insn.subdev = subdev; + insn.chanspec = CR_PACK(chan, 0, 0); + + return comedi_do_insn(dev, &insn); +} + +int comedi_dio_bitfield(comedi_t * dev, unsigned int subdev, unsigned int mask, + unsigned int *bits) +{ + comedi_insn insn; + lsampl_t data[2]; + int ret; + + memset(&insn, 0, sizeof(insn)); + insn.insn = INSN_BITS; + insn.n = 2; + insn.data = data; + insn.subdev = subdev; + + data[0] = mask; + data[1] = *bits; + + ret = comedi_do_insn(dev, &insn); + + *bits = data[1]; + + return ret; +} diff --git a/drivers/staging/comedi/kcomedilib/get.c b/drivers/staging/comedi/kcomedilib/get.c new file mode 100644 index 00000000000..2004ad4480c --- /dev/null +++ b/drivers/staging/comedi/kcomedilib/get.c @@ -0,0 +1,294 @@ +/* + kcomedilib/get.c + a comedlib interface for kernel modules + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#define __NO_VERSION__ +#include "../comedi.h" +#include "../comedilib.h" +#include "../comedidev.h" + +int comedi_get_n_subdevices(comedi_t * d) +{ + comedi_device *dev = (comedi_device *) d; + + return dev->n_subdevices; +} + +int comedi_get_version_code(comedi_t * d) +{ + return COMEDI_VERSION_CODE; +} + +const char *comedi_get_driver_name(comedi_t * d) +{ + comedi_device *dev = (comedi_device *) d; + + return dev->driver->driver_name; +} + +const char *comedi_get_board_name(comedi_t * d) +{ + comedi_device *dev = (comedi_device *) d; + + return dev->board_name; +} + +int comedi_get_subdevice_type(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + + return s->type; +} + +unsigned int comedi_get_subdevice_flags(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + + return s->subdev_flags; +} + +int comedi_find_subdevice_by_type(comedi_t * d, int type, unsigned int subd) +{ + comedi_device *dev = (comedi_device *) d; + + if (subd > dev->n_subdevices) + return -ENODEV; + + for (; subd < dev->n_subdevices; subd++) { + if (dev->subdevices[subd].type == type) + return subd; + } + return -1; +} + +int comedi_get_n_channels(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + + return s->n_chan; +} + +int comedi_get_len_chanlist(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + + return s->len_chanlist; +} + +lsampl_t comedi_get_maxdata(comedi_t * d, unsigned int subdevice, + unsigned int chan) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + + if (s->maxdata_list) + return s->maxdata_list[chan]; + + return s->maxdata; +} + +#ifdef KCOMEDILIB_DEPRECATED +int comedi_get_rangetype(comedi_t * d, unsigned int subdevice, + unsigned int chan) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + int ret; + + if (s->range_table_list) { + ret = s->range_table_list[chan]->length; + } else { + ret = s->range_table->length; + } + + ret = ret | (dev->minor << 28) | (subdevice << 24) | (chan << 16); + + return ret; +} +#endif + +int comedi_get_n_ranges(comedi_t * d, unsigned int subdevice, unsigned int chan) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + int ret; + + if (s->range_table_list) { + ret = s->range_table_list[chan]->length; + } else { + ret = s->range_table->length; + } + + return ret; +} + +/* + * ALPHA (non-portable) +*/ +int comedi_get_krange(comedi_t * d, unsigned int subdevice, unsigned int chan, + unsigned int range, comedi_krange * krange) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + const comedi_lrange *lr; + + if (s->range_table_list) { + lr = s->range_table_list[chan]; + } else { + lr = s->range_table; + } + if (range >= lr->length) { + return -EINVAL; + } + memcpy(krange, lr->range + range, sizeof(comedi_krange)); + + return 0; +} + +/* + * ALPHA (may be renamed) +*/ +unsigned int comedi_get_buf_head_pos(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + comedi_async *async; + + async = s->async; + if (async == NULL) + return 0; + + return async->buf_write_count; +} + +int comedi_get_buffer_contents(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + comedi_async *async; + unsigned int num_bytes; + + if (subdevice >= dev->n_subdevices) + return -1; + async = s->async; + if (async == NULL) + return 0; + num_bytes = comedi_buf_read_n_available(s->async); + return num_bytes; +} + +/* + * ALPHA +*/ +int comedi_set_user_int_count(comedi_t * d, unsigned int subdevice, + unsigned int buf_user_count) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + comedi_async *async; + int num_bytes; + + async = s->async; + if (async == NULL) + return -1; + + num_bytes = buf_user_count - async->buf_read_count; + if (num_bytes < 0) + return -1; + comedi_buf_read_alloc(async, num_bytes); + comedi_buf_read_free(async, num_bytes); + + return 0; +} + +int comedi_mark_buffer_read(comedi_t * d, unsigned int subdevice, + unsigned int num_bytes) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + comedi_async *async; + + if (subdevice >= dev->n_subdevices) + return -1; + async = s->async; + if (async == NULL) + return -1; + + comedi_buf_read_alloc(async, num_bytes); + comedi_buf_read_free(async, num_bytes); + + return 0; +} + +int comedi_mark_buffer_written(comedi_t * d, unsigned int subdevice, + unsigned int num_bytes) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + comedi_async *async; + int bytes_written; + + if (subdevice >= dev->n_subdevices) + return -1; + async = s->async; + if (async == NULL) + return -1; + bytes_written = comedi_buf_write_alloc(async, num_bytes); + comedi_buf_write_free(async, bytes_written); + if (bytes_written != num_bytes) + return -1; + return 0; +} + +int comedi_get_buffer_size(comedi_t * d, unsigned int subdev) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdev; + comedi_async *async; + + if (subdev >= dev->n_subdevices) + return -1; + async = s->async; + if (async == NULL) + return 0; + + return async->prealloc_bufsz; +} + +int comedi_get_buffer_offset(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices + subdevice; + comedi_async *async; + + if (subdevice >= dev->n_subdevices) + return -1; + async = s->async; + if (async == NULL) + return 0; + + return async->buf_read_ptr; +} diff --git a/drivers/staging/comedi/kcomedilib/kcomedilib_main.c b/drivers/staging/comedi/kcomedilib/kcomedilib_main.c new file mode 100644 index 00000000000..510fbdcec49 --- /dev/null +++ b/drivers/staging/comedi/kcomedilib/kcomedilib_main.c @@ -0,0 +1,567 @@ +/* + kcomedilib/kcomedilib.c + a comedlib interface for kernel modules + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#define __NO_VERSION__ +#include <linux/module.h> + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/fcntl.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <asm/io.h> + +#include "../comedi.h" +#include "../comedilib.h" +#include "../comedidev.h" + +MODULE_AUTHOR("David Schleef <ds@schleef.org>"); +MODULE_DESCRIPTION("Comedi kernel library"); +MODULE_LICENSE("GPL"); + +comedi_t *comedi_open(const char *filename) +{ + struct comedi_device_file_info *dev_file_info; + comedi_device *dev; + unsigned int minor; + + if (strncmp(filename, "/dev/comedi", 11) != 0) + return NULL; + + minor = simple_strtoul(filename + 11, NULL, 0); + + if (minor >= COMEDI_NUM_BOARD_MINORS) + return NULL; + + dev_file_info = comedi_get_device_file_info(minor); + if(dev_file_info == NULL) + return NULL; + dev = dev_file_info->device; + + if(dev == NULL || !dev->attached) + return NULL; + + if (!try_module_get(dev->driver->module)) + return NULL; + + return (comedi_t *) dev; +} + +comedi_t *comedi_open_old(unsigned int minor) +{ + struct comedi_device_file_info *dev_file_info; + comedi_device *dev; + + if (minor >= COMEDI_NUM_MINORS) + return NULL; + + dev_file_info = comedi_get_device_file_info(minor); + if(dev_file_info == NULL) + return NULL; + dev = dev_file_info->device; + + if(dev == NULL || !dev->attached) + return NULL; + + return (comedi_t *) dev; +} + +int comedi_close(comedi_t * d) +{ + comedi_device *dev = (comedi_device *) d; + + module_put(dev->driver->module); + + return 0; +} + +int comedi_loglevel(int newlevel) +{ + return 0; +} + +void comedi_perror(const char *message) +{ + rt_printk("%s: unknown error\n", message); +} + +char *comedi_strerror(int err) +{ + return "unknown error"; +} + +int comedi_fileno(comedi_t * d) +{ + comedi_device *dev = (comedi_device *) d; + + /* return something random */ + return dev->minor; +} + +int comedi_command(comedi_t * d, comedi_cmd * cmd) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s; + comedi_async *async; + unsigned runflags; + + if (cmd->subdev >= dev->n_subdevices) + return -ENODEV; + + s = dev->subdevices + cmd->subdev; + if (s->type == COMEDI_SUBD_UNUSED) + return -EIO; + + async = s->async; + if (async == NULL) + return -ENODEV; + + if (s->busy) + return -EBUSY; + s->busy = d; + + if (async->cb_mask & COMEDI_CB_EOS) + cmd->flags |= TRIG_WAKE_EOS; + + async->cmd = *cmd; + + runflags = SRF_RUNNING; + +#ifdef CONFIG_COMEDI_RT + if (comedi_switch_to_rt(dev) == 0) + runflags |= SRF_RT; +#endif + comedi_set_subdevice_runflags(s, ~0, runflags); + + comedi_reset_async_buf(async); + + return s->do_cmd(dev, s); +} + +int comedi_command_test(comedi_t * d, comedi_cmd * cmd) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s; + + if (cmd->subdev >= dev->n_subdevices) + return -ENODEV; + + s = dev->subdevices + cmd->subdev; + if (s->type == COMEDI_SUBD_UNUSED) + return -EIO; + + if (s->async == NULL) + return -ENODEV; + + return s->do_cmdtest(dev, s, cmd); +} + +/* + * COMEDI_INSN + * perform an instruction + */ +int comedi_do_insn(comedi_t * d, comedi_insn * insn) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s; + int ret = 0; + + if (insn->insn & INSN_MASK_SPECIAL) { + switch (insn->insn) { + case INSN_GTOD: + { + struct timeval tv; + + do_gettimeofday(&tv); + insn->data[0] = tv.tv_sec; + insn->data[1] = tv.tv_usec; + ret = 2; + + break; + } + case INSN_WAIT: + /* XXX isn't the value supposed to be nanosecs? */ + if (insn->n != 1 || insn->data[0] >= 100) { + ret = -EINVAL; + break; + } + comedi_udelay(insn->data[0]); + ret = 1; + break; + case INSN_INTTRIG: + if (insn->n != 1) { + ret = -EINVAL; + break; + } + if (insn->subdev >= dev->n_subdevices) { + rt_printk("%d not usable subdevice\n", + insn->subdev); + ret = -EINVAL; + break; + } + s = dev->subdevices + insn->subdev; + if (!s->async) { + rt_printk("no async\n"); + ret = -EINVAL; + break; + } + if (!s->async->inttrig) { + rt_printk("no inttrig\n"); + ret = -EAGAIN; + break; + } + ret = s->async->inttrig(dev, s, insn->data[0]); + if (ret >= 0) + ret = 1; + break; + default: + ret = -EINVAL; + } + } else { + /* a subdevice instruction */ + if (insn->subdev >= dev->n_subdevices) { + ret = -EINVAL; + goto error; + } + s = dev->subdevices + insn->subdev; + + if (s->type == COMEDI_SUBD_UNUSED) { + rt_printk("%d not useable subdevice\n", insn->subdev); + ret = -EIO; + goto error; + } + + /* XXX check lock */ + + if ((ret = check_chanlist(s, 1, &insn->chanspec)) < 0) { + rt_printk("bad chanspec\n"); + ret = -EINVAL; + goto error; + } + + if (s->busy) { + ret = -EBUSY; + goto error; + } + s->busy = d; + + switch (insn->insn) { + case INSN_READ: + ret = s->insn_read(dev, s, insn, insn->data); + break; + case INSN_WRITE: + ret = s->insn_write(dev, s, insn, insn->data); + break; + case INSN_BITS: + ret = s->insn_bits(dev, s, insn, insn->data); + break; + case INSN_CONFIG: + /* XXX should check instruction length */ + ret = s->insn_config(dev, s, insn, insn->data); + break; + default: + ret = -EINVAL; + break; + } + + s->busy = NULL; + } + if (ret < 0) + goto error; +#if 0 + /* XXX do we want this? -- abbotti #if'ed it out for now. */ + if (ret != insn->n) { + rt_printk("BUG: result of insn != insn.n\n"); + ret = -EINVAL; + goto error; + } +#endif + error: + + return ret; +} + +/* + COMEDI_LOCK + lock subdevice + + arg: + subdevice number + + reads: + none + + writes: + none + + necessary locking: + - ioctl/rt lock (this type) + - lock while subdevice busy + - lock while subdevice being programmed + +*/ +int comedi_lock(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s; + unsigned long flags; + int ret = 0; + + if (subdevice >= dev->n_subdevices) { + return -EINVAL; + } + s = dev->subdevices + subdevice; + + comedi_spin_lock_irqsave(&s->spin_lock, flags); + + if (s->busy) { + ret = -EBUSY; + } else { + if (s->lock) { + ret = -EBUSY; + } else { + s->lock = d; + } + } + + comedi_spin_unlock_irqrestore(&s->spin_lock, flags); + + return ret; +} + +/* + COMEDI_UNLOCK + unlock subdevice + + arg: + subdevice number + + reads: + none + + writes: + none + +*/ +int comedi_unlock(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s; + unsigned long flags; + comedi_async *async; + int ret; + + if (subdevice >= dev->n_subdevices) { + return -EINVAL; + } + s = dev->subdevices + subdevice; + + async = s->async; + + comedi_spin_lock_irqsave(&s->spin_lock, flags); + + if (s->busy) { + ret = -EBUSY; + } else if (s->lock && s->lock != (void *)d) { + ret = -EACCES; + } else { + s->lock = NULL; + + if (async) { + async->cb_mask = 0; + async->cb_func = NULL; + async->cb_arg = NULL; + } + + ret = 0; + } + + comedi_spin_unlock_irqrestore(&s->spin_lock, flags); + + return ret; +} + +/* + COMEDI_CANCEL + cancel acquisition ioctl + + arg: + subdevice number + + reads: + nothing + + writes: + nothing + +*/ +int comedi_cancel(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s; + int ret = 0; + + if (subdevice >= dev->n_subdevices) { + return -EINVAL; + } + s = dev->subdevices + subdevice; + + if (s->lock && s->lock != d) + return -EACCES; + +#if 0 + if (!s->busy) + return 0; + + if (s->busy != d) + return -EBUSY; +#endif + + if (!s->cancel || !s->async) + return -EINVAL; + + if ((ret = s->cancel(dev, s))) + return ret; + +#ifdef CONFIG_COMEDI_RT + if (comedi_get_subdevice_runflags(s) & SRF_RT) { + comedi_switch_to_non_rt(dev); + } +#endif + comedi_set_subdevice_runflags(s, SRF_RUNNING | SRF_RT, 0); + s->async->inttrig = NULL; + s->busy = NULL; + + return 0; +} + +/* + registration of callback functions + */ +int comedi_register_callback(comedi_t * d, unsigned int subdevice, + unsigned int mask, int (*cb) (unsigned int, void *), void *arg) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s; + comedi_async *async; + + if (subdevice >= dev->n_subdevices) { + return -EINVAL; + } + s = dev->subdevices + subdevice; + + async = s->async; + if (s->type == COMEDI_SUBD_UNUSED || !async) + return -EIO; + + /* are we locked? (ioctl lock) */ + if (s->lock && s->lock != d) + return -EACCES; + + /* are we busy? */ + if (s->busy) + return -EBUSY; + + if (!mask) { + async->cb_mask = 0; + async->cb_func = NULL; + async->cb_arg = NULL; + } else { + async->cb_mask = mask; + async->cb_func = cb; + async->cb_arg = arg; + } + + return 0; +} + +int comedi_poll(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s = dev->subdevices; + comedi_async *async; + + if (subdevice >= dev->n_subdevices) { + return -EINVAL; + } + s = dev->subdevices + subdevice; + + async = s->async; + if (s->type == COMEDI_SUBD_UNUSED || !async) + return -EIO; + + /* are we locked? (ioctl lock) */ + if (s->lock && s->lock != d) + return -EACCES; + + /* are we running? XXX wrong? */ + if (!s->busy) + return -EIO; + + return s->poll(dev, s); +} + +/* WARNING: not portable */ +int comedi_map(comedi_t * d, unsigned int subdevice, void *ptr) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s; + + if (subdevice >= dev->n_subdevices) { + return -EINVAL; + } + s = dev->subdevices + subdevice; + + if (!s->async) + return -EINVAL; + + if (ptr) { + *((void **)ptr) = s->async->prealloc_buf; + } + + /* XXX no reference counting */ + + return 0; +} + +/* WARNING: not portable */ +int comedi_unmap(comedi_t * d, unsigned int subdevice) +{ + comedi_device *dev = (comedi_device *) d; + comedi_subdevice *s; + + if (subdevice >= dev->n_subdevices) { + return -EINVAL; + } + s = dev->subdevices + subdevice; + + if (!s->async) + return -EINVAL; + + /* XXX no reference counting */ + + return 0; +} diff --git a/drivers/staging/comedi/kcomedilib/ksyms.c b/drivers/staging/comedi/kcomedilib/ksyms.c new file mode 100644 index 00000000000..76b45063a9f --- /dev/null +++ b/drivers/staging/comedi/kcomedilib/ksyms.c @@ -0,0 +1,144 @@ +/* + comedi/kcomedilib/ksyms.c + a comedlib interface for kernel modules + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2001 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. + +*/ + +#ifndef EXPORT_SYMTAB +#define EXPORT_SYMTAB +#endif + +#include "../comedi.h" +#include "../comedilib.h" +#include "../comedidev.h" + +#include <linux/module.h> + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/fcntl.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> + +#if LINUX_VERSION_CODE >= 0x020200 + +/* functions specific to kcomedilib */ + +EXPORT_SYMBOL(comedi_register_callback); +EXPORT_SYMBOL(comedi_get_krange); +EXPORT_SYMBOL(comedi_get_buf_head_pos); +EXPORT_SYMBOL(comedi_set_user_int_count); +EXPORT_SYMBOL(comedi_map); +EXPORT_SYMBOL(comedi_unmap); + +/* This list comes from user-space comedilib, to show which + * functions are not ported yet. */ + +EXPORT_SYMBOL(comedi_open); +EXPORT_SYMBOL(comedi_close); + +/* logging */ +EXPORT_SYMBOL(comedi_loglevel); +EXPORT_SYMBOL(comedi_perror); +EXPORT_SYMBOL(comedi_strerror); +//EXPORT_SYMBOL(comedi_errno); +EXPORT_SYMBOL(comedi_fileno); + +/* device queries */ +EXPORT_SYMBOL(comedi_get_n_subdevices); +EXPORT_SYMBOL(comedi_get_version_code); +EXPORT_SYMBOL(comedi_get_driver_name); +EXPORT_SYMBOL(comedi_get_board_name); + +/* subdevice queries */ +EXPORT_SYMBOL(comedi_get_subdevice_type); +EXPORT_SYMBOL(comedi_find_subdevice_by_type); +EXPORT_SYMBOL(comedi_get_subdevice_flags); +EXPORT_SYMBOL(comedi_get_n_channels); +//EXPORT_SYMBOL(comedi_range_is_chan_specific); +//EXPORT_SYMBOL(comedi_maxdata_is_chan_specific); + +/* channel queries */ +EXPORT_SYMBOL(comedi_get_maxdata); +#ifdef KCOMEDILIB_DEPRECATED +EXPORT_SYMBOL(comedi_get_rangetype); +#endif +EXPORT_SYMBOL(comedi_get_n_ranges); +//EXPORT_SYMBOL(comedi_find_range); + +/* buffer queries */ +EXPORT_SYMBOL(comedi_get_buffer_size); +//EXPORT_SYMBOL(comedi_get_max_buffer_size); +//EXPORT_SYMBOL(comedi_set_buffer_size); +EXPORT_SYMBOL(comedi_get_buffer_contents); +EXPORT_SYMBOL(comedi_get_buffer_offset); + +/* low-level stuff */ +//EXPORT_SYMBOL(comedi_trigger); +//EXPORT_SYMBOL(comedi_do_insnlist); +EXPORT_SYMBOL(comedi_do_insn); +EXPORT_SYMBOL(comedi_lock); +EXPORT_SYMBOL(comedi_unlock); + +/* physical units */ +//EXPORT_SYMBOL(comedi_to_phys); +//EXPORT_SYMBOL(comedi_from_phys); + +/* synchronous stuff */ +EXPORT_SYMBOL(comedi_data_read); +EXPORT_SYMBOL(comedi_data_read_hint); +EXPORT_SYMBOL(comedi_data_read_delayed); +EXPORT_SYMBOL(comedi_data_write); +EXPORT_SYMBOL(comedi_dio_config); +EXPORT_SYMBOL(comedi_dio_read); +EXPORT_SYMBOL(comedi_dio_write); +EXPORT_SYMBOL(comedi_dio_bitfield); + +/* slowly varying stuff */ +//EXPORT_SYMBOL(comedi_sv_init); +//EXPORT_SYMBOL(comedi_sv_update); +//EXPORT_SYMBOL(comedi_sv_measure); + +/* commands */ +//EXPORT_SYMBOL(comedi_get_cmd_src_mask); +//EXPORT_SYMBOL(comedi_get_cmd_generic_timed); +EXPORT_SYMBOL(comedi_cancel); +EXPORT_SYMBOL(comedi_command); +EXPORT_SYMBOL(comedi_command_test); +EXPORT_SYMBOL(comedi_poll); + +/* buffer configuration */ +EXPORT_SYMBOL(comedi_mark_buffer_read); +EXPORT_SYMBOL(comedi_mark_buffer_written); + +//EXPORT_SYMBOL(comedi_get_range); +EXPORT_SYMBOL(comedi_get_len_chanlist); + +/* deprecated */ +//EXPORT_SYMBOL(comedi_get_timer); +//EXPORT_SYMBOL(comedi_timed_1chan); + +/* alpha */ +//EXPORT_SYMBOL(comedi_set_global_oor_behavior); + +#endif diff --git a/drivers/staging/comedi/pci_ids.h b/drivers/staging/comedi/pci_ids.h new file mode 100644 index 00000000000..c61ba90f960 --- /dev/null +++ b/drivers/staging/comedi/pci_ids.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef __COMPAT_LINUX_PCI_IDS_H +#define __COMPAT_LINUX_PCI_IDS_H + +#include <linux/pci_ids.h> + +#ifndef PCI_VENDOR_ID_AMCC +#define PCI_VENDOR_ID_AMCC 0x10e8 +#endif + +#ifndef PCI_VENDOR_ID_CBOARDS +#define PCI_VENDOR_ID_CBOARDS 0x1307 +#endif + +#ifndef PCI_VENDOR_ID_QUANCOM +#define PCI_VENDOR_ID_QUANCOM 0x8008 +#endif + +#ifndef PCI_DEVICE_ID_QUANCOM_GPIB +#define PCI_DEVICE_ID_QUANCOM_GPIB 0x3302 +#endif + +#endif // __COMPAT_LINUX_PCI_IDS_H diff --git a/drivers/staging/comedi/proc.c b/drivers/staging/comedi/proc.c new file mode 100644 index 00000000000..5a2b72d8757 --- /dev/null +++ b/drivers/staging/comedi/proc.c @@ -0,0 +1,102 @@ +/* + module/proc.c + /proc interface for comedi + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1998 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. + +*/ + +/* + This is some serious bloatware. + + Taken from Dave A.'s PCL-711 driver, 'cuz I thought it + was cool. +*/ + +#define __NO_VERSION__ +#include "comedidev.h" +#include <linux/proc_fs.h> +//#include <linux/string.h> + +int comedi_read_procmem(char *buf, char **start, off_t offset, int len, + int *eof, void *data); + +extern comedi_driver *comedi_drivers; + +int comedi_read_procmem(char *buf, char **start, off_t offset, int len, + int *eof, void *data) +{ + int i; + int devices_q = 0; + int l = 0; + comedi_driver *driv; + + l += sprintf(buf + l, + "comedi version " COMEDI_RELEASE "\n" + "format string: %s\n", + "\"%2d: %-20s %-20s %4d\",i,driver_name,board_name,n_subdevices"); + + for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) { + struct comedi_device_file_info *dev_file_info = comedi_get_device_file_info(i); + comedi_device *dev; + + if(dev_file_info == NULL) continue; + dev = dev_file_info->device; + + if (dev->attached) { + devices_q = 1; + l += sprintf(buf + l, "%2d: %-20s %-20s %4d\n", + i, + dev->driver->driver_name, + dev->board_name, dev->n_subdevices); + } + } + if (!devices_q) { + l += sprintf(buf + l, "no devices\n"); + } + + for (driv = comedi_drivers; driv; driv = driv->next) { + l += sprintf(buf + l, "%s:\n", driv->driver_name); + for (i = 0; i < driv->num_names; i++) { + l += sprintf(buf + l, " %s\n", + *(char **)((char *)driv->board_name + + i * driv->offset)); + } + if (!driv->num_names) { + l += sprintf(buf + l, " %s\n", driv->driver_name); + } + } + + return l; +} + +#ifdef CONFIG_PROC_FS +void comedi_proc_init(void) +{ + struct proc_dir_entry *comedi_proc; + + comedi_proc = create_proc_entry("comedi", S_IFREG | S_IRUGO, 0); + if (comedi_proc) + comedi_proc->read_proc = comedi_read_procmem; +} + +void comedi_proc_cleanup(void) +{ + remove_proc_entry("comedi", 0); +} +#endif diff --git a/drivers/staging/comedi/range.c b/drivers/staging/comedi/range.c new file mode 100644 index 00000000000..61dc3cd6a9f --- /dev/null +++ b/drivers/staging/comedi/range.c @@ -0,0 +1,161 @@ +/* + module/range.c + comedi routines for voltage ranges + + 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. + +*/ + +#include "comedidev.h" +#include <asm/uaccess.h> + +const comedi_lrange range_bipolar10 = { 1, {BIP_RANGE(10)} }; +const comedi_lrange range_bipolar5 = { 1, {BIP_RANGE(5)} }; +const comedi_lrange range_bipolar2_5 = { 1, {BIP_RANGE(2.5)} }; +const comedi_lrange range_unipolar10 = { 1, {UNI_RANGE(10)} }; +const comedi_lrange range_unipolar5 = { 1, {UNI_RANGE(5)} }; +const comedi_lrange range_unknown = { 1, {{0, 1000000, UNIT_none}} }; + +/* + COMEDI_RANGEINFO + range information ioctl + + arg: + pointer to rangeinfo structure + + reads: + range info structure + + writes: + n comedi_krange structures to rangeinfo->range_ptr +*/ +int do_rangeinfo_ioctl(comedi_device * dev, comedi_rangeinfo * arg) +{ + comedi_rangeinfo it; + int subd, chan; + const comedi_lrange *lr; + comedi_subdevice *s; + + if (copy_from_user(&it, arg, sizeof(comedi_rangeinfo))) + return -EFAULT; + subd = (it.range_type >> 24) & 0xf; + chan = (it.range_type >> 16) & 0xff; + + if (!dev->attached) + return -EINVAL; + if (subd >= dev->n_subdevices) + return -EINVAL; + s = dev->subdevices + subd; + if (s->range_table) { + lr = s->range_table; + } else if (s->range_table_list) { + if (chan >= s->n_chan) + return -EINVAL; + lr = s->range_table_list[chan]; + } else { + return -EINVAL; + } + + if (RANGE_LENGTH(it.range_type) != lr->length) { + DPRINTK("wrong length %d should be %d (0x%08x)\n", + RANGE_LENGTH(it.range_type), lr->length, it.range_type); + return -EINVAL; + } + + if (copy_to_user(it.range_ptr, lr->range, + sizeof(comedi_krange) * lr->length)) + return -EFAULT; + + return 0; +} + +static int aref_invalid(comedi_subdevice * s, unsigned int chanspec) +{ + unsigned int aref; + + // disable reporting invalid arefs... maybe someday + return 0; + + aref = CR_AREF(chanspec); + switch (aref) { + case AREF_DIFF: + if (s->subdev_flags & SDF_DIFF) + return 0; + break; + case AREF_COMMON: + if (s->subdev_flags & SDF_COMMON) + return 0; + break; + case AREF_GROUND: + if (s->subdev_flags & SDF_GROUND) + return 0; + break; + case AREF_OTHER: + if (s->subdev_flags & SDF_OTHER) + return 0; + break; + default: + break; + } + DPRINTK("subdevice does not support aref %i", aref); + return 1; +} + +/* + This function checks each element in a channel/gain list to make + make sure it is valid. +*/ +int check_chanlist(comedi_subdevice * s, int n, unsigned int *chanlist) +{ + int i; + int chan; + + if (s->range_table) { + for (i = 0; i < n; i++) + if (CR_CHAN(chanlist[i]) >= s->n_chan || + CR_RANGE(chanlist[i]) >= s->range_table->length + || aref_invalid(s, chanlist[i])) { + rt_printk + ("bad chanlist[%d]=0x%08x n_chan=%d range length=%d\n", + i, chanlist[i], s->n_chan, + s->range_table->length); +#if 0 + for (i = 0; i < n; i++) { + printk("[%d]=0x%08x\n", i, chanlist[i]); + } +#endif + return -EINVAL; + } + } else if (s->range_table_list) { + for (i = 0; i < n; i++) { + chan = CR_CHAN(chanlist[i]); + if (chan >= s->n_chan || + CR_RANGE(chanlist[i]) >= + s->range_table_list[chan]->length + || aref_invalid(s, chanlist[i])) { + rt_printk("bad chanlist[%d]=0x%08x\n", i, + chanlist[i]); + return -EINVAL; + } + } + } else { + rt_printk("comedi: (bug) no range type list!\n"); + return -EINVAL; + } + return 0; +} diff --git a/drivers/staging/comedi/rt.c b/drivers/staging/comedi/rt.c new file mode 100644 index 00000000000..385b81b94ac --- /dev/null +++ b/drivers/staging/comedi/rt.c @@ -0,0 +1,412 @@ +/* + comedi/rt.c + comedi kernel module + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#undef DEBUG + +#define __NO_VERSION__ +#include <linux/comedidev.h> + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/fcntl.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <asm/io.h> + +#include "rt_pend_tq.h" + +#ifdef CONFIG_COMEDI_RTAI +#include <rtai.h> +#endif + +#ifdef CONFIG_COMEDI_FUSION +#include <nucleus/asm/hal.h> +#endif + +#ifdef CONFIG_COMEDI_RTL +#include <rtl_core.h> +#include <rtl_sync.h> +#endif + +struct comedi_irq_struct { + int rt; + int irq; + irqreturn_t(*handler) (int irq, void *dev_id PT_REGS_ARG); + unsigned long flags; + const char *device; + comedi_device *dev_id; +}; + +static int comedi_rt_get_irq(struct comedi_irq_struct *it); +static int comedi_rt_release_irq(struct comedi_irq_struct *it); + +static struct comedi_irq_struct *comedi_irqs[NR_IRQS]; + +int comedi_request_irq(unsigned irq, irqreturn_t(*handler) (int, + void *PT_REGS_ARG), unsigned long flags, const char *device, + comedi_device * dev_id) +{ + struct comedi_irq_struct *it; + int ret; + /* null shared interrupt flag, since rt interrupt handlers do not + * support it, and this version of comedi_request_irq() is only + * called for kernels with rt support */ + unsigned long unshared_flags = flags & ~IRQF_SHARED; + + ret = request_irq(irq, handler, unshared_flags, device, dev_id); + if (ret < 0) { + // we failed, so fall back on allowing shared interrupt (which we won't ever make RT) + if (flags & IRQF_SHARED) { + rt_printk + ("comedi: cannot get unshared interrupt, will not use RT interrupts.\n"); + ret = request_irq(irq, handler, flags, device, dev_id); + } + if (ret < 0) { + return ret; + } + } else { + it = kzalloc(sizeof(struct comedi_irq_struct), GFP_KERNEL); + if (!it) + return -ENOMEM; + + it->handler = handler; + it->irq = irq; + it->dev_id = dev_id; + it->device = device; + it->flags = unshared_flags; + comedi_irqs[irq] = it; + } + return 0; +} + +void comedi_free_irq(unsigned int irq, comedi_device * dev_id) +{ + struct comedi_irq_struct *it; + + free_irq(irq, dev_id); + + it = comedi_irqs[irq]; + if (it == NULL) + return; + + if (it->rt) { + printk("real-time IRQ allocated at board removal (ignore)\n"); + comedi_rt_release_irq(it); + } + + kfree(it); + comedi_irqs[irq] = NULL; +} + +int comedi_switch_to_rt(comedi_device * dev) +{ + struct comedi_irq_struct *it; + unsigned long flags; + + it = comedi_irqs[dev->irq]; + /* drivers might not be using an interrupt for commands, + or we might not have been able to get an unshared irq */ + if (it == NULL) + return -1; + + comedi_spin_lock_irqsave(&dev->spinlock, flags); + + if (!dev->rt) + comedi_rt_get_irq(it); + + dev->rt++; + it->rt = 1; + + comedi_spin_unlock_irqrestore(&dev->spinlock, flags); + + return 0; +} + +void comedi_switch_to_non_rt(comedi_device * dev) +{ + struct comedi_irq_struct *it; + unsigned long flags; + + it = comedi_irqs[dev->irq]; + if (it == NULL) + return; + + comedi_spin_lock_irqsave(&dev->spinlock, flags); + + dev->rt--; + if (!dev->rt) + comedi_rt_release_irq(it); + + it->rt = 0; + + comedi_spin_unlock_irqrestore(&dev->spinlock, flags); +} + +void wake_up_int_handler(int arg1, void *arg2) +{ + wake_up_interruptible((wait_queue_head_t *) arg2); +} + +void comedi_rt_pend_wakeup(wait_queue_head_t * q) +{ + rt_pend_call(wake_up_int_handler, 0, q); +} + +/* RTAI section */ +#ifdef CONFIG_COMEDI_RTAI + +#ifndef HAVE_RT_REQUEST_IRQ_WITH_ARG +#define DECLARE_VOID_IRQ(irq) \ +static void handle_void_irq_ ## irq (void){ handle_void_irq(irq);} + +static void handle_void_irq(int irq) +{ + struct comedi_irq_struct *it; + + it = comedi_irqs[irq]; + if (it == NULL) { + rt_printk("comedi: null irq struct?\n"); + return; + } + it->handler(irq, it->dev_id PT_REGS_NULL); + rt_enable_irq(irq); //needed by rtai-adeos, seems like it shouldn't hurt earlier versions +} + +DECLARE_VOID_IRQ(0); +DECLARE_VOID_IRQ(1); +DECLARE_VOID_IRQ(2); +DECLARE_VOID_IRQ(3); +DECLARE_VOID_IRQ(4); +DECLARE_VOID_IRQ(5); +DECLARE_VOID_IRQ(6); +DECLARE_VOID_IRQ(7); +DECLARE_VOID_IRQ(8); +DECLARE_VOID_IRQ(9); +DECLARE_VOID_IRQ(10); +DECLARE_VOID_IRQ(11); +DECLARE_VOID_IRQ(12); +DECLARE_VOID_IRQ(13); +DECLARE_VOID_IRQ(14); +DECLARE_VOID_IRQ(15); +DECLARE_VOID_IRQ(16); +DECLARE_VOID_IRQ(17); +DECLARE_VOID_IRQ(18); +DECLARE_VOID_IRQ(19); +DECLARE_VOID_IRQ(20); +DECLARE_VOID_IRQ(21); +DECLARE_VOID_IRQ(22); +DECLARE_VOID_IRQ(23); + +typedef void (*V_FP_V) (void); +static V_FP_V handle_void_irq_ptrs[] = { + handle_void_irq_0, + handle_void_irq_1, + handle_void_irq_2, + handle_void_irq_3, + handle_void_irq_4, + handle_void_irq_5, + handle_void_irq_6, + handle_void_irq_7, + handle_void_irq_8, + handle_void_irq_9, + handle_void_irq_10, + handle_void_irq_11, + handle_void_irq_12, + handle_void_irq_13, + handle_void_irq_14, + handle_void_irq_15, + handle_void_irq_16, + handle_void_irq_17, + handle_void_irq_18, + handle_void_irq_19, + handle_void_irq_20, + handle_void_irq_21, + handle_void_irq_22, + handle_void_irq_23, +}; + +static int comedi_rt_get_irq(struct comedi_irq_struct *it) +{ + rt_request_global_irq(it->irq, handle_void_irq_ptrs[it->irq]); + rt_startup_irq(it->irq); + + return 0; +} + +static int comedi_rt_release_irq(struct comedi_irq_struct *it) +{ + rt_shutdown_irq(it->irq); + rt_free_global_irq(it->irq); + return 0; +} +#else + +static int comedi_rt_get_irq(struct comedi_irq_struct *it) +{ + int ret; + + ret = rt_request_global_irq_arg(it->irq, it->handler, it->flags, + it->device, it->dev_id); + if (ret < 0) { + rt_printk("rt_request_global_irq_arg() returned %d\n", ret); + return ret; + } + rt_startup_irq(it->irq); + + return 0; +} + +static int comedi_rt_release_irq(struct comedi_irq_struct *it) +{ + rt_shutdown_irq(it->irq); + rt_free_global_irq(it->irq); + return 0; +} +#endif + +void comedi_rt_init(void) +{ + rt_mount_rtai(); + rt_pend_tq_init(); +} + +void comedi_rt_cleanup(void) +{ + rt_umount_rtai(); + rt_pend_tq_cleanup(); +} + +#endif + +/* Fusion section */ +#ifdef CONFIG_COMEDI_FUSION + +static void fusion_handle_irq(unsigned int irq, void *cookie) +{ + struct comedi_irq_struct *it = cookie; + + it->handler(irq, it->dev_id PT_REGS_NULL); + rthal_irq_enable(irq); +} + +static int comedi_rt_get_irq(struct comedi_irq_struct *it) +{ + rthal_irq_request(it->irq, fusion_handle_irq, it); + rthal_irq_enable(it->irq); + return 0; +} + +static int comedi_rt_release_irq(struct comedi_irq_struct *it) +{ + rthal_irq_disable(it->irq); + rthal_irq_release(it->irq); + return 0; +} + +void comedi_rt_init(void) +{ + rt_pend_tq_init(); +} + +void comedi_rt_cleanup(void) +{ + rt_pend_tq_cleanup(); +} + +#endif /*CONFIG_COMEDI_FUSION */ + +/* RTLinux section */ +#ifdef CONFIG_COMEDI_RTL + +static unsigned int handle_rtl_irq(unsigned int irq PT_REGS_ARG) +{ + struct comedi_irq_struct *it; + + it = comedi_irqs[irq]; + if (it == NULL) + return 0; + it->handler(irq, it->dev_id PT_REGS_NULL); + rtl_hard_enable_irq(irq); + return 0; +} + +static int comedi_rt_get_irq(struct comedi_irq_struct *it) +{ + rtl_request_global_irq(it->irq, handle_rtl_irq); + return 0; +} + +static int comedi_rt_release_irq(struct comedi_irq_struct *it) +{ + rtl_free_global_irq(it->irq); + return 0; +} + +void comedi_rt_init(void) +{ + rt_pend_tq_init(); +} + +void comedi_rt_cleanup(void) +{ + rt_pend_tq_cleanup(); +} + +#endif + +#ifdef CONFIG_COMEDI_PIRQ +static int comedi_rt_get_irq(struct comedi_irq_struct *it) +{ + int ret; + + free_irq(it->irq, it->dev_id); + ret = request_irq(it->irq, it->handler, it->flags | SA_PRIORITY, + it->device, it->dev_id); + + return ret; +} + +static int comedi_rt_release_irq(struct comedi_irq_struct *it) +{ + int ret; + + free_irq(it->irq, it->dev_id); + ret = request_irq(it->irq, it->handler, it->flags, + it->device, it->dev_id); + + return ret; +} + +void comedi_rt_init(void) +{ + //rt_pend_tq_init(); +} + +void comedi_rt_cleanup(void) +{ + //rt_pend_tq_cleanup(); +} +#endif diff --git a/drivers/staging/comedi/rt_pend_tq.c b/drivers/staging/comedi/rt_pend_tq.c new file mode 100644 index 00000000000..995f076e0af --- /dev/null +++ b/drivers/staging/comedi/rt_pend_tq.c @@ -0,0 +1,113 @@ +#define __NO_VERSION__ +/* rt_pend_tq.c */ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/sched.h> +#include "comedidev.h" // for rt spinlocks +#include "rt_pend_tq.h" +#ifdef CONFIG_COMEDI_RTAI +#include <rtai.h> +#endif +#ifdef CONFIG_COMEDI_FUSION +#include <nucleus/asm/hal.h> +#endif +#ifdef CONFIG_COMEDI_RTL +#include <rtl_core.h> +#endif + +#ifdef standalone +#include <linux/module.h> +#define rt_pend_tq_init init_module +#define rt_pend_tq_cleanup cleanup_module +#endif + +volatile static struct rt_pend_tq rt_pend_tq[RT_PEND_TQ_SIZE]; +volatile static struct rt_pend_tq *volatile rt_pend_head = rt_pend_tq, + *volatile rt_pend_tail = rt_pend_tq; +int rt_pend_tq_irq = 0; +spinlock_t rt_pend_tq_lock = SPIN_LOCK_UNLOCKED; + +// WARNING: following code not checked against race conditions yet. +#define INC_CIRCULAR_PTR(ptr,begin,size) do {if(++(ptr)>=(begin)+(size)) (ptr)=(begin); } while(0) +#define DEC_CIRCULAR_PTR(ptr,begin,size) do {if(--(ptr)<(begin)) (ptr)=(begin)+(size)-1; } while(0) + +int rt_pend_call(void (*func) (int arg1, void *arg2), int arg1, void *arg2) +{ + unsigned long flags; + + if (func == NULL) + return -EINVAL; + if (rt_pend_tq_irq <= 0) + return -ENODEV; + comedi_spin_lock_irqsave(&rt_pend_tq_lock, flags); + INC_CIRCULAR_PTR(rt_pend_head, rt_pend_tq, RT_PEND_TQ_SIZE); + if (rt_pend_head == rt_pend_tail) { + // overflow, we just refuse to take this request + DEC_CIRCULAR_PTR(rt_pend_head, rt_pend_tq, RT_PEND_TQ_SIZE); + comedi_spin_unlock_irqrestore(&rt_pend_tq_lock, flags); + return -EAGAIN; + } + rt_pend_head->func = func; + rt_pend_head->arg1 = arg1; + rt_pend_head->arg2 = arg2; + comedi_spin_unlock_irqrestore(&rt_pend_tq_lock, flags); +#ifdef CONFIG_COMEDI_RTAI + rt_pend_linux_srq(rt_pend_tq_irq); +#endif +#ifdef CONFIG_COMEDI_FUSION + rthal_apc_schedule(rt_pend_tq_irq); +#endif +#ifdef CONFIG_COMEDI_RTL + rtl_global_pend_irq(rt_pend_tq_irq); + +#endif + return 0; +} + +#ifdef CONFIG_COMEDI_RTAI +void rt_pend_irq_handler(void) +#elif defined(CONFIG_COMEDI_FUSION) +void rt_pend_irq_handler(void *cookie) +#elif defined(CONFIG_COMEDI_RTL) +void rt_pend_irq_handler(int irq, void *dev PT_REGS_ARG) +#endif +{ + while (rt_pend_head != rt_pend_tail) { + INC_CIRCULAR_PTR(rt_pend_tail, rt_pend_tq, RT_PEND_TQ_SIZE); + rt_pend_tail->func(rt_pend_tail->arg1, rt_pend_tail->arg2); + } +} + +int rt_pend_tq_init(void) +{ + rt_pend_head = rt_pend_tail = rt_pend_tq; +#ifdef CONFIG_COMEDI_RTAI + rt_pend_tq_irq = rt_request_srq(0, rt_pend_irq_handler, NULL); +#endif +#ifdef CONFIG_COMEDI_FUSION + rt_pend_tq_irq = + rthal_apc_alloc("comedi APC", rt_pend_irq_handler, NULL); +#endif +#ifdef CONFIG_COMEDI_RTL + rt_pend_tq_irq = rtl_get_soft_irq(rt_pend_irq_handler, "rt_pend_irq"); +#endif + if (rt_pend_tq_irq > 0) + printk("rt_pend_tq: RT bottom half scheduler initialized OK\n"); + else + printk("rt_pend_tq: rtl_get_soft_irq failed\n"); + return 0; +} + +void rt_pend_tq_cleanup(void) +{ + printk("rt_pend_tq: unloading\n"); +#ifdef CONFIG_COMEDI_RTAI + rt_free_srq(rt_pend_tq_irq); +#endif +#ifdef CONFIG_COMEDI_FUSION + rthal_apc_free(rt_pend_tq_irq); +#endif +#ifdef CONFIG_COMEDI_RTL + free_irq(rt_pend_tq_irq, NULL); +#endif +} diff --git a/drivers/staging/comedi/rt_pend_tq.h b/drivers/staging/comedi/rt_pend_tq.h new file mode 100644 index 00000000000..01ed71bf409 --- /dev/null +++ b/drivers/staging/comedi/rt_pend_tq.h @@ -0,0 +1,10 @@ +#define RT_PEND_TQ_SIZE 16 +struct rt_pend_tq { + void (*func) (int arg1, void *arg2); + int arg1; + void *arg2; +}; +extern int rt_pend_call(void (*func) (int arg1, void *arg2), int arg1, + void *arg2); +extern int rt_pend_tq_init(void); +extern void rt_pend_tq_cleanup(void); diff --git a/drivers/staging/comedi/wrapper.h b/drivers/staging/comedi/wrapper.h new file mode 100644 index 00000000000..77fc673900e --- /dev/null +++ b/drivers/staging/comedi/wrapper.h @@ -0,0 +1,25 @@ +/* + linux/wrapper.h compatibility header + + 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. + */ + +#ifndef __COMPAT_LINUX_WRAPPER_H_ +#define __COMPAT_LINUX_WRAPPER_H_ + +#define mem_map_reserve(p) set_bit(PG_reserved, &((p)->flags)) +#define mem_map_unreserve(p) clear_bit(PG_reserved, &((p)->flags)) + +#endif /* __COMPAT_LINUX_WRAPPER_H_ */ |