/* * 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 Xenomai; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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) */ /* * Integration with Xenomai/Analogy layer based on the * comedi driver. Adaptation made by * Julien Delange */ #include #include #include #include "../intel/8255.h" #include "ni_stc.h" #include "ni_mio.h" #include "ni_tio.h" #include "mite.h" enum io_direction { DIRECTION_INPUT = 0, DIRECTION_OUTPUT = 1, DIRECTION_OPENDRAIN = 2 }; enum ni_660x_constants { min_counter_pfi_chan = 8, max_dio_pfi_chan = 31, counters_per_chip = 4 }; struct ni_660x_subd_priv { int io_bits; unsigned int state; uint16_t readback[2]; uint16_t config; struct ni_gpct* counter; }; #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 static struct a4l_channels_desc chandesc_ni660x = { .mode = A4L_CHAN_GLOBAL_CHANDESC, .length = NUM_PFI_CHANNELS, .chans = { {A4L_CHAN_AREF_GROUND, sizeof(sampl_t)}, }, }; #define subdev_priv ((struct ni_660x_subd_priv*)s->priv) /* See Register-Level Programmer Manual page 3.1 */ enum NI_660x_Register { 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, }; 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; } struct NI_660xRegisterData { 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 */ }; static const struct 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: First chip is at base-address +0x00, etc. */ static const unsigned GPCT_OFFSET[2] = { 0x0, 0x800 }; /* Board description */ struct ni_660x_board { unsigned short dev_id; /* `lspci` will show you this */ const char *name; unsigned n_chips; /* total number of TIO chips */ }; static const struct 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 const struct pci_device_id 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); struct ni_660x_private { 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]; rtdm_lock_t mite_channel_lock; /* Interrupt_lock prevents races between interrupt and comedi_poll */ rtdm_lock_t interrupt_lock; unsigned int dma_configuration_soft_copies[NI_660X_MAX_NUM_CHIPS]; rtdm_lock_t soft_reg_copy_lock; unsigned short pfi_output_selects[NUM_PFI_CHANNELS]; struct ni_660x_board *board_ptr; }; #undef devpriv #define devpriv ((struct ni_660x_private *)dev->priv) static inline struct ni_660x_private *private(struct a4l_device *dev) { return (struct ni_660x_private*) dev->priv; } /* Initialized in ni_660x_find_device() */ static inline const struct ni_660x_board *board(struct a4l_device *dev) { return ((struct ni_660x_private*)dev->priv)->board_ptr; } #define n_ni_660x_boards ARRAY_SIZE(ni_660x_boards) static int ni_660x_attach(struct a4l_device *dev, a4l_lnkdesc_t *arg); static int ni_660x_detach(struct a4l_device *dev); static void init_tio_chip(struct a4l_device *dev, int chipset); static void ni_660x_select_pfi_output(struct a4l_device *dev, unsigned pfi_channel, unsigned output_select); static struct a4l_driver ni_660x_drv = { .board_name = "analogy_ni_660x", .driver_name = "ni_660x", .owner = THIS_MODULE, .attach = ni_660x_attach, .detach = ni_660x_detach, .privdata_size = sizeof(struct ni_660x_private), }; static int ni_660x_set_pfi_routing(struct a4l_device *dev, unsigned chan, unsigned source); /* Possible instructions for a GPCT */ static int ni_660x_GPCT_rinsn( struct a4l_subdevice *s, struct a4l_kernel_instruction *insn); static int ni_660x_GPCT_insn_config( struct a4l_subdevice *s, struct a4l_kernel_instruction *insn); static int ni_660x_GPCT_winsn( struct a4l_subdevice *s, struct a4l_kernel_instruction *insn); /* Possible instructions for Digital IO */ static int ni_660x_dio_insn_config( struct a4l_subdevice *s, struct a4l_kernel_instruction *insn); static int ni_660x_dio_insn_bits( struct a4l_subdevice *s, struct a4l_kernel_instruction *insn); static inline unsigned ni_660x_num_counters(struct a4l_device *dev) { return board(dev)->n_chips * counters_per_chip; } static enum NI_660x_Register ni_gpct_to_660x_register(enum ni_gpct_register reg) { enum 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: __a4l_err("%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(struct a4l_device *dev, unsigned chip_index, unsigned bits, enum 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: __a4l_err("%s: %s: bug! unhandled case (reg=0x%x) in switch.\n", __FILE__, __FUNCTION__, reg); BUG(); break; } } static inline unsigned ni_660x_read_register(struct a4l_device *dev, unsigned chip_index, enum 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: __a4l_err("%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 int bits, enum ni_gpct_register reg) { struct a4l_device *dev = counter->counter_dev->dev; enum 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) { struct a4l_device *dev = counter->counter_dev->dev; enum 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(struct 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(struct a4l_device *dev, unsigned int mite_channel, struct ni_gpct *counter) { unsigned long flags; rtdm_lock_get_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(); rtdm_lock_put_irqrestore(&private(dev)->soft_reg_copy_lock, flags); } static inline void ni_660x_unset_dma_channel(struct a4l_device *dev, unsigned int mite_channel, struct ni_gpct *counter) { unsigned long flags; rtdm_lock_get_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(); rtdm_lock_put_irqrestore(&private(dev)->soft_reg_copy_lock, flags); } static int ni_660x_request_mite_channel(struct a4l_device *dev, struct ni_gpct *counter, enum io_direction direction) { unsigned long flags; struct mite_channel *mite_chan; rtdm_lock_get_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) { rtdm_lock_put_irqrestore(&private(dev)->mite_channel_lock, flags); a4l_err(dev, "%s: failed to reserve mite dma channel for counter.\n", __FUNCTION__); return -EBUSY; } mite_chan->dir = direction; a4l_ni_tio_set_mite_channel(counter, mite_chan); ni_660x_set_dma_channel(dev, mite_chan->channel, counter); rtdm_lock_put_irqrestore(&private(dev)->mite_channel_lock, flags); return 0; } void ni_660x_release_mite_channel(struct a4l_device *dev, struct ni_gpct *counter) { unsigned long flags; rtdm_lock_get_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); a4l_ni_tio_set_mite_channel(counter, NULL); a4l_mite_release_channel(mite_chan); } rtdm_lock_put_irqrestore(&private(dev)->mite_channel_lock, flags); } static int ni_660x_cmd(struct a4l_subdevice *s, struct a4l_cmd_desc* cmd) { int retval; struct ni_gpct *counter = subdev_priv->counter; retval = ni_660x_request_mite_channel(s->dev, counter, A4L_INPUT); if (retval) { a4l_err(s->dev, "%s: no dma channel available for use by counter", __FUNCTION__); return retval; } a4l_ni_tio_acknowledge_and_confirm (counter, NULL, NULL, NULL, NULL); retval = a4l_ni_tio_cmd(counter, cmd); return retval; } static int ni_660x_cmdtest(struct a4l_subdevice *s, struct a4l_cmd_desc *cmd) { struct ni_gpct *counter = subdev_priv->counter; return a4l_ni_tio_cmdtest(counter, cmd); } static int ni_660x_cancel(struct a4l_subdevice *s) { struct ni_gpct *counter = subdev_priv->counter; int retval; retval = a4l_ni_tio_cancel(counter); ni_660x_release_mite_channel(s->dev, counter); return retval; } static void set_tio_counterswap(struct a4l_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(struct a4l_device *dev, struct a4l_subdevice *s) { struct a4l_buffer *buf = s->buf; a4l_ni_tio_handle_interrupt(subdev_priv->counter, dev); if ( test_bit(A4L_BUF_EOA_NR, &buf->flags) && test_bit(A4L_BUF_ERROR_NR, &buf->flags) && test_bit(A4L_BUF_EOA_NR, &buf->flags)) ni_660x_cancel(s); else a4l_buf_evt(s, 0); } static int ni_660x_interrupt(unsigned int irq, void *d) { struct a4l_device *dev = d; unsigned long flags; if (test_bit(A4L_DEV_ATTACHED_NR, &dev->flags)) return -ENOENT; /* Lock to avoid race with comedi_poll */ rtdm_lock_get_irqsave(&private(dev)->interrupt_lock, flags); smp_mb(); while (&dev->subdvsq != dev->subdvsq.next) { struct list_head *this = dev->subdvsq.next; struct a4l_subdevice *tmp = list_entry(this, struct a4l_subdevice, list); ni_660x_handle_gpct_interrupt(dev, tmp); } rtdm_lock_put_irqrestore(&private(dev)->interrupt_lock, flags); return 0; } static int ni_660x_alloc_mite_rings(struct a4l_device *dev) { unsigned int i; unsigned int 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(struct a4l_device *dev) { unsigned int i; unsigned int 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 __init driver_ni_660x_init_module(void) { return a4l_register_drv (&ni_660x_drv); } static void __exit driver_ni_660x_cleanup_module(void) { a4l_unregister_drv (&ni_660x_drv); } module_init(driver_ni_660x_init_module); module_exit(driver_ni_660x_cleanup_module); static int ni_660x_attach(struct a4l_device *dev, a4l_lnkdesc_t *arg) { struct a4l_subdevice *s; int ret; int err; int bus, slot; unsigned i; int nsubdev = 0; unsigned global_interrupt_config_bits; struct mite_struct *mitedev; struct ni_660x_board* boardptr = NULL; ret = 0; bus = slot = 0; mitedev = NULL; nsubdev = 0; if(arg->opts == NULL || arg->opts_size == 0) bus = slot = 0; else { bus = arg->opts_size >= sizeof(unsigned long) ? ((unsigned long *)arg->opts)[0] : 0; slot = arg->opts_size >= sizeof(unsigned long) * 2 ? ((unsigned long *)arg->opts)[1] : 0; } for (i = 0; ( i < n_ni_660x_boards ) && ( mitedev == NULL ); i++) { mitedev = a4l_mite_find_device(bus, slot, ni_660x_boards[i].dev_id); boardptr = (struct ni_660x_board*) &ni_660x_boards[i]; } if(mitedev == NULL) { a4l_info(dev, "mite device not found\n"); return -ENOENT; } a4l_info(dev, "Board found (name=%s), continue initialization ...", boardptr->name); private(dev)->mite = mitedev; private(dev)->board_ptr = boardptr; rtdm_lock_init(&private(dev)->mite_channel_lock); rtdm_lock_init(&private(dev)->interrupt_lock); rtdm_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; } ret = a4l_mite_setup(private(dev)->mite, 1); if (ret < 0) { a4l_err(dev, "%s: error setting up mite\n", __FUNCTION__); return ret; } ret = ni_660x_alloc_mite_rings(dev); if (ret < 0) { a4l_err(dev, "%s: error setting up mite rings\n", __FUNCTION__); return ret; } /* Setup first subdevice */ s = a4l_alloc_subd(sizeof(struct ni_660x_subd_priv), NULL); if (s == NULL) return -ENOMEM; s->flags = A4L_SUBD_UNUSED; err = a4l_add_subd(dev, s); if (err != nsubdev) { a4l_info(dev, "cannot add first subdevice, returns %d, expect %d\n", err, i); return err; } nsubdev++; /* Setup second subdevice */ s = a4l_alloc_subd(sizeof(struct ni_660x_subd_priv), NULL); if (s == NULL) { a4l_info(dev, "cannot allocate second subdevice\n"); return -ENOMEM; } s->flags = A4L_SUBD_DIO; s->flags |= A4L_SUBD_CMD; s->chan_desc = &chandesc_ni660x; s->rng_desc = &range_digital; s->insn_bits = ni_660x_dio_insn_bits; s->insn_config = ni_660x_dio_insn_config; s->dev = dev; subdev_priv->io_bits = 0; ni_660x_write_register(dev, 0, 0, STCDIOControl); err = a4l_add_subd(dev, s); if (err != nsubdev) return err; nsubdev++; private(dev)->counter_dev = a4l_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_num_counters(dev); ++i) { /* TODO: check why there are kmalloc here... and in pcimio */ private(dev)->counter_dev->counters[i] = kmalloc(sizeof(struct ni_gpct), GFP_KERNEL); private(dev)->counter_dev->counters[i]->counter_dev = private(dev)->counter_dev; rtdm_lock_init(&(private(dev)->counter_dev->counters[i]->lock)); } for (i = 0; i < NI_660X_MAX_NUM_COUNTERS; ++i) { if (i < ni_660x_num_counters(dev)) { /* Setup other subdevice */ s = a4l_alloc_subd(sizeof(struct ni_660x_subd_priv), NULL); if (s == NULL) return -ENOMEM; s->flags = A4L_SUBD_COUNTER; s->chan_desc = rtdm_malloc (sizeof (struct a4l_channels_desc)); s->chan_desc->length = 3; 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->do_cmdtest = &ni_660x_cmdtest; s->cancel = &ni_660x_cancel; subdev_priv->counter = 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 = a4l_alloc_subd(sizeof(struct ni_660x_subd_priv), NULL); if (s == NULL) return -ENOMEM; s->flags = A4L_SUBD_UNUSED; } err = a4l_add_subd(dev, s); if (err != nsubdev) return err; nsubdev++; } for (i = 0; i < board(dev)->n_chips; ++i) init_tio_chip(dev, i); for (i = 0; i < ni_660x_num_counters(dev); ++i) a4l_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); ret = a4l_request_irq(dev, mite_irq(private(dev)->mite), ni_660x_interrupt, RTDM_IRQTYPE_SHARED, dev); if (ret < 0) { a4l_err(dev, "%s: IRQ not available\n", __FUNCTION__); return ret; } 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); a4l_info(dev, "attach succeed, ready to be used\n"); return 0; } static int ni_660x_detach(struct a4l_device *dev) { int i; a4l_info(dev, "begin to detach the driver ..."); /* Free irq */ if(a4l_get_irq(dev)!=A4L_IRQ_UNUSED) a4l_free_irq(dev,a4l_get_irq(dev)); if (dev->priv) { if (private(dev)->counter_dev) { for (i = 0; i < ni_660x_num_counters(dev); ++i) if ((private(dev)->counter_dev->counters[i]) != NULL) kfree (private(dev)->counter_dev->counters[i]); a4l_ni_gpct_device_destroy(private(dev)->counter_dev); } if (private(dev)->mite) { ni_660x_free_mite_rings(dev); a4l_mite_unsetup(private(dev)->mite); } } a4l_info(dev, "driver detached !\n"); return 0; } static int ni_660x_GPCT_rinsn(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn) { return a4l_ni_tio_rinsn(subdev_priv->counter, insn); } static void init_tio_chip(struct a4l_device *dev, int chipset) { unsigned int 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(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn) { return a4l_ni_tio_insn_config (subdev_priv->counter, insn); } static int ni_660x_GPCT_winsn(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn) { return a4l_ni_tio_winsn(subdev_priv->counter, insn); } static int ni_660x_dio_insn_bits(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn) { unsigned int* data = (unsigned int*) insn->data; unsigned int base_bitfield_channel = CR_CHAN(insn->chan_desc); /* Check if we have to write some bits */ if (data[0]) { subdev_priv->state &= ~(data[0] << base_bitfield_channel); subdev_priv->state |= (data[0] & data[1]) << base_bitfield_channel; /* Write out the new digital output lines */ ni_660x_write_register(s->dev, 0, subdev_priv->state, DIO32Output); } /* On return, data[1] contains the value of the digital input and output lines. */ data[1] = ni_660x_read_register(s->dev, 0,DIO32Input) >> base_bitfield_channel; return 0; } static void ni_660x_select_pfi_output(struct a4l_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(struct a4l_device *dev, unsigned chan, unsigned source) { BUG_ON(chan >= NUM_PFI_CHANNELS); 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(struct a4l_device *dev, unsigned chan) { BUG_ON(chan >= NUM_PFI_CHANNELS); return private(dev)->pfi_output_selects[chan]; } static void ni660x_config_filter(struct a4l_device *dev, unsigned pfi_channel, int filter) { unsigned int bits; 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(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn) { unsigned int* data = insn->data; int chan = CR_CHAN(insn->chan_desc); struct a4l_device* dev = s->dev; if (data == NULL) 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. */ switch (data[0]) { case A4L_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 A4L_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 A4L_INSN_CONFIG_DIO_QUERY: data[1] = (private(dev)->pfi_direction_bits & (((uint64_t) 1) << chan)) ? A4L_OUTPUT : A4L_INPUT; return 0; case A4L_INSN_CONFIG_SET_ROUTING: return ni_660x_set_pfi_routing(dev, chan, data[1]); break; case A4L_INSN_CONFIG_GET_ROUTING: data[1] = ni_660x_get_pfi_routing(dev, chan); break; case A4L_INSN_CONFIG_FILTER: ni660x_config_filter(dev, chan, data[1]); break; default: return -EINVAL; break; }; return 0; } MODULE_DESCRIPTION("Analogy driver for NI660x series cards"); MODULE_LICENSE("GPL");