From 6adecf5d1dc11c9a18301e1e142c0539e651936d Mon Sep 17 00:00:00 2001 From: J.P. Mellor Date: Thu, 19 Feb 2009 09:42:53 -0800 Subject: Staging: comedi: add ni_660x driver From: J.P. Mellor Driver for National Instruments 660x counter/timer boards (PCI-6601, PCI-6602, PXI-6602, and PXI-6608) From: J.P. Mellor Cc: Herman Bruyninckx Cc: Wim Meeussen Cc: Klass Gadeyne Cc: David Schleef Cc: Ian Abbott Cc: Frank Mori Hess Signed-off-by: Greg Kroah-Hartman --- drivers/staging/comedi/drivers/ni_660x.c | 1322 +++++++++++++++++++++++++++++++ 1 file changed, 1322 insertions(+) --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_660x.c @@ -0,0 +1,1322 @@ +/* + 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 +Description: National Instruments 660x counter/timer boards +Devices: +[National Instruments] PCI-6601 (ni_660x), PCI-6602, PXI-6602, + PXI-6608 +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: Thu Oct 18 12:56:06 EDT 2007 +Status: experimental + +Encoders work. PulseGeneration (both single pulse and pulse train) +works. Buffered commands work for input but not output. + +References: +DAQ 660x Register-Level Programmer Manual (NI 370505A-01) +DAQ 6601/6602 User Manual (NI 322137B-01) + +*/ + +#include "../comedidev.h" +#include "mite.h" +#include "ni_tio.h" + +enum ni_660x_constants { + min_counter_pfi_chan = 8, + max_dio_pfi_chan = 31, + counters_per_chip = 4 +}; + +#define NUM_PFI_CHANNELS 40 +// really there are only up to 3 dma channels, but the register layout allows for 4 +#define MAX_DMA_CHANNEL 4 + +/* See Register-Level Programmer Manual page 3.1 */ +typedef enum { + G0InterruptAcknowledge, + G0StatusRegister, + G1InterruptAcknowledge, + G1StatusRegister, + G01StatusRegister, + G0CommandRegister, + STCDIOParallelInput, + G1CommandRegister, + G0HWSaveRegister, + G1HWSaveRegister, + STCDIOOutput, + STCDIOControl, + G0SWSaveRegister, + G1SWSaveRegister, + G0ModeRegister, + G01JointStatus1Register, + G1ModeRegister, + STCDIOSerialInput, + G0LoadARegister, + G01JointStatus2Register, + G0LoadBRegister, + G1LoadARegister, + G1LoadBRegister, + G0InputSelectRegister, + G1InputSelectRegister, + G0AutoincrementRegister, + G1AutoincrementRegister, + 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, + G2AutoincrementRegister, + G3AutoincrementRegister, + G23JointResetRegister, + G2InterruptEnable, + G3InterruptEnable, + G2CountingModeRegister, + G3CountingModeRegister, + G3SecondGateRegister, + G2SecondGateRegister, + G2DMAConfigRegister, + G2DMAStatusRegister, + G3DMAConfigRegister, + G3DMAStatusRegister, + DIO32Input, + DIO32Output, + ClockConfigRegister, + GlobalInterruptStatusRegister, + DMAConfigRegister, + GlobalInterruptConfigRegister, + IOConfigReg0_1, + IOConfigReg2_3, + IOConfigReg4_5, + IOConfigReg6_7, + IOConfigReg8_9, + IOConfigReg10_11, + IOConfigReg12_13, + IOConfigReg14_15, + IOConfigReg16_17, + IOConfigReg18_19, + IOConfigReg20_21, + IOConfigReg22_23, + IOConfigReg24_25, + IOConfigReg26_27, + IOConfigReg28_29, + IOConfigReg30_31, + IOConfigReg32_33, + IOConfigReg34_35, + IOConfigReg36_37, + IOConfigReg38_39, + NumRegisters, +} NI_660x_Register; + +static inline unsigned IOConfigReg(unsigned pfi_channel) +{ + unsigned reg = IOConfigReg0_1 + pfi_channel / 2; + BUG_ON(reg > IOConfigReg38_39); + return reg; +} + +enum ni_660x_register_width { + DATA_1B, + DATA_2B, + DATA_4B +}; + +enum ni_660x_register_direction { + NI_660x_READ, + NI_660x_WRITE, + NI_660x_READ_WRITE +}; + +enum ni_660x_pfi_output_select { + pfi_output_select_high_Z = 0, + pfi_output_select_counter = 1, + pfi_output_select_do = 2, + num_pfi_output_selects +}; + +enum ni_660x_subdevices { + NI_660X_DIO_SUBDEV = 1, + NI_660X_GPCT_SUBDEV_0 = 2 +}; +static inline unsigned NI_660X_GPCT_SUBDEV(unsigned index) +{ + return NI_660X_GPCT_SUBDEV_0 + index; +} + +typedef struct { + const char *name; // Register Name + int offset; // Offset from base address from GPCT chip + enum ni_660x_register_direction direction; + enum ni_660x_register_width size; // 1 byte, 2 bytes, or 4 bytes +} NI_660xRegisterData; + +static const NI_660xRegisterData registerData[NumRegisters] = { + {"G0 Interrupt Acknowledge", 0x004, NI_660x_WRITE, DATA_2B}, + {"G0 Status Register", 0x004, NI_660x_READ, DATA_2B}, + {"G1 Interrupt Acknowledge", 0x006, NI_660x_WRITE, DATA_2B}, + {"G1 Status Register", 0x006, NI_660x_READ, DATA_2B}, + {"G01 Status Register ", 0x008, NI_660x_READ, DATA_2B}, + {"G0 Command Register", 0x00C, NI_660x_WRITE, DATA_2B}, + {"STC DIO Parallel Input", 0x00E, NI_660x_READ, DATA_2B}, + {"G1 Command Register", 0x00E, NI_660x_WRITE, DATA_2B}, + {"G0 HW Save Register", 0x010, NI_660x_READ, DATA_4B}, + {"G1 HW Save Register", 0x014, NI_660x_READ, DATA_4B}, + {"STC DIO Output", 0x014, NI_660x_WRITE, DATA_2B}, + {"STC DIO Control", 0x016, NI_660x_WRITE, DATA_2B}, + {"G0 SW Save Register", 0x018, NI_660x_READ, DATA_4B}, + {"G1 SW Save Register", 0x01C, NI_660x_READ, DATA_4B}, + {"G0 Mode Register", 0x034, NI_660x_WRITE, DATA_2B}, + {"G01 Joint Status 1 Register", 0x036, NI_660x_READ, DATA_2B}, + {"G1 Mode Register", 0x036, NI_660x_WRITE, DATA_2B}, + {"STC DIO Serial Input", 0x038, NI_660x_READ, DATA_2B}, + {"G0 Load A Register", 0x038, NI_660x_WRITE, DATA_4B}, + {"G01 Joint Status 2 Register", 0x03A, NI_660x_READ, DATA_2B}, + {"G0 Load B Register", 0x03C, NI_660x_WRITE, DATA_4B}, + {"G1 Load A Register", 0x040, NI_660x_WRITE, DATA_4B}, + {"G1 Load B Register", 0x044, NI_660x_WRITE, DATA_4B}, + {"G0 Input Select Register", 0x048, NI_660x_WRITE, DATA_2B}, + {"G1 Input Select Register", 0x04A, NI_660x_WRITE, DATA_2B}, + {"G0 Autoincrement Register", 0x088, NI_660x_WRITE, DATA_2B}, + {"G1 Autoincrement Register", 0x08A, NI_660x_WRITE, DATA_2B}, + {"G01 Joint Reset Register", 0x090, NI_660x_WRITE, DATA_2B}, + {"G0 Interrupt Enable", 0x092, NI_660x_WRITE, DATA_2B}, + {"G1 Interrupt Enable", 0x096, NI_660x_WRITE, DATA_2B}, + {"G0 Counting Mode Register", 0x0B0, NI_660x_WRITE, DATA_2B}, + {"G1 Counting Mode Register", 0x0B2, NI_660x_WRITE, DATA_2B}, + {"G0 Second Gate Register", 0x0B4, NI_660x_WRITE, DATA_2B}, + {"G1 Second Gate Register", 0x0B6, NI_660x_WRITE, DATA_2B}, + {"G0 DMA Config Register", 0x0B8, NI_660x_WRITE, DATA_2B}, + {"G0 DMA Status Register", 0x0B8, NI_660x_READ, DATA_2B}, + {"G1 DMA Config Register", 0x0BA, NI_660x_WRITE, DATA_2B}, + {"G1 DMA Status Register", 0x0BA, NI_660x_READ, DATA_2B}, + {"G2 Interrupt Acknowledge", 0x104, NI_660x_WRITE, DATA_2B}, + {"G2 Status Register", 0x104, NI_660x_READ, DATA_2B}, + {"G3 Interrupt Acknowledge", 0x106, NI_660x_WRITE, DATA_2B}, + {"G3 Status Register", 0x106, NI_660x_READ, DATA_2B}, + {"G23 Status Register", 0x108, NI_660x_READ, DATA_2B}, + {"G2 Command Register", 0x10C, NI_660x_WRITE, DATA_2B}, + {"G3 Command Register", 0x10E, NI_660x_WRITE, DATA_2B}, + {"G2 HW Save Register", 0x110, NI_660x_READ, DATA_4B}, + {"G3 HW Save Register", 0x114, NI_660x_READ, DATA_4B}, + {"G2 SW Save Register", 0x118, NI_660x_READ, DATA_4B}, + {"G3 SW Save Register", 0x11C, NI_660x_READ, DATA_4B}, + {"G2 Mode Register", 0x134, NI_660x_WRITE, DATA_2B}, + {"G23 Joint Status 1 Register", 0x136, NI_660x_READ, DATA_2B}, + {"G3 Mode Register", 0x136, NI_660x_WRITE, DATA_2B}, + {"G2 Load A Register", 0x138, NI_660x_WRITE, DATA_4B}, + {"G23 Joint Status 2 Register", 0x13A, NI_660x_READ, DATA_2B}, + {"G2 Load B Register", 0x13C, NI_660x_WRITE, DATA_4B}, + {"G3 Load A Register", 0x140, NI_660x_WRITE, DATA_4B}, + {"G3 Load B Register", 0x144, NI_660x_WRITE, DATA_4B}, + {"G2 Input Select Register", 0x148, NI_660x_WRITE, DATA_2B}, + {"G3 Input Select Register", 0x14A, NI_660x_WRITE, DATA_2B}, + {"G2 Autoincrement Register", 0x188, NI_660x_WRITE, DATA_2B}, + {"G3 Autoincrement Register", 0x18A, NI_660x_WRITE, DATA_2B}, + {"G23 Joint Reset Register", 0x190, NI_660x_WRITE, DATA_2B}, + {"G2 Interrupt Enable", 0x192, NI_660x_WRITE, DATA_2B}, + {"G3 Interrupt Enable", 0x196, NI_660x_WRITE, DATA_2B}, + {"G2 Counting Mode Register", 0x1B0, NI_660x_WRITE, DATA_2B}, + {"G3 Counting Mode Register", 0x1B2, NI_660x_WRITE, DATA_2B}, + {"G3 Second Gate Register", 0x1B6, NI_660x_WRITE, DATA_2B}, + {"G2 Second Gate Register", 0x1B4, NI_660x_WRITE, DATA_2B}, + {"G2 DMA Config Register", 0x1B8, NI_660x_WRITE, DATA_2B}, + {"G2 DMA Status Register", 0x1B8, NI_660x_READ, DATA_2B}, + {"G3 DMA Config Register", 0x1BA, NI_660x_WRITE, DATA_2B}, + {"G3 DMA Status Register", 0x1BA, NI_660x_READ, DATA_2B}, + {"32 bit Digital Input", 0x414, NI_660x_READ, DATA_4B}, + {"32 bit Digital Output", 0x510, NI_660x_WRITE, DATA_4B}, + {"Clock Config Register", 0x73C, NI_660x_WRITE, DATA_4B}, + {"Global Interrupt Status Register", 0x754, NI_660x_READ, DATA_4B}, + {"DMA Configuration Register", 0x76C, NI_660x_WRITE, DATA_4B}, + {"Global Interrupt Config Register", 0x770, NI_660x_WRITE, DATA_4B}, + {"IO Config Register 0-1", 0x77C, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 2-3", 0x77E, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 4-5", 0x780, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 6-7", 0x782, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 8-9", 0x784, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 10-11", 0x786, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 12-13", 0x788, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 14-15", 0x78A, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 16-17", 0x78C, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 18-19", 0x78E, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 20-21", 0x790, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 22-23", 0x792, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 24-25", 0x794, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 26-27", 0x796, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 28-29", 0x798, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 30-31", 0x79A, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 32-33", 0x79C, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 34-35", 0x79E, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 36-37", 0x7A0, NI_660x_READ_WRITE, DATA_2B}, + {"IO Config Register 38-39", 0x7A2, NI_660x_READ_WRITE, DATA_2B} +}; + +// kind of ENABLE for the second counter +enum clock_config_register_bits { + CounterSwap = 0x1 << 21 +}; + +// ioconfigreg +static inline unsigned ioconfig_bitshift(unsigned pfi_channel) +{ + if (pfi_channel % 2) + return 0; + else + return 8; +} +static inline unsigned pfi_output_select_mask(unsigned pfi_channel) +{ + return 0x3 << ioconfig_bitshift(pfi_channel); +} +static inline unsigned pfi_output_select_bits(unsigned pfi_channel, + unsigned output_select) +{ + return (output_select & 0x3) << ioconfig_bitshift(pfi_channel); +} +static inline unsigned pfi_input_select_mask(unsigned pfi_channel) +{ + return 0x7 << (4 + ioconfig_bitshift(pfi_channel)); +} +static inline unsigned pfi_input_select_bits(unsigned pfi_channel, + unsigned input_select) +{ + return (input_select & 0x7) << (4 + ioconfig_bitshift(pfi_channel)); +} + +// dma configuration register bits +static inline unsigned dma_select_mask(unsigned dma_channel) +{ + BUG_ON(dma_channel >= MAX_DMA_CHANNEL); + return 0x1f << (8 * dma_channel); +} +enum dma_selection { + dma_selection_none = 0x1f, +}; +static inline unsigned dma_selection_counter(unsigned counter_index) +{ + BUG_ON(counter_index >= counters_per_chip); + return counter_index; +} +static inline unsigned dma_select_bits(unsigned dma_channel, unsigned selection) +{ + BUG_ON(dma_channel >= MAX_DMA_CHANNEL); + return (selection << (8 * dma_channel)) & dma_select_mask(dma_channel); +} +static inline unsigned dma_reset_bit(unsigned dma_channel) +{ + BUG_ON(dma_channel >= MAX_DMA_CHANNEL); + return 0x80 << (8 * dma_channel); +} + +enum global_interrupt_status_register_bits { + Counter_0_Int_Bit = 0x100, + Counter_1_Int_Bit = 0x200, + Counter_2_Int_Bit = 0x400, + Counter_3_Int_Bit = 0x800, + Cascade_Int_Bit = 0x20000000, + Global_Int_Bit = 0x80000000 +}; + +enum global_interrupt_config_register_bits { + Cascade_Int_Enable_Bit = 0x20000000, + Global_Int_Polarity_Bit = 0x40000000, + Global_Int_Enable_Bit = 0x80000000 +}; + +// Offset of the GPCT chips from the base-adress of the card +static const unsigned GPCT_OFFSET[2] = { 0x0, 0x800 }; /* First chip is at base-address + + 0x00, etc. */ + +/* Board description*/ +typedef struct { + unsigned short dev_id; /* `lspci` will show you this */ + const char *name; + unsigned n_chips; /* total number of TIO chips */ +} ni_660x_board; + +static const ni_660x_board ni_660x_boards[] = { + { + dev_id: 0x2c60, + name: "PCI-6601", + n_chips: 1, + }, + { + dev_id: 0x1310, + name: "PCI-6602", + n_chips: 2, + }, + { + dev_id: 0x1360, + name: "PXI-6602", + n_chips: 2, + }, + { + dev_id: 0x2cc0, + name: "PXI-6608", + n_chips: 2, + }, +}; + +#define NI_660X_MAX_NUM_CHIPS 2 +#define NI_660X_MAX_NUM_COUNTERS (NI_660X_MAX_NUM_CHIPS * counters_per_chip) + +static DEFINE_PCI_DEVICE_TABLE(ni_660x_pci_table) = { + {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}, + {PCI_VENDOR_ID_NATINST, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {PCI_VENDOR_ID_NATINST, 0x2cc0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, + {0} +}; + +MODULE_DEVICE_TABLE(pci, ni_660x_pci_table); + +typedef struct { + struct mite_struct *mite; + struct ni_gpct_device *counter_dev; + uint64_t pfi_direction_bits; + struct mite_dma_descriptor_ring + *mite_rings[NI_660X_MAX_NUM_CHIPS][counters_per_chip]; + spinlock_t mite_channel_lock; + unsigned dma_configuration_soft_copies[NI_660X_MAX_NUM_CHIPS]; + spinlock_t soft_reg_copy_lock; + unsigned short pfi_output_selects[NUM_PFI_CHANNELS]; +} ni_660x_private; + +static inline ni_660x_private *private(comedi_device * dev) +{ + return dev->private; +} + +/* initialized in ni_660x_find_device() */ +static inline const ni_660x_board *board(comedi_device * dev) +{ + return dev->board_ptr; +} + +#define n_ni_660x_boards (sizeof(ni_660x_boards)/sizeof(ni_660x_boards[0])) + +static int ni_660x_attach(comedi_device * dev, comedi_devconfig * it); +static int ni_660x_detach(comedi_device * dev); +static void init_tio_chip(comedi_device * dev, int chipset); +static void ni_660x_select_pfi_output(comedi_device * dev, unsigned pfi_channel, + unsigned output_select); + +static comedi_driver driver_ni_660x = { + driver_name:"ni_660x", + module:THIS_MODULE, + attach:ni_660x_attach, + detach:ni_660x_detach, +}; + +COMEDI_PCI_INITCLEANUP(driver_ni_660x, ni_660x_pci_table); + +static int ni_660x_find_device(comedi_device * dev, int bus, int slot); +static int ni_660x_set_pfi_routing(comedi_device * dev, unsigned chan, + unsigned source); + +/* 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); + +/* Possible instructions for Digital IO */ +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 inline unsigned ni_660x_num_counters(comedi_device * dev) +{ + return board(dev)->n_chips * counters_per_chip; +} + +static NI_660x_Register ni_gpct_to_660x_register(enum ni_gpct_register reg) +{ + NI_660x_Register ni_660x_register; + switch (reg) { + case NITIO_G0_Autoincrement_Reg: + ni_660x_register = G0AutoincrementRegister; + break; + case NITIO_G1_Autoincrement_Reg: + ni_660x_register = G1AutoincrementRegister; + break; + case NITIO_G2_Autoincrement_Reg: + ni_660x_register = G2AutoincrementRegister; + break; + case NITIO_G3_Autoincrement_Reg: + ni_660x_register = G3AutoincrementRegister; + break; + case NITIO_G0_Command_Reg: + ni_660x_register = G0CommandRegister; + break; + case NITIO_G1_Command_Reg: + ni_660x_register = G1CommandRegister; + break; + case NITIO_G2_Command_Reg: + ni_660x_register = G2CommandRegister; + break; + case NITIO_G3_Command_Reg: + ni_660x_register = G3CommandRegister; + break; + case NITIO_G0_HW_Save_Reg: + ni_660x_register = G0HWSaveRegister; + break; + case NITIO_G1_HW_Save_Reg: + ni_660x_register = G1HWSaveRegister; + break; + case NITIO_G2_HW_Save_Reg: + ni_660x_register = G2HWSaveRegister; + break; + case NITIO_G3_HW_Save_Reg: + ni_660x_register = G3HWSaveRegister; + break; + case NITIO_G0_SW_Save_Reg: + ni_660x_register = G0SWSaveRegister; + break; + case NITIO_G1_SW_Save_Reg: + ni_660x_register = G1SWSaveRegister; + break; + case NITIO_G2_SW_Save_Reg: + ni_660x_register = G2SWSaveRegister; + break; + case NITIO_G3_SW_Save_Reg: + ni_660x_register = G3SWSaveRegister; + break; + case NITIO_G0_Mode_Reg: + ni_660x_register = G0ModeRegister; + break; + case NITIO_G1_Mode_Reg: + ni_660x_register = G1ModeRegister; + break; + case NITIO_G2_Mode_Reg: + ni_660x_register = G2ModeRegister; + break; + case NITIO_G3_Mode_Reg: + ni_660x_register = G3ModeRegister; + break; + case NITIO_G0_LoadA_Reg: + ni_660x_register = G0LoadARegister; + break; + case NITIO_G1_LoadA_Reg: + ni_660x_register = G1LoadARegister; + break; + case NITIO_G2_LoadA_Reg: + ni_660x_register = G2LoadARegister; + break; + case NITIO_G3_LoadA_Reg: + ni_660x_register = G3LoadARegister; + break; + case NITIO_G0_LoadB_Reg: + ni_660x_register = G0LoadBRegister; + break; + case NITIO_G1_LoadB_Reg: + ni_660x_register = G1LoadBRegister; + break; + case NITIO_G2_LoadB_Reg: + ni_660x_register = G2LoadBRegister; + break; + case NITIO_G3_LoadB_Reg: + ni_660x_register = G3LoadBRegister; + break; + case NITIO_G0_Input_Select_Reg: + ni_660x_register = G0InputSelectRegister; + break; + case NITIO_G1_Input_Select_Reg: + ni_660x_register = G1InputSelectRegister; + break; + case NITIO_G2_Input_Select_Reg: + ni_660x_register = G2InputSelectRegister; + break; + case NITIO_G3_Input_Select_Reg: + ni_660x_register = G3InputSelectRegister; + break; + case NITIO_G01_Status_Reg: + ni_660x_register = G01StatusRegister; + break; + case NITIO_G23_Status_Reg: + ni_660x_register = G23StatusRegister; + break; + case NITIO_G01_Joint_Reset_Reg: + ni_660x_register = G01JointResetRegister; + break; + case NITIO_G23_Joint_Reset_Reg: + ni_660x_register = G23JointResetRegister; + break; + case NITIO_G01_Joint_Status1_Reg: + ni_660x_register = G01JointStatus1Register; + break; + case NITIO_G23_Joint_Status1_Reg: + ni_660x_register = G23JointStatus1Register; + break; + case NITIO_G01_Joint_Status2_Reg: + ni_660x_register = G01JointStatus2Register; + break; + case NITIO_G23_Joint_Status2_Reg: + ni_660x_register = G23JointStatus2Register; + break; + case NITIO_G0_Counting_Mode_Reg: + ni_660x_register = G0CountingModeRegister; + break; + case NITIO_G1_Counting_Mode_Reg: + ni_660x_register = G1CountingModeRegister; + break; + case NITIO_G2_Counting_Mode_Reg: + ni_660x_register = G2CountingModeRegister; + break; + case NITIO_G3_Counting_Mode_Reg: + ni_660x_register = G3CountingModeRegister; + break; + case NITIO_G0_Second_Gate_Reg: + ni_660x_register = G0SecondGateRegister; + break; + case NITIO_G1_Second_Gate_Reg: + ni_660x_register = G1SecondGateRegister; + break; + case NITIO_G2_Second_Gate_Reg: + ni_660x_register = G2SecondGateRegister; + break; + case NITIO_G3_Second_Gate_Reg: + ni_660x_register = G3SecondGateRegister; + break; + case NITIO_G0_DMA_Config_Reg: + ni_660x_register = G0DMAConfigRegister; + break; + case NITIO_G0_DMA_Status_Reg: + ni_660x_register = G0DMAStatusRegister; + break; + case NITIO_G1_DMA_Config_Reg: + ni_660x_register = G1DMAConfigRegister; + break; + case NITIO_G1_DMA_Status_Reg: + ni_660x_register = G1DMAStatusRegister; + break; + case NITIO_G2_DMA_Config_Reg: + ni_660x_register = G2DMAConfigRegister; + break; + case NITIO_G2_DMA_Status_Reg: + ni_660x_register = G2DMAStatusRegister; + break; + case NITIO_G3_DMA_Config_Reg: + ni_660x_register = G3DMAConfigRegister; + break; + case NITIO_G3_DMA_Status_Reg: + ni_660x_register = G3DMAStatusRegister; + break; + case NITIO_G0_Interrupt_Acknowledge_Reg: + ni_660x_register = G0InterruptAcknowledge; + break; + case NITIO_G1_Interrupt_Acknowledge_Reg: + ni_660x_register = G1InterruptAcknowledge; + break; + case NITIO_G2_Interrupt_Acknowledge_Reg: + ni_660x_register = G2InterruptAcknowledge; + break; + case NITIO_G3_Interrupt_Acknowledge_Reg: + ni_660x_register = G3InterruptAcknowledge; + break; + case NITIO_G0_Status_Reg: + ni_660x_register = G0StatusRegister; + break; + case NITIO_G1_Status_Reg: + ni_660x_register = G0StatusRegister; + break; + case NITIO_G2_Status_Reg: + ni_660x_register = G0StatusRegister; + break; + case NITIO_G3_Status_Reg: + ni_660x_register = G0StatusRegister; + break; + case NITIO_G0_Interrupt_Enable_Reg: + ni_660x_register = G0InterruptEnable; + break; + case NITIO_G1_Interrupt_Enable_Reg: + ni_660x_register = G1InterruptEnable; + break; + case NITIO_G2_Interrupt_Enable_Reg: + ni_660x_register = G2InterruptEnable; + break; + case NITIO_G3_Interrupt_Enable_Reg: + ni_660x_register = G3InterruptEnable; + break; + default: + rt_printk("%s: unhandled register 0x%x in switch.\n", + __FUNCTION__, reg); + BUG(); + return 0; + break; + } + return ni_660x_register; +} + +static inline void ni_660x_write_register(comedi_device * dev, + unsigned chip_index, unsigned bits, NI_660x_Register reg) +{ + void *const write_address = + private(dev)->mite->daq_io_addr + GPCT_OFFSET[chip_index] + + registerData[reg].offset; + + switch (registerData[reg].size) { + case DATA_2B: + writew(bits, write_address); + break; + case DATA_4B: + writel(bits, write_address); + break; + default: + rt_printk("%s: %s: bug! unhandled case (reg=0x%x) in switch.\n", + __FILE__, __FUNCTION__, reg); + BUG(); + break; + } +} + +static inline unsigned ni_660x_read_register(comedi_device * dev, + unsigned chip_index, NI_660x_Register reg) +{ + void *const read_address = + private(dev)->mite->daq_io_addr + GPCT_OFFSET[chip_index] + + registerData[reg].offset; + + switch (registerData[reg].size) { + case DATA_2B: + return readw(read_address); + break; + case DATA_4B: + return readl(read_address); + break; + default: + rt_printk("%s: %s: bug! unhandled case (reg=0x%x) in switch.\n", + __FILE__, __FUNCTION__, reg); + BUG(); + break; + } + return 0; +} + +static void ni_gpct_write_register(struct ni_gpct *counter, unsigned bits, + enum ni_gpct_register reg) +{ + comedi_device *dev = counter->counter_dev->dev; + NI_660x_Register ni_660x_register = ni_gpct_to_660x_register(reg); + ni_660x_write_register(dev, counter->chip_index, bits, + ni_660x_register); +} + +static unsigned ni_gpct_read_register(struct ni_gpct *counter, + enum ni_gpct_register reg) +{ + comedi_device *dev = counter->counter_dev->dev; + NI_660x_Register ni_660x_register = ni_gpct_to_660x_register(reg); + return ni_660x_read_register(dev, counter->chip_index, + ni_660x_register); +} + +static inline struct mite_dma_descriptor_ring *mite_ring(ni_660x_private * priv, + struct ni_gpct *counter) +{ + return priv->mite_rings[counter->chip_index][counter->counter_index]; +} + +static inline void ni_660x_set_dma_channel(comedi_device * dev, + unsigned mite_channel, struct ni_gpct *counter) +{ + unsigned long flags; + comedi_spin_lock_irqsave(&private(dev)->soft_reg_copy_lock, flags); + private(dev)->dma_configuration_soft_copies[counter->chip_index] &= + ~dma_select_mask(mite_channel); + private(dev)->dma_configuration_soft_copies[counter->chip_index] |= + dma_select_bits(mite_channel, + dma_selection_counter(counter->counter_index)); + ni_660x_write_register(dev, counter->chip_index, + private(dev)->dma_configuration_soft_copies[counter-> + chip_index] | dma_reset_bit(mite_channel), + DMAConfigRegister); + mmiowb(); + comedi_spin_unlock_irqrestore(&private(dev)->soft_reg_copy_lock, flags); +} + +static inline void ni_660x_unset_dma_channel(comedi_device * dev, + unsigned mite_channel, struct ni_gpct *counter) +{ + unsigned long flags; + comedi_spin_lock_irqsave(&private(dev)->soft_reg_copy_lock, flags); + private(dev)->dma_configuration_soft_copies[counter->chip_index] &= + ~dma_select_mask(mite_channel); + private(dev)->dma_configuration_soft_copies[counter->chip_index] |= + dma_select_bits(mite_channel, dma_selection_none); + ni_660x_write_register(dev, counter->chip_index, + private(dev)->dma_configuration_soft_copies[counter-> + chip_index], DMAConfigRegister); + mmiowb(); + comedi_spin_unlock_irqrestore(&private(dev)->soft_reg_copy_lock, flags); +} + +static int ni_660x_request_mite_channel(comedi_device * dev, + struct ni_gpct *counter, enum comedi_io_direction direction) +{ + unsigned long flags; + struct mite_channel *mite_chan; + + comedi_spin_lock_irqsave(&private(dev)->mite_channel_lock, flags); + BUG_ON(counter->mite_chan); + mite_chan = + mite_request_channel(private(dev)->mite, mite_ring(private(dev), + counter)); + if (mite_chan == NULL) { + comedi_spin_unlock_irqrestore(&private(dev)->mite_channel_lock, + flags); + comedi_error(dev, + "failed to reserve mite dma channel for counter."); + return -EBUSY; + } + mite_chan->dir = direction; + ni_tio_set_mite_channel(counter, mite_chan); + ni_660x_set_dma_channel(dev, mite_chan->channel, counter); + comedi_spin_unlock_irqrestore(&private(dev)->mite_channel_lock, flags); + return 0; +} + +void ni_660x_release_mite_channel(comedi_device * dev, struct ni_gpct *counter) +{ + unsigned long flags; + + comedi_spin_lock_irqsave(&private(dev)->mite_channel_lock, flags); + if (counter->mite_chan) { + struct mite_channel *mite_chan = counter->mite_chan; + + ni_660x_unset_dma_channel(dev, mite_chan->channel, counter); + ni_tio_set_mite_channel(counter, NULL); + mite_release_channel(mite_chan); + } + comedi_spin_unlock_irqrestore(&private(dev)->mite_channel_lock, flags); +} + +static int ni_660x_cmd(comedi_device * dev, comedi_subdevice * s) +{ + int retval; + + struct ni_gpct *counter = subdev_to_counter(s); +// const comedi_cmd *cmd = &s->async->cmd; + + retval = ni_660x_request_mite_channel(dev, counter, COMEDI_INPUT); + if (retval) { + comedi_error(dev, + "no dma channel available for use by counter"); + return retval; + } + ni_tio_acknowledge_and_confirm(counter, NULL, NULL, NULL, NULL); + retval = ni_tio_cmd(counter, s->async); + + return retval; +} + +static int ni_660x_cmdtest(comedi_device * dev, comedi_subdevice * s, + comedi_cmd * cmd) +{ + struct ni_gpct *counter = subdev_to_counter(s); + + return ni_tio_cmdtest(counter, cmd); +} + +static int ni_660x_cancel(comedi_device * dev, comedi_subdevice * s) +{ + struct ni_gpct *counter = subdev_to_counter(s); + int retval; + + retval = ni_tio_cancel(counter); + ni_660x_release_mite_channel(dev, counter); + return retval; +} + +static void set_tio_counterswap(comedi_device * dev, int chipset) +{ + /* See P. 3.5 of the Register-Level Programming manual. The + CounterSwap bit has to be set on the second chip, otherwise + it will try to use the same pins as the first chip. + */ + if (chipset) + ni_660x_write_register(dev, chipset, CounterSwap, + ClockConfigRegister); + else + ni_660x_write_register(dev, chipset, 0, ClockConfigRegister); +} + +static void ni_660x_handle_gpct_interrupt(comedi_device * dev, + comedi_subdevice * s) +{ + ni_tio_handle_interrupt(subdev_to_counter(s), s); + if (s->async->events) { + if (s->async-> + events & (COMEDI_CB_EOA | COMEDI_CB_ERROR | + COMEDI_CB_OVERFLOW)) { + ni_660x_cancel(dev, s); + } + comedi_event(dev, s); + } +} + +static irqreturn_t ni_660x_interrupt(int irq, void *d PT_REGS_ARG) +{ + comedi_device *dev = d; + comedi_subdevice *s; + unsigned i; + + if (dev->attached == 0) + return IRQ_NONE; + smp_mb(); + for (i = 0; i < ni_660x_num_counters(dev); ++i) { + s = dev->subdevices + NI_660X_GPCT_SUBDEV(i); + ni_660x_handle_gpct_interrupt(dev, s); + } + return IRQ_HANDLED; +} + +static int ni_660x_buf_change(comedi_device * dev, comedi_subdevice * s, + unsigned long new_size) +{ + int ret; + + ret = mite_buf_change(mite_ring(private(dev), subdev_to_counter(s)), + s->async); + if (ret < 0) + return ret; + + return 0; +} + +static int ni_660x_allocate_private(comedi_device * dev) +{ + int retval; + unsigned i; + + if ((retval = alloc_private(dev, sizeof(ni_660x_private))) < 0) + return retval; + spin_lock_init(&private(dev)->mite_channel_lock); + spin_lock_init(&private(dev)->soft_reg_copy_lock); + for (i = 0; i < NUM_PFI_CHANNELS; ++i) { + private(dev)->pfi_output_selects[i] = pfi_output_select_counter; + } + return 0; +} + +static int ni_660x_alloc_mite_rings(comedi_device * dev) +{ + unsigned i; + unsigned j; + + for (i = 0; i < board(dev)->n_chips; ++i) { + for (j = 0; j < counters_per_chip; ++j) { + private(dev)->mite_rings[i][j] = + mite_alloc_ring(private(dev)->mite); + if (private(dev)->mite_rings[i][j] == NULL) { + return -ENOMEM; + } + } + } + return 0; +} + +static void ni_660x_free_mite_rings(comedi_device * dev) +{ + unsigned i; + unsigned j; + + for (i = 0; i < board(dev)->n_chips; ++i) { + for (j = 0; j < counters_per_chip; ++j) { + mite_free_ring(private(dev)->mite_rings[i][j]); + } + } +} + +static int ni_660x_attach(comedi_device * dev, comedi_devconfig * it) +{ + comedi_subdevice *s; + int ret; + unsigned i; + unsigned global_interrupt_config_bits; + + printk("comedi%d: ni_660x: ", dev->minor); + + ret = ni_660x_allocate_private(dev); + if (ret < 0) + return ret; + ret = ni_660x_find_device(dev, it->options[0], it->options[1]); + if (ret < 0) + return ret; + + dev->board_name = board(dev)->name; + + ret = mite_setup2(private(dev)->mite, 1); + if (ret < 0) { + printk("error setting up mite\n"); + return ret; + } + comedi_set_hw_dev(dev, &private(dev)->mite->pcidev->dev); + ret = ni_660x_alloc_mite_rings(dev); + if (ret < 0) + return ret; + + printk(" %s ", dev->board_name); + + dev->n_subdevices = 2 + NI_660X_MAX_NUM_COUNTERS; + + if (alloc_subdevices(dev, dev->n_subdevices) < 0) + return -ENOMEM; + + s = dev->subdevices + 0; + /* Old GENERAL-PURPOSE COUNTER/TIME (GPCT) subdevice, no longer used */ + s->type = COMEDI_SUBD_UNUSED; + + s = dev->subdevices + NI_660X_DIO_SUBDEV; + /* DIGITAL I/O SUBDEVICE */ + s->type = COMEDI_SUBD_DIO; + s->subdev_flags = SDF_READABLE | SDF_WRITABLE; + s->n_chan = NUM_PFI_CHANNELS; + s->maxdata = 1; + s->range_table = &range_digital; + 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 */ + // we use the ioconfig registers to control dio direction, so zero output enables in stc dio control reg + ni_660x_write_register(dev, 0, 0, STCDIOControl); + + private(dev)->counter_dev = ni_gpct_device_construct(dev, + &ni_gpct_write_register, &ni_gpct_read_register, + ni_gpct_variant_660x, ni_660x_num_counters(dev)); + if (private(dev)->counter_dev == NULL) + return -ENOMEM; + for (i = 0; i < NI_660X_MAX_NUM_COUNTERS; ++i) { + s = dev->subdevices + NI_660X_GPCT_SUBDEV(i); + if (i < ni_660x_num_counters(dev)) { + s->type = COMEDI_SUBD_COUNTER; + s->subdev_flags = + SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL | + SDF_CMD_READ /* | SDF_CMD_WRITE */ ; + s->n_chan = 3; + s->maxdata = 0xffffffff; + s->insn_read = ni_660x_GPCT_rinsn; + s->insn_write = ni_660x_GPCT_winsn; + s->insn_config = ni_660x_GPCT_insn_config; + s->do_cmd = &ni_660x_cmd; + s->len_chanlist = 1; + s->do_cmdtest = &ni_660x_cmdtest; + s->cancel = &ni_660x_cancel; + s->async_dma_dir = DMA_BIDIRECTIONAL; + s->buf_change = &ni_660x_buf_change; + s->private = &private(dev)->counter_dev->counters[i]; + + private(dev)->counter_dev->counters[i].chip_index = + i / counters_per_chip; + private(dev)->counter_dev->counters[i].counter_index = + i % counters_per_chip; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + } + for (i = 0; i < board(dev)->n_chips; ++i) { + init_tio_chip(dev, i); + } + for (i = 0; i < ni_660x_num_counters(dev); ++i) { + ni_tio_init_counter(&private(dev)->counter_dev->counters[i]); + } + for (i = 0; i < NUM_PFI_CHANNELS; ++i) { + if (i < min_counter_pfi_chan) + ni_660x_set_pfi_routing(dev, i, pfi_output_select_do); + else + ni_660x_set_pfi_routing(dev, i, + pfi_output_select_counter); + ni_660x_select_pfi_output(dev, i, pfi_output_select_high_Z); + } + /* to be safe, set counterswap bits on tio chips after all the counter + outputs have been set to high impedance mode */ + for (i = 0; i < board(dev)->n_chips; ++i) { + set_tio_counterswap(dev, i); + } + if ((ret = comedi_request_irq(mite_irq(private(dev)->mite), + &ni_660x_interrupt, IRQF_SHARED, "ni_660x", + dev)) < 0) { + printk(" irq not available\n"); + return ret; + } + dev->irq = mite_irq(private(dev)->mite); + global_interrupt_config_bits = Global_Int_Enable_Bit; + if (board(dev)->n_chips > 1) + global_interrupt_config_bits |= Cascade_Int_Enable_Bit; + ni_660x_write_register(dev, 0, global_interrupt_config_bits, + GlobalInterruptConfigRegister); + printk("attached\n"); + return 0; +} + +static int ni_660x_detach(comedi_device * dev) +{ + printk("comedi%d: ni_660x: remove\n", dev->minor); + + /* Free irq */ + if (dev->irq) + comedi_free_irq(dev->irq, dev); + + if (dev->private) { + if (private(dev)->counter_dev) + ni_gpct_device_destroy(private(dev)->counter_dev); + if (private(dev)->mite) { + ni_660x_free_mite_rings(dev); + mite_unsetup(private(dev)->mite); + } + } + return 0; +} + +static int +ni_660x_GPCT_rinsn(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + return ni_tio_rinsn(subdev_to_counter(s), insn, data); +} + +static void init_tio_chip(comedi_device * dev, int chipset) +{ + unsigned i; + + // init dma configuration register + private(dev)->dma_configuration_soft_copies[chipset] = 0; + for (i = 0; i < MAX_DMA_CHANNEL; ++i) { + private(dev)->dma_configuration_soft_copies[chipset] |= + dma_select_bits(i, + dma_selection_none) & dma_select_mask(i); + } + ni_660x_write_register(dev, chipset, + private(dev)->dma_configuration_soft_copies[chipset], + DMAConfigRegister); + for(i = 0; i < NUM_PFI_CHANNELS; ++i) + { + ni_660x_write_register(dev, chipset, 0, IOConfigReg(i)); + } +} + +static int +ni_660x_GPCT_insn_config(comedi_device * dev, comedi_subdevice * s, + comedi_insn * insn, lsampl_t * data) +{ + return ni_tio_insn_config(subdev_to_counter(s), insn, data); +} + +static int ni_660x_GPCT_winsn(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + return ni_tio_winsn(subdev_to_counter(s), insn, data); +} + +static int ni_660x_find_device(comedi_device * dev, int bus, int slot) +{ + struct mite_struct *mite; + int i; + + for (mite = mite_devices; mite; mite = mite->next) { + if (mite->used) + continue; + if (bus || slot) { + if (bus != mite->pcidev->bus->number || + slot != PCI_SLOT(mite->pcidev->devfn)) + continue; + } + + for (i = 0; i < n_ni_660x_boards; i++) { + if (mite_device_id(mite) == ni_660x_boards[i].dev_id) { + dev->board_ptr = ni_660x_boards + i; + private(dev)->mite = mite; + return 0; + } + } + } + printk("no device found\n"); + mite_list_devices(); + return -EIO; +} + +static int ni_660x_dio_insn_bits(comedi_device * dev, + comedi_subdevice * s, comedi_insn * insn, lsampl_t * data) +{ + unsigned base_bitfield_channel = CR_CHAN(insn->chanspec); + + // Check if we have to write some bits + if (data[0]) { + s->state &= ~(data[0] << base_bitfield_channel); + s->state |= (data[0] & data[1]) << base_bitfield_channel; + /* Write out the new digital output lines */ + ni_660x_write_register(dev, 0, s->state, DIO32Output); + } + /* on return, data[1] contains the value of the digital + * input and output lines. */ + data[1] = + (ni_660x_read_register(dev, 0, + DIO32Input) >> base_bitfield_channel); + return 2; +} + +static void ni_660x_select_pfi_output(comedi_device * dev, unsigned pfi_channel, + unsigned output_select) +{ + static const unsigned counter_4_7_first_pfi = 8; + static const unsigned counter_4_7_last_pfi = 23; + unsigned active_chipset = 0; + unsigned idle_chipset = 0; + unsigned active_bits; + unsigned idle_bits; + + if(board(dev)->n_chips > 1) { + if(output_select == pfi_output_select_counter && + pfi_channel >= counter_4_7_first_pfi && + pfi_channel <= counter_4_7_last_pfi) { + active_chipset = 1; + idle_chipset = 0; + }else { + active_chipset = 0; + idle_chipset = 1; + } + } + + if(idle_chipset != active_chipset) { + idle_bits = ni_660x_read_register(dev, idle_chipset, IOConfigReg(pfi_channel)); + idle_bits &= ~pfi_output_select_mask(pfi_channel); + idle_bits |= pfi_output_select_bits(pfi_channel, pfi_output_select_high_Z); + ni_660x_write_register(dev, idle_chipset, idle_bits, IOConfigReg(pfi_channel)); + } + + active_bits = ni_660x_read_register(dev, active_chipset, IOConfigReg(pfi_channel)); + active_bits &= ~pfi_output_select_mask(pfi_channel); + active_bits |= pfi_output_select_bits(pfi_channel, output_select); + ni_660x_write_register(dev, active_chipset, active_bits, IOConfigReg(pfi_channel)); +} + +static int ni_660x_set_pfi_routing(comedi_device * dev, unsigned chan, + unsigned source) +{ + if (source > num_pfi_output_selects) + return -EINVAL; + if (source == pfi_output_select_high_Z) + return -EINVAL; + if (chan < min_counter_pfi_chan) { + if (source == pfi_output_select_counter) + return -EINVAL; + } else if (chan > max_dio_pfi_chan) { + if (source == pfi_output_select_do) + return -EINVAL; + } + BUG_ON(chan >= NUM_PFI_CHANNELS); + + private(dev)->pfi_output_selects[chan] = source; + if (private(dev)->pfi_direction_bits & (((uint64_t) 1) << chan)) + ni_660x_select_pfi_output(dev, chan, + private(dev)->pfi_output_selects[chan]); + return 0; +} + +static unsigned ni_660x_get_pfi_routing(comedi_device * dev, unsigned chan) +{ + BUG_ON(chan >= NUM_PFI_CHANNELS); + return private(dev)->pfi_output_selects[chan]; +} + +static void ni660x_config_filter(comedi_device * dev, unsigned pfi_channel, + enum ni_gpct_filter_select filter) +{ + unsigned bits = ni_660x_read_register(dev, 0, IOConfigReg(pfi_channel)); + bits &= ~pfi_input_select_mask(pfi_channel); + bits |= pfi_input_select_bits(pfi_channel, filter); + ni_660x_write_register(dev, 0, bits, IOConfigReg(pfi_channel)); +} + +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); + + /* The input or output configuration of each digital line is + * configured by a special insn_config instruction. chanspec + * contains the channel to be changed, and data[0] contains the + * value COMEDI_INPUT or COMEDI_OUTPUT. */ + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + private(dev)->pfi_direction_bits |= ((uint64_t) 1) << chan; + ni_660x_select_pfi_output(dev, chan, + private(dev)->pfi_output_selects[chan]); + break; + case INSN_CONFIG_DIO_INPUT: + private(dev)->pfi_direction_bits &= ~(((uint64_t) 1) << chan); + ni_660x_select_pfi_output(dev, chan, pfi_output_select_high_Z); + break; + case INSN_CONFIG_DIO_QUERY: + data[1] = + (private(dev)-> + pfi_direction_bits & (((uint64_t) 1) << chan)) ? + COMEDI_OUTPUT : COMEDI_INPUT; + return 0; + case INSN_CONFIG_SET_ROUTING: + return ni_660x_set_pfi_routing(dev, chan, data[1]); + break; + case INSN_CONFIG_GET_ROUTING: + data[1] = ni_660x_get_pfi_routing(dev, chan); + break; + case INSN_CONFIG_FILTER: + ni660x_config_filter(dev, chan, data[1]); + break; + default: + return -EINVAL; + break; + }; + return 0; +}