| /* |
| 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 <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: 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 <linux/interrupt.h> |
| #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 */ |
| 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 DEFINE_PCI_DEVICE_TABLE(ni_660x_pci_table) = { |
| {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2c60)}, |
| {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1310)}, |
| {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x1360)}, |
| {PCI_DEVICE(PCI_VENDOR_ID_NI, 0x2cc0)}, |
| {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]; |
| spinlock_t mite_channel_lock; |
| /* interrupt_lock prevents races between interrupt and comedi_poll */ |
| spinlock_t interrupt_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]; |
| }; |
| |
| static inline struct ni_660x_private *private(struct comedi_device *dev) |
| { |
| return dev->private; |
| } |
| |
| /* initialized in ni_660x_find_device() */ |
| static inline const struct ni_660x_board *board(struct comedi_device *dev) |
| { |
| return dev->board_ptr; |
| } |
| |
| #define n_ni_660x_boards ARRAY_SIZE(ni_660x_boards) |
| |
| static int ni_660x_attach(struct comedi_device *dev, |
| struct comedi_devconfig *it); |
| static int ni_660x_detach(struct comedi_device *dev); |
| static void init_tio_chip(struct comedi_device *dev, int chipset); |
| static void ni_660x_select_pfi_output(struct comedi_device *dev, |
| unsigned pfi_channel, |
| unsigned output_select); |
| |
| static struct 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(struct comedi_device *dev, int bus, int slot); |
| static int ni_660x_set_pfi_routing(struct comedi_device *dev, unsigned chan, |
| unsigned source); |
| |
| /* Possible instructions for a GPCT */ |
| static int ni_660x_GPCT_rinsn(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data); |
| static int ni_660x_GPCT_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data); |
| static int ni_660x_GPCT_winsn(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data); |
| |
| /* Possible instructions for Digital IO */ |
| static int ni_660x_dio_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data); |
| static int ni_660x_dio_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data); |
| |
| static inline unsigned ni_660x_num_counters(struct comedi_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 = G1StatusRegister; |
| break; |
| case NITIO_G2_Status_Reg: |
| ni_660x_register = G2StatusRegister; |
| break; |
| case NITIO_G3_Status_Reg: |
| ni_660x_register = G3StatusRegister; |
| 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: |
| printk(KERN_WARNING "%s: unhandled register 0x%x in switch.\n", |
| __func__, reg); |
| BUG(); |
| return 0; |
| break; |
| } |
| return ni_660x_register; |
| } |
| |
| static inline void ni_660x_write_register(struct comedi_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: |
| printk(KERN_WARNING "%s: %s: bug! unhandled case (reg=0x%x) in switch.\n", |
| __FILE__, __func__, reg); |
| BUG(); |
| break; |
| } |
| } |
| |
| static inline unsigned ni_660x_read_register(struct comedi_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: |
| printk(KERN_WARNING "%s: %s: bug! unhandled case (reg=0x%x) in switch.\n", |
| __FILE__, __func__, reg); |
| BUG(); |
| break; |
| } |
| return 0; |
| } |
| |
| static void ni_gpct_write_register(struct ni_gpct *counter, unsigned bits, |
| enum ni_gpct_register reg) |
| { |
| struct comedi_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 comedi_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 comedi_device *dev, |
| unsigned mite_channel, |
| struct ni_gpct *counter) |
| { |
| unsigned long flags; |
| 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(); |
| spin_unlock_irqrestore(&private(dev)->soft_reg_copy_lock, flags); |
| } |
| |
| static inline void ni_660x_unset_dma_channel(struct comedi_device *dev, |
| unsigned mite_channel, |
| struct ni_gpct *counter) |
| { |
| unsigned long flags; |
| 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(); |
| spin_unlock_irqrestore(&private(dev)->soft_reg_copy_lock, flags); |
| } |
| |
| static int ni_660x_request_mite_channel(struct comedi_device *dev, |
| struct ni_gpct *counter, |
| enum comedi_io_direction direction) |
| { |
| unsigned long flags; |
| struct mite_channel *mite_chan; |
| |
| 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) { |
| 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); |
| spin_unlock_irqrestore(&private(dev)->mite_channel_lock, flags); |
| return 0; |
| } |
| |
| void ni_660x_release_mite_channel(struct comedi_device *dev, |
| struct ni_gpct *counter) |
| { |
| unsigned long flags; |
| |
| 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); |
| } |
| spin_unlock_irqrestore(&private(dev)->mite_channel_lock, flags); |
| } |
| |
| static int ni_660x_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
| { |
| int retval; |
| |
| struct ni_gpct *counter = subdev_to_counter(s); |
| /* const struct 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(struct comedi_device *dev, |
| struct comedi_subdevice *s, struct comedi_cmd *cmd) |
| { |
| struct ni_gpct *counter = subdev_to_counter(s); |
| |
| return ni_tio_cmdtest(counter, cmd); |
| } |
| |
| static int ni_660x_cancel(struct comedi_device *dev, struct 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(struct 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(struct comedi_device *dev, |
| struct 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) |
| { |
| struct comedi_device *dev = d; |
| struct comedi_subdevice *s; |
| unsigned i; |
| unsigned long flags; |
| |
| if (dev->attached == 0) |
| return IRQ_NONE; |
| /* lock to avoid race with comedi_poll */ |
| spin_lock_irqsave(&private(dev)->interrupt_lock, flags); |
| 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); |
| } |
| spin_unlock_irqrestore(&private(dev)->interrupt_lock, flags); |
| return IRQ_HANDLED; |
| } |
| |
| static int ni_660x_input_poll(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| unsigned long flags; |
| /* lock to avoid race with comedi_poll */ |
| spin_lock_irqsave(&private(dev)->interrupt_lock, flags); |
| mite_sync_input_dma(subdev_to_counter(s)->mite_chan, s->async); |
| spin_unlock_irqrestore(&private(dev)->interrupt_lock, flags); |
| return comedi_buf_read_n_available(s->async); |
| } |
| |
| static int ni_660x_buf_change(struct comedi_device *dev, |
| struct 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(struct comedi_device *dev) |
| { |
| int retval; |
| unsigned i; |
| |
| retval = alloc_private(dev, sizeof(struct ni_660x_private)); |
| if (retval < 0) |
| return retval; |
| |
| spin_lock_init(&private(dev)->mite_channel_lock); |
| spin_lock_init(&private(dev)->interrupt_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(struct 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(struct 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(struct comedi_device *dev, |
| struct comedi_devconfig *it) |
| { |
| struct comedi_subdevice *s; |
| int ret; |
| unsigned i; |
| unsigned global_interrupt_config_bits; |
| |
| printk(KERN_INFO "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(KERN_WARNING "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(KERN_INFO " %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->poll = &ni_660x_input_poll; |
| 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); |
| |
| ret = request_irq(mite_irq(private(dev)->mite), ni_660x_interrupt, |
| IRQF_SHARED, "ni_660x", dev); |
| if (ret < 0) { |
| printk(KERN_WARNING " 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(KERN_INFO "attached\n"); |
| return 0; |
| } |
| |
| static int ni_660x_detach(struct comedi_device *dev) |
| { |
| printk(KERN_INFO "comedi%d: ni_660x: remove\n", dev->minor); |
| |
| /* Free irq */ |
| if (dev->irq) |
| 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(struct comedi_device *dev, struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data) |
| { |
| return ni_tio_rinsn(subdev_to_counter(s), insn, data); |
| } |
| |
| static void init_tio_chip(struct 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(struct comedi_device *dev, struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data) |
| { |
| return ni_tio_insn_config(subdev_to_counter(s), insn, data); |
| } |
| |
| static int ni_660x_GPCT_winsn(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data) |
| { |
| return ni_tio_winsn(subdev_to_counter(s), insn, data); |
| } |
| |
| static int ni_660x_find_device(struct 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(KERN_WARNING "no device found\n"); |
| mite_list_devices(); |
| return -EIO; |
| } |
| |
| static int ni_660x_dio_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *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(struct 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(struct 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(struct comedi_device *dev, |
| unsigned chan) |
| { |
| BUG_ON(chan >= NUM_PFI_CHANNELS); |
| return private(dev)->pfi_output_selects[chan]; |
| } |
| |
| static void ni660x_config_filter(struct 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(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *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; |
| } |