diff options
author | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
---|---|---|
committer | André Fabian Silva Delgado <emulatorman@parabola.nu> | 2015-08-05 17:04:01 -0300 |
commit | 57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch) | |
tree | 5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/staging/comedi/drivers/ni_660x.c |
Initial import
Diffstat (limited to 'drivers/staging/comedi/drivers/ni_660x.c')
-rw-r--r-- | drivers/staging/comedi/drivers/ni_660x.c | 1222 |
1 files changed, 1222 insertions, 0 deletions
diff --git a/drivers/staging/comedi/drivers/ni_660x.c b/drivers/staging/comedi/drivers/ni_660x.c new file mode 100644 index 000000000..46647c64f --- /dev/null +++ b/drivers/staging/comedi/drivers/ni_660x.c @@ -0,0 +1,1222 @@ +/* + 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. +*/ + +/* + * Driver: ni_660x + * Description: National Instruments 660x counter/timer boards + * Devices: [National Instruments] PCI-6601 (ni_660x), PCI-6602, PXI-6602, + * PXI-6608, PXI-6624 + * 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: Fri, 15 Mar 2013 10:47:56 +0000 + * 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/module.h> +#include <linux/interrupt.h> + +#include "../comedi_pci.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 { + NI660X_G0_INT_ACK, + NI660X_G0_STATUS, + NI660X_G1_INT_ACK, + NI660X_G1_STATUS, + NI660X_G01_STATUS, + NI660X_G0_CMD, + NI660X_STC_DIO_PARALLEL_INPUT, + NI660X_G1_CMD, + NI660X_G0_HW_SAVE, + NI660X_G1_HW_SAVE, + NI660X_STC_DIO_OUTPUT, + NI660X_STC_DIO_CONTROL, + NI660X_G0_SW_SAVE, + NI660X_G1_SW_SAVE, + NI660X_G0_MODE, + NI660X_G01_STATUS1, + NI660X_G1_MODE, + NI660X_STC_DIO_SERIAL_INPUT, + NI660X_G0_LOADA, + NI660X_G01_STATUS2, + NI660X_G0_LOADB, + NI660X_G1_LOADA, + NI660X_G1_LOADB, + NI660X_G0_INPUT_SEL, + NI660X_G1_INPUT_SEL, + NI660X_G0_AUTO_INC, + NI660X_G1_AUTO_INC, + NI660X_G01_RESET, + NI660X_G0_INT_ENA, + NI660X_G1_INT_ENA, + NI660X_G0_CNT_MODE, + NI660X_G1_CNT_MODE, + NI660X_G0_GATE2, + NI660X_G1_GATE2, + NI660X_G0_DMA_CFG, + NI660X_G0_DMA_STATUS, + NI660X_G1_DMA_CFG, + NI660X_G1_DMA_STATUS, + NI660X_G2_INT_ACK, + NI660X_G2_STATUS, + NI660X_G3_INT_ACK, + NI660X_G3_STATUS, + NI660X_G23_STATUS, + NI660X_G2_CMD, + NI660X_G3_CMD, + NI660X_G2_HW_SAVE, + NI660X_G3_HW_SAVE, + NI660X_G2_SW_SAVE, + NI660X_G3_SW_SAVE, + NI660X_G2_MODE, + NI660X_G23_STATUS1, + NI660X_G3_MODE, + NI660X_G2_LOADA, + NI660X_G23_STATUS2, + NI660X_G2_LOADB, + NI660X_G3_LOADA, + NI660X_G3_LOADB, + NI660X_G2_INPUT_SEL, + NI660X_G3_INPUT_SEL, + NI660X_G2_AUTO_INC, + NI660X_G3_AUTO_INC, + NI660X_G23_RESET, + NI660X_G2_INT_ENA, + NI660X_G3_INT_ENA, + NI660X_G2_CNT_MODE, + NI660X_G3_CNT_MODE, + NI660X_G3_GATE2, + NI660X_G2_GATE2, + NI660X_G2_DMA_CFG, + NI660X_G2_DMA_STATUS, + NI660X_G3_DMA_CFG, + NI660X_G3_DMA_STATUS, + NI660X_DIO32_INPUT, + NI660X_DIO32_OUTPUT, + NI660X_CLK_CFG, + NI660X_GLOBAL_INT_STATUS, + NI660X_DMA_CFG, + NI660X_GLOBAL_INT_CFG, + NI660X_IO_CFG_0_1, + NI660X_IO_CFG_2_3, + NI660X_IO_CFG_4_5, + NI660X_IO_CFG_6_7, + NI660X_IO_CFG_8_9, + NI660X_IO_CFG_10_11, + NI660X_IO_CFG_12_13, + NI660X_IO_CFG_14_15, + NI660X_IO_CFG_16_17, + NI660X_IO_CFG_18_19, + NI660X_IO_CFG_20_21, + NI660X_IO_CFG_22_23, + NI660X_IO_CFG_24_25, + NI660X_IO_CFG_26_27, + NI660X_IO_CFG_28_29, + NI660X_IO_CFG_30_31, + NI660X_IO_CFG_32_33, + NI660X_IO_CFG_34_35, + NI660X_IO_CFG_36_37, + NI660X_IO_CFG_38_39, + NI660X_NUM_REGS, +}; + +static inline unsigned IOConfigReg(unsigned pfi_channel) +{ + unsigned reg = NI660X_IO_CFG_0_1 + pfi_channel / 2; + + BUG_ON(reg > NI660X_IO_CFG_38_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[NI660X_NUM_REGS] = { + {"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) +{ + return (pfi_channel % 2) ? 0 : 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_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-address of the card */ +/* First chip is at base-address + 0x00, etc. */ +static const unsigned GPCT_OFFSET[2] = { 0x0, 0x800 }; + +enum ni_660x_boardid { + BOARD_PCI6601, + BOARD_PCI6602, + BOARD_PXI6602, + BOARD_PXI6608, + BOARD_PXI6624 +}; + +struct ni_660x_board { + const char *name; + unsigned n_chips; /* total number of TIO chips */ +}; + +static const struct ni_660x_board ni_660x_boards[] = { + [BOARD_PCI6601] = { + .name = "PCI-6601", + .n_chips = 1, + }, + [BOARD_PCI6602] = { + .name = "PCI-6602", + .n_chips = 2, + }, + [BOARD_PXI6602] = { + .name = "PXI-6602", + .n_chips = 2, + }, + [BOARD_PXI6608] = { + .name = "PXI-6608", + .n_chips = 2, + }, + [BOARD_PXI6624] = { + .name = "PXI-6624", + .n_chips = 2, + }, +}; + +#define NI_660X_MAX_NUM_CHIPS 2 +#define NI_660X_MAX_NUM_COUNTERS (NI_660X_MAX_NUM_CHIPS * counters_per_chip) + +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 unsigned ni_660x_num_counters(struct comedi_device *dev) +{ + const struct ni_660x_board *board = dev->board_ptr; + + return board->n_chips * counters_per_chip; +} + +static enum ni_660x_register ni_gpct_to_660x_register(enum ni_gpct_register reg) +{ + switch (reg) { + case NITIO_G0_AUTO_INC: + return NI660X_G0_AUTO_INC; + case NITIO_G1_AUTO_INC: + return NI660X_G1_AUTO_INC; + case NITIO_G2_AUTO_INC: + return NI660X_G2_AUTO_INC; + case NITIO_G3_AUTO_INC: + return NI660X_G3_AUTO_INC; + case NITIO_G0_CMD: + return NI660X_G0_CMD; + case NITIO_G1_CMD: + return NI660X_G1_CMD; + case NITIO_G2_CMD: + return NI660X_G2_CMD; + case NITIO_G3_CMD: + return NI660X_G3_CMD; + case NITIO_G0_HW_SAVE: + return NI660X_G0_HW_SAVE; + case NITIO_G1_HW_SAVE: + return NI660X_G1_HW_SAVE; + case NITIO_G2_HW_SAVE: + return NI660X_G2_HW_SAVE; + case NITIO_G3_HW_SAVE: + return NI660X_G3_HW_SAVE; + case NITIO_G0_SW_SAVE: + return NI660X_G0_SW_SAVE; + case NITIO_G1_SW_SAVE: + return NI660X_G1_SW_SAVE; + case NITIO_G2_SW_SAVE: + return NI660X_G2_SW_SAVE; + case NITIO_G3_SW_SAVE: + return NI660X_G3_SW_SAVE; + case NITIO_G0_MODE: + return NI660X_G0_MODE; + case NITIO_G1_MODE: + return NI660X_G1_MODE; + case NITIO_G2_MODE: + return NI660X_G2_MODE; + case NITIO_G3_MODE: + return NI660X_G3_MODE; + case NITIO_G0_LOADA: + return NI660X_G0_LOADA; + case NITIO_G1_LOADA: + return NI660X_G1_LOADA; + case NITIO_G2_LOADA: + return NI660X_G2_LOADA; + case NITIO_G3_LOADA: + return NI660X_G3_LOADA; + case NITIO_G0_LOADB: + return NI660X_G0_LOADB; + case NITIO_G1_LOADB: + return NI660X_G1_LOADB; + case NITIO_G2_LOADB: + return NI660X_G2_LOADB; + case NITIO_G3_LOADB: + return NI660X_G3_LOADB; + case NITIO_G0_INPUT_SEL: + return NI660X_G0_INPUT_SEL; + case NITIO_G1_INPUT_SEL: + return NI660X_G1_INPUT_SEL; + case NITIO_G2_INPUT_SEL: + return NI660X_G2_INPUT_SEL; + case NITIO_G3_INPUT_SEL: + return NI660X_G3_INPUT_SEL; + case NITIO_G01_STATUS: + return NI660X_G01_STATUS; + case NITIO_G23_STATUS: + return NI660X_G23_STATUS; + case NITIO_G01_RESET: + return NI660X_G01_RESET; + case NITIO_G23_RESET: + return NI660X_G23_RESET; + case NITIO_G01_STATUS1: + return NI660X_G01_STATUS1; + case NITIO_G23_STATUS1: + return NI660X_G23_STATUS1; + case NITIO_G01_STATUS2: + return NI660X_G01_STATUS2; + case NITIO_G23_STATUS2: + return NI660X_G23_STATUS2; + case NITIO_G0_CNT_MODE: + return NI660X_G0_CNT_MODE; + case NITIO_G1_CNT_MODE: + return NI660X_G1_CNT_MODE; + case NITIO_G2_CNT_MODE: + return NI660X_G2_CNT_MODE; + case NITIO_G3_CNT_MODE: + return NI660X_G3_CNT_MODE; + case NITIO_G0_GATE2: + return NI660X_G0_GATE2; + case NITIO_G1_GATE2: + return NI660X_G1_GATE2; + case NITIO_G2_GATE2: + return NI660X_G2_GATE2; + case NITIO_G3_GATE2: + return NI660X_G3_GATE2; + case NITIO_G0_DMA_CFG: + return NI660X_G0_DMA_CFG; + case NITIO_G0_DMA_STATUS: + return NI660X_G0_DMA_STATUS; + case NITIO_G1_DMA_CFG: + return NI660X_G1_DMA_CFG; + case NITIO_G1_DMA_STATUS: + return NI660X_G1_DMA_STATUS; + case NITIO_G2_DMA_CFG: + return NI660X_G2_DMA_CFG; + case NITIO_G2_DMA_STATUS: + return NI660X_G2_DMA_STATUS; + case NITIO_G3_DMA_CFG: + return NI660X_G3_DMA_CFG; + case NITIO_G3_DMA_STATUS: + return NI660X_G3_DMA_STATUS; + case NITIO_G0_INT_ACK: + return NI660X_G0_INT_ACK; + case NITIO_G1_INT_ACK: + return NI660X_G1_INT_ACK; + case NITIO_G2_INT_ACK: + return NI660X_G2_INT_ACK; + case NITIO_G3_INT_ACK: + return NI660X_G3_INT_ACK; + case NITIO_G0_STATUS: + return NI660X_G0_STATUS; + case NITIO_G1_STATUS: + return NI660X_G1_STATUS; + case NITIO_G2_STATUS: + return NI660X_G2_STATUS; + case NITIO_G3_STATUS: + return NI660X_G3_STATUS; + case NITIO_G0_INT_ENA: + return NI660X_G0_INT_ENA; + case NITIO_G1_INT_ENA: + return NI660X_G1_INT_ENA; + case NITIO_G2_INT_ENA: + return NI660X_G2_INT_ENA; + case NITIO_G3_INT_ENA: + return NI660X_G3_INT_ENA; + default: + BUG(); + return 0; + } +} + +static inline void ni_660x_write_register(struct comedi_device *dev, + unsigned chip, unsigned bits, + enum ni_660x_register reg) +{ + unsigned int addr = GPCT_OFFSET[chip] + registerData[reg].offset; + + switch (registerData[reg].size) { + case DATA_2B: + writew(bits, dev->mmio + addr); + break; + case DATA_4B: + writel(bits, dev->mmio + addr); + break; + default: + BUG(); + break; + } +} + +static inline unsigned ni_660x_read_register(struct comedi_device *dev, + unsigned chip, + enum ni_660x_register reg) +{ + unsigned int addr = GPCT_OFFSET[chip] + registerData[reg].offset; + + switch (registerData[reg].size) { + case DATA_2B: + return readw(dev->mmio + addr); + case DATA_4B: + return readl(dev->mmio + addr); + default: + 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); + unsigned chip = counter->chip_index; + + ni_660x_write_register(dev, chip, 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); + unsigned chip = counter->chip_index; + + return ni_660x_read_register(dev, chip, ni_660x_register); +} + +static inline struct mite_dma_descriptor_ring *mite_ring(struct ni_660x_private + *priv, + struct ni_gpct + *counter) +{ + unsigned chip = counter->chip_index; + + return priv->mite_rings[chip][counter->counter_index]; +} + +static inline void ni_660x_set_dma_channel(struct comedi_device *dev, + unsigned mite_channel, + struct ni_gpct *counter) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned chip = counter->chip_index; + unsigned long flags; + + spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags); + devpriv->dma_configuration_soft_copies[chip] &= + ~dma_select_mask(mite_channel); + devpriv->dma_configuration_soft_copies[chip] |= + dma_select_bits(mite_channel, counter->counter_index); + ni_660x_write_register(dev, chip, + devpriv->dma_configuration_soft_copies[chip] | + dma_reset_bit(mite_channel), NI660X_DMA_CFG); + mmiowb(); + spin_unlock_irqrestore(&devpriv->soft_reg_copy_lock, flags); +} + +static inline void ni_660x_unset_dma_channel(struct comedi_device *dev, + unsigned mite_channel, + struct ni_gpct *counter) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned chip = counter->chip_index; + unsigned long flags; + + spin_lock_irqsave(&devpriv->soft_reg_copy_lock, flags); + devpriv->dma_configuration_soft_copies[chip] &= + ~dma_select_mask(mite_channel); + devpriv->dma_configuration_soft_copies[chip] |= + dma_select_bits(mite_channel, dma_selection_none); + ni_660x_write_register(dev, chip, + devpriv->dma_configuration_soft_copies[chip], + NI660X_DMA_CFG); + mmiowb(); + spin_unlock_irqrestore(&devpriv->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) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned long flags; + struct mite_channel *mite_chan; + + spin_lock_irqsave(&devpriv->mite_channel_lock, flags); + BUG_ON(counter->mite_chan); + mite_chan = mite_request_channel(devpriv->mite, + mite_ring(devpriv, counter)); + if (!mite_chan) { + spin_unlock_irqrestore(&devpriv->mite_channel_lock, flags); + dev_err(dev->class_dev, + "failed to reserve mite dma channel for counter\n"); + 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(&devpriv->mite_channel_lock, flags); + return 0; +} + +static void ni_660x_release_mite_channel(struct comedi_device *dev, + struct ni_gpct *counter) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned long flags; + + spin_lock_irqsave(&devpriv->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(&devpriv->mite_channel_lock, flags); +} + +static int ni_660x_cmd(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + int retval; + + retval = ni_660x_request_mite_channel(dev, counter, COMEDI_INPUT); + if (retval) { + dev_err(dev->class_dev, + "no dma channel available for use by counter\n"); + return retval; + } + ni_tio_acknowledge(counter); + + return ni_tio_cmd(dev, s); +} + +static int ni_660x_cancel(struct comedi_device *dev, struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + 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 chip) +{ + unsigned bits = 0; + + /* + * 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 (chip) + bits = CounterSwap; + + ni_660x_write_register(dev, chip, bits, NI660X_CLK_CFG); +} + +static void ni_660x_handle_gpct_interrupt(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_gpct *counter = s->private; + + ni_tio_handle_interrupt(counter, s); + comedi_handle_events(dev, s); +} + +static irqreturn_t ni_660x_interrupt(int irq, void *d) +{ + struct comedi_device *dev = d; + struct ni_660x_private *devpriv = dev->private; + struct comedi_subdevice *s; + unsigned i; + unsigned long flags; + + if (!dev->attached) + return IRQ_NONE; + /* lock to avoid race with comedi_poll */ + spin_lock_irqsave(&devpriv->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(&devpriv->interrupt_lock, flags); + return IRQ_HANDLED; +} + +static int ni_660x_input_poll(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_660x_private *devpriv = dev->private; + struct ni_gpct *counter = s->private; + unsigned long flags; + + /* lock to avoid race with comedi_poll */ + spin_lock_irqsave(&devpriv->interrupt_lock, flags); + mite_sync_input_dma(counter->mite_chan, s); + spin_unlock_irqrestore(&devpriv->interrupt_lock, flags); + return comedi_buf_read_n_available(s); +} + +static int ni_660x_buf_change(struct comedi_device *dev, + struct comedi_subdevice *s) +{ + struct ni_660x_private *devpriv = dev->private; + struct ni_gpct *counter = s->private; + int ret; + + ret = mite_buf_change(mite_ring(devpriv, counter), s); + if (ret < 0) + return ret; + + return 0; +} + +static int ni_660x_allocate_private(struct comedi_device *dev) +{ + struct ni_660x_private *devpriv; + unsigned i; + + devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); + if (!devpriv) + return -ENOMEM; + + spin_lock_init(&devpriv->mite_channel_lock); + spin_lock_init(&devpriv->interrupt_lock); + spin_lock_init(&devpriv->soft_reg_copy_lock); + for (i = 0; i < NUM_PFI_CHANNELS; ++i) + devpriv->pfi_output_selects[i] = pfi_output_select_counter; + + return 0; +} + +static int ni_660x_alloc_mite_rings(struct comedi_device *dev) +{ + const struct ni_660x_board *board = dev->board_ptr; + struct ni_660x_private *devpriv = dev->private; + unsigned i; + unsigned j; + + for (i = 0; i < board->n_chips; ++i) { + for (j = 0; j < counters_per_chip; ++j) { + devpriv->mite_rings[i][j] = + mite_alloc_ring(devpriv->mite); + if (!devpriv->mite_rings[i][j]) + return -ENOMEM; + } + } + return 0; +} + +static void ni_660x_free_mite_rings(struct comedi_device *dev) +{ + const struct ni_660x_board *board = dev->board_ptr; + struct ni_660x_private *devpriv = dev->private; + unsigned i; + unsigned j; + + for (i = 0; i < board->n_chips; ++i) { + for (j = 0; j < counters_per_chip; ++j) + mite_free_ring(devpriv->mite_rings[i][j]); + } +} + +static void init_tio_chip(struct comedi_device *dev, int chipset) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned i; + + /* init dma configuration register */ + devpriv->dma_configuration_soft_copies[chipset] = 0; + for (i = 0; i < MAX_DMA_CHANNEL; ++i) { + devpriv->dma_configuration_soft_copies[chipset] |= + dma_select_bits(i, dma_selection_none) & dma_select_mask(i); + } + ni_660x_write_register(dev, chipset, + devpriv->dma_configuration_soft_copies[chipset], + NI660X_DMA_CFG); + for (i = 0; i < NUM_PFI_CHANNELS; ++i) + ni_660x_write_register(dev, chipset, 0, IOConfigReg(i)); +} + +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, NI660X_DIO32_OUTPUT); + } + /* on return, data[1] contains the value of the digital + * input and output lines. */ + data[1] = (ni_660x_read_register(dev, 0, NI660X_DIO32_INPUT) >> + base_bitfield_channel); + + return insn->n; +} + +static void ni_660x_select_pfi_output(struct comedi_device *dev, + unsigned pfi_channel, + unsigned output_select) +{ + const struct ni_660x_board *board = dev->board_ptr; + 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->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) +{ + struct ni_660x_private *devpriv = dev->private; + + 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; + } + + devpriv->pfi_output_selects[chan] = source; + if (devpriv->pfi_direction_bits & (((uint64_t) 1) << chan)) + ni_660x_select_pfi_output(dev, chan, + devpriv->pfi_output_selects[chan]); + return 0; +} + +static int ni_660x_dio_insn_config(struct comedi_device *dev, + struct comedi_subdevice *s, + struct comedi_insn *insn, + unsigned int *data) +{ + struct ni_660x_private *devpriv = dev->private; + unsigned int chan = CR_CHAN(insn->chanspec); + uint64_t bit = 1ULL << chan; + unsigned int val; + int ret; + + switch (data[0]) { + case INSN_CONFIG_DIO_OUTPUT: + devpriv->pfi_direction_bits |= bit; + ni_660x_select_pfi_output(dev, chan, + devpriv->pfi_output_selects[chan]); + break; + + case INSN_CONFIG_DIO_INPUT: + devpriv->pfi_direction_bits &= ~bit; + ni_660x_select_pfi_output(dev, chan, pfi_output_select_high_Z); + break; + + case INSN_CONFIG_DIO_QUERY: + data[1] = (devpriv->pfi_direction_bits & bit) ? COMEDI_OUTPUT + : COMEDI_INPUT; + break; + + case INSN_CONFIG_SET_ROUTING: + ret = ni_660x_set_pfi_routing(dev, chan, data[1]); + if (ret) + return ret; + break; + + case INSN_CONFIG_GET_ROUTING: + data[1] = devpriv->pfi_output_selects[chan]; + break; + + case INSN_CONFIG_FILTER: + val = ni_660x_read_register(dev, 0, IOConfigReg(chan)); + val &= ~pfi_input_select_mask(chan); + val |= pfi_input_select_bits(chan, data[1]); + ni_660x_write_register(dev, 0, val, IOConfigReg(chan)); + break; + + default: + return -EINVAL; + } + + return insn->n; +} + +static int ni_660x_auto_attach(struct comedi_device *dev, + unsigned long context) +{ + struct pci_dev *pcidev = comedi_to_pci_dev(dev); + const struct ni_660x_board *board = NULL; + struct ni_660x_private *devpriv; + struct comedi_subdevice *s; + int ret; + unsigned i; + unsigned global_interrupt_config_bits; + + if (context < ARRAY_SIZE(ni_660x_boards)) + board = &ni_660x_boards[context]; + if (!board) + return -ENODEV; + dev->board_ptr = board; + dev->board_name = board->name; + + ret = comedi_pci_enable(dev); + if (ret) + return ret; + + ret = ni_660x_allocate_private(dev); + if (ret < 0) + return ret; + devpriv = dev->private; + + devpriv->mite = mite_alloc(pcidev); + if (!devpriv->mite) + return -ENOMEM; + + ret = mite_setup2(dev, devpriv->mite, true); + if (ret < 0) + return ret; + + ret = ni_660x_alloc_mite_rings(dev); + if (ret < 0) + return ret; + + ret = comedi_alloc_subdevices(dev, 2 + NI_660X_MAX_NUM_COUNTERS); + if (ret) + return ret; + + 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; + /* 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, NI660X_STC_DIO_CONTROL); + + devpriv->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 (!devpriv->counter_dev) + 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; + s->n_chan = 3; + s->maxdata = 0xffffffff; + s->insn_read = ni_tio_insn_read; + s->insn_write = ni_tio_insn_write; + s->insn_config = ni_tio_insn_config; + s->do_cmd = &ni_660x_cmd; + s->len_chanlist = 1; + s->do_cmdtest = ni_tio_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 = &devpriv->counter_dev->counters[i]; + + devpriv->counter_dev->counters[i].chip_index = + i / counters_per_chip; + devpriv->counter_dev->counters[i].counter_index = + i % counters_per_chip; + } else { + s->type = COMEDI_SUBD_UNUSED; + } + } + for (i = 0; i < board->n_chips; ++i) + init_tio_chip(dev, i); + + for (i = 0; i < ni_660x_num_counters(dev); ++i) + ni_tio_init_counter(&devpriv->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->n_chips; ++i) + set_tio_counterswap(dev, i); + + ret = request_irq(pcidev->irq, ni_660x_interrupt, IRQF_SHARED, + dev->board_name, dev); + if (ret < 0) { + dev_warn(dev->class_dev, " irq not available\n"); + return ret; + } + dev->irq = pcidev->irq; + global_interrupt_config_bits = Global_Int_Enable_Bit; + if (board->n_chips > 1) + global_interrupt_config_bits |= Cascade_Int_Enable_Bit; + ni_660x_write_register(dev, 0, global_interrupt_config_bits, + NI660X_GLOBAL_INT_CFG); + + return 0; +} + +static void ni_660x_detach(struct comedi_device *dev) +{ + struct ni_660x_private *devpriv = dev->private; + + if (dev->irq) + free_irq(dev->irq, dev); + if (devpriv) { + if (devpriv->counter_dev) + ni_gpct_device_destroy(devpriv->counter_dev); + ni_660x_free_mite_rings(dev); + mite_detach(devpriv->mite); + } + if (dev->mmio) + iounmap(dev->mmio); + comedi_pci_disable(dev); +} + +static struct comedi_driver ni_660x_driver = { + .driver_name = "ni_660x", + .module = THIS_MODULE, + .auto_attach = ni_660x_auto_attach, + .detach = ni_660x_detach, +}; + +static int ni_660x_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + return comedi_pci_auto_config(dev, &ni_660x_driver, id->driver_data); +} + +static const struct pci_device_id ni_660x_pci_table[] = { + { PCI_VDEVICE(NI, 0x1310), BOARD_PCI6602 }, + { PCI_VDEVICE(NI, 0x1360), BOARD_PXI6602 }, + { PCI_VDEVICE(NI, 0x2c60), BOARD_PCI6601 }, + { PCI_VDEVICE(NI, 0x2cc0), BOARD_PXI6608 }, + { PCI_VDEVICE(NI, 0x1e40), BOARD_PXI6624 }, + { 0 } +}; +MODULE_DEVICE_TABLE(pci, ni_660x_pci_table); + +static struct pci_driver ni_660x_pci_driver = { + .name = "ni_660x", + .id_table = ni_660x_pci_table, + .probe = ni_660x_pci_probe, + .remove = comedi_pci_auto_unconfig, +}; +module_comedi_pci_driver(ni_660x_driver, ni_660x_pci_driver); + +MODULE_AUTHOR("Comedi http://www.comedi.org"); +MODULE_DESCRIPTION("Comedi low-level driver"); +MODULE_LICENSE("GPL"); |