diff options
author | Stephen Rothwell <sfr@canb.auug.org.au> | 2008-11-18 16:27:14 +1100 |
---|---|---|
committer | Stephen Rothwell <sfr@canb.auug.org.au> | 2008-11-18 16:27:14 +1100 |
commit | ca491540d35e0d924f08b8363b354c34c9de4be5 (patch) | |
tree | 22d40fb676b316a4bdab2771ed9bee28e065de68 /drivers/staging/comedi | |
parent | 67d3bcee8cf5f5a1cd6c923ba25554c37582ca62 (diff) | |
parent | b6dd71f343743bbcb4ad06303a57b79ef7264932 (diff) |
Merge branch 'quilt/staging'
Diffstat (limited to 'drivers/staging/comedi')
42 files changed, 16793 insertions, 0 deletions
diff --git a/drivers/staging/comedi/Kconfig b/drivers/staging/comedi/Kconfig new file mode 100644 index 000000000000..b501bfb9c754 --- /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 000000000000..afd1a19c1b87 --- /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 000000000000..557812958464 --- /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 000000000000..36d2e1b01e78 --- /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 000000000000..7d0116bcb9ff --- /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 000000000000..0ca01642c165 --- /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 000000000000..018c964396df --- /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 000000000000..63f8df558e85 --- /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 000000000000..90d57282efb8 --- /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 000000000000..61852bf5adcc --- /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 000000000000..3735355d3c58 --- /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 000000000000..fc5fc015726b --- /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 000000000000..06372b227bb2 --- /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 000000000000..158660994765 --- /dev/null +++ b/drivers/staging/comedi/drivers/Makefile @@ -0,0 +1,16 @@ +# 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 + +# 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 000000000000..3a65f0316fc9 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_bond.c @@ -0,0 +1,522 @@ +/* + 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 + +int debug = 0; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, + "If true, print extra cryptic debugging output useful only to developers probably."); + +#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; +}; +typedef struct BondingBoard BondingBoard; + +static const BondingBoard bondingBoards[] = { + { + name: MODULE_NAME, + }, +}; + +/* + * Useful for shorthand access to the particular board structure + */ +#define thisboard ((const 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. */ +}; +typedef struct BondedDevice BondedDevice; + +/* 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; +}; +typedef struct Private Private; + +/* + * most drivers define the following macro to make it easy to + * access the private structure. + */ +#define devpriv ((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(BondingBoard), + num_names:sizeof(bondingBoards) / sizeof(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(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) { + 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. */ + lsampl_t subdevMask = ((1 << bdev->nchans) - 1); /* Bits corresponding + to this subdev. */ + 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; + 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; + } + chan -= bdev->chanid_offset; /* 'real' channel id for this subdev.. */ + 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) +{ +#define MIN(a,b) (a < b ? a : b) + void *newmem = kmalloc(newlen, GFP_KERNEL); + if (newmem && oldmem) + memcpy(newmem, oldmem, MIN(oldlen, newlen)); + if (oldmem) + 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; + BondedDevice *bdev = 0; + + 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) { + if ((nchans = comedi_get_n_channels(d, sdev)) <= 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) { + BondedDevice *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); + } + if (devpriv->devs) { + kfree(devpriv->devs); + devpriv->devs = 0; + } + kfree(devpriv); + dev->private = 0; + } +} + +int __init init(void) +{ + return comedi_driver_register(&driver_bonding); +} + +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 000000000000..da8e9d2844f0 --- /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; +} + +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; +} + +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; +} + +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); + +EXPORT_SYMBOL(cfc_write_array_to_buffer); +EXPORT_SYMBOL(cfc_read_array_from_buffer); +EXPORT_SYMBOL(cfc_handle_events); diff --git a/drivers/staging/comedi/drivers/comedi_fc.h b/drivers/staging/comedi/drivers/comedi_fc.h new file mode 100644 index 000000000000..b8edd673edfd --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_fc.h @@ -0,0 +1,75 @@ +/* + 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 000000000000..9530c47806cb --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_parport.c @@ -0,0 +1,387 @@ +/* + 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); + +typedef struct parport_private_struct { + unsigned int a_data; + unsigned int c_data; + int enable_irq; +} parport_private; +#define devpriv ((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? + } + + 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("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("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("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"; + + if ((ret = alloc_subdevices(dev, 4)) < 0) + return ret; + if ((ret = alloc_private(dev, sizeof(parport_private))) < 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 000000000000..140787839153 --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_pci.h @@ -0,0 +1,89 @@ +/* + 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/version.h> +#include <linux/pci.h> + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20) +#define PCI_ENABLE_IS_REFCOUNTED +#endif + +/* + * Enables PCI device without requesting regions. Just a simple wrapper + * for pci_enable_device(). + */ +static inline int comedi_pci_enable_no_regions(struct pci_dev *pdev) +{ + return pci_enable_device(pdev); +} + +/* + * Called to disable PCI device if PCI device has been enabled, but + * PCI regions have not been reserved. + * + * It only disables the PCI device if the kernel supports reference + * counting of PCI enables, otherwise it might stop the device working + * in another driver instance. + */ +static inline void comedi_pci_disable_no_regions(struct pci_dev *pdev) +{ +#ifdef PCI_ENABLE_IS_REFCOUNTED + pci_disable_device(pdev); +#endif +} + +/* + * 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) { + comedi_pci_disable_no_regions(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 000000000000..356a9e9c3abd --- /dev/null +++ b/drivers/staging/comedi/drivers/comedi_test.c @@ -0,0 +1,526 @@ +/* + 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 */ +typedef struct waveform_board_struct { + const char *name; + int ai_chans; + int ai_bits; + int have_dio; +} waveform_board; + +#define N_CHANS 8 + +static const waveform_board waveform_boards[] = { + { + name: "comedi_test", + ai_chans:N_CHANS, + ai_bits: 16, + have_dio:0, + }, +}; + +#define thisboard ((const waveform_board *)dev->board_ptr) + +/* Data unique to this driver */ +typedef struct { + 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 + volatile unsigned long usec_current; // current time (modulo waveform period) + volatile unsigned long usec_remainder; // usec since last scan; + volatile unsigned long ai_count; // number of conversions remaining + unsigned int scan_period; // scan period in usec + unsigned int convert_period; // conversion period in usec + volatile unsigned timer_running:1; + volatile lsampl_t ao_loopbacks[N_CHANS]; +} waveform_private; +#define devpriv ((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(waveform_board), + num_names:sizeof(waveform_boards) / sizeof(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); + +static const int nano_per_micro = 1000; // 1000 nanosec in a microsec + +// 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]; + + printk("comedi%d: comedi_test: ", dev->minor); + + dev->board_name = thisboard->name; + + if (alloc_private(dev, sizeof(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; + + printk("%i microvolt, %li microsecond waveform ", + devpriv->uvolt_amplitude, devpriv->usec_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 = 0; + s->do_cmdtest = 0; + s->cancel = 0; + { + /* Our default loopback value is just a 0V flatline */ + int i; + 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("attached\n"); + + 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 000000000000..d6fb16240061 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt9812.c @@ -0,0 +1,957 @@ +/* + * 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/version.h> +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + +#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 <asm/uaccess.h> +#include <linux/usb.h> +#include "../comedidev.h" +#include "dt9812.h" + +#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); + +typedef 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; +} usb_dt9812_t; + +typedef struct comedi_dt9812 { + struct slot_dt9812 *slot; + u32 serial; +} comedi_dt9812_t; + +typedef struct slot_dt9812 { + struct semaphore mutex; + u32 serial; + usb_dt9812_t *usb; + comedi_dt9812_t *comedi; +} slot_dt9812_t; + +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 slot_dt9812_t dt9812[DT9812_NUM_SLOTS]; + +// Useful shorthand access to private data +#define devpriv ((comedi_dt9812_t *)dev->private) + +static inline usb_dt9812_t *to_dt9812_dev(struct kref *d) +{ + return container_of(d, usb_dt9812_t, kref); +} + +static void dt9812_delete(struct kref *kref) +{ + usb_dt9812_t *dev = to_dt9812_dev(kref); + + usb_put_dev(dev->udev); + kfree(dev); +} + +static int dt9812_read_info(usb_dt9812_t * dev, + int offset, void *buf, size_t buf_size) +{ + dt9812_usb_cmd_t 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); + + count = 32; + retval = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, dev->command_write.addr), &cmd, 32, // DT9812 only responds to 32 byte writes!! + &count, HZ * 1); + if (retval == 0) { + 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(usb_dt9812_t * dev, + int reg_count, u8 * address, u8 * value) +{ + dt9812_usb_cmd_t 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]; + } + count = 32; + retval = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, dev->command_write.addr), &cmd, 32, // DT9812 only responds to 32 byte writes!! + &count, HZ * 1); + if (retval == 0) { + 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(usb_dt9812_t * dev, + int reg_count, u8 * address, u8 * value) +{ + dt9812_usb_cmd_t 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]; + } + retval = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, dev->command_write.addr), &cmd, 32, // DT9812 only responds to 32 byte writes!! + &count, HZ * 1); + return retval; +} + +static int dt9812_rmw_multiple_registers(usb_dt9812_t * dev, + int reg_count, dt9812_rmw_byte_t rmw[]) +{ + dt9812_usb_cmd_t 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]; + } + retval = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, dev->command_write.addr), &cmd, 32, // DT9812 only responds to 32 byte writes!! + &count, HZ * 1); + return retval; +} + +static int dt9812_digital_in(slot_dt9812_t * 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(slot_dt9812_t * 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(slot_dt9812_t * 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(usb_dt9812_t * dev, + dt9812_rmw_byte_t * 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, the 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(usb_dt9812_t * dev, + dt9812_rmw_byte_t * rmw, dt9812_gain_t 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(slot_dt9812_t * slot, + int channel, u16 * value, dt9812_gain_t gain) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + dt9812_rmw_byte_t rmw[3]; + + // 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 == 0) { + // read the status and ADC + u8 reg[3] = { F020_SFR_ADC0CN, F020_SFR_ADC0H, + F020_SFR_ADC0L + }; + u8 val[3]; + result = dt9812_read_multiple_registers(slot->usb, 3, + reg, val); + if (result == 0) { + // 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; + } + } + } + } + } + up(&slot->mutex); + return result; +} + +static int dt9812_analog_out_shadow(slot_dt9812_t * 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(slot_dt9812_t * slot, int channel, u16 value) +{ + int result = -ENODEV; + + down(&slot->mutex); + if (slot->usb) { + dt9812_rmw_byte_t 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; + usb_dt9812_t *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) { + err("Out of memory"); + 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) { + err("Endpoint has wrong direction."); + 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 + int i; + + usb_reset_configuration(dev->udev); + for (i = 0; i < 10; i++) { + retval = dt9812_read_info(dev, 1, &fw, sizeof(fw)); + if (retval == 0) { + printk("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 a + 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 + slot_dt9812_t *first = NULL; + slot_dt9812_t *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) +{ + usb_dt9812_t *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 = { +#ifdef COMEDI_HAVE_USB_DRIVER_OWNER + .owner = THIS_MODULE, +#endif + .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; + + for (n = 0; n < insn->n; n++) { + u16 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(comedi_dt9812_t)) < 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 = 0; + 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 = 0; + s->insn_write = &dt9812_ao_winsn; + s->insn_read = &dt9812_ao_rinsn; + + printk("comedi%d: successfully attached to dt9812.\n", dev->minor); + + down(&dt9812_mutex); + // Find a slot for the comedi device + { + slot_dt9812_t *first = NULL; + slot_dt9812_t *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) { + err("usb_register failed. Error number %d", 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"); + +#endif // LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) diff --git a/drivers/staging/comedi/drivers/dt9812.h b/drivers/staging/comedi/drivers/dt9812.h new file mode 100644 index 000000000000..180fd5a39575 --- /dev/null +++ b/drivers/staging/comedi/drivers/dt9812.h @@ -0,0 +1,176 @@ +#ifndef __DT9812_H__ +#define __DT9812_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 + +typedef 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 +} dt9812_devid_t; + +typedef enum { + 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, +} dt9812_gain_t; + +typedef enum { + DT9812_LEAST_USB_FIRMWARE_CMD_CODE = 0, + DT9812_W_FLASH_DATA = 0, // Write Flash memory + DT9812_R_FLASH_DATA = 1, // Read Flash memory (misc config info) + + // Register read/write commands for processor + DT9812_R_SINGLE_BYTE_REG = 2, // Read a single byte of USB memory + DT9812_W_SINGLE_BYTE_REG = 3, // Write a single byte of USB memory + DT9812_R_MULTI_BYTE_REG = 4, // Multiple Reads of USB memory + DT9812_W_MULTI_BYTE_REG = 5, // Multiple Writes of USB memory + DT9812_RMW_SINGLE_BYTE_REG = 6, // Read, (AND) with mask, OR value, + // then write (single) + DT9812_RMW_MULTI_BYTE_REG = 7, // Read, (AND) with mask, OR value, + // then write (multiple) + + // Register read/write commands for SMBus + DT9812_R_SINGLE_BYTE_SMBUS = 8, // Read a single byte of SMBus + DT9812_W_SINGLE_BYTE_SMBUS = 9, // Write a single byte of SMBus + DT9812_R_MULTI_BYTE_SMBUS = 10, // Multiple Reads of SMBus + DT9812_W_MULTI_BYTE_SMBUS = 11, // Multiple Writes of SMBus + + // Register read/write commands for a device + DT9812_R_SINGLE_BYTE_DEV = 12, // Read a single byte of a device + DT9812_W_SINGLE_BYTE_DEV = 13, // Write a single byte of a device + DT9812_R_MULTI_BYTE_DEV = 14, // Multiple Reads of a device + DT9812_W_MULTI_BYTE_DEV = 15, // Multiple Writes of a device + + DT9812_W_DAC_THRESHOLD = 16, // Not sure if we'll need this + + DT9812_W_INT_ON_CHANGE_MASK = 17, // Set interrupt on change mask + + DT9812_W_CGL = 18, // Write (or Clear) the CGL for the ADC + DT9812_R_MULTI_BYTE_USBMEM = 19, // Multiple Reads of USB memory + DT9812_W_MULTI_BYTE_USBMEM = 20, // Multiple Writes to USB memory + + DT9812_START_SUBSYSTEM = 21, // Issue a start command to a + // given subsystem + DT9812_STOP_SUBSYSTEM = 22, // Issue a stop command to a + // given subsystem + + DT9812_CALIBRATE_POT = 23, //calibrate the board using CAL_POT_CMD + DT9812_W_DAC_FIFO_SIZE = 24, // set the DAC FIFO size + DT9812_W_CGL_DAC = 25, // Write (or Clear) the CGL for the DAC + DT9812_R_SINGLE_VALUE_CMD = 26, // Read a single value from a subsystem + DT9812_W_SINGLE_VALUE_CMD = 27, // Write a single value to a subsystem + DT9812_MAX_USB_FIRMWARE_CMD_CODE // Valid DT9812_USB_FIRMWARE_CMD_CODE's + // will be less than this number +} dt9812_usb_firmware_cmd_t; + +typedef struct { + u16 numbytes; + u16 address; +} dt9812_flash_data_t; + +#define DT9812_MAX_NUM_MULTI_BYTE_RDS \ + ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / sizeof(u8)) + +typedef struct { + u8 count; + u8 address[DT9812_MAX_NUM_MULTI_BYTE_RDS]; +} dt9812_read_multi_t; + +typedef struct { + u8 address; + u8 value; +} dt9812_write_byte_t; + +#define DT9812_MAX_NUM_MULTI_BYTE_WRTS \ + ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / sizeof(dt9812_write_byte_t)) + +typedef struct { + u8 count; + dt9812_write_byte_t write[DT9812_MAX_NUM_MULTI_BYTE_WRTS]; +} dt9812_write_multi_t; + +typedef struct { + u8 address; + u8 and_mask; + u8 or_value; +} dt9812_rmw_byte_t; + +#define DT9812_MAX_NUM_MULTI_BYTE_RMWS \ + ((DT9812_MAX_WRITE_CMD_PIPE_SIZE - 4 - 1) / sizeof(dt9812_rmw_byte_t)) + +typedef struct { + u8 count; + dt9812_rmw_byte_t rmw[DT9812_MAX_NUM_MULTI_BYTE_RMWS]; +} dt9812_rmw_multi_t; + +typedef struct dt9812_usb_cmd { + + u32 cmd; + union { + dt9812_flash_data_t flash_data_info; + dt9812_read_multi_t read_multi_info; + dt9812_write_multi_t write_multi_info; + dt9812_rmw_multi_t 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 +} dt9812_usb_cmd_t; + +#endif diff --git a/drivers/staging/comedi/drivers/mite.c b/drivers/staging/comedi/drivers/mite.c new file mode 100644 index 000000000000..9cc527424d04 --- /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 000000000000..90a656b5c5b7 --- /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 "../comedidev.h" +#include "../pci.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/usbdux.c b/drivers/staging/comedi/drivers/usbdux.c new file mode 100644 index 000000000000..33205530b0d3 --- /dev/null +++ b/drivers/staging/comedi/drivers/usbdux.c @@ -0,0 +1,2981 @@ +#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" +#include "../usb.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 +#define NUMOFINBUFFERSHIGH 10 // must have more buffers due to buggy USB ctr + +// Number of out-URBs which send the data: min=5 +#define NUMOFOUTBUFFERSHIGH 10 // must have more buffers due to buggy USB ctr + +// 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 +typedef struct { + // 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; +#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; + // 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; +} usbduxsub_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 usbduxsub_t 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(usbduxsub_t * usbduxsub_tmp) +{ + int i = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,8) + int j = 0; +#endif + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbIn) { + for (i = 0; i < usbduxsub_tmp->numOfInBuffers; i++) { + if (usbduxsub_tmp->urbIn[i]) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,8) + j = usb_unlink_urb(usbduxsub_tmp->urbIn[i]); + if (j < 0) { + err = j; + } +#else + // We wait here until all transfers + // have been cancelled. + usb_kill_urb(usbduxsub_tmp->urbIn[i]); +#endif + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi: usbdux: unlinked InURB %d, err=%d\n", + i, err); +#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 usbdux_ai_stop(usbduxsub_t * this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) { + printk("comedi?: usbdux_ai_stop: this_usbduxsub=NULL!\n"); + return -EFAULT; + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi: usbdux_ai_stop\n"); +#endif + + 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) +{ + usbduxsub_t *this_usbduxsub; + int res = 0; + + // force unlink of all urbs +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi: usbdux_ai_cancel\n"); +#endif + this_usbduxsub = dev->private; + if (!this_usbduxsub) { + printk("comedi: usbdux_ai_cancel: this_usbduxsub=NULL\n"); + return -EFAULT; + } + // 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 +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +static void usbduxsub_ai_IsocIrq(struct urb *urb) +#else +static void usbduxsub_ai_IsocIrq(struct urb *urb PT_REGS_ARG) +#endif +{ + int i, err, n; + usbduxsub_t *this_usbduxsub; + comedi_device *this_comedidev; + comedi_subdevice *s; + + // sanity checks + // is the urb there? + if (!urb) { + printk("comedi_: usbdux_: ao int-handler called with urb=NULL!\n"); + return; + } + // the context variable points to the subdevice + this_comedidev = urb->context; + if (unlikely(!this_comedidev)) { + printk("comedi_: usbdux_: BUG! urb context is a NULL pointer!\n"); + return; + } + // the private structure of the subdevice is usbduxsub_t + this_usbduxsub = this_comedidev->private; + if (unlikely(!this_usbduxsub)) { + printk("comedi_: usbdux_: BUG! private of comedi subdev is a NULL pointer!\n"); + 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: + // 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 +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbdux: CRC error in ISO IN stream.\n", + this_usbduxsub->comedidev->minor); +#endif + + break; + + // happens after an unlink command + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + 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; + + // a real error on the bus + default: + // pass error to comedi if we are really running a command + if (this_usbduxsub->ai_cmd_running) { + printk("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); + if (unlikely(err < 0)) { + printk("comedi_: usbdux_: urb resubmit failed in int-context! err=%d ", err); + if (err == -EL2NSYNC) { + printk("--> buggy USB host controller or bug in IRQ handler!\n"); + } else { + printk("\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(usbduxsub_t * usbduxsub_tmp) +{ + int i = 0; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,8) + int j = 0; +#endif + + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbOut) { + for (i = 0; i < usbduxsub_tmp->numOfOutBuffers; i++) { + if (usbduxsub_tmp->urbOut[i]) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,8) + j = usb_unlink_urb(usbduxsub_tmp->urbOut[i]); + if (j < err) { + err = j; + } +#else + usb_kill_urb(usbduxsub_tmp->urbOut[i]); +#endif + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi: usbdux: unlinked OutURB %d: res=%d\n", + i, err); +#endif + } + } + return err; +} + +/* This will cancel a running acquisition operation + * in any context. + */ +static int usbdux_ao_stop(usbduxsub_t * this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) { +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi?: usbdux_ao_stop: this_usbduxsub=NULL!\n"); +#endif + return -EFAULT; + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi: usbdux_ao_cancel\n"); +#endif + 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) +{ + usbduxsub_t *this_usbduxsub = dev->private; + int res = 0; + + if (!this_usbduxsub) { + printk("comedi: usbdux_ao_cancel: this_usbduxsub=NULL\n"); + 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; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +static void usbduxsub_ao_IsocIrq(struct urb *urb) +{ +#else +static void usbduxsub_ao_IsocIrq(struct urb *urb PT_REGS_ARG) +{ +#endif + int i, ret; + int8_t *datap; + usbduxsub_t *this_usbduxsub; + comedi_device *this_comedidev; + comedi_subdevice *s; + + if (!urb) { + printk("comedi_: usbdux_: ao urb handler called with NULL ptr.\n"); + return; + } + // the context variable points to the subdevice + this_comedidev = urb->context; + if (!this_comedidev) { + printk("comedi_: usbdux_: ao urb int-context is a NULL pointer.\n"); + return; + } + // the private structure of the subdevice is usbduxsub_t + this_usbduxsub = this_comedidev->private; + if (!this_usbduxsub) { + printk("comedi_: usbdux_: private data structure of ao subdev is NULL p.\n"); + return; + } + + s = this_comedidev->subdevices + SUBDEV_DA; + + switch (urb->status) { + case 0: + /* success */ + break; + + // after an unlink command, unplug, ... etc + // no unlink needed here. Already shutting down. + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + 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; + + // a real error + default: + if (this_usbduxsub->ao_cmd_running) { + printk("comedi_: usbdux_: 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) { + printk("comedi: usbdux: 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; + if ((ret = USB_SUBMIT_URB(urb)) < 0) { + printk("comedi_: usbdux_: ao urb resubm failed in int-cont."); + printk("ret=%d", ret); + if (ret == EL2NSYNC) { + printk("--> buggy USB host controller or bug in IRQ handling!\n"); + } else { + printk("\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(usbduxsub_t * 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) { + printk("comedi_: usbdux_: control msg failed (start)\n"); + return errcode; + } + } + return 0; +} + +static int usbduxsub_stop(usbduxsub_t * 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) { + printk("comedi_: usbdux: control msg failed (stop)\n"); + return errcode; + } + } + return 0; +} + +static int usbduxsub_upload(usbduxsub_t * usbduxsub, + uint8_t * local_transfer_buffer, + unsigned int startAddr, unsigned int len) +{ + int errcode; + + if (usbduxsub->probed) { +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbdux: uploading %d bytes", + usbduxsub->comedidev->minor, len); + printk(" to addr %d, first byte=%d.\n", + startAddr, local_transfer_buffer[0]); +#endif + 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); +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi_: usbdux: result=%d\n", errcode); +#endif + if (errcode < 0) { + printk("comedi_: usbdux: uppload failed\n"); + return errcode; + } + } else { + // no device on the bus for this index + return -EFAULT; + } + return 0; +} + +int firmwareUpload(usbduxsub_t * usbduxsub, + uint8_t * firmwareBinary, int sizeFirmware) +{ + int ret; + + if (!firmwareBinary) { + return 0; + } + ret = usbduxsub_stop(usbduxsub); + if (ret < 0) { + printk("comedi_: usbdux: can not stop firmware\n"); + return ret; + } + ret = usbduxsub_upload(usbduxsub, firmwareBinary, 0, sizeFirmware); + if (ret < 0) { + printk("comedi_: usbdux: firmware upload failed\n"); + return ret; + } + ret = usbduxsub_start(usbduxsub); + if (ret < 0) { + printk("comedi_: usbdux: can not start firmware\n"); + return ret; + } + return 0; +} + +int usbduxsub_submit_InURBs(usbduxsub_t * 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; +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: usbdux: 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)); +#endif + errFlag = USB_SUBMIT_URB(usbduxsub->urbIn[i]); + if (errFlag) { + printk("comedi_: usbdux: ai: "); + printk("USB_SUBMIT_URB(%d)", i); + printk(" error %d\n", errFlag); + return errFlag; + } + } + return 0; +} + +int usbduxsub_submit_OutURBs(usbduxsub_t * usbduxsub) +{ + int i, errFlag; + + if (!usbduxsub) { + return -EFAULT; + } + for (i = 0; i < usbduxsub->numOfOutBuffers; i++) { +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi_: usbdux: submitting out-urb[%d]\n", i); +#endif + // 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]); + if (errFlag) { + printk("comedi_: usbdux: ao: "); + printk("USB_SUBMIT_URB(%d)", i); + printk(" error %d\n", 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; + usbduxsub_t *this_usbduxsub = dev->private; + if (!(this_usbduxsub->probed)) { + return -ENODEV; + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: usbdux_ai_cmdtest\n", dev->minor); +#endif + /* 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(usbduxsub_t * this_usbduxsub, int cmd_type) +{ + int result, nsent; + + this_usbduxsub->dux_commands[0] = cmd_type; +#ifdef NOISY_DUX_DEBUGBUG + printk("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 * HZ); + if (result < 0) { + printk("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(usbduxsub_t * 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 * HZ); + if (result < 0) { + printk("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 + printk("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; + usbduxsub_t *this_usbduxsub = dev->private; + if (!this_usbduxsub) { + return -EFAULT; + } + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: usbdux_ai_inttrig\n", dev->minor); +#endif + + if (trignum != 0) { + printk("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) { + printk("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 { + printk("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; + usbduxsub_t *this_usbduxsub = dev->private; + int result; + +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: usbdux_ai_cmd\n", dev->minor); +#endif + if (!this_usbduxsub) { + return -EFAULT; + } + // 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) { + printk("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) { + printk("comedi%d: channel list too long\n", dev->minor); + break; + } + this_usbduxsub->dux_commands[i + 2] = + create_adc_command(chan, range); + } + +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi %d: sending commands to the usb device: ", dev->minor); + printk("size=%u\n", NUMCHANNELS); +#endif + if ((result = send_dux_commands(this_usbduxsub, SENDADCOMMANDS)) < 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) { + printk("comedi%d: usbdux: 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; + usbduxsub_t *this_usbduxsub = dev->private; + + if (!this_usbduxsub) { + printk("comedi%d: ai_insn_read: no usb dev.\n", dev->minor); + return 0; + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: ai_insn_read, insn->n=%d, insn->subdev=%d\n", + dev->minor, insn->n, insn->subdev); +#endif + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ai_cmd_running) { + printk("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 + if ((err = send_dux_commands(this_usbduxsub, SENDSINGLEAD)) < 0) { + up(&this_usbduxsub->sem); + return err; + } + + for (i = 0; i < insn->n; i++) { + if ((err = receive_dux_commands(this_usbduxsub, + SENDSINGLEAD)) < 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); + usbduxsub_t *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); + usbduxsub_t *this_usbduxsub = dev->private; + +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: ao_insn_write\n", dev->minor); +#endif + if (!this_usbduxsub) { + return -EFAULT; + } + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } + if (this_usbduxsub->ao_cmd_running) { + printk("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++) { +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: ao_insn_write: data[chan=%d,i=%d]=%d\n", + dev->minor, chan, i, data[i]); +#endif + // 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); + if ((err = send_dux_commands(this_usbduxsub, + SENDDACOMMANDS)) < 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; + usbduxsub_t *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) { + printk("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) { + printk("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 { + printk("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; + usbduxsub_t *this_usbduxsub = dev->private; + + if (!this_usbduxsub) { + return -EFAULT; + } + if (!(this_usbduxsub->probed)) { + return -ENODEV; + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: usbdux_ao_cmdtest\n", dev->minor); +#endif + /* 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++; + } + } + +#ifdef NOISY_DUX_DEBUGBUG + printk("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); +#endif + + 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; + usbduxsub_t *this_usbduxsub = dev->private; + + if (!this_usbduxsub) { + return -EFAULT; + } + down(&this_usbduxsub->sem); + if (!(this_usbduxsub->probed)) { + up(&this_usbduxsub->sem); + return -ENODEV; + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: usbdux_ao_cmd\n", dev->minor); +#endif + + // 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) { + printk("comedi%d: usbdux_ao_cmd: channel list too long\n", dev->minor); + break; + } + this_usbduxsub->dac_commands[i] = (chan << 6); +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: dac command for ch %d is %x\n", + dev->minor, i, this_usbduxsub->dac_commands[i]); +#endif + } + + // 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; +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: usbdux: 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); + printk("comedi%d: usbdux: ao_timer=%d (ms)\n", + dev->minor, this_usbduxsub->ao_timer); +#endif + if (this_usbduxsub->ao_timer < 1) { + printk("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) +{ + + usbduxsub_t *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 + if ((err = send_dux_commands(this_usbduxsub, SENDDIOBITSCOMMAND)) < 0) { + up(&this_usbduxsub->sem); + return err; + } + if ((err = receive_dux_commands(this_usbduxsub, + SENDDIOBITSCOMMAND)) < 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) +{ + usbduxsub_t *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; + } + + if ((err = send_dux_commands(this_usbduxsub, READCOUNTERCOMMAND)) < 0) { + up(&this_usbduxsub->sem); + return err; + } + + if ((err = receive_dux_commands(this_usbduxsub, + READCOUNTERCOMMAND)) < 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) +{ + usbduxsub_t *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); + + if ((err = send_dux_commands(this_usbduxsub, WRITECOUNTERCOMMAND)) < 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(usbduxsub_t * usbduxsub_tmp) +{ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,8) + int j = 0; +#endif + + int err = 0; + + if (usbduxsub_tmp && usbduxsub_tmp->urbPwm) { + if (usbduxsub_tmp->urbPwm) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,8) + j = usb_unlink_urb(usbduxsub_tmp->urbPwm); + if (j < err) { + err = j; + } +#else + usb_kill_urb(usbduxsub_tmp->urbPwm); +#endif + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi: usbdux: unlinked PwmURB: res=%d\n", err); +#endif + } + return err; +} + +/* This cancels a running acquisition operation + * in any context. + */ +static int usbdux_pwm_stop(usbduxsub_t * this_usbduxsub, int do_unlink) +{ + int ret = 0; + + if (!this_usbduxsub) { +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi?: usbdux_pwm_stop: this_usbduxsub=NULL!\n"); +#endif + return -EFAULT; + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi: usbdux_pwm_cancel\n"); +#endif + 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) +{ + usbduxsub_t *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); + +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi %d: sending pwm off command to the usb device.\n", + dev->minor); +#endif + if ((res = send_dux_commands(this_usbduxsub, SENDPWMOFF)) < 0) { + return res; + } + + return res; +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) || LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19) +static void usbduxsub_pwm_irq(struct urb *urb) +{ +#else +static void usbduxsub_pwm_irq(struct urb *urb, struct pt_regs *regs) +{ +#endif + int ret; + usbduxsub_t *this_usbduxsub; + comedi_device *this_comedidev; + comedi_subdevice *s; + + // printk("PWM: IRQ\n"); + + if (!urb) { + printk("comedi_: usbdux_: pwm urb handler called with NULL ptr.\n"); + return; + } + // the context variable points to the subdevice + this_comedidev = urb->context; + if (!this_comedidev) { + printk("comedi_: usbdux_: pwm urb int-context is a NULL pointer.\n"); + return; + } + // the private structure of the subdevice is usbduxsub_t + this_usbduxsub = this_comedidev->private; + if (!this_usbduxsub) { + printk("comedi_: usbdux_: private data structure of pwm subdev is NULL p.\n"); + return; + } + + s = this_comedidev->subdevices + SUBDEV_DA; + + switch (urb->status) { + case 0: + /* success */ + break; + + // after an unlink command, unplug, ... etc + // no unlink needed here. Already shutting down. + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -ECONNABORTED: + if (this_usbduxsub->pwm_cmd_running) { + usbdux_pwm_stop(this_usbduxsub, 0); + } + return; + + // a real error + default: + if (this_usbduxsub->pwm_cmd_running) { + printk("comedi_: usbdux_: 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) { + if ((ret = USB_SUBMIT_URB(urb)) < 0) { + printk("comedi_: usbdux_: pwm urb resubm failed in int-cont."); + printk("ret=%d", ret); + if (ret == EL2NSYNC) { + printk("--> buggy USB host controller or bug in IRQ handling!\n"); + } else { + printk("\n"); + } + // don't do an unlink here + usbdux_pwm_stop(this_usbduxsub, 0); + } + } +} + +int usbduxsub_submit_PwmURBs(usbduxsub_t * usbduxsub) +{ + int errFlag; + + if (!usbduxsub) { + return -EFAULT; + } +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi_: usbdux: submitting pwm-urb\n"); +#endif + // 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); + if (errFlag) { + printk("comedi_: usbdux: pwm: "); + printk("USB_SUBMIT_URB"); + printk(" error %d\n", errFlag); + return errFlag; + } + return 0; +} + +static int usbdux_pwm_period(comedi_device * dev, comedi_subdevice * s, + lsampl_t period) +{ + usbduxsub_t *this_usbduxsub = dev->private; + int fx2delay=255; + if (period < MIN_PWM_PERIOD) + { + printk("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) { + printk("comedi%d: period %d for pwm is too low.\n", + dev->minor, period); + return -EAGAIN; + } + } + this_usbduxsub->pwmDelay=fx2delay; + this_usbduxsub->pwmPeriod=period; +#ifdef NOISY_DUX_DEBUGBUG + printk("usbdux_pwm_period: frequ=%d, period=%d\n",period,fx2delay); +#endif + 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; + usbduxsub_t *this_usbduxsub = dev->private; + +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: usbdux_pwm_start\n", dev->minor); +#endif + if (this_usbduxsub->pwm_cmd_running) { + // already running + return 0; + } + + this_usbduxsub->dux_commands[1] = ((int8_t) this_usbduxsub->pwmDelay); + if ((ret = send_dux_commands(this_usbduxsub, SENDPWMON)) < 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) +{ + usbduxsub_t *this_usbduxsub = dev->private; + int i, szbuf; + char *pBuf; + char pwm_mask,sgn_mask,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) +{ + usbduxsub_t *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 + return usbdux_pwm_pattern(dev,s, + CR_CHAN(insn->chanspec), + data[0], + 0); // relay sign 0 by default +} + + +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) +{ + usbduxsub_t *this_usbduxsub = dev->private; + switch (data[0]) { + case INSN_CONFIG_ARM: +#ifdef NOISY_DUX_DEBUGBUG + // switch it on + printk("comedi%d: pwm_insn_config: pwm on\n", + dev->minor); +#endif + // 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: +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: pwm_insn_config: pwm off\n", + dev->minor); +#endif + 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: +#ifdef NOISY_DUX_DEBUGBUG + printk("comedi%d: pwm_insn_config: setting period\n", + dev->minor); +#endif + 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, + CR_CHAN(insn->chanspec), // the channel number + data[1], // actual PWM data + (data[2]!=0)); // just a sign + 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(usbduxsub_t * usbduxsub_tmp) +{ + int i; + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbdux: tiding up\n"); +#endif + if (!usbduxsub_tmp) { + return; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) + // shows the usb subsystem that the driver is down + if (usbduxsub_tmp->interface) { + usb_set_intfdata(usbduxsub_tmp->interface, NULL); + } +#endif + + 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++) { + if (usbduxsub_tmp->urbIn[i]->transfer_buffer) { + kfree(usbduxsub_tmp->urbIn[i]->transfer_buffer); + usbduxsub_tmp->urbIn[i]->transfer_buffer = NULL; + } + if (usbduxsub_tmp->urbIn[i]) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,8) + usb_kill_urb(usbduxsub_tmp->urbIn[i]); +#endif + 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]) { +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,8) + usb_kill_urb(usbduxsub_tmp->urbOut[i]); +#endif + 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); + } + if (usbduxsub_tmp->urbPwm->transfer_buffer) { + kfree(usbduxsub_tmp->urbPwm->transfer_buffer); + usbduxsub_tmp->urbPwm->transfer_buffer = NULL; + } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,8) + usb_kill_urb(usbduxsub_tmp->urbPwm); +#endif + usb_free_urb(usbduxsub_tmp->urbPwm); + usbduxsub_tmp->urbPwm = NULL; + } + if (usbduxsub_tmp->inBuffer) { + kfree(usbduxsub_tmp->inBuffer); + usbduxsub_tmp->inBuffer = NULL; + } + if (usbduxsub_tmp->insnBuffer) { + kfree(usbduxsub_tmp->insnBuffer); + usbduxsub_tmp->insnBuffer = NULL; + } + if (usbduxsub_tmp->inBuffer) { + kfree(usbduxsub_tmp->inBuffer); + usbduxsub_tmp->inBuffer = NULL; + } + if (usbduxsub_tmp->dac_commands) { + kfree(usbduxsub_tmp->dac_commands); + usbduxsub_tmp->dac_commands = NULL; + } + if (usbduxsub_tmp->dux_commands) { + 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(usbduxsub_t * usbduxsub, void *firmwarePtr, long size) +{ + 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) { + printk("comedi_: usbdux: 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_: usbdux: 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_: usbdux: 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_: usbdux: firmware upload goes beyond FX2 RAM boundaries."); + return -EFAULT; + } + //printk("comedi_: usbdux: 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_: usbdux: 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_: usbdux: 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 +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +static void *usbduxsub_probe(struct usb_device *udev, + unsigned int interfnum, const struct usb_device_id *id) +{ +#else +static int usbduxsub_probe(struct usb_interface *uinterf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(uinterf); +#endif + int i; + int index; + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbdux_: finding a free structure for the usb-device\n"); +#endif + 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) { + printk("Too many usbdux-devices connected.\n"); + up(&start_stop_sem); + return PROBE_ERR_RETURN(-EMFILE); + } +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbdux: usbduxsub[%d] is ready to connect to comedi.\n", index); +#endif + + init_MUTEX(&(usbduxsub[index].sem)); + // save a pointer to the usb device + usbduxsub[index].usbdev = udev; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + // save the interface number + usbduxsub[index].ifnum = interfnum; +#else + // 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])); +#endif + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbdux: ifnum=%d\n", usbduxsub[index].ifnum); +#endif + // 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) { + printk("comedi_: usbdux: error alloc space for dac commands\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_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) { + printk("comedi_: usbdux: error alloc space for dac commands\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_RETURN(-ENOMEM); + } + // create space for the in buffer and set it to zero + usbduxsub[index].inBuffer = kzalloc(SIZEINBUF, GFP_KERNEL); + if (!(usbduxsub[index].inBuffer)) { + printk("comedi_: usbdux: could not alloc space for inBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_RETURN(-ENOMEM); + } + // create space of the instruction buffer + usbduxsub[index].insnBuffer = kzalloc(SIZEINSNBUF, GFP_KERNEL); + if (!(usbduxsub[index].insnBuffer)) { + printk("comedi_: usbdux: could not alloc space for insnBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_RETURN(-ENOMEM); + } + // create space for the outbuffer + usbduxsub[index].outBuffer = kzalloc(SIZEOUTBUF, GFP_KERNEL); + if (!(usbduxsub[index].outBuffer)) { + printk("comedi_: usbdux: could not alloc space for outBuffer\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_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) { + printk("comedi_: usbdux%d: could not set alternate setting 3 in high speed.\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_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)) { + printk("comedi_: usbdux: Could not alloc. urbIn array\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_RETURN(-ENOMEM); + } + for (i = 0; i < usbduxsub[index].numOfInBuffers; i++) { + // one frame: 1ms + usbduxsub[index].urbIn[i] = USB_ALLOC_URB(1); + if (usbduxsub[index].urbIn[i] == NULL) { + printk("comedi_: usbdux%d: Could not alloc. urb(%d)\n", + index, i); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_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)) { + printk("comedi_: usbdux%d: could not alloc. transb.\n", + index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_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)) { + printk("comedi_: usbdux: Could not alloc. urbOut array\n"); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_RETURN(-ENOMEM); + } + for (i = 0; i < usbduxsub[index].numOfOutBuffers; i++) { + // one frame: 1ms + usbduxsub[index].urbOut[i] = USB_ALLOC_URB(1); + if (usbduxsub[index].urbOut[i] == NULL) { + printk("comedi_: usbdux%d: Could not alloc. urb(%d)\n", + index, i); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_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)) { + printk("comedi_: usbdux%d: could not alloc. transb.\n", + index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_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) { + usbduxsub[index].sizePwmBuf = 512; // max bulk ep size in high speed + usbduxsub[index].urbPwm = USB_ALLOC_URB(0); + if (usbduxsub[index].urbPwm == NULL) { + printk("comedi_: usbdux%d: Could not alloc. pwm urb\n", + index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_RETURN(-ENOMEM); + } + usbduxsub[index].urbPwm->transfer_buffer = + kzalloc(usbduxsub[index].sizePwmBuf, GFP_KERNEL); + if (!(usbduxsub[index].urbPwm->transfer_buffer)) { + printk("comedi_: usbdux%d: could not alloc. transb. for pwm\n", index); + tidy_up(&(usbduxsub[index])); + up(&start_stop_sem); + return PROBE_ERR_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); + printk("comedi_: usbdux%d has been successfully initialised.\n", index); +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + return (void *)(&usbduxsub[index]); +#else + // success + return 0; +#endif +} + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +static void usbduxsub_disconnect(struct usb_device *udev, void *ptr) +{ + usbduxsub_t *usbduxsub_tmp = (usbduxsub_t *) ptr; +#else +static void usbduxsub_disconnect(struct usb_interface *intf) +{ + usbduxsub_t *usbduxsub_tmp = usb_get_intfdata(intf); + struct usb_device *udev = interface_to_usbdev(intf); +#endif + if (!usbduxsub_tmp) { + printk("comedi_: usbdux: disconnect called with null pointer.\n"); + return; + } + if (usbduxsub_tmp->usbdev != udev) { + printk("comedi_: usbdux: 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); +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbdux: disconnected from the usb\n"); +#endif +} + +// is called when comedi-config is called +static int usbdux_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 < NUMUSBDUX; i++) { + if ((usbduxsub[i].probed) && (!usbduxsub[i].attached)) { + index = i; + break; + } + } + + if (index < 0) { + printk("comedi%d: usbdux: error: attach failed, no usbdux devs connected to the usb bus.\n", dev->minor); + up(&start_stop_sem); + return -ENODEV; + } + + down(&(usbduxsub[index].sem)); + // pointer back to the corresponding comedi device + usbduxsub[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(usbduxsub + index, + comedi_aux_data(it->options, 0), + it->options[COMEDI_DEVCONF_AUX_DATA_LENGTH]); + } + + dev->board_name = BOARDNAME; + + /* set number of subdevices */ + if (usbduxsub[index].high_speed) { + // with pwm + dev->n_subdevices = 5; + } else { + // without pwm + dev->n_subdevices = 4; + } + + // allocate space for the subdevices + if ((ret = alloc_subdevices(dev, dev->n_subdevices)) < 0) { + printk("comedi%d: usbdux: error alloc space for subdev\n", + dev->minor); + up(&start_stop_sem); + return ret; + } + + printk("comedi%d: usbdux: usb-device %d is attached to comedi.\n", + dev->minor, index); + // private structure is also simply the usb-structure + dev->private = usbduxsub + 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; + // 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 (usbduxsub[index].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 = usbduxsub[index].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 + usbduxsub[index].attached = 1; + + up(&(usbduxsub[index].sem)); + + up(&start_stop_sem); + + printk("comedi%d: attached to usbdux.\n", dev->minor); + + return 0; +} + +static int usbdux_detach(comedi_device * dev) +{ + usbduxsub_t *usbduxsub_tmp; + +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbdux: detach usb device\n", dev->minor); +#endif + + if (!dev) { + printk("comedi?: usbdux: detach without dev variable...\n"); + return -EFAULT; + } + + usbduxsub_tmp = dev->private; + if (!usbduxsub_tmp) { + printk("comedi?: usbdux: detach without ptr to usbduxsub[]\n"); + return -EFAULT; + } + + 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; +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi%d: usbdux: detach: successfully removed\n", dev->minor); +#endif + 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; +#ifdef CONFIG_COMEDI_DEBUG + printk("comedi_: usbdux: 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 < 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 = { +#ifdef COMEDI_HAVE_USB_DRIVER_OWNER + owner:THIS_MODULE, +#endif + 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 000000000000..403a614000b1 --- /dev/null +++ b/drivers/staging/comedi/drivers/usbduxfast.c @@ -0,0 +1,1779 @@ +#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" +#include "../usb.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; + if ((err = USB_SUBMIT_URB(urb)) < 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); + if (errFlag) { + printk("comedi_: usbduxfast: ai: "); + printk("USB_SUBMIT_URB"); + printk(" 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 PROBE_ERR_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 PROBE_ERR_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 PROBE_ERR_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 PROBE_ERR_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 PROBE_ERR_RETURN(-ENODEV); + } + usbduxfastsub[index].urbIn = USB_ALLOC_URB(0); + if (usbduxfastsub[index].urbIn == NULL) { + printk("comedi_: usbduxfast%d: Could not alloc. urb\n", index); + tidy_up(&(usbduxfastsub[index])); + up(&start_stop_sem); + return PROBE_ERR_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 PROBE_ERR_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 000000000000..16532ec7a4b1 --- /dev/null +++ b/drivers/staging/comedi/interrupt.h @@ -0,0 +1,67 @@ +/* + 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 + +/* if interrupt handler prototype has pt_regs* parameter */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 19) +#define PT_REGS_ARG , struct pt_regs *regs +#define PT_REGS_CALL , regs +#define PT_REGS_NULL , NULL +#else +#define PT_REGS_ARG +#define PT_REGS_CALL +#define PT_REGS_NULL +#endif + +#endif diff --git a/drivers/staging/comedi/kcomedilib/Makefile b/drivers/staging/comedi/kcomedilib/Makefile new file mode 100644 index 000000000000..ffcc9ad32adb --- /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 000000000000..79aec2041150 --- /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 000000000000..a9f488aed369 --- /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 000000000000..2004ad4480c0 --- /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 000000000000..510fbdcec493 --- /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 000000000000..76b45063a9fc --- /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.h b/drivers/staging/comedi/pci.h new file mode 100644 index 000000000000..962e4348ac22 --- /dev/null +++ b/drivers/staging/comedi/pci.h @@ -0,0 +1,31 @@ +/* + * linux/pci.h compatibility header + */ + +#ifndef _COMPAT_PCI_H +#define _COMPAT_PCI_H + +#include <linux/version.h> + +#include <linux/pci.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +#define pci_get_device pci_find_device +#define pci_get_subsys pci_find_subsys +#define pci_dev_get(x) (x) +#define pci_dev_put(x) +#endif + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,4,22) +static inline char *pci_name(struct pci_dev *pdev) +{ + return pdev->slot_name; +} +#endif + +#ifndef DEFINE_PCI_DEVICE_TABLE +#define DEFINE_PCI_DEVICE_TABLE(_table) \ + struct pci_device_id _table[] +#endif + +#endif /* _COMPAT_PCI_H */ diff --git a/drivers/staging/comedi/pci_ids.h b/drivers/staging/comedi/pci_ids.h new file mode 100644 index 000000000000..c61ba90f9601 --- /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 000000000000..7db12ac0d280 --- /dev/null +++ b/drivers/staging/comedi/proc.c @@ -0,0 +1,100 @@ +/* + 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; +} + +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); +} diff --git a/drivers/staging/comedi/range.c b/drivers/staging/comedi/range.c new file mode 100644 index 000000000000..61dc3cd6a9fd --- /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 000000000000..385b81b94ac5 --- /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 000000000000..995f076e0af3 --- /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 000000000000..01ed71bf409d --- /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/usb.h b/drivers/staging/comedi/usb.h new file mode 100644 index 000000000000..40ad651d86c0 --- /dev/null +++ b/drivers/staging/comedi/usb.h @@ -0,0 +1,75 @@ +/* + + linux/usb.h compatibility header + + Copyright (C) 2003 Bernd Porr, Bernd.Porr@cn.stir.ac.uk + + 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_USB_H_ +#define __COMPAT_LINUX_USB_H_ + +#include <linux/version.h> +#include <linux/time.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +#include <linux/kernel.h> + +#define USB_ALLOC_URB(x) usb_alloc_urb(x) +#define USB_SUBMIT_URB(x) usb_submit_urb(x) +#define URB_ISO_ASAP USB_ISO_ASAP +#define PROBE_ERR_RETURN(x) NULL +#define usb_get_dev(x) (x) +#define usb_put_dev(x) +#define interface_to_usbdev(intf) NULL +#else +#define USB_ALLOC_URB(x) usb_alloc_urb(x,GFP_KERNEL) +#define USB_SUBMIT_URB(x) usb_submit_urb(x,GFP_ATOMIC) +#define PROBE_ERR_RETURN(x) (x) +#endif + +#include <linux/usb.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,12) +static inline int USB_CONTROL_MSG(struct usb_device *dev, unsigned int pipe, + __u8 request, __u8 requesttype, __u16 value, __u16 index, + void *data, __u16 size, int millisec_timeout) +{ + return usb_control_msg(dev, pipe, request, requesttype, value, index, + data, size, msecs_to_jiffies(millisec_timeout)); +} +static inline int USB_BULK_MSG(struct usb_device *usb_dev, unsigned int pipe, + void *data, int len, int *actual_length, int millisec_timeout) +{ + return usb_bulk_msg(usb_dev, pipe, data, len, actual_length, + msecs_to_jiffies(millisec_timeout)); +} +#else +#define USB_CONTROL_MSG usb_control_msg +#define USB_BULK_MSG usb_bulk_msg +#endif + +/* + * Determine whether we need the "owner" member of struct usb_driver and + * define COMEDI_HAVE_USB_DRIVER_OWNER if we need it. + */ +#if LINUX_VERSION_CODE > KERNEL_VERSION(2,4,19) \ + && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,16) +#define COMEDI_HAVE_USB_DRIVER_OWNER +#endif + +#endif diff --git a/drivers/staging/comedi/wrapper.h b/drivers/staging/comedi/wrapper.h new file mode 100644 index 000000000000..77fc673900e9 --- /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_ */ |