From 3f363c623f87ff5d53e46598531594e124949437 Mon Sep 17 00:00:00 2001 From: Frank Mori Hess Date: Sun, 23 Nov 2003 18:55:48 +0000 Subject: [PATCH] added patch from Klaas Gadeyne, with my own modifications. --- comedi/drivers/comedi_counter_unstable.h | 105 ++ comedi/drivers/ni_660x.c | 1608 +++++++++++++++++++--- 2 files changed, 1499 insertions(+), 214 deletions(-) create mode 100644 comedi/drivers/comedi_counter_unstable.h diff --git a/comedi/drivers/comedi_counter_unstable.h b/comedi/drivers/comedi_counter_unstable.h new file mode 100644 index 00000000..7e802fad --- /dev/null +++ b/comedi/drivers/comedi_counter_unstable.h @@ -0,0 +1,105 @@ +/* + comedi_counter_unstable.h + + This is for the development of comedi's counter API. Any API + described here should be considered under development and + subject to change! Eventually, this should get merged into + comedi.h when consensus is reached on a good API. + + Copyright (C) 2003 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. + +************************************************************************/ + +/* ALPHA counter stuff */ +/* + +ID=INSN_CONFIG_COUNTER_ALPHA: counter configuration + + The subdevice has an accumulator which changes based on + triggers. + + [0] - ID + [1] - trigger0 (comedi_counter_trigger_bits() is helpful) + [2] - action0 (action associated with trigger0, see comedi_counter_actions + for possibilities) + [3] - trigger1 + [4] - action 1 + .... + + You can specify as many trigger/action pairs as you like. + + Example Modes: + Up counter, counting falling edges on input 0 + + data[1] = comedi_counter_trigger_bits(0, CR_EDGE | CR_INVERT); + data[2] = COMEDI_INC_ACCUMULATOR; + + Down counter, counting rising edges on input 0, and using input 1 as a gate + (input 1 must be high to count): + + data[1] = comedi_counter_trigger_bits(0, CR_EDGE) | comedi_counter_trigger_bits(1, 0); + data[2] = COMEDI_DEC_ACCUMULATOR; + + Quadrature x1 encoding: + data[1] = comedi_counter_trigger_bits(0, CR_EDGE) | + comedi_counter_trigger_bits(1, CR_INVERT); + data[2] = COMEDI_INC_ACCUMULATOR; + data[3] = comedi_counter_trigger_bits(0, CR_EDGE | CR_INVERT) | + comedi_counter_trigger_bits(1, CR_INVERT); + data[4] = COMEDI_DEC_ACCUMULATOR; + + Notes: + Could add fields for specifying what inputs are connected to (input 0 connected + to external input pin, or internal oscillator?). + + Could make triggers/actions more involved, for example "trigger on accumulator + reaches 0", or specify a value to reload the counter with for the + COMEDI_RESET_ACCUMULATOR action. Could add an action that sets the counter's + output, like "pulse output when accumulator reaches 0". + + Using this instruction is a bit of a pain. On the user-end, the user has + to figure out exactly how to describe what her counter is doing in order to + program it, even if the counter only supports a few different modes of + operation. This could be + eased by providing helper functions that fill out an insn with the + appropriate values for particular pieces of hardware. On the driver end, + it might make things easier if we provided some functions for operations + on sets of trigger/action pairs. For example, queries like "is A a subset + of B" where A and B are sets of trigger/action pairs. +*/ +#define INSN_CONFIG_COUNTER_ALPHA 0x1000 + +static inline int comedi_counter_trigger_bits(unsigned int input_num, int flags) +{ + static const int low_bit = 0x1; + static const int edge_bit = 0x2; + static const int valid_bit = 0x3; + static const int bits_per_channel = 3; + int bits = valid_bit; + + if(flags & CR_EDGE) + bits |= edge_bit; + if(flags & CR_INVERT) + bits |= low_bit; + return bits << (input_num * bits_per_channel); +} +enum comedi_counter_actions +{ + COMEDI_INC_ACCUMULATOR, + COMEDI_DEC_ACCUMULATOR, + COMEDI_RESET_ACCUMULATOR, +}; diff --git a/comedi/drivers/ni_660x.c b/comedi/drivers/ni_660x.c index 06e78ba4..acc3f518 100644 --- a/comedi/drivers/ni_660x.c +++ b/comedi/drivers/ni_660x.c @@ -1,93 +1,649 @@ /* - comedi/drivers/ni_660x.c - Hardware driver for NI 660x devices - - 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. + comedi/drivers/ni_660x.c + Hardware driver for NI 660x devices + + 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: ni_660x.o -Description: National Instruments 660x -Author: J.P. Mellor -Updated: -Devices: [National Instruments] PCI-6601 (ni_660x), PCI-6602 -Status: unknown +Description: National Instruments 660x counter/timer boards +Devices: +[National Instruments] PCI-6601 (ni_660x), PCI-6602 +Author: J.P. Mellor , + Herman.Bruyninckx@mech.kuleuven.ac.be, + Wim.Meeussen@mech.kuleuven.ac.be, + Klaas.Gadeyne@mech.kuleuven.ac.be, + Frank Mori Hess +Updated: Sun Nov 16 18:46:11 UTC 2003 +Status: experimental + +Encoders work, but only with instructions, commands are not +supported yet. PulseGeneration (both single pulse and pulse train) +works. DIO is experimental (8 channels only). Interrupts do not +work. + +The counter API is unstable, see comedi_counter_unstable.h and +the directory comedi/Documentation/comedi/ (the files counter-spec and +insn_config). + +References: +DAQ 660x Register-Level Programmer Manual (NI 370505A-01) +DAQ 6601/6602 User Manual (NI 322137B-01) + +Things to do: +- Add DMA support (see mite.c and ni_pcidio.c for examples) +- Add commands (copy from ni_pcidio.c ?) +- Add interrupts +- Extend "Application possibilities" for the GPCT subdevice (eg. Time +Measurement, ...) +*/ -Commands are not supported. +#include +#include "mite.h" +#include "comedi_counter_unstable.h" + +#define CTRS_PER_CHIP 4 // The number of counters per ni-tio chip +#define DATA_1B 0x1 // 1 byte = 8 bits data +#define DATA_2B 0x2 // 2 bytes = 16 bit data +#define DATA_4B 0x4 // 4 bytes = 32 bit data + +/* See Register-Level Programmer Manual page 3.1 */ +typedef enum + { + G0InterruptAcknowledge, + G0StatusRegister, + G1InterruptAcknowledge, + G1StatusRegister, + G01StatusRegister, + G0CommandRegister, + G1CommandRegister, + G0HWSaveRegister, + G1HWSaveRegister, + G0SWSaveRegister, + G1SWSaveRegister, + G0ModeRegister, + G01JointStatus1Register, + G1ModeRegister, + G0LoadARegister, + G01JointStatus2Register, + G0LoadBRegister, + G1LoadARegister, + G1LoadBRegister, + G0InputSelectRegister, + G1InputSelectRegister, + G01JointResetRegister, + G0InterruptEnable, + G1InterruptEnable, + G0CountingModeRegister, + G1CountingModeRegister, + G0SecondGateRegister, + G1SecondGateRegister, + G0DMAConfigRegister, + G0DMAStatusRegister, + G1DMAConfigRegister, + G1DMAStatusRegister, + G2InterruptAcknowledge, + G2StatusRegister, + G3InterruptAcknowledge, + G3StatusRegister, + G23StatusRegister, + G2CommandRegister, + G3CommandRegister, + G2HWSaveRegister, + G3HWSaveRegister, + G2SWSaveRegister, + G3SWSaveRegister, + G2ModeRegister, + G23JointStatus1Register, + G3ModeRegister, + G2LoadARegister, + G23JointStatus2Register, + G2LoadBRegister, + G3LoadARegister, + G3LoadBRegister, + G2InputSelectRegister, + G3InputSelectRegister, + G23JointResetRegister, + G2InterruptEnable, + G3InterruptEnable, + G2CountingModeRegister, + G3CountingModeRegister, + G3SecondGateRegister, + G2SecondGateRegister, + G2DMAConfigRegister, + G2DMAStatusRegister, + G3DMAConfigRegister, + G3DMAStatusRegister, + ClockConfigRegister, + IOConfigReg0_3, + IOConfigReg4_7, + IOConfigReg8_11, + IOConfigReg12_15, + IOConfigReg16_19, + IOConfigReg20_23, + IOConfigReg24_27, + IOConfigReg28_31, + IOConfigReg32_35, + IOConfigReg36_39, + STCDIOParallelInput, + STCDIOOutput, + STCDIOControl, + STCDIOSerialInput, + NumRegisters, + } NI_660xRegisters; +static inline int GxCommandRegister(int counter_channel) +{ + switch(counter_channel) + { + case 0: + return G0CommandRegister; + break; + case 1: + return G1CommandRegister; + break; + case 2: + return G2CommandRegister; + break; + case 3: + return G3CommandRegister; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + return 0; +} +static inline int GxCountingModeRegister(int counter_channel) +{ + switch(counter_channel) + { + case 0: + return G0CountingModeRegister; + break; + case 1: + return G1CountingModeRegister; + break; + case 2: + return G2CountingModeRegister; + break; + case 3: + return G3CountingModeRegister; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + return 0; +} +static inline int GxInputSelectRegister(int counter_channel) +{ + switch(counter_channel) + { + case 0: + return G0InputSelectRegister; + break; + case 1: + return G1InputSelectRegister; + break; + case 2: + return G2InputSelectRegister; + break; + case 3: + return G3InputSelectRegister; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + return 0; +} +static inline int GxxJointResetRegister(int counter_channel) +{ + switch(counter_channel) + { + case 0: + case 1: + return G01JointResetRegister; + break; + case 2: + case 3: + return G23JointResetRegister; + break; + default: + DPRINTK("ni_660x.c: bug!, line %i", __LINE__); + break; + } + return 0; +} +static inline int GxLoadARegister(int counter_channel) +{ + switch(counter_channel) + { + case 0: + return G0LoadARegister; + break; + case 1: + return G1LoadARegister; + break; + case 2: + return G2LoadARegister; + break; + case 3: + return G3LoadARegister; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + return 0; +} +static inline int GxLoadBRegister(int counter_channel) +{ + switch(counter_channel) + { + case 0: + return G0LoadBRegister; + break; + case 1: + return G1LoadBRegister; + break; + case 2: + return G2LoadBRegister; + break; + case 3: + return G3LoadBRegister; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + return 0; +} +static inline int GxModeRegister(int counter_channel) +{ + switch(counter_channel) + { + case 0: + return G0ModeRegister; + break; + case 1: + return G1ModeRegister; + break; + case 2: + return G2ModeRegister; + break; + case 3: + return G3ModeRegister; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + return 0; +} +static inline int GxSWSaveRegister(int counter_channel) +{ + switch(counter_channel) + { + case 0: + return G0SWSaveRegister; + break; + case 1: + return G1SWSaveRegister; + break; + case 2: + return G2SWSaveRegister; + break; + case 3: + return G3SWSaveRegister; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + return 0; +} +static inline int IOConfigReg(int chipset, int counter_channel) +{ + if(chipset == 0) + { + switch(counter_channel) + { + case 0: + return IOConfigReg36_39; + break; + case 1: + return IOConfigReg32_35; + break; + case 2: + return IOConfigReg28_31; + break; + case 3: + return IOConfigReg24_27; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + }else + { + switch(counter_channel) + { + case 0: + return IOConfigReg20_23; + break; + case 1: + return IOConfigReg16_19; + break; + case 2: + return IOConfigReg12_15; + break; + case 3: + return IOConfigReg8_11; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + } + return 0; +} +typedef struct +{ + char *name; // Register Name + int offset; // Offset from base address from GPCT chip + int direction; // read or write, ie INSN_READ or ... + int size; // 1 byte, 2 bytes, or 4 bytes +} NI_660xRegisterData; + +#define INSN_RW 3 // Unused, could be used to check that register can + // both be written or read + +const NI_660xRegisterData registerData[NumRegisters] = + { + {"G0 Interrupt Acknowledge", 0x004, INSN_WRITE, DATA_2B}, + {"G0 Status Register", 0x004, INSN_READ, DATA_2B}, + {"G1 Interrupt Acknowledge", 0x006, INSN_WRITE, DATA_2B}, + {"G1 Status Register", 0x006, INSN_READ, DATA_2B}, + {"G01 Status Register ", 0x008, INSN_READ, DATA_2B}, + {"G0 Command Register", 0x00C, INSN_WRITE, DATA_2B}, + {"G1 Command Register", 0x00E, INSN_WRITE, DATA_2B}, + {"G0 HW Save Register", 0x010, INSN_READ, DATA_4B}, + {"G1 HW Save Register", 0x014, INSN_READ, DATA_4B}, + {"G0 SW Save Register", 0x018, INSN_READ, DATA_4B}, + {"G1 SW Save Register", 0x01C, INSN_READ, DATA_4B}, + {"G0 Mode Register", 0x034, INSN_WRITE, DATA_2B}, + {"G01 Joint Status 1 Register", 0x036, INSN_READ, DATA_2B}, + {"G1 Mode Register", 0x036, INSN_WRITE, DATA_2B}, + {"G0 Load A Register", 0x038, INSN_WRITE, DATA_4B}, + {"G01 Joint Status 2 Register", 0x03A, INSN_READ, DATA_2B}, + {"G0 Load B Register", 0x03C, INSN_WRITE, DATA_4B}, + {"G1 Load A Register", 0x040, INSN_WRITE, DATA_4B}, + {"G1 Load B Register", 0x044, INSN_WRITE, DATA_4B}, + {"G0 Input Select Register", 0x048, INSN_WRITE, DATA_2B}, + {"G1 Input Select Register", 0x04A, INSN_WRITE, DATA_2B}, + {"G01 Joint Reset Register", 0x090, INSN_WRITE, DATA_2B}, + {"G0 Interrupt Enable", 0x092, INSN_WRITE, DATA_2B}, + {"G1 Interrupt Enable", 0x096, INSN_WRITE, DATA_2B}, + {"G0 Counting Mode Register", 0x0B0, INSN_WRITE, DATA_2B}, + {"G1 Counting Mode Register", 0x0B2, INSN_WRITE, DATA_2B}, + {"G0 Second Gate Register", 0x0B4, INSN_WRITE, DATA_2B}, + {"G1 Second Gate Register", 0x0B6, INSN_WRITE, DATA_2B}, + {"G0 DMA Config Register", 0x0B8, INSN_WRITE, DATA_2B}, + {"G0 DMA Status Register", 0x0B8, INSN_READ, DATA_2B}, + {"G1 DMA Config Register", 0x0BA, INSN_WRITE, DATA_2B}, + {"G1 DMA Status Register", 0x0BA, INSN_READ, DATA_2B}, + {"G2 Interrupt Acknowledge", 0x104, INSN_WRITE, DATA_2B}, + {"G2 Status Register", 0x104, INSN_READ, DATA_2B}, + {"G3 Interrupt Acknowledge", 0x106, INSN_WRITE, DATA_2B}, + {"G3 Status Register", 0x106, INSN_READ, DATA_2B}, + {"G23 Status Register", 0x108, INSN_READ, DATA_2B}, + {"G2 Command Register", 0x10C, INSN_WRITE, DATA_2B}, + {"G3 Command Register", 0x10E, INSN_WRITE, DATA_2B}, + {"G2 HW Save Register", 0x110, INSN_READ, DATA_4B}, + {"G3 HW Save Register", 0x114, INSN_READ, DATA_4B}, + {"G2 SW Save Register", 0x118, INSN_READ, DATA_4B}, + {"G3 SW Save Register", 0x11C, INSN_READ, DATA_4B}, + {"G2 Mode Register", 0x134, INSN_WRITE, DATA_2B}, + {"G23 Joint Status 1 Register", 0x136, INSN_READ, DATA_2B}, + {"G3 Mode Register", 0x136, INSN_WRITE, DATA_2B}, + {"G2 Load A Register", 0x138, INSN_WRITE, DATA_4B}, + {"G23 Joint Status 2 Register", 0x13A, INSN_READ, DATA_2B}, + {"G2 Load B Register", 0x13C, INSN_WRITE, DATA_4B}, + {"G3 Load A Register", 0x140, INSN_WRITE, DATA_4B}, + {"G3 Load B Register", 0x144, INSN_WRITE, DATA_4B}, + {"G2 Input Select Register", 0x148, INSN_WRITE, DATA_2B}, + {"G3 Input Select Register", 0x14A, INSN_WRITE, DATA_2B}, + {"G23 Joint Reset Register", 0x190, INSN_WRITE, DATA_2B}, + {"G2 Interrupt Enable", 0x192, INSN_WRITE, DATA_2B}, + {"G3 Interrupt Enable", 0x196, INSN_WRITE, DATA_2B}, + {"G2 Counting Mode Register", 0x1B0, INSN_WRITE, DATA_2B}, + {"G3 Counting Mode Register", 0x1B2, INSN_WRITE, DATA_2B}, + {"G3 Second Gate Register", 0x1B6, INSN_WRITE, DATA_2B}, + {"G2 Second Gate Register", 0x1B4, INSN_WRITE, DATA_2B}, + + {"G2 DMA Config Register", 0x1B8, INSN_WRITE, DATA_2B}, + {"G2 DMA Status Register", 0x1B8, INSN_READ, DATA_2B}, + {"G3 DMA Config Register", 0x1BA, INSN_WRITE, DATA_2B}, + {"G3 DMA Status Register", 0x1BA, INSN_READ, DATA_2B}, + {"Clock Config Register", 0x73C, INSN_WRITE, DATA_4B}, + {"IO Config Register 0-3", 0x77C, INSN_RW, DATA_4B}, // READWRITE + {"IO Config Register 4-7", 0x780, INSN_RW, DATA_4B}, + {"IO Config Register 8-11", 0x784, INSN_RW, DATA_4B}, + {"IO Config Register 12-15", 0x788, INSN_RW, DATA_4B}, + {"IO Config Register 16-19", 0x78C, INSN_RW, DATA_4B}, + {"IO Config Register 20-23", 0x790, INSN_RW, DATA_4B}, + {"IO Config Register 24-27", 0x794, INSN_RW, DATA_4B}, + {"IO Config Register 28-31", 0x798, INSN_RW, DATA_4B}, + {"IO Config Register 32-35", 0x79C, INSN_RW, DATA_4B}, + {"IO Config Register 36-39", 0x7A0, INSN_RW, DATA_4B}, + {"STD DIO Parallel Input", 0x00E, INSN_READ, DATA_2B}, + {"STD DIO Output", 0x014, INSN_WRITE, DATA_2B}, + {"STD DIO Control", 0x016, INSN_WRITE, DATA_2B}, + {"STD DIO Serial Input", 0x038, INSN_READ, DATA_2B}, + }; + + +/* Different Application Classes for GPCT Subdevices */ +/* The list is not exhaustive and needs discussion! */ +typedef enum + { + CountingAndTimeMeasurement, + SinglePulseGeneration, + PulseTrainGeneration, + PositionMeasurement, + Miscellaneous + } NI_660x_GPCT_AppClass; + + +/* Config struct for different GPCT subdevice Application Classes and + their options */ +typedef struct +{ + NI_660x_GPCT_AppClass App; + /* Application dependent data, eg. for encoders, See mail Herman Bruyninckx + + and Adapted by Klaas Gadeyne with real-life experience :-) + */ + int data[6]; +} NI_660x_GPCT_Config; + +#define NI_660X_GPCT_MAXCHANNELS 8 // To avoid dyn. mem. allocation +NI_660x_GPCT_Config ni_660x_gpct_config[NI_660X_GPCT_MAXCHANNELS]; + +/* Some bits to write in different registers */ +#define UpDownDown 0x0<<5 // always count down +#define UpDownUp 0x1<<5 // always count up +#define UpDownHardware 0x1<<6 // up/down depending on + // hardware pin +#define UpDownGate 0x3<<5 // depending on hardware + // internal GATE +#define Disarm 0x1<<4 +#define Load 0x1<<2 +#define Arm 0x1<<0 + +#define IndexPhaseLowLow 0x0<<5 // Index Pulse active when both + // A and B are low +#define IndexPhaseLowHigh 0x1<<5 // ... +#define IndexPhaseHighLow 0x2<<5 +#define IndexPhaseHighHigh 0x3<<5 + +#define IndexMode 0x1<<4 +// For quadrature encoders +#define CountingModeNormal 0x0<<0 +#define CountingModeQuadX1 0x1<<0 +#define CountingModeQuadX2 0x2<<0 +#define CountingModeQuadX4 0x3<<0 + +// For 2-pulse encoders +#define CountingModeTwoPulse 0x4<<0 +#define CountingModeSynchronous 0x6<<0 + +#define GateSelectPin38 0x1<<8 // Take internal time-based 20 + // MHz clockx +#define SourceSelectTimebase1 0x0<<2 +#define SourceSelectTimebase2 0x12<<2 +#define SourceSelectTimebase3 0x1e<<2 +#define GateSelectSource 0x0<<7 + +#define TriggerModeStartStop 0x0<<3 +#define TriggerModeStopStart 0x1<<3 +#define TriggerModeStart 0x2<<3 +#define TriggerModeNotUsed 0x3<<3 +#define GatingModeDisabled 0x0<<0 +#define GatingModeLevel 0x1<<0 +#define GatingModeRising 0x2<<0 +#define GatingModeFalling 0x3<<0 +#define G1Reset 0x1<<3 +#define G0Reset 0x1<<2 +#define G1Armed 0x1<<9 +#define G0Armed 0x1<<8 +// kind of ENABLE for the second counter +#define CounterSwap 0x1<<21 +static inline int GxReset(int counter_channel) +{ + switch(counter_channel) + { + case 0: + return G0Reset; + break; + case 1: + return G1Reset; + break; + case 2: + return G0Reset; + break; + case 3: + return G1Reset; + break; + default: + DPRINTK("ni_660x: bug!, line %i\n", __LINE__); + break; + } + return 0; +} -/* Things to do: +#define LoadOnTC 0x1<<12 - Add General-Purpose Counter/Timer as a subdevice -*/ +#define OutputIsTC 0x1<<8 +#define OutputTogglesOnTC 0x2<<8 +#define OutputTogglesOnTCorGate 0x3<<8 -#include -#include -#include -#include -#include -#include -#include -#include -#include +#define DisarmAtTCStopsCounting 0x1<<11 +#define NoHardwareDisarm 0x0<<11 +#define StopOn2ndTC 0x1<<6 +#define LoadSourceSelectA 0x0<<7 -#include +#define SynchroniseGate 0x1<<8 -#include -#include "mite.h" +// For pulse train generation +#define BankSwitchEnable 0x1<<12 +#define BankSwitchOnGate 0x0<<11 +#define BankSwitchOnSoftware 0x1<<11 +#define BankSwitchStart 0x1<<10 +#define ReloadSourceSwitching 0x1<<15 -#define PCI_VENDOR_ID_NATINST 0x1093 +// ioconfigreg +/*pin index 0 corresponds to pin A in manual, index 1 is pin B, etc*/ +static inline int pin_is_output(int pin_index) +{ + return 0x1 << (24 - 8 * pin_index); +} +static inline int pin_input_select(int pin_index, int input_selection) +{ + input_selection &= 0x7; + return input_selection << (28 - 8 * pin_index); +} + +// For configuring input pins +#define Digital_Filter_A_Is_Off 0x000<<28 +#define Digital_Filter_A_Is_Timebase3 0x001<<28 +#define Digital_Filter_A_Is_100 0x010<<28 +#define Digital_Filter_A_Is_20 0x011<<28 +#define Digital_Filter_A_Is_10 0x100<<28 +#define Digital_Filter_A_Is_2 0x101<<28 +#define Digital_Filter_A_Is_2_Timebase3 0x110<<28 -#define DATA_1B 0x1 -#define DATA_2B 0x2 -#define DATA_4B 0x4 -/* Board description*/ -typedef struct ni_660x_board_struct +// Offset of the GPCT chips from the base-adress of the card +const int GPCT_OFFSET[2] = {0x0,0x800}; /* First chip is at base-adress + + 0x00, etc. */ + +/* Board description*/ +typedef struct { - unsigned short dev_id; - char *name; -}ni_660x_board; + unsigned short dev_id; /* `lspci` will show you this */ + char *name; + int n_ctrs; /* total number of counters */ + int cnt_bits; /* number of bits in each counter */ +} ni_660x_board; static ni_660x_board ni_660x_boards[] = -{ - { - dev_id : 0x2c60, - name : "PCI-6601", - }, - { - dev_id : 0x0, /* ????? */ - name : "PCI-6602", - }, -}; + { + { + dev_id : 0x2c60, + name : "PCI-6601", + n_ctrs : 1*CTRS_PER_CHIP, + cnt_bits : 32, + }, + { + dev_id : 0x1310, + name : "PCI-6602", + n_ctrs : 2*CTRS_PER_CHIP, + cnt_bits : 32, + }, + }; static struct pci_device_id ni_660x_pci_table[] __devinitdata = { - { PCI_VENDOR_ID_NATINST, 0x2c60, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - //{ PCI_VENDOR_ID_NATINST, 0x0000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, - { 0 } + { PCI_VENDOR_ID_NATINST, 0x2c60, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { PCI_VENDOR_ID_NATINST, 0x1310, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, + { 0 } }; MODULE_DEVICE_TABLE(pci, ni_660x_pci_table); #define thisboard ((ni_660x_board *)dev->board_ptr) +/* initialized in ni_660x_find_device() */ typedef struct { - struct mite_struct *mite; - int boardtype; + struct mite_struct *mite; + int boardtype; }ni_660x_private; #define devpriv ((ni_660x_private *)dev->private) @@ -97,62 +653,136 @@ static int ni_660x_attach(comedi_device *dev,comedi_devconfig *it); static int ni_660x_detach(comedi_device *dev); static comedi_driver driver_ni_660x= -{ - driver_name: "ni_660x", - module: THIS_MODULE, - attach: ni_660x_attach, - detach: ni_660x_detach, -}; + { + driver_name: "ni_660x", + module: THIS_MODULE, + attach: ni_660x_attach, + detach: ni_660x_detach, + }; COMEDI_INITCLEANUP(driver_ni_660x); static int ni_660x_find_device(comedi_device *dev,int bus,int slot); -static int ni_660x_tio_winsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); -static int ni_660x_tio_rinsn(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); -static int ni_660x_tio_insn_bits(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); -static int ni_660x_tio_insn_config(comedi_device *dev,comedi_subdevice *s,comedi_insn *insn,lsampl_t *data); +/* Possible instructions for a GPCT */ +static int ni_660x_GPCT_rinsn(comedi_device *dev, + comedi_subdevice *s, + comedi_insn *insn, + lsampl_t *data); +static int ni_660x_GPCT_insn_config(comedi_device *dev, + comedi_subdevice *s, + comedi_insn *insn, + lsampl_t *data); +static int ni_660x_GPCT_winsn(comedi_device *dev, + comedi_subdevice *s, + comedi_insn *insn, + lsampl_t * data); +// Internal triggering +/* Currently only used to stop the pulsegenerator */ +static int ni_660x_GPCT_inttrig(comedi_device *dev, + comedi_subdevice *subdev, + unsigned int trig_num); + +// NYI +static int ni_660x_GPCT_cmdtest(comedi_device *dev,comedi_subdevice *s, + comedi_cmd *cmd); +static int ni_660x_GPCT_cmd(comedi_device *dev,comedi_subdevice *s); + +/* Possible instructions for Digital IO: Not implemented yet! */ +static int ni_660x_dio_insn_config(comedi_device *dev, + comedi_subdevice *s, + comedi_insn *insn, + lsampl_t *data); +static int ni_660x_dio_insn_bits(comedi_device *dev, + comedi_subdevice *s, + comedi_insn *insn, + lsampl_t *data); + +static int ni_660x_GPCT_cmdtest(comedi_device *dev,comedi_subdevice *s, + comedi_cmd *cmd) +{ + DPRINTK("NI_660X: COMMANDS not implemented yet for GPCT\n"); + return -EINVAL; +} +static int ni_660x_GPCT_cmd(comedi_device *dev,comedi_subdevice *s) +{ + DPRINTK("NI_660X: COMMANDS not implemented yet for GPCT\n"); + return -EINVAL; +} static int ni_660x_attach(comedi_device *dev,comedi_devconfig *it) { - comedi_subdevice *s; - int ret; - - printk("comedi%d: ni_660x: ",dev->minor); - - if ((ret=alloc_private(dev,sizeof(ni_660x_private))) < 0) return ret; - - ret = ni_660x_find_device(dev, it->options[0], it->options[1]); - if (ret<0) return ret; - - ret = mite_setup(devpriv->mite); - if (ret < 0) { - printk("error setting up mite\n"); - return ret; - } - dev->iobase = mite_iobase(devpriv->mite); - dev->board_name = thisboard->name; - dev->irq = mite_irq(devpriv->mite); - printk(" %s ", dev->board_name); - - if (alloc_subdevices(dev, 1)<0) return -ENOMEM; - - s=dev->subdevices+0; - /* general-purpose counter/time ASIC */ - s->type = COMEDI_SUBD_COUNTER; - s->subdev_flags = SDF_CMD|SDF_READABLE|SDF_WRITABLE; - s->n_chan = 8; - s->maxdata = 1; - s->range_table = &range_digital; - s->insn_write = &ni_660x_tio_winsn; - s->insn_read = &ni_660x_tio_rinsn; - s->insn_bits = ni_660x_tio_insn_bits; - s->insn_config = ni_660x_tio_insn_config; - - printk("attached\n"); - - return 1; + comedi_subdevice *s; + int ret; + + printk("comedi%d: ni_660x: ",dev->minor); + + if ((ret=alloc_private(dev,sizeof(ni_660x_private))) < 0) return ret; + + ret = ni_660x_find_device(dev, it->options[0], it->options[1]); + if (ret<0) return ret; + + ret = mite_setup(devpriv->mite); + if (ret < 0) { + printk("error setting up mite\n"); + return ret; + } + dev->iobase = mite_iobase(devpriv->mite); + dev->board_name = thisboard->name; +/* we don't support the interrupt yet */ +// dev->irq = mite_irq(devpriv->mite); + + printk(" %s ", dev->board_name); + + /* Currently there is 1 subdevice for the GPCT functionality, + and another subdevice for DIO */ + dev->n_subdevices = 2; + + if (alloc_subdevices(dev,dev->n_subdevices)<0) return -ENOMEM; + + s=dev->subdevices+0; + /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */ + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL; + /* KG: What does SDF_LSAMPL (see multiq3.c) mean? */ + s->n_chan = thisboard->n_ctrs; + s->maxdata = 0xffffffff; /* 32 bit counter */ + s->insn_read = ni_660x_GPCT_rinsn; + s->insn_config = ni_660x_GPCT_insn_config; + s->insn_write = ni_660x_GPCT_winsn; + + /* Command are not implemented yet, however they are necessary to + allocate the necessary memory for the comedi_async struct (used + to trigger the GPCT in case of pulsegenerator function */ + s->do_cmd = ni_660x_GPCT_cmd; + s->do_cmdtest = ni_660x_GPCT_cmdtest; + //s->cancel = ni_660x_gpct_cancel; + + s=dev->subdevices+1; + /* DIGITAL I/O SUBDEVICE */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE|SDF_WRITABLE; + s->n_chan = 8; // Only using 8 bits for now, instead of 32!! + s->maxdata = 1; + s->range_table = &range_digital; + /* (Copied from skel.c) 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 */ + // Not implemented yet + s->insn_bits = ni_660x_dio_insn_bits; + s->insn_config = ni_660x_dio_insn_config; + s->io_bits = 0; /* all bits default to input */ + + printk("attached\n"); + + /* What does this "return value" mean? Is this fixed by API?? + - skel_attach in skel.c returns 1; + - ni_E_init in ni_mio_common.c returns "0" ... */ + return 1; } @@ -164,147 +794,637 @@ ni_660x_detach(comedi_device *dev) if (dev->private && devpriv->mite) mite_unsetup(devpriv->mite); - if(dev->irq) - comedi_free_irq(dev->irq,dev); + /* Free irq */ + if(dev->irq) comedi_free_irq(dev->irq,dev); + + /* Same question as with attach ... */ return 0; } -static int -ni_660x_tio_winsn(comedi_device *dev, comedi_subdevice *s, - comedi_insn *insn, lsampl_t *data) +// Help function: Check what chipset the counter channel is on +static int GPCT_check_chipset_from_channel(comedi_device *dev, int channel) { - int i=0; - - switch (insn->chanspec) { - - case DATA_1B: - for (i=0; in; i+=2) { - writeb(data[i+1], dev->iobase+data[i]); + int chipset; + if ( (channel >= 0) && (channel < CTRS_PER_CHIP) ) + { + chipset = 0; } - break; - - case DATA_2B: - for (i=0; in; i+=2) { - writew(data[i+1], dev->iobase+data[i]); + else if ( (channel >= CTRS_PER_CHIP) && (channel < thisboard->n_ctrs) ) + { + chipset = 1; + // DPRINTK("NI_660x: Moving to chipset 1\n"); } - break; - - case DATA_4B: - for (i=0; in; i+=2) { - writel(data[i+1], dev->iobase+data[i]); + else + { + DPRINTK("NI_660x: Channel specification not between limits\n"); + return -EINVAL; } - break; - - default: - return insn->n; - - } - - return i; + return chipset; +} +int GPCT_check_counter_channel_from_subdev_channel(int channel) +{ + return channel % CTRS_PER_CHIP; } static int -ni_660x_tio_rinsn(comedi_device *dev, comedi_subdevice *s, - comedi_insn *insn, lsampl_t *data) +ni_660x_GPCT_rinsn(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) { - int i=0; - - switch (insn->chanspec) { - - case DATA_1B: - for (i=0; in; i+=2) { - data[i+1] = readb(dev->iobase+data[i]); - } - break; - - case DATA_2B: - for (i=0; in; i+=2) { - data[i+1] = readw(dev->iobase+data[i]); - } - break; - - case DATA_4B: - for (i=0; in; i+=2) { - data[i+1] = readl(dev->iobase+data[i]); - } - break; + int i; // counts the Data + int subdev_channel = CR_CHAN(insn->chanspec); + int counter_channel = GPCT_check_counter_channel_from_subdev_channel(subdev_channel);// Unpack chanspec + int chipset = GPCT_check_chipset_from_channel(dev, subdev_channel); + + /* See Chapter 2.2 Reading Counter Value of the NI Register Level + Programming Manual: "Reading counter values of armed counters". + We need to take several measurements to be sure what the counter + value is + */ + int tmpdata[2]; + int address; + + /* ============================================================ */ + /* 1 subdevice with 8 channels, differentation based on channel */ + DPRINTK("NI_660x: INSN_READ on channel %d\n", subdev_channel); + + // Check what Application of Counter this channel is configured for + switch(ni_660x_gpct_config[subdev_channel].App) + { + case PositionMeasurement: + // Check if (n > 0) + if ( insn->n <= 0 ) + { + DPRINTK("NI_660X: INSN_READ: n should be > 0\n"); + return -EINVAL; + } + // Now proceed with reading data + address = dev->iobase + + GPCT_OFFSET[chipset] + + registerData[GxSWSaveRegister(counter_channel)].offset; + for ( i=0 ; i < insn->n ; i++ ) + { + tmpdata[0] = readl(address); + tmpdata[1] = readl(address); + if (tmpdata[0] != tmpdata[1]) + { + // In case they differ, the 3d measurement is the + // correct value + data[i] = readl(address); + } + // Otherwise, they are the same and the correct counter + // value + else data[i] = tmpdata[0]; + } + return i; + break; + case SinglePulseGeneration: case PulseTrainGeneration: + DPRINTK("NI_660x: INSN_READ irrelevant for this application\n"); + return -EINVAL; + break; + default: // The rest is not implemented yet :-) + DPRINTK("NI_660x: INSN_READ: Functionality not implemented\n"); + return -EINVAL; + break; + }// End switch(ni_660x_gpct_config[channel].App) +} - default: - return insn->n; +static inline int z_reload_trigger(int a_state, int b_state) +{ + int bits = comedi_counter_trigger_bits(2, 0); + + if(a_state) + bits |= comedi_counter_trigger_bits(0, 0); + else + bits |= comedi_counter_trigger_bits(0, CR_INVERT); + if(b_state) + bits |= comedi_counter_trigger_bits(1, 0); + else + bits |= comedi_counter_trigger_bits(1, CR_INVERT); + return bits; +} - } +static int ni_660x_find_counter_trigger(const comedi_insn *insn, const lsampl_t *data, + lsampl_t trigger, lsampl_t action) +{ + static const int first_trigger_index = 1; + int i; - return i; + for(i = first_trigger_index; i + 1 < insn->n; i += 2) + { + if(data[i] == trigger && data[i + 1] == action) + { + return 1; + } + } + return 0; } -// this is being called as part of opening the device -static int -ni_660x_tio_insn_bits(comedi_device *dev, comedi_subdevice *s, - comedi_insn *insn, lsampl_t *data) +static int find_z_reload_trigger(const comedi_insn *insn, const lsampl_t *data) { - int i=0; + int retval; + + retval = ni_660x_find_counter_trigger(insn, data, z_reload_trigger(0, 0), COMEDI_RESET_ACCUMULATOR); + if(retval) + return 0x0; + retval = ni_660x_find_counter_trigger(insn, data, z_reload_trigger(0, 1), COMEDI_RESET_ACCUMULATOR); + if(retval) + return 0x1; + retval = ni_660x_find_counter_trigger(insn, data, z_reload_trigger(1, 0), COMEDI_RESET_ACCUMULATOR); + if(retval) + return 0x2; + retval = ni_660x_find_counter_trigger(insn, data, z_reload_trigger(1, 1), COMEDI_RESET_ACCUMULATOR); + if(retval) + return 0x3; + return -1; +} - switch (insn->chanspec) { +static int ni_660x_find_quad_encoding(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int quad1_inc_trigger = comedi_counter_trigger_bits(0, CR_EDGE) | + comedi_counter_trigger_bits(1, CR_INVERT); + int quad1_dec_trigger = comedi_counter_trigger_bits(0, CR_EDGE | CR_INVERT) | + comedi_counter_trigger_bits(1, CR_INVERT); + int quad2_inc_trigger = comedi_counter_trigger_bits(0, CR_EDGE | CR_INVERT) | + comedi_counter_trigger_bits(1, 0); + int quad2_dec_trigger = comedi_counter_trigger_bits(0, CR_EDGE) | + comedi_counter_trigger_bits(1, 0); + int quad4_inc_trigger0 = comedi_counter_trigger_bits(1, CR_EDGE | CR_INVERT) | + comedi_counter_trigger_bits(0, CR_INVERT); + int quad4_dec_trigger0 = comedi_counter_trigger_bits(1, CR_EDGE) | + comedi_counter_trigger_bits(0, CR_INVERT); + int quad4_inc_trigger1 = comedi_counter_trigger_bits(1, CR_EDGE) | + comedi_counter_trigger_bits(0, 0); + int quad4_dec_trigger1 = comedi_counter_trigger_bits(1, CR_EDGE | CR_INVERT) | + comedi_counter_trigger_bits(0, 0); + static const int first_trigger_index = 1; + int triggers_remaining = insn->n - first_trigger_index; + int z_reload_phase; + int retval; + + /* look for z index reload */ + z_reload_phase = find_z_reload_trigger(insn, data); + if(z_reload_phase >= 0) + { + triggers_remaining--; + } + /* look for quad x1 triggers */ + retval = ni_660x_find_counter_trigger(insn, data, quad1_inc_trigger, COMEDI_INC_ACCUMULATOR); + if(retval == 0) + { + DPRINTK("NI_660x: INSN_CONFIG: no good quad x1 inc trigger found\n"); + return -EINVAL; + } + triggers_remaining--; + retval = ni_660x_find_counter_trigger(insn, data, quad1_dec_trigger, COMEDI_DEC_ACCUMULATOR); + if(retval == 0) + { + DPRINTK("NI_660x: INSN_CONFIG: no good quad x1 dec trigger found\n"); + return -EINVAL; + } + triggers_remaining--; + if(triggers_remaining == 0) + { + return 1; + } + /* look for quad x2 triggers */ + retval = ni_660x_find_counter_trigger(insn, data, quad2_inc_trigger, COMEDI_INC_ACCUMULATOR); + if(retval == 0) + { + DPRINTK("NI_660x: INSN_CONFIG: no good quad x2 inc trigger found\n"); + return -EINVAL; + } + triggers_remaining--; + retval = ni_660x_find_counter_trigger(insn, data, quad2_dec_trigger, COMEDI_DEC_ACCUMULATOR); + if(retval == 0) + { + DPRINTK("NI_660x: INSN_CONFIG: no good quad x2 dec trigger found\n"); + return -EINVAL; + } + triggers_remaining--; + if(triggers_remaining == 0) + { + return 2; + } + /* look for quad x4 triggers */ + retval = ni_660x_find_counter_trigger(insn, data, quad4_inc_trigger0, COMEDI_INC_ACCUMULATOR); + if(retval == 0) + { + DPRINTK("NI_660x: INSN_CONFIG: no good quad x4 inc trigger found\n"); + return -EINVAL; + } + triggers_remaining--; + retval = ni_660x_find_counter_trigger(insn, data, quad4_dec_trigger0, COMEDI_DEC_ACCUMULATOR); + if(retval == 0) + { + DPRINTK("NI_660x: INSN_CONFIG: no good quad x4 dec trigger found\n"); + return -EINVAL; + } + triggers_remaining--; + retval = ni_660x_find_counter_trigger(insn, data, quad4_inc_trigger1, COMEDI_INC_ACCUMULATOR); + if(retval == 0) + { + DPRINTK("NI_660x: INSN_CONFIG: no good quad x4 inc trigger found\n"); + return -EINVAL; + } + triggers_remaining--; + retval = ni_660x_find_counter_trigger(insn, data, quad4_dec_trigger1, COMEDI_DEC_ACCUMULATOR); + if(retval == 0) + { + DPRINTK("NI_660x: INSN_CONFIG: no good quad x4 dec trigger found\n"); + return -EINVAL; + } + triggers_remaining--; + if(triggers_remaining == 0) + { + return 4; + } + return -EINVAL; +} - case DATA_1B: - for (i=0; in; i+=2) { - data[i+1] = readb(dev->iobase+data[i]); - } - break; +static void ni_660x_program_quad_encoder(comedi_device *dev, int subdev_channel) +{ + int chipset = GPCT_check_chipset_from_channel(dev,subdev_channel); + int counter_channel = GPCT_check_counter_channel_from_subdev_channel(subdev_channel); + + // Reset the counter + writew(GxReset(counter_channel),dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxxJointResetRegister(counter_channel)].offset); + // Disarm + writew(Disarm,dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + // Put 0 as initial counter value in the load register + writel(0x0,dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxLoadARegister(counter_channel)].offset); + // Load (latch) this value into the counter + writew(Load,dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + /* - Set Counting Mode into GPCT_X1 / 2 / 4 (as set by user + - Take into account Z pulse if set by user (index pulse) + - When to take into account the Z pulse + TODO: Make this latter an option + TODO: See p. 3.10 of the Register Level Programming + Manual. Find out how bit 4 (Gi Index mode) really + works. I think this bit determines if the counter is + reset + */ + writew(((ni_660x_gpct_config[subdev_channel]).data[0] | (ni_660x_gpct_config[subdev_channel]).data[1] | + (ni_660x_gpct_config[subdev_channel]).data[1]), dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCountingModeRegister(counter_channel)].offset); + // Put counter in input mode + // Not necessary at this point, since this is the default ... + +/* writel(0, dev->iobase + + GPCT_OFFSET[chipset] + + registerData[IOConfigReg(chipset, counter_channel)].offset); +*/ + // Arm the counter and put it into Hardware UpDown mode (depending + // on the UP/DOWN IO pin: 0 = down + writew(UpDownHardware | Arm, dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); +} - case DATA_2B: - for (i=0; in; i+=2) { - data[i+1] = readw(dev->iobase+data[i]); - } - break; +static int ni_660x_setup_quad_encoder(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int z_reload_phase; + int retval; + int channel = CR_CHAN(insn->chanspec);// Unpack chanspec - case DATA_4B: - for (i=0; in; i+=2) { - data[i+1] = readl(dev->iobase+data[i]); - } - break; + DPRINTK("NI_660x: INSN_CONFIG: Configuring Encoder\n"); - default: - return insn->n; + retval = ni_660x_find_quad_encoding(dev, s, insn, data); + switch(retval) + { + case 1: + /* quad x1 encoding*/ + (ni_660x_gpct_config[channel]).data[0] = CountingModeQuadX1; + break; + case 2: + /* quad x2 encoding*/ + (ni_660x_gpct_config[channel]).data[0] = CountingModeQuadX2; + break; + case 4: + /* quad x4 encoding*/ + (ni_660x_gpct_config[channel]).data[0] = CountingModeQuadX4; + break; + default: + return -EINVAL; + break; + } + + ni_660x_gpct_config[channel].App = PositionMeasurement; + /* look for z index reload */ + z_reload_phase = find_z_reload_trigger(insn, data); + // When to take into account the indexpulse: + switch(z_reload_phase) + { + case 0x0: + (ni_660x_gpct_config[channel]).data[1] = IndexPhaseLowLow; + break; + case 0x1: + (ni_660x_gpct_config[channel]).data[1] = IndexPhaseLowHigh; + break; + case 0x2: + (ni_660x_gpct_config[channel]).data[1] = IndexPhaseHighLow; + break; + case 0x3: + (ni_660x_gpct_config[channel]).data[1] = IndexPhaseHighHigh; + break; + default: + break; + } + if(z_reload_phase >= 0) + (ni_660x_gpct_config[channel]).data[2] = IndexMode; + else + (ni_660x_gpct_config[channel]).data[2] = 0; + + ni_660x_program_quad_encoder(dev, channel); + return 0; +} - } +static int ni_660x_setup_counter(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) +{ + int retval; - return i; + retval = ni_660x_setup_quad_encoder(dev, s, insn, data); + if(retval == 0) return 0; + comedi_error(dev, "unsupported INSN_CONFIG_COUNTER mode"); + return -EINVAL; } static int -ni_660x_tio_insn_config(comedi_device *dev, comedi_subdevice *s, - comedi_insn *insn, lsampl_t *data) +ni_660x_GPCT_insn_config(comedi_device *dev, comedi_subdevice *s, + comedi_insn *insn, lsampl_t *data) { - int i=0; - - switch (insn->chanspec) { - - case DATA_1B: - for (i=0; in; i+=2) { - writeb(data[i+1], dev->iobase+data[i]); + int subdev_channel = CR_CHAN(insn->chanspec);// Unpack chanspec + int chipset = GPCT_check_chipset_from_channel(dev, subdev_channel); + int counter_channel = GPCT_check_counter_channel_from_subdev_channel(subdev_channel); + int retval; + + DPRINTK("NI_660x: INSN_CONFIG: Configuring Channel %d\n", subdev_channel); + + /* See P. 3.5 of the Register-Level Programming manual. This + bit has to be set, otherwise, you can't use the second chip. + */ + /* XXX this should be done in attach */ + if ( chipset == 1) + { + writel(CounterSwap,dev->iobase + GPCT_OFFSET[chipset] + + registerData[ClockConfigRegister].offset); } - break; + else + { + writel(0x0,dev->iobase + GPCT_OFFSET[chipset] + + registerData[ClockConfigRegister].offset); + } - case DATA_2B: - for (i=0; in; i+=2) { - writew(data[i+1], dev->iobase+data[i]); - } - break; + switch(insn->data[0]) + { + case INSN_CONFIG_COUNTER_ALPHA: + retval = ni_660x_setup_counter(dev, s, insn, data); + if(retval < 0) return retval; + break; +#if 0 +/* XXX not sure about pulse generation API yet */ +#define GPCT_CONT_PULSE_OUT 0x0200 +#define GPCT_SINGLE_PULSE_OUT 0x0400 +#define GPCT_SINGLE_PULSE_GENERATOR 1 // Use CTR as single pulsegenerator +#define GPCT_PULSE_TRAIN_GENERATOR 2 // Use CTR as pulsetraingenerator + case GPCT_SINGLE_PULSE_GENERATOR: + DPRINTK("NI_660x: INSN_CONFIG: Configuring SPG\n"); + ni_660x_gpct_config[subdev_channel].App = SinglePulseGeneration; + /* data[1] contains the PULSE_WIDTH + data[2] contains the PULSE_DELAY + @pre PULSE_WIDTH > 0 && PULSE_DELAY > 0 + The above periods must be expressed as a multiple of the + pulse frequency on the selected source, see the + Register-Level Programmer Manual p2-11 (pulse generation) + */ + if(insn->data[2] > 1 && insn->data[1] > 1) + { + (ni_660x_gpct_config[subdev_channel]).data[0] = insn->data[1]; + (ni_660x_gpct_config[subdev_channel]).data[1] = insn->data[2]; + } + else + { + DPRINTK("NI_660x: INSN_CONFIG: SPG: Problem with Pulse params\n"); + return -EINVAL; + } + // Reset the counter + writew(GxReset(counter_channel), dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxxJointResetRegister(counter_channel)].offset); + // Disarm + writew(Disarm, dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + /* Put PULSE_DELAY as initial counter value into load + register A */ + writel((ni_660x_gpct_config[subdev_channel]).data[1], dev->iobase + + GPCT_OFFSET[chipset] + + registerData[GxLoadARegister(counter_channel)].offset); + // Load (latch) this value into the counter + writew(Load,dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + // Now Put PULSE_WIDTH in the LOAD register A + writel((ni_660x_gpct_config[subdev_channel]).data[0],dev->iobase + + GPCT_OFFSET[chipset] + + registerData[GxLoadARegister(counter_channel)].offset); + // Put Source input to internal 20 MHz clock + /* ================================================== + TODO: MAKE THIS A DATA FIELD!! to allow different clocks + (See TODO) + ================================================== */ + writew(SourceSelectTimebase1, dev->iobase + + GPCT_OFFSET[chipset] + + registerData[GxInputSelectRegister(counter_channel)].offset); + /* Choose to Load on reaching TC and + Change State of G_OUT on TC (Terminal Count) + Stop counting after second TC + Choose Load register A to load from */ + writew(LoadOnTC | OutputTogglesOnTC | StopOn2ndTC | LoadSourceSelectA, + dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxModeRegister(counter_channel)].offset); + // Configure Counter for output + writel(pin_is_output(0), dev->iobase + + GPCT_OFFSET[chipset] + + registerData[IOConfigReg(chipset, counter_channel)].offset); + case GPCT_PULSE_TRAIN_GENERATOR: + DPRINTK("NI_660x: INSN_CONFIG: PTG linking inttrig\n"); + s->async->inttrig = ni_660x_GPCT_inttrig; + DPRINTK("NI_660x: INSN_CONFIG: Configuring PTG\n"); + + ni_660x_gpct_config[subdev_channel].App = PulseTrainGeneration; + + /* data[1] contains the PULSE_WIDTH + data[2] contains the PULSE_PERIOD + @pre PULSE_PERIOD > PULSE_WIDTH > 0 + The above periods must be expressed as a multiple of the + pulse frequency on the selected source, see the + Register-Level Programmer Manual p2-11 (pulse generation) + */ + if ( (insn->data[2] > insn->data[1]) && (insn->data[1] > 0 ) ) + { + (ni_660x_gpct_config[subdev_channel]).data[0] = insn->data[1]; + (ni_660x_gpct_config[subdev_channel]).data[1] = insn->data[2]; + } + else + { + DPRINTK("%d \t %d\n",insn->data[1],insn->data[2]); + DPRINTK("NI_660x: INSN_CONFIG: PTG: Problem with Pulse params\n"); + return -EINVAL; + } + // Reset the counter + writew(GxReset(counter_channel),dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxxJointResetRegister(counter_channel)].offset); + // Disarm counter + writew(Disarm,dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + // Put PULSE_WIDTH as initial counter value into load register A + writel((ni_660x_gpct_config[subdev_channel]).data[0],dev->iobase + + GPCT_OFFSET[chipset] + + registerData[GxLoadARegister(counter_channel)].offset); + // Load (latch) this value into the counter + writew(Load,dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + // Now Put (PULSE_PERIOD - PULSE_WIDTH) in the load register B + writel((ni_660x_gpct_config[subdev_channel]).data[1] + - (ni_660x_gpct_config[subdev_channel]).data[0], + dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxLoadBRegister(counter_channel)].offset); + // Put Source input to internal 20 MHz clock + /* ================================================== + TODO: MAKE THIS A DATA FIELD!! to allow different clocks + (See TODO) + ================================================== */ + writew(SourceSelectTimebase1,dev->iobase + + GPCT_OFFSET[chipset] + + registerData[GxInputSelectRegister(counter_channel)].offset); + /* Switch between Load registers everytime + Choose to Load on reaching TC and + Change State of G_OUT on TC (Terminal Count) + Choose Load register A to load from */ + writew(ReloadSourceSwitching|LoadOnTC|OutputTogglesOnTC, + dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxModeRegister(counter_channel)].offset); + // Configure Counter for output + writel(pin_is_output(0), dev->iobase + + GPCT_OFFSET[chipset] + + registerData[IOConfigReg(chipset, counter_channel)].offset); + // Arm the counter and tell it to count down + writew(Arm|UpDownDown,dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + break; +#endif + default: + DPRINTK("NI_660x: unsupported insn_config\n"); + return -EINVAL; + break; + } + + return insn->n; +} - case DATA_4B: - for (i=0; in; i+=2) { - writel(data[i+1], dev->iobase+data[i]); - } - break; +static int ni_660x_GPCT_winsn(comedi_device *dev, + comedi_subdevice *s, + comedi_insn *insn, + lsampl_t * data) +{ + int subdev_channel = CR_CHAN(insn->chanspec);// Unpack chanspec + int chipset = GPCT_check_chipset_from_channel(dev, subdev_channel); + int counter_channel = GPCT_check_counter_channel_from_subdev_channel(subdev_channel); - default: - return insn->n; + DPRINTK("NI_660X: INSN_WRITE on channel %d\n", subdev_channel); + // Check what Application of Counter this channel is configured for + switch(ni_660x_gpct_config[subdev_channel].App) + { + case PositionMeasurement: + // Disarm the counter + writew(Disarm, dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + // Write the value into the load register + writel(*data, dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxLoadARegister(counter_channel)].offset); + // Latch the value into the counter + writew(Load, dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + // Arm the counter again and put UpDownHardware in! + writew(UpDownHardware|Arm, dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + break; + case SinglePulseGeneration: + DPRINTK("NI_660X: INSN_WRITE: SPG: Arming the counter\n"); + // Tell the counter to count down and arm + writew(Arm|UpDownDown, dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxCommandRegister(counter_channel)].offset); + break; + case PulseTrainGeneration: + /* data[0] contains the PULSE_WIDTH + data[1] contains the PULSE_PERIOD + @pre PULSE_PERIOD > PULSE_WIDTH > 0 + The above periods must be expressed as a multiple of the + pulse frequency on the selected source, see the + Register-Level Programmer Manual p2-11 (pulse generation) + */ + if ( (insn->data[1] > insn->data[0]) && (insn->data[0] > 0 ) ) + { + (ni_660x_gpct_config[subdev_channel]).data[0] = insn->data[0]; + (ni_660x_gpct_config[subdev_channel]).data[1] = insn->data[1]; + } + else + { + DPRINTK("%d \t %d\n",insn->data[1],insn->data[2]); + DPRINTK("NI_660x: INSN_WRITE: PTG: Problem with Pulse params\n"); + return -EINVAL; + } + // Put PULSE_WIDTH as initial counter value into load register A + writel((ni_660x_gpct_config[subdev_channel]).data[0],dev->iobase + + GPCT_OFFSET[chipset] + + registerData[GxLoadARegister(counter_channel)].offset); + // Put (PULSE_PERIOD - PULSE_WIDTH) in the load register B + writel( (ni_660x_gpct_config[subdev_channel]).data[1] + - (ni_660x_gpct_config[subdev_channel]).data[0], + dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxLoadBRegister(counter_channel)].offset); + break; + default: // Impossible + DPRINTK("NI_660X: INSN_WRITE: Functionality %d not implemented yet\n", + ni_660x_gpct_config[subdev_channel].App); + return -EINVAL; + break; + } + // return the number of samples written + return insn->n ; +} - } +/* Trigger instruction is currently only used to STOP the + pulsegenerator +*/ +static int ni_660x_GPCT_inttrig(comedi_device *dev, + comedi_subdevice *subdev, + unsigned int trig_num) +{ + int subdev_channel = trig_num; + int chipset = GPCT_check_chipset_from_channel(dev, subdev_channel); + int counter_channel = GPCT_check_counter_channel_from_subdev_channel(subdev_channel); - return i; + DPRINTK("Triggering channel %d\n", subdev_channel); + if ( chipset == 1) + { + writel(CounterSwap,dev->iobase + GPCT_OFFSET[chipset] + + registerData[ClockConfigRegister].offset); + } + else + { + writel(0x0,dev->iobase + GPCT_OFFSET[chipset] + + registerData[ClockConfigRegister].offset); + } + + // Reset the counter + writew(GxReset(counter_channel),dev->iobase + GPCT_OFFSET[chipset] + + registerData[GxxJointResetRegister(counter_channel)].offset); + return 0; } static int @@ -332,3 +1452,63 @@ ni_660x_find_device(comedi_device *dev, int bus, int slot) mite_list_devices(); return -EIO; } + + +static int ni_660x_dio_insn_bits(comedi_device *dev, + comedi_subdevice *s, + comedi_insn *insn, + lsampl_t *data) +{ + if(insn->n!=2)return -EINVAL; + /* The insn data is a write_mask in data[0] and the new data + * in data[1], each channel corresponding to a bit. */ + + // Check if we have to write some bits + if(data[0]) + { + // Copied from skel.c, unverified + s->state &= ~data[0]; + s->state |= data[0]&data[1]; + /* Write out the new digital output lines */ + /* Check if data < n_chan ?? */ + writew(s->state,dev->iobase + registerData[STCDIOOutput].offset); + } + /* on return, data[1] contains the value of the digital + * input and output lines. */ + data[1]=readw(dev->iobase + registerData[STCDIOParallelInput].offset); + return 2; +} + +static int ni_660x_dio_insn_config(comedi_device *dev, + comedi_subdevice *s, + comedi_insn *insn, + lsampl_t *data) +{ + int chan=CR_CHAN(insn->chanspec); + + if(insn->n!=1)return -EINVAL; + + /* 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. */ + + if(data[0]==COMEDI_OUTPUT) + { + s->io_bits |= 1<io_bits &= ~(1<io_bits,dev->iobase + registerData[STCDIOControl].offset); + /* Should we do also something with the IO configuration registers, + see p 3-38 of register level prog. manual + */ + + return 1; + return -EINVAL; +} + + -- 2.26.2