5f8a5f4fa4
Use the new 'mmio' member in the comedi_device for the ioremap'ed 'daq_io_addr'. Move the iounmap of this address from mite_detach() to the drivers that use the mite module. Signed-off-by: H Hartley Sweeten <hsweeten@visionengravers.com> Reviewed-by: Ian Abbott <abbotti@mev.co.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1226 lines
35 KiB
C
1226 lines
35 KiB
C
/*
|
|
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/pci.h>
|
|
#include <linux/interrupt.h>
|
|
|
|
#include "../comedidev.h"
|
|
|
|
#include "comedi_fc.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 = comedi_board(dev);
|
|
|
|
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 == NULL) {
|
|
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);
|
|
cfc_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 = comedi_board(dev);
|
|
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] == NULL)
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void ni_660x_free_mite_rings(struct comedi_device *dev)
|
|
{
|
|
const struct ni_660x_board *board = comedi_board(dev);
|
|
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 = comedi_board(dev);
|
|
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 == 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;
|
|
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");
|