/*
|
* comedi/drivers/ni_660x.c
|
* Hardware driver for NI 660x devices
|
*
|
* This program is free software; you can redistribute it and/or modify
|
* it under the terms of the GNU General Public License as published by
|
* the Free Software Foundation; either version 2 of the License, or
|
* (at your option) any later version.
|
*
|
* This program is distributed in the hope that it will be useful,
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* GNU General Public License for more details.
|
*
|
* You should have received a copy of the GNU General Public License
|
* along with Xenomai; if not, write to the Free Software Foundation,
|
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
*/
|
|
/*
|
* Driver: ni_660x
|
* Description: National Instruments 660x counter/timer boards
|
* Devices:
|
* [National Instruments] PCI-6601 (ni_660x), PCI-6602, PXI-6602,
|
* PXI-6608
|
* Author: J.P. Mellor <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)
|
*/
|
|
/*
|
* Integration with Xenomai/Analogy layer based on the
|
* comedi driver. Adaptation made by
|
* Julien Delange <julien.delange@esa.int>
|
*/
|
|
#include <linux/interrupt.h>
|
|
#include <linux/module.h>
|
#include <rtdm/analogy/device.h>
|
|
#include "../intel/8255.h"
|
#include "ni_stc.h"
|
#include "ni_mio.h"
|
#include "ni_tio.h"
|
#include "mite.h"
|
|
enum io_direction {
|
DIRECTION_INPUT = 0,
|
DIRECTION_OUTPUT = 1,
|
DIRECTION_OPENDRAIN = 2
|
};
|
|
|
enum ni_660x_constants {
|
min_counter_pfi_chan = 8,
|
max_dio_pfi_chan = 31,
|
counters_per_chip = 4
|
};
|
|
struct ni_660x_subd_priv {
|
int io_bits;
|
unsigned int state;
|
uint16_t readback[2];
|
uint16_t config;
|
struct ni_gpct* counter;
|
};
|
|
#define NUM_PFI_CHANNELS 40
|
/* Really there are only up to 3 dma channels, but the register layout
|
allows for 4 */
|
#define MAX_DMA_CHANNEL 4
|
|
static struct a4l_channels_desc chandesc_ni660x = {
|
.mode = A4L_CHAN_GLOBAL_CHANDESC,
|
.length = NUM_PFI_CHANNELS,
|
.chans = {
|
{A4L_CHAN_AREF_GROUND, sizeof(sampl_t)},
|
},
|
};
|
|
#define subdev_priv ((struct ni_660x_subd_priv*)s->priv)
|
|
/* See Register-Level Programmer Manual page 3.1 */
|
enum NI_660x_Register {
|
G0InterruptAcknowledge,
|
G0StatusRegister,
|
G1InterruptAcknowledge,
|
G1StatusRegister,
|
G01StatusRegister,
|
G0CommandRegister,
|
STCDIOParallelInput,
|
G1CommandRegister,
|
G0HWSaveRegister,
|
G1HWSaveRegister,
|
STCDIOOutput,
|
STCDIOControl,
|
G0SWSaveRegister,
|
G1SWSaveRegister,
|
G0ModeRegister,
|
G01JointStatus1Register,
|
G1ModeRegister,
|
STCDIOSerialInput,
|
G0LoadARegister,
|
G01JointStatus2Register,
|
G0LoadBRegister,
|
G1LoadARegister,
|
G1LoadBRegister,
|
G0InputSelectRegister,
|
G1InputSelectRegister,
|
G0AutoincrementRegister,
|
G1AutoincrementRegister,
|
G01JointResetRegister,
|
G0InterruptEnable,
|
G1InterruptEnable,
|
G0CountingModeRegister,
|
G1CountingModeRegister,
|
G0SecondGateRegister,
|
G1SecondGateRegister,
|
G0DMAConfigRegister,
|
G0DMAStatusRegister,
|
G1DMAConfigRegister,
|
G1DMAStatusRegister,
|
G2InterruptAcknowledge,
|
G2StatusRegister,
|
G3InterruptAcknowledge,
|
G3StatusRegister,
|
G23StatusRegister,
|
G2CommandRegister,
|
G3CommandRegister,
|
G2HWSaveRegister,
|
G3HWSaveRegister,
|
G2SWSaveRegister,
|
G3SWSaveRegister,
|
G2ModeRegister,
|
G23JointStatus1Register,
|
G3ModeRegister,
|
G2LoadARegister,
|
G23JointStatus2Register,
|
G2LoadBRegister,
|
G3LoadARegister,
|
G3LoadBRegister,
|
G2InputSelectRegister,
|
G3InputSelectRegister,
|
G2AutoincrementRegister,
|
G3AutoincrementRegister,
|
G23JointResetRegister,
|
G2InterruptEnable,
|
G3InterruptEnable,
|
G2CountingModeRegister,
|
G3CountingModeRegister,
|
G3SecondGateRegister,
|
G2SecondGateRegister,
|
G2DMAConfigRegister,
|
G2DMAStatusRegister,
|
G3DMAConfigRegister,
|
G3DMAStatusRegister,
|
DIO32Input,
|
DIO32Output,
|
ClockConfigRegister,
|
GlobalInterruptStatusRegister,
|
DMAConfigRegister,
|
GlobalInterruptConfigRegister,
|
IOConfigReg0_1,
|
IOConfigReg2_3,
|
IOConfigReg4_5,
|
IOConfigReg6_7,
|
IOConfigReg8_9,
|
IOConfigReg10_11,
|
IOConfigReg12_13,
|
IOConfigReg14_15,
|
IOConfigReg16_17,
|
IOConfigReg18_19,
|
IOConfigReg20_21,
|
IOConfigReg22_23,
|
IOConfigReg24_25,
|
IOConfigReg26_27,
|
IOConfigReg28_29,
|
IOConfigReg30_31,
|
IOConfigReg32_33,
|
IOConfigReg34_35,
|
IOConfigReg36_37,
|
IOConfigReg38_39,
|
NumRegisters,
|
};
|
|
static inline unsigned IOConfigReg(unsigned pfi_channel)
|
{
|
unsigned reg = IOConfigReg0_1 + pfi_channel / 2;
|
BUG_ON(reg > IOConfigReg38_39);
|
return reg;
|
}
|
|
enum ni_660x_register_width {
|
DATA_1B,
|
DATA_2B,
|
DATA_4B
|
};
|
|
enum ni_660x_register_direction {
|
NI_660x_READ,
|
NI_660x_WRITE,
|
NI_660x_READ_WRITE
|
};
|
|
enum ni_660x_pfi_output_select {
|
pfi_output_select_high_Z = 0,
|
pfi_output_select_counter = 1,
|
pfi_output_select_do = 2,
|
num_pfi_output_selects
|
};
|
|
enum ni_660x_subdevices {
|
NI_660X_DIO_SUBDEV = 1,
|
NI_660X_GPCT_SUBDEV_0 = 2
|
};
|
|
static inline unsigned NI_660X_GPCT_SUBDEV(unsigned index)
|
{
|
return NI_660X_GPCT_SUBDEV_0 + index;
|
}
|
|
struct NI_660xRegisterData {
|
|
const char *name; /* Register Name */
|
int offset; /* Offset from base address from GPCT chip */
|
enum ni_660x_register_direction direction;
|
enum ni_660x_register_width size; /* 1 byte, 2 bytes, or 4 bytes */
|
};
|
|
static const struct NI_660xRegisterData registerData[NumRegisters] = {
|
{"G0 Interrupt Acknowledge", 0x004, NI_660x_WRITE, DATA_2B},
|
{"G0 Status Register", 0x004, NI_660x_READ, DATA_2B},
|
{"G1 Interrupt Acknowledge", 0x006, NI_660x_WRITE, DATA_2B},
|
{"G1 Status Register", 0x006, NI_660x_READ, DATA_2B},
|
{"G01 Status Register ", 0x008, NI_660x_READ, DATA_2B},
|
{"G0 Command Register", 0x00C, NI_660x_WRITE, DATA_2B},
|
{"STC DIO Parallel Input", 0x00E, NI_660x_READ, DATA_2B},
|
{"G1 Command Register", 0x00E, NI_660x_WRITE, DATA_2B},
|
{"G0 HW Save Register", 0x010, NI_660x_READ, DATA_4B},
|
{"G1 HW Save Register", 0x014, NI_660x_READ, DATA_4B},
|
{"STC DIO Output", 0x014, NI_660x_WRITE, DATA_2B},
|
{"STC DIO Control", 0x016, NI_660x_WRITE, DATA_2B},
|
{"G0 SW Save Register", 0x018, NI_660x_READ, DATA_4B},
|
{"G1 SW Save Register", 0x01C, NI_660x_READ, DATA_4B},
|
{"G0 Mode Register", 0x034, NI_660x_WRITE, DATA_2B},
|
{"G01 Joint Status 1 Register", 0x036, NI_660x_READ, DATA_2B},
|
{"G1 Mode Register", 0x036, NI_660x_WRITE, DATA_2B},
|
{"STC DIO Serial Input", 0x038, NI_660x_READ, DATA_2B},
|
{"G0 Load A Register", 0x038, NI_660x_WRITE, DATA_4B},
|
{"G01 Joint Status 2 Register", 0x03A, NI_660x_READ, DATA_2B},
|
{"G0 Load B Register", 0x03C, NI_660x_WRITE, DATA_4B},
|
{"G1 Load A Register", 0x040, NI_660x_WRITE, DATA_4B},
|
{"G1 Load B Register", 0x044, NI_660x_WRITE, DATA_4B},
|
{"G0 Input Select Register", 0x048, NI_660x_WRITE, DATA_2B},
|
{"G1 Input Select Register", 0x04A, NI_660x_WRITE, DATA_2B},
|
{"G0 Autoincrement Register", 0x088, NI_660x_WRITE, DATA_2B},
|
{"G1 Autoincrement Register", 0x08A, NI_660x_WRITE, DATA_2B},
|
{"G01 Joint Reset Register", 0x090, NI_660x_WRITE, DATA_2B},
|
{"G0 Interrupt Enable", 0x092, NI_660x_WRITE, DATA_2B},
|
{"G1 Interrupt Enable", 0x096, NI_660x_WRITE, DATA_2B},
|
{"G0 Counting Mode Register", 0x0B0, NI_660x_WRITE, DATA_2B},
|
{"G1 Counting Mode Register", 0x0B2, NI_660x_WRITE, DATA_2B},
|
{"G0 Second Gate Register", 0x0B4, NI_660x_WRITE, DATA_2B},
|
{"G1 Second Gate Register", 0x0B6, NI_660x_WRITE, DATA_2B},
|
{"G0 DMA Config Register", 0x0B8, NI_660x_WRITE, DATA_2B},
|
{"G0 DMA Status Register", 0x0B8, NI_660x_READ, DATA_2B},
|
{"G1 DMA Config Register", 0x0BA, NI_660x_WRITE, DATA_2B},
|
{"G1 DMA Status Register", 0x0BA, NI_660x_READ, DATA_2B},
|
{"G2 Interrupt Acknowledge", 0x104, NI_660x_WRITE, DATA_2B},
|
{"G2 Status Register", 0x104, NI_660x_READ, DATA_2B},
|
{"G3 Interrupt Acknowledge", 0x106, NI_660x_WRITE, DATA_2B},
|
{"G3 Status Register", 0x106, NI_660x_READ, DATA_2B},
|
{"G23 Status Register", 0x108, NI_660x_READ, DATA_2B},
|
{"G2 Command Register", 0x10C, NI_660x_WRITE, DATA_2B},
|
{"G3 Command Register", 0x10E, NI_660x_WRITE, DATA_2B},
|
{"G2 HW Save Register", 0x110, NI_660x_READ, DATA_4B},
|
{"G3 HW Save Register", 0x114, NI_660x_READ, DATA_4B},
|
{"G2 SW Save Register", 0x118, NI_660x_READ, DATA_4B},
|
{"G3 SW Save Register", 0x11C, NI_660x_READ, DATA_4B},
|
{"G2 Mode Register", 0x134, NI_660x_WRITE, DATA_2B},
|
{"G23 Joint Status 1 Register", 0x136, NI_660x_READ, DATA_2B},
|
{"G3 Mode Register", 0x136, NI_660x_WRITE, DATA_2B},
|
{"G2 Load A Register", 0x138, NI_660x_WRITE, DATA_4B},
|
{"G23 Joint Status 2 Register", 0x13A, NI_660x_READ, DATA_2B},
|
{"G2 Load B Register", 0x13C, NI_660x_WRITE, DATA_4B},
|
{"G3 Load A Register", 0x140, NI_660x_WRITE, DATA_4B},
|
{"G3 Load B Register", 0x144, NI_660x_WRITE, DATA_4B},
|
{"G2 Input Select Register", 0x148, NI_660x_WRITE, DATA_2B},
|
{"G3 Input Select Register", 0x14A, NI_660x_WRITE, DATA_2B},
|
{"G2 Autoincrement Register", 0x188, NI_660x_WRITE, DATA_2B},
|
{"G3 Autoincrement Register", 0x18A, NI_660x_WRITE, DATA_2B},
|
{"G23 Joint Reset Register", 0x190, NI_660x_WRITE, DATA_2B},
|
{"G2 Interrupt Enable", 0x192, NI_660x_WRITE, DATA_2B},
|
{"G3 Interrupt Enable", 0x196, NI_660x_WRITE, DATA_2B},
|
{"G2 Counting Mode Register", 0x1B0, NI_660x_WRITE, DATA_2B},
|
{"G3 Counting Mode Register", 0x1B2, NI_660x_WRITE, DATA_2B},
|
{"G3 Second Gate Register", 0x1B6, NI_660x_WRITE, DATA_2B},
|
{"G2 Second Gate Register", 0x1B4, NI_660x_WRITE, DATA_2B},
|
{"G2 DMA Config Register", 0x1B8, NI_660x_WRITE, DATA_2B},
|
{"G2 DMA Status Register", 0x1B8, NI_660x_READ, DATA_2B},
|
{"G3 DMA Config Register", 0x1BA, NI_660x_WRITE, DATA_2B},
|
{"G3 DMA Status Register", 0x1BA, NI_660x_READ, DATA_2B},
|
{"32 bit Digital Input", 0x414, NI_660x_READ, DATA_4B},
|
{"32 bit Digital Output", 0x510, NI_660x_WRITE, DATA_4B},
|
{"Clock Config Register", 0x73C, NI_660x_WRITE, DATA_4B},
|
{"Global Interrupt Status Register", 0x754, NI_660x_READ, DATA_4B},
|
{"DMA Configuration Register", 0x76C, NI_660x_WRITE, DATA_4B},
|
{"Global Interrupt Config Register", 0x770, NI_660x_WRITE, DATA_4B},
|
{"IO Config Register 0-1", 0x77C, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 2-3", 0x77E, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 4-5", 0x780, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 6-7", 0x782, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 8-9", 0x784, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 10-11", 0x786, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 12-13", 0x788, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 14-15", 0x78A, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 16-17", 0x78C, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 18-19", 0x78E, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 20-21", 0x790, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 22-23", 0x792, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 24-25", 0x794, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 26-27", 0x796, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 28-29", 0x798, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 30-31", 0x79A, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 32-33", 0x79C, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 34-35", 0x79E, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 36-37", 0x7A0, NI_660x_READ_WRITE, DATA_2B},
|
{"IO Config Register 38-39", 0x7A2, NI_660x_READ_WRITE, DATA_2B}
|
};
|
|
/* kind of ENABLE for the second counter */
|
enum clock_config_register_bits {
|
CounterSwap = 0x1 << 21
|
};
|
|
/* ioconfigreg */
|
static inline unsigned ioconfig_bitshift(unsigned pfi_channel)
|
{
|
if (pfi_channel % 2)
|
return 0;
|
else
|
return 8;
|
}
|
|
static inline unsigned pfi_output_select_mask(unsigned pfi_channel)
|
{
|
return 0x3 << ioconfig_bitshift(pfi_channel);
|
}
|
|
static inline unsigned pfi_output_select_bits(unsigned pfi_channel,
|
unsigned output_select)
|
{
|
return (output_select & 0x3) << ioconfig_bitshift(pfi_channel);
|
}
|
|
static inline unsigned pfi_input_select_mask(unsigned pfi_channel)
|
{
|
return 0x7 << (4 + ioconfig_bitshift(pfi_channel));
|
}
|
|
static inline unsigned pfi_input_select_bits(unsigned pfi_channel,
|
unsigned input_select)
|
{
|
return (input_select & 0x7) << (4 + ioconfig_bitshift(pfi_channel));
|
}
|
|
/* Dma configuration register bits */
|
static inline unsigned dma_select_mask(unsigned dma_channel)
|
{
|
BUG_ON(dma_channel >= MAX_DMA_CHANNEL);
|
return 0x1f << (8 * dma_channel);
|
}
|
|
enum dma_selection {
|
dma_selection_none = 0x1f,
|
};
|
|
static inline unsigned dma_selection_counter(unsigned counter_index)
|
{
|
BUG_ON(counter_index >= counters_per_chip);
|
return counter_index;
|
}
|
|
static inline unsigned dma_select_bits(unsigned dma_channel, unsigned selection)
|
{
|
BUG_ON(dma_channel >= MAX_DMA_CHANNEL);
|
return (selection << (8 * dma_channel)) & dma_select_mask(dma_channel);
|
}
|
|
static inline unsigned dma_reset_bit(unsigned dma_channel)
|
{
|
BUG_ON(dma_channel >= MAX_DMA_CHANNEL);
|
return 0x80 << (8 * dma_channel);
|
}
|
|
enum global_interrupt_status_register_bits {
|
Counter_0_Int_Bit = 0x100,
|
Counter_1_Int_Bit = 0x200,
|
Counter_2_Int_Bit = 0x400,
|
Counter_3_Int_Bit = 0x800,
|
Cascade_Int_Bit = 0x20000000,
|
Global_Int_Bit = 0x80000000
|
};
|
|
enum global_interrupt_config_register_bits {
|
Cascade_Int_Enable_Bit = 0x20000000,
|
Global_Int_Polarity_Bit = 0x40000000,
|
Global_Int_Enable_Bit = 0x80000000
|
};
|
|
/* Offset of the GPCT chips from the base-adress of the card:
|
First chip is at base-address +0x00, etc. */
|
static const unsigned GPCT_OFFSET[2] = { 0x0, 0x800 };
|
|
/* Board description */
|
struct ni_660x_board {
|
unsigned short dev_id; /* `lspci` will show you this */
|
const char *name;
|
unsigned n_chips; /* total number of TIO chips */
|
};
|
|
static const struct ni_660x_board ni_660x_boards[] = {
|
{
|
.dev_id = 0x2c60,
|
.name = "PCI-6601",
|
.n_chips = 1,
|
},
|
{
|
.dev_id = 0x1310,
|
.name = "PCI-6602",
|
.n_chips = 2,
|
},
|
{
|
.dev_id = 0x1360,
|
.name = "PXI-6602",
|
.n_chips = 2,
|
},
|
{
|
.dev_id = 0x2cc0,
|
.name = "PXI-6608",
|
.n_chips = 2,
|
},
|
};
|
|
#define NI_660X_MAX_NUM_CHIPS 2
|
#define NI_660X_MAX_NUM_COUNTERS (NI_660X_MAX_NUM_CHIPS * counters_per_chip)
|
|
static const struct pci_device_id ni_660x_pci_table[] = {
|
{
|
PCI_VENDOR_ID_NATINST, 0x2c60, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {
|
PCI_VENDOR_ID_NATINST, 0x1310, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {
|
PCI_VENDOR_ID_NATINST, 0x1360, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {
|
PCI_VENDOR_ID_NATINST, 0x2cc0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, {
|
0}
|
};
|
|
MODULE_DEVICE_TABLE(pci, ni_660x_pci_table);
|
|
struct ni_660x_private {
|
struct mite_struct *mite;
|
struct ni_gpct_device *counter_dev;
|
uint64_t pfi_direction_bits;
|
|
struct mite_dma_descriptor_ring
|
*mite_rings[NI_660X_MAX_NUM_CHIPS][counters_per_chip];
|
|
rtdm_lock_t mite_channel_lock;
|
/* Interrupt_lock prevents races between interrupt and
|
comedi_poll */
|
rtdm_lock_t interrupt_lock;
|
unsigned int dma_configuration_soft_copies[NI_660X_MAX_NUM_CHIPS];
|
rtdm_lock_t soft_reg_copy_lock;
|
unsigned short pfi_output_selects[NUM_PFI_CHANNELS];
|
|
struct ni_660x_board *board_ptr;
|
};
|
|
#undef devpriv
|
#define devpriv ((struct ni_660x_private *)dev->priv)
|
|
static inline struct ni_660x_private *private(struct a4l_device *dev)
|
{
|
return (struct ni_660x_private*) dev->priv;
|
}
|
|
/* Initialized in ni_660x_find_device() */
|
static inline const struct ni_660x_board *board(struct a4l_device *dev)
|
{
|
return ((struct ni_660x_private*)dev->priv)->board_ptr;
|
}
|
|
#define n_ni_660x_boards ARRAY_SIZE(ni_660x_boards)
|
|
static int ni_660x_attach(struct a4l_device *dev,
|
a4l_lnkdesc_t *arg);
|
static int ni_660x_detach(struct a4l_device *dev);
|
static void init_tio_chip(struct a4l_device *dev, int chipset);
|
static void ni_660x_select_pfi_output(struct a4l_device *dev,
|
unsigned pfi_channel,
|
unsigned output_select);
|
|
static struct a4l_driver ni_660x_drv = {
|
.board_name = "analogy_ni_660x",
|
.driver_name = "ni_660x",
|
.owner = THIS_MODULE,
|
.attach = ni_660x_attach,
|
.detach = ni_660x_detach,
|
.privdata_size = sizeof(struct ni_660x_private),
|
};
|
|
static int ni_660x_set_pfi_routing(struct a4l_device *dev, unsigned chan,
|
unsigned source);
|
|
/* Possible instructions for a GPCT */
|
static int ni_660x_GPCT_rinsn(
|
struct a4l_subdevice *s,
|
struct a4l_kernel_instruction *insn);
|
static int ni_660x_GPCT_insn_config(
|
struct a4l_subdevice *s,
|
struct a4l_kernel_instruction *insn);
|
static int ni_660x_GPCT_winsn(
|
struct a4l_subdevice *s,
|
struct a4l_kernel_instruction *insn);
|
|
/* Possible instructions for Digital IO */
|
static int ni_660x_dio_insn_config(
|
struct a4l_subdevice *s,
|
struct a4l_kernel_instruction *insn);
|
static int ni_660x_dio_insn_bits(
|
struct a4l_subdevice *s,
|
struct a4l_kernel_instruction *insn);
|
|
static inline unsigned ni_660x_num_counters(struct a4l_device *dev)
|
{
|
return board(dev)->n_chips * counters_per_chip;
|
}
|
|
static enum NI_660x_Register ni_gpct_to_660x_register(enum ni_gpct_register reg)
|
{
|
|
enum NI_660x_Register ni_660x_register;
|
switch (reg) {
|
case NITIO_G0_Autoincrement_Reg:
|
ni_660x_register = G0AutoincrementRegister;
|
break;
|
case NITIO_G1_Autoincrement_Reg:
|
ni_660x_register = G1AutoincrementRegister;
|
break;
|
case NITIO_G2_Autoincrement_Reg:
|
ni_660x_register = G2AutoincrementRegister;
|
break;
|
case NITIO_G3_Autoincrement_Reg:
|
ni_660x_register = G3AutoincrementRegister;
|
break;
|
case NITIO_G0_Command_Reg:
|
ni_660x_register = G0CommandRegister;
|
break;
|
case NITIO_G1_Command_Reg:
|
ni_660x_register = G1CommandRegister;
|
break;
|
case NITIO_G2_Command_Reg:
|
ni_660x_register = G2CommandRegister;
|
break;
|
case NITIO_G3_Command_Reg:
|
ni_660x_register = G3CommandRegister;
|
break;
|
case NITIO_G0_HW_Save_Reg:
|
ni_660x_register = G0HWSaveRegister;
|
break;
|
case NITIO_G1_HW_Save_Reg:
|
ni_660x_register = G1HWSaveRegister;
|
break;
|
case NITIO_G2_HW_Save_Reg:
|
ni_660x_register = G2HWSaveRegister;
|
break;
|
case NITIO_G3_HW_Save_Reg:
|
ni_660x_register = G3HWSaveRegister;
|
break;
|
case NITIO_G0_SW_Save_Reg:
|
ni_660x_register = G0SWSaveRegister;
|
break;
|
case NITIO_G1_SW_Save_Reg:
|
ni_660x_register = G1SWSaveRegister;
|
break;
|
case NITIO_G2_SW_Save_Reg:
|
ni_660x_register = G2SWSaveRegister;
|
break;
|
case NITIO_G3_SW_Save_Reg:
|
ni_660x_register = G3SWSaveRegister;
|
break;
|
case NITIO_G0_Mode_Reg:
|
ni_660x_register = G0ModeRegister;
|
break;
|
case NITIO_G1_Mode_Reg:
|
ni_660x_register = G1ModeRegister;
|
break;
|
case NITIO_G2_Mode_Reg:
|
ni_660x_register = G2ModeRegister;
|
break;
|
case NITIO_G3_Mode_Reg:
|
ni_660x_register = G3ModeRegister;
|
break;
|
case NITIO_G0_LoadA_Reg:
|
ni_660x_register = G0LoadARegister;
|
break;
|
case NITIO_G1_LoadA_Reg:
|
ni_660x_register = G1LoadARegister;
|
break;
|
case NITIO_G2_LoadA_Reg:
|
ni_660x_register = G2LoadARegister;
|
break;
|
case NITIO_G3_LoadA_Reg:
|
ni_660x_register = G3LoadARegister;
|
break;
|
case NITIO_G0_LoadB_Reg:
|
ni_660x_register = G0LoadBRegister;
|
break;
|
case NITIO_G1_LoadB_Reg:
|
ni_660x_register = G1LoadBRegister;
|
break;
|
case NITIO_G2_LoadB_Reg:
|
ni_660x_register = G2LoadBRegister;
|
break;
|
case NITIO_G3_LoadB_Reg:
|
ni_660x_register = G3LoadBRegister;
|
break;
|
case NITIO_G0_Input_Select_Reg:
|
ni_660x_register = G0InputSelectRegister;
|
break;
|
case NITIO_G1_Input_Select_Reg:
|
ni_660x_register = G1InputSelectRegister;
|
break;
|
case NITIO_G2_Input_Select_Reg:
|
ni_660x_register = G2InputSelectRegister;
|
break;
|
case NITIO_G3_Input_Select_Reg:
|
ni_660x_register = G3InputSelectRegister;
|
break;
|
case NITIO_G01_Status_Reg:
|
ni_660x_register = G01StatusRegister;
|
break;
|
case NITIO_G23_Status_Reg:
|
ni_660x_register = G23StatusRegister;
|
break;
|
case NITIO_G01_Joint_Reset_Reg:
|
ni_660x_register = G01JointResetRegister;
|
break;
|
case NITIO_G23_Joint_Reset_Reg:
|
ni_660x_register = G23JointResetRegister;
|
break;
|
case NITIO_G01_Joint_Status1_Reg:
|
ni_660x_register = G01JointStatus1Register;
|
break;
|
case NITIO_G23_Joint_Status1_Reg:
|
ni_660x_register = G23JointStatus1Register;
|
break;
|
case NITIO_G01_Joint_Status2_Reg:
|
ni_660x_register = G01JointStatus2Register;
|
break;
|
case NITIO_G23_Joint_Status2_Reg:
|
ni_660x_register = G23JointStatus2Register;
|
break;
|
case NITIO_G0_Counting_Mode_Reg:
|
ni_660x_register = G0CountingModeRegister;
|
break;
|
case NITIO_G1_Counting_Mode_Reg:
|
ni_660x_register = G1CountingModeRegister;
|
break;
|
case NITIO_G2_Counting_Mode_Reg:
|
ni_660x_register = G2CountingModeRegister;
|
break;
|
case NITIO_G3_Counting_Mode_Reg:
|
ni_660x_register = G3CountingModeRegister;
|
break;
|
case NITIO_G0_Second_Gate_Reg:
|
ni_660x_register = G0SecondGateRegister;
|
break;
|
case NITIO_G1_Second_Gate_Reg:
|
ni_660x_register = G1SecondGateRegister;
|
break;
|
case NITIO_G2_Second_Gate_Reg:
|
ni_660x_register = G2SecondGateRegister;
|
break;
|
case NITIO_G3_Second_Gate_Reg:
|
ni_660x_register = G3SecondGateRegister;
|
break;
|
case NITIO_G0_DMA_Config_Reg:
|
ni_660x_register = G0DMAConfigRegister;
|
break;
|
case NITIO_G0_DMA_Status_Reg:
|
ni_660x_register = G0DMAStatusRegister;
|
break;
|
case NITIO_G1_DMA_Config_Reg:
|
ni_660x_register = G1DMAConfigRegister;
|
break;
|
case NITIO_G1_DMA_Status_Reg:
|
ni_660x_register = G1DMAStatusRegister;
|
break;
|
case NITIO_G2_DMA_Config_Reg:
|
ni_660x_register = G2DMAConfigRegister;
|
break;
|
case NITIO_G2_DMA_Status_Reg:
|
ni_660x_register = G2DMAStatusRegister;
|
break;
|
case NITIO_G3_DMA_Config_Reg:
|
ni_660x_register = G3DMAConfigRegister;
|
break;
|
case NITIO_G3_DMA_Status_Reg:
|
ni_660x_register = G3DMAStatusRegister;
|
break;
|
case NITIO_G0_Interrupt_Acknowledge_Reg:
|
ni_660x_register = G0InterruptAcknowledge;
|
break;
|
case NITIO_G1_Interrupt_Acknowledge_Reg:
|
ni_660x_register = G1InterruptAcknowledge;
|
break;
|
case NITIO_G2_Interrupt_Acknowledge_Reg:
|
ni_660x_register = G2InterruptAcknowledge;
|
break;
|
case NITIO_G3_Interrupt_Acknowledge_Reg:
|
ni_660x_register = G3InterruptAcknowledge;
|
break;
|
case NITIO_G0_Status_Reg:
|
ni_660x_register = G0StatusRegister;
|
break;
|
case NITIO_G1_Status_Reg:
|
ni_660x_register = G0StatusRegister;
|
break;
|
case NITIO_G2_Status_Reg:
|
ni_660x_register = G0StatusRegister;
|
break;
|
case NITIO_G3_Status_Reg:
|
ni_660x_register = G0StatusRegister;
|
break;
|
case NITIO_G0_Interrupt_Enable_Reg:
|
ni_660x_register = G0InterruptEnable;
|
break;
|
case NITIO_G1_Interrupt_Enable_Reg:
|
ni_660x_register = G1InterruptEnable;
|
break;
|
case NITIO_G2_Interrupt_Enable_Reg:
|
ni_660x_register = G2InterruptEnable;
|
break;
|
case NITIO_G3_Interrupt_Enable_Reg:
|
ni_660x_register = G3InterruptEnable;
|
break;
|
default:
|
__a4l_err("%s: unhandled register 0x%x in switch.\n",
|
__FUNCTION__, reg);
|
BUG();
|
return 0;
|
break;
|
}
|
return ni_660x_register;
|
}
|
|
static inline void ni_660x_write_register(struct a4l_device *dev,
|
unsigned chip_index, unsigned bits,
|
enum NI_660x_Register reg)
|
{
|
void *const write_address =
|
private(dev)->mite->daq_io_addr + GPCT_OFFSET[chip_index] +
|
registerData[reg].offset;
|
|
switch (registerData[reg].size) {
|
case DATA_2B:
|
writew(bits, write_address);
|
break;
|
case DATA_4B:
|
writel(bits, write_address);
|
break;
|
default:
|
__a4l_err("%s: %s: bug! unhandled case (reg=0x%x) in switch.\n",
|
__FILE__, __FUNCTION__, reg);
|
BUG();
|
break;
|
}
|
}
|
|
static inline unsigned ni_660x_read_register(struct a4l_device *dev,
|
unsigned chip_index,
|
enum NI_660x_Register reg)
|
{
|
void *const read_address =
|
private(dev)->mite->daq_io_addr + GPCT_OFFSET[chip_index] +
|
registerData[reg].offset;
|
|
switch (registerData[reg].size) {
|
case DATA_2B:
|
return readw(read_address);
|
break;
|
case DATA_4B:
|
return readl(read_address);
|
break;
|
default:
|
__a4l_err("%s: %s: bug! unhandled case (reg=0x%x) in switch.\n",
|
__FILE__, __FUNCTION__, reg);
|
BUG();
|
break;
|
}
|
return 0;
|
}
|
|
static void ni_gpct_write_register(struct ni_gpct *counter,
|
unsigned int bits, enum ni_gpct_register reg)
|
{
|
struct a4l_device *dev = counter->counter_dev->dev;
|
enum NI_660x_Register ni_660x_register = ni_gpct_to_660x_register(reg);
|
|
ni_660x_write_register(dev, counter->chip_index, bits,
|
ni_660x_register);
|
}
|
|
static unsigned ni_gpct_read_register(struct ni_gpct *counter,
|
enum ni_gpct_register reg)
|
{
|
struct a4l_device *dev = counter->counter_dev->dev;
|
enum NI_660x_Register ni_660x_register = ni_gpct_to_660x_register(reg);
|
|
return ni_660x_read_register(dev, counter->chip_index,
|
ni_660x_register);
|
}
|
|
static inline
|
struct mite_dma_descriptor_ring *mite_ring(struct ni_660x_private *priv,
|
struct ni_gpct *counter)
|
{
|
|
return priv->mite_rings[counter->chip_index][counter->counter_index];
|
}
|
|
static inline
|
void ni_660x_set_dma_channel(struct a4l_device *dev,
|
unsigned int mite_channel, struct ni_gpct *counter)
|
{
|
unsigned long flags;
|
|
rtdm_lock_get_irqsave(&private(dev)->soft_reg_copy_lock, flags);
|
private(dev)->dma_configuration_soft_copies[counter->chip_index] &=
|
~dma_select_mask(mite_channel);
|
private(dev)->dma_configuration_soft_copies[counter->chip_index] |=
|
dma_select_bits(mite_channel,
|
dma_selection_counter(counter->counter_index));
|
ni_660x_write_register(dev, counter->chip_index,
|
private(dev)->
|
dma_configuration_soft_copies
|
[counter->chip_index] |
|
dma_reset_bit(mite_channel), DMAConfigRegister);
|
mmiowb();
|
rtdm_lock_put_irqrestore(&private(dev)->soft_reg_copy_lock, flags);
|
}
|
|
static inline
|
void ni_660x_unset_dma_channel(struct a4l_device *dev,
|
unsigned int mite_channel,
|
struct ni_gpct *counter)
|
{
|
unsigned long flags;
|
rtdm_lock_get_irqsave(&private(dev)->soft_reg_copy_lock, flags);
|
private(dev)->dma_configuration_soft_copies[counter->chip_index] &=
|
~dma_select_mask(mite_channel);
|
private(dev)->dma_configuration_soft_copies[counter->chip_index] |=
|
dma_select_bits(mite_channel, dma_selection_none);
|
ni_660x_write_register(dev, counter->chip_index,
|
private(dev)->
|
dma_configuration_soft_copies
|
[counter->chip_index], DMAConfigRegister);
|
mmiowb();
|
rtdm_lock_put_irqrestore(&private(dev)->soft_reg_copy_lock, flags);
|
}
|
|
static int ni_660x_request_mite_channel(struct a4l_device *dev,
|
struct ni_gpct *counter,
|
enum io_direction direction)
|
{
|
unsigned long flags;
|
struct mite_channel *mite_chan;
|
|
rtdm_lock_get_irqsave(&private(dev)->mite_channel_lock, flags);
|
BUG_ON(counter->mite_chan);
|
mite_chan = mite_request_channel(private(dev)->mite,
|
mite_ring(private(dev), counter));
|
if (mite_chan == NULL) {
|
rtdm_lock_put_irqrestore(&private(dev)->mite_channel_lock, flags);
|
a4l_err(dev,
|
"%s: failed to reserve mite dma channel for counter.\n",
|
__FUNCTION__);
|
return -EBUSY;
|
}
|
mite_chan->dir = direction;
|
a4l_ni_tio_set_mite_channel(counter, mite_chan);
|
ni_660x_set_dma_channel(dev, mite_chan->channel, counter);
|
rtdm_lock_put_irqrestore(&private(dev)->mite_channel_lock, flags);
|
return 0;
|
}
|
|
void ni_660x_release_mite_channel(struct a4l_device *dev,
|
struct ni_gpct *counter)
|
{
|
unsigned long flags;
|
|
rtdm_lock_get_irqsave(&private(dev)->mite_channel_lock, flags);
|
if (counter->mite_chan) {
|
struct mite_channel *mite_chan = counter->mite_chan;
|
|
ni_660x_unset_dma_channel(dev, mite_chan->channel, counter);
|
a4l_ni_tio_set_mite_channel(counter, NULL);
|
a4l_mite_release_channel(mite_chan);
|
}
|
rtdm_lock_put_irqrestore(&private(dev)->mite_channel_lock, flags);
|
}
|
|
static int ni_660x_cmd(struct a4l_subdevice *s, struct a4l_cmd_desc* cmd)
|
{
|
int retval;
|
|
struct ni_gpct *counter = subdev_priv->counter;
|
|
retval = ni_660x_request_mite_channel(s->dev, counter, A4L_INPUT);
|
if (retval) {
|
a4l_err(s->dev,
|
"%s: no dma channel available for use by counter",
|
__FUNCTION__);
|
return retval;
|
}
|
|
a4l_ni_tio_acknowledge_and_confirm (counter, NULL, NULL, NULL, NULL);
|
retval = a4l_ni_tio_cmd(counter, cmd);
|
|
return retval;
|
}
|
|
static int ni_660x_cmdtest(struct a4l_subdevice *s, struct a4l_cmd_desc *cmd)
|
{
|
struct ni_gpct *counter = subdev_priv->counter;
|
return a4l_ni_tio_cmdtest(counter, cmd);
|
}
|
|
static int ni_660x_cancel(struct a4l_subdevice *s)
|
{
|
struct ni_gpct *counter = subdev_priv->counter;
|
int retval;
|
|
retval = a4l_ni_tio_cancel(counter);
|
ni_660x_release_mite_channel(s->dev, counter);
|
return retval;
|
}
|
|
static void set_tio_counterswap(struct a4l_device *dev, int chipset)
|
{
|
/* See P. 3.5 of the Register-Level Programming manual. The
|
CounterSwap bit has to be set on the second chip, otherwise
|
it will try to use the same pins as the first chip.
|
*/
|
|
if (chipset)
|
ni_660x_write_register(dev,
|
chipset,
|
CounterSwap, ClockConfigRegister);
|
else
|
ni_660x_write_register(dev,
|
chipset, 0, ClockConfigRegister);
|
}
|
|
static void ni_660x_handle_gpct_interrupt(struct a4l_device *dev,
|
struct a4l_subdevice *s)
|
{
|
struct a4l_buffer *buf = s->buf;
|
|
a4l_ni_tio_handle_interrupt(subdev_priv->counter, dev);
|
if ( test_bit(A4L_BUF_EOA_NR, &buf->flags) &&
|
test_bit(A4L_BUF_ERROR_NR, &buf->flags) &&
|
test_bit(A4L_BUF_EOA_NR, &buf->flags))
|
ni_660x_cancel(s);
|
else
|
a4l_buf_evt(s, 0);
|
}
|
|
static int ni_660x_interrupt(unsigned int irq, void *d)
|
{
|
struct a4l_device *dev = d;
|
unsigned long flags;
|
|
if (test_bit(A4L_DEV_ATTACHED_NR, &dev->flags))
|
return -ENOENT;
|
|
/* Lock to avoid race with comedi_poll */
|
rtdm_lock_get_irqsave(&private(dev)->interrupt_lock, flags);
|
smp_mb();
|
|
while (&dev->subdvsq != dev->subdvsq.next) {
|
struct list_head *this = dev->subdvsq.next;
|
struct a4l_subdevice *tmp = list_entry(this, struct a4l_subdevice, list);
|
ni_660x_handle_gpct_interrupt(dev, tmp);
|
}
|
|
rtdm_lock_put_irqrestore(&private(dev)->interrupt_lock, flags);
|
return 0;
|
}
|
|
static int ni_660x_alloc_mite_rings(struct a4l_device *dev)
|
{
|
unsigned int i;
|
unsigned int j;
|
|
for (i = 0; i < board(dev)->n_chips; ++i) {
|
for (j = 0; j < counters_per_chip; ++j) {
|
private(dev)->mite_rings[i][j] =
|
mite_alloc_ring(private(dev)->mite);
|
if (private(dev)->mite_rings[i][j] == NULL)
|
return -ENOMEM;
|
}
|
}
|
|
return 0;
|
}
|
|
static void ni_660x_free_mite_rings(struct a4l_device *dev)
|
{
|
unsigned int i;
|
unsigned int j;
|
|
for (i = 0; i < board(dev)->n_chips; ++i)
|
for (j = 0; j < counters_per_chip; ++j)
|
mite_free_ring(private(dev)->mite_rings[i][j]);
|
}
|
|
|
static int __init driver_ni_660x_init_module(void)
|
{
|
return a4l_register_drv (&ni_660x_drv);
|
}
|
|
static void __exit driver_ni_660x_cleanup_module(void)
|
{
|
a4l_unregister_drv (&ni_660x_drv);
|
}
|
|
module_init(driver_ni_660x_init_module);
|
module_exit(driver_ni_660x_cleanup_module);
|
|
static int ni_660x_attach(struct a4l_device *dev, a4l_lnkdesc_t *arg)
|
{
|
struct a4l_subdevice *s;
|
int ret;
|
int err;
|
int bus, slot;
|
unsigned i;
|
int nsubdev = 0;
|
unsigned global_interrupt_config_bits;
|
struct mite_struct *mitedev;
|
struct ni_660x_board* boardptr = NULL;
|
|
ret = 0;
|
bus = slot = 0;
|
mitedev = NULL;
|
nsubdev = 0;
|
|
if(arg->opts == NULL || arg->opts_size == 0)
|
bus = slot = 0;
|
else {
|
bus = arg->opts_size >= sizeof(unsigned long) ?
|
((unsigned long *)arg->opts)[0] : 0;
|
slot = arg->opts_size >= sizeof(unsigned long) * 2 ?
|
((unsigned long *)arg->opts)[1] : 0;
|
}
|
|
for (i = 0; ( i < n_ni_660x_boards ) && ( mitedev == NULL ); i++) {
|
mitedev = a4l_mite_find_device(bus, slot,
|
ni_660x_boards[i].dev_id);
|
boardptr = (struct ni_660x_board*) &ni_660x_boards[i];
|
}
|
|
|
if(mitedev == NULL) {
|
a4l_info(dev, "mite device not found\n");
|
return -ENOENT;
|
}
|
|
a4l_info(dev, "Board found (name=%s), continue initialization ...",
|
boardptr->name);
|
|
private(dev)->mite = mitedev;
|
private(dev)->board_ptr = boardptr;
|
|
rtdm_lock_init(&private(dev)->mite_channel_lock);
|
rtdm_lock_init(&private(dev)->interrupt_lock);
|
rtdm_lock_init(&private(dev)->soft_reg_copy_lock);
|
for (i = 0; i < NUM_PFI_CHANNELS; ++i) {
|
private(dev)->pfi_output_selects[i] = pfi_output_select_counter;
|
}
|
|
ret = a4l_mite_setup(private(dev)->mite, 1);
|
if (ret < 0) {
|
a4l_err(dev, "%s: error setting up mite\n", __FUNCTION__);
|
return ret;
|
}
|
|
ret = ni_660x_alloc_mite_rings(dev);
|
if (ret < 0) {
|
a4l_err(dev, "%s: error setting up mite rings\n", __FUNCTION__);
|
return ret;
|
}
|
|
/* Setup first subdevice */
|
s = a4l_alloc_subd(sizeof(struct ni_660x_subd_priv), NULL);
|
if (s == NULL)
|
return -ENOMEM;
|
|
s->flags = A4L_SUBD_UNUSED;
|
|
err = a4l_add_subd(dev, s);
|
if (err != nsubdev) {
|
a4l_info(dev, "cannot add first subdevice, returns %d, expect %d\n", err, i);
|
return err;
|
}
|
|
nsubdev++;
|
|
/* Setup second subdevice */
|
s = a4l_alloc_subd(sizeof(struct ni_660x_subd_priv), NULL);
|
if (s == NULL) {
|
a4l_info(dev, "cannot allocate second subdevice\n");
|
return -ENOMEM;
|
}
|
|
s->flags = A4L_SUBD_DIO;
|
s->flags |= A4L_SUBD_CMD;
|
s->chan_desc = &chandesc_ni660x;
|
s->rng_desc = &range_digital;
|
s->insn_bits = ni_660x_dio_insn_bits;
|
s->insn_config = ni_660x_dio_insn_config;
|
s->dev = dev;
|
subdev_priv->io_bits = 0;
|
ni_660x_write_register(dev, 0, 0, STCDIOControl);
|
|
err = a4l_add_subd(dev, s);
|
if (err != nsubdev)
|
return err;
|
|
nsubdev++;
|
|
private(dev)->counter_dev =
|
a4l_ni_gpct_device_construct(dev,
|
&ni_gpct_write_register,
|
&ni_gpct_read_register,
|
ni_gpct_variant_660x,
|
ni_660x_num_counters (dev));
|
if (private(dev)->counter_dev == NULL)
|
return -ENOMEM;
|
|
for (i = 0; i < ni_660x_num_counters(dev); ++i) {
|
/* TODO: check why there are kmalloc here... and in pcimio */
|
private(dev)->counter_dev->counters[i] =
|
kmalloc(sizeof(struct ni_gpct), GFP_KERNEL);
|
private(dev)->counter_dev->counters[i]->counter_dev =
|
private(dev)->counter_dev;
|
rtdm_lock_init(&(private(dev)->counter_dev->counters[i]->lock));
|
}
|
|
for (i = 0; i < NI_660X_MAX_NUM_COUNTERS; ++i) {
|
if (i < ni_660x_num_counters(dev)) {
|
/* Setup other subdevice */
|
s = a4l_alloc_subd(sizeof(struct ni_660x_subd_priv), NULL);
|
|
if (s == NULL)
|
return -ENOMEM;
|
|
s->flags = A4L_SUBD_COUNTER;
|
s->chan_desc = rtdm_malloc (sizeof (struct a4l_channels_desc));
|
s->chan_desc->length = 3;
|
s->insn_read = ni_660x_GPCT_rinsn;
|
s->insn_write = ni_660x_GPCT_winsn;
|
s->insn_config = ni_660x_GPCT_insn_config;
|
s->do_cmd = &ni_660x_cmd;
|
s->do_cmdtest = &ni_660x_cmdtest;
|
s->cancel = &ni_660x_cancel;
|
|
subdev_priv->counter = private(dev)->counter_dev->counters[i];
|
|
private(dev)->counter_dev->counters[i]->chip_index =
|
i / counters_per_chip;
|
private(dev)->counter_dev->counters[i]->counter_index =
|
i % counters_per_chip;
|
} else {
|
s = a4l_alloc_subd(sizeof(struct ni_660x_subd_priv), NULL);
|
if (s == NULL)
|
return -ENOMEM;
|
s->flags = A4L_SUBD_UNUSED;
|
}
|
|
err = a4l_add_subd(dev, s);
|
|
if (err != nsubdev)
|
return err;
|
|
nsubdev++;
|
}
|
|
for (i = 0; i < board(dev)->n_chips; ++i)
|
init_tio_chip(dev, i);
|
|
for (i = 0; i < ni_660x_num_counters(dev); ++i)
|
a4l_ni_tio_init_counter(private(dev)->counter_dev->counters[i]);
|
|
for (i = 0; i < NUM_PFI_CHANNELS; ++i) {
|
if (i < min_counter_pfi_chan)
|
ni_660x_set_pfi_routing(dev, i, pfi_output_select_do);
|
else
|
ni_660x_set_pfi_routing(dev, i,
|
pfi_output_select_counter);
|
ni_660x_select_pfi_output(dev, i, pfi_output_select_high_Z);
|
}
|
|
|
/* To be safe, set counterswap bits on tio chips after all the
|
counter outputs have been set to high impedance mode */
|
|
for (i = 0; i < board(dev)->n_chips; ++i)
|
set_tio_counterswap(dev, i);
|
|
ret = a4l_request_irq(dev,
|
mite_irq(private(dev)->mite),
|
ni_660x_interrupt, RTDM_IRQTYPE_SHARED, dev);
|
|
if (ret < 0) {
|
a4l_err(dev, "%s: IRQ not available\n", __FUNCTION__);
|
return ret;
|
}
|
|
global_interrupt_config_bits = Global_Int_Enable_Bit;
|
if (board(dev)->n_chips > 1)
|
global_interrupt_config_bits |= Cascade_Int_Enable_Bit;
|
|
ni_660x_write_register(dev, 0, global_interrupt_config_bits,
|
GlobalInterruptConfigRegister);
|
|
a4l_info(dev, "attach succeed, ready to be used\n");
|
|
return 0;
|
}
|
|
static int ni_660x_detach(struct a4l_device *dev)
|
{
|
int i;
|
|
a4l_info(dev, "begin to detach the driver ...");
|
|
/* Free irq */
|
if(a4l_get_irq(dev)!=A4L_IRQ_UNUSED)
|
a4l_free_irq(dev,a4l_get_irq(dev));
|
|
if (dev->priv) {
|
|
if (private(dev)->counter_dev) {
|
|
for (i = 0; i < ni_660x_num_counters(dev); ++i)
|
if ((private(dev)->counter_dev->counters[i]) != NULL)
|
kfree (private(dev)->counter_dev->counters[i]);
|
|
a4l_ni_gpct_device_destroy(private(dev)->counter_dev);
|
}
|
|
if (private(dev)->mite) {
|
ni_660x_free_mite_rings(dev);
|
a4l_mite_unsetup(private(dev)->mite);
|
}
|
}
|
|
a4l_info(dev, "driver detached !\n");
|
|
return 0;
|
}
|
|
static int ni_660x_GPCT_rinsn(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn)
|
{
|
return a4l_ni_tio_rinsn(subdev_priv->counter, insn);
|
}
|
|
static void init_tio_chip(struct a4l_device *dev, int chipset)
|
{
|
unsigned int i;
|
|
/* Init dma configuration register */
|
private(dev)->dma_configuration_soft_copies[chipset] = 0;
|
for (i = 0; i < MAX_DMA_CHANNEL; ++i) {
|
private(dev)->dma_configuration_soft_copies[chipset] |=
|
dma_select_bits(i, dma_selection_none) & dma_select_mask(i);
|
}
|
|
ni_660x_write_register(dev, chipset,
|
private(dev)->
|
dma_configuration_soft_copies[chipset],
|
DMAConfigRegister);
|
|
for (i = 0; i < NUM_PFI_CHANNELS; ++i)
|
ni_660x_write_register(dev, chipset, 0, IOConfigReg(i));
|
}
|
|
static int ni_660x_GPCT_insn_config(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn)
|
{
|
return a4l_ni_tio_insn_config (subdev_priv->counter, insn);
|
}
|
|
static int ni_660x_GPCT_winsn(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn)
|
{
|
return a4l_ni_tio_winsn(subdev_priv->counter, insn);
|
}
|
|
static int ni_660x_dio_insn_bits(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn)
|
{
|
unsigned int* data = (unsigned int*) insn->data;
|
unsigned int base_bitfield_channel = CR_CHAN(insn->chan_desc);
|
|
/* Check if we have to write some bits */
|
if (data[0]) {
|
subdev_priv->state &= ~(data[0] << base_bitfield_channel);
|
subdev_priv->state |= (data[0] & data[1]) << base_bitfield_channel;
|
/* Write out the new digital output lines */
|
ni_660x_write_register(s->dev, 0, subdev_priv->state, DIO32Output);
|
}
|
|
/* On return, data[1] contains the value of the digital input
|
and output lines. */
|
data[1] = ni_660x_read_register(s->dev, 0,DIO32Input) >>
|
base_bitfield_channel;
|
|
return 0;
|
}
|
|
static void ni_660x_select_pfi_output(struct a4l_device *dev,
|
unsigned pfi_channel,
|
unsigned output_select)
|
{
|
static const unsigned counter_4_7_first_pfi = 8;
|
static const unsigned counter_4_7_last_pfi = 23;
|
unsigned active_chipset = 0;
|
unsigned idle_chipset = 0;
|
unsigned active_bits;
|
unsigned idle_bits;
|
|
if (board(dev)->n_chips > 1) {
|
if (output_select == pfi_output_select_counter &&
|
pfi_channel >= counter_4_7_first_pfi &&
|
pfi_channel <= counter_4_7_last_pfi) {
|
active_chipset = 1;
|
idle_chipset = 0;
|
} else {
|
active_chipset = 0;
|
idle_chipset = 1;
|
}
|
}
|
|
if (idle_chipset != active_chipset) {
|
|
idle_bits =ni_660x_read_register(dev, idle_chipset,
|
IOConfigReg(pfi_channel));
|
idle_bits &= ~pfi_output_select_mask(pfi_channel);
|
idle_bits |=
|
pfi_output_select_bits(pfi_channel,
|
pfi_output_select_high_Z);
|
ni_660x_write_register(dev, idle_chipset, idle_bits,
|
IOConfigReg(pfi_channel));
|
}
|
|
active_bits =
|
ni_660x_read_register(dev, active_chipset,
|
IOConfigReg(pfi_channel));
|
active_bits &= ~pfi_output_select_mask(pfi_channel);
|
active_bits |= pfi_output_select_bits(pfi_channel, output_select);
|
ni_660x_write_register(dev, active_chipset, active_bits,
|
IOConfigReg(pfi_channel));
|
}
|
|
static int ni_660x_set_pfi_routing(struct a4l_device *dev, unsigned chan,
|
unsigned source)
|
{
|
BUG_ON(chan >= NUM_PFI_CHANNELS);
|
|
if (source > num_pfi_output_selects)
|
return -EINVAL;
|
if (source == pfi_output_select_high_Z)
|
return -EINVAL;
|
if (chan < min_counter_pfi_chan) {
|
if (source == pfi_output_select_counter)
|
return -EINVAL;
|
} else if (chan > max_dio_pfi_chan) {
|
if (source == pfi_output_select_do)
|
return -EINVAL;
|
}
|
BUG_ON(chan >= NUM_PFI_CHANNELS);
|
|
private(dev)->pfi_output_selects[chan] = source;
|
if (private(dev)->pfi_direction_bits & (((uint64_t) 1) << chan))
|
ni_660x_select_pfi_output(dev, chan,
|
private(dev)->
|
pfi_output_selects[chan]);
|
return 0;
|
}
|
|
static unsigned ni_660x_get_pfi_routing(struct a4l_device *dev,
|
unsigned chan)
|
{
|
BUG_ON(chan >= NUM_PFI_CHANNELS);
|
return private(dev)->pfi_output_selects[chan];
|
}
|
|
static void ni660x_config_filter(struct a4l_device *dev,
|
unsigned pfi_channel,
|
int filter)
|
{
|
unsigned int bits;
|
|
bits = ni_660x_read_register(dev, 0, IOConfigReg(pfi_channel));
|
bits &= ~pfi_input_select_mask(pfi_channel);
|
bits |= pfi_input_select_bits(pfi_channel, filter);
|
ni_660x_write_register(dev, 0, bits, IOConfigReg(pfi_channel));
|
}
|
|
static int ni_660x_dio_insn_config(struct a4l_subdevice *s, struct a4l_kernel_instruction *insn)
|
{
|
unsigned int* data = insn->data;
|
int chan = CR_CHAN(insn->chan_desc);
|
struct a4l_device* dev = s->dev;
|
|
if (data == NULL)
|
return -EINVAL;
|
|
/* The input or output configuration of each digital line is
|
* configured by a special insn_config instruction. chanspec
|
* contains the channel to be changed, and data[0] contains the
|
* value COMEDI_INPUT or COMEDI_OUTPUT. */
|
|
switch (data[0]) {
|
case A4L_INSN_CONFIG_DIO_OUTPUT:
|
private(dev)->pfi_direction_bits |= ((uint64_t) 1) << chan;
|
ni_660x_select_pfi_output(dev, chan,
|
private(dev)->
|
pfi_output_selects[chan]);
|
break;
|
case A4L_INSN_CONFIG_DIO_INPUT:
|
private(dev)->pfi_direction_bits &= ~(((uint64_t) 1) << chan);
|
ni_660x_select_pfi_output(dev, chan, pfi_output_select_high_Z);
|
break;
|
case A4L_INSN_CONFIG_DIO_QUERY:
|
data[1] =
|
(private(dev)->pfi_direction_bits &
|
(((uint64_t) 1) << chan)) ? A4L_OUTPUT : A4L_INPUT;
|
return 0;
|
case A4L_INSN_CONFIG_SET_ROUTING:
|
return ni_660x_set_pfi_routing(dev, chan, data[1]);
|
break;
|
case A4L_INSN_CONFIG_GET_ROUTING:
|
data[1] = ni_660x_get_pfi_routing(dev, chan);
|
break;
|
case A4L_INSN_CONFIG_FILTER:
|
ni660x_config_filter(dev, chan, data[1]);
|
break;
|
default:
|
return -EINVAL;
|
break;
|
};
|
|
return 0;
|
}
|
|
|
MODULE_DESCRIPTION("Analogy driver for NI660x series cards");
|
MODULE_LICENSE("GPL");
|