/*
- 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 <jpmellor@rose-hulman.edu>
-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 <jpmellor@rose-hulman.edu>,
+ Herman.Bruyninckx@mech.kuleuven.ac.be,
+ Wim.Meeussen@mech.kuleuven.ac.be,
+ Klaas.Gadeyne@mech.kuleuven.ac.be,
+ Frank Mori Hess <fmhess@users.sourceforge.net>
+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 <linux/comedidev.h>
+#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
+ <https://cvs.comedi.org/pipermail/comedi/2003-April/004381.html>
+ 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 <linux/kernel.h>
-#include <linux/module.h>
-#include <linux/sched.h>
-#include <linux/mm.h>
-#include <linux/slab.h>
-#include <linux/errno.h>
-#include <linux/delay.h>
-#include <linux/interrupt.h>
-#include <linux/init.h>
+#define DisarmAtTCStopsCounting 0x1<<11
+#define NoHardwareDisarm 0x0<<11
+#define StopOn2ndTC 0x1<<6
+#define LoadSourceSelectA 0x0<<7
-#include <asm/io.h>
+#define SynchroniseGate 0x1<<8
-#include <linux/comedidev.h>
-#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)
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;
}
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; i<insn->n; 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; i<insn->n; 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; i<insn->n; 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; i<insn->n; i+=2) {
- data[i+1] = readb(dev->iobase+data[i]);
- }
- break;
-
- case DATA_2B:
- for (i=0; i<insn->n; i+=2) {
- data[i+1] = readw(dev->iobase+data[i]);
- }
- break;
-
- case DATA_4B:
- for (i=0; i<insn->n; 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; i<insn->n; 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; i<insn->n; 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; i<insn->n; 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; i<insn->n; 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; i<insn->n; 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; i<insn->n; 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
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<<chan;
+ }
+ else
+ {
+ s->io_bits &= ~(1<<chan);
+ }
+ // No GPCT_OFFSET[chipset] offset here??
+ writew(s->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;
+}
+
+